##// END OF EJS Templates
configs: moved all gunicorn config to python file
super-admin -
r1143:7071fe53 default
parent child Browse files
Show More
@@ -1,275 +1,204 b''
1 1 #
2 2
3 3 ; #################################
4 4 ; RHODECODE VCSSERVER CONFIGURATION
5 5 ; #################################
6 6
7 7 [server:main]
8 8 ; COMMON HOST/IP CONFIG
9 9 host = 0.0.0.0
10 port = 9900
10 port = 10010
11 11
12 12 ; ##################################################
13 13 ; WAITRESS WSGI SERVER - Recommended for Development
14 14 ; ##################################################
15 15
16 16 ; use server type
17 17 use = egg:waitress#main
18 18
19 19 ; number of worker threads
20 20 threads = 5
21 21
22 22 ; MAX BODY SIZE 100GB
23 23 max_request_body_size = 107374182400
24 24
25 25 ; Use poll instead of select, fixes file descriptors limits problems.
26 26 ; May not work on old windows systems.
27 27 asyncore_use_poll = true
28 28
29 29
30 30 ; ###########################
31 31 ; GUNICORN APPLICATION SERVER
32 32 ; ###########################
33 33
34 34 ; run with gunicorn --paste rhodecode.ini
35 35
36 36 ; Module to use, this setting shouldn't be changed
37 37 #use = egg:gunicorn#main
38 38
39 ; Sets the number of process workers. More workers means more concurrent connections
40 ; RhodeCode can handle at the same time. Each additional worker also it increases
41 ; memory usage as each has it's own set of caches.
42 ; Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
43 ; than 8-10 unless for really big deployments .e.g 700-1000 users.
44 ; `instance_id = *` must be set in the [app:main] section below (which is the default)
45 ; when using more than 1 worker.
46 #workers = 2
47
48 ; Gunicorn access log level
49 #loglevel = info
50
51 ; Process name visible in process list
52 #proc_name = rhodecode_vcsserver
53
54 ; Type of worker class, one of `sync`, `gevent`
55 ; currently `sync` is the only option allowed.
56 #worker_class = sync
57
58 ; The maximum number of simultaneous clients. Valid only for gevent
59 #worker_connections = 10
60
61 ; Max number of requests that worker will handle before being gracefully restarted.
62 ; Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
63 #max_requests = 1000
64 #max_requests_jitter = 30
65
66 ; Amount of time a worker can spend with handling a request before it
67 ; gets killed and restarted. By default set to 21600 (6hrs)
68 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
69 #timeout = 21600
70
71 ; The maximum size of HTTP request line in bytes.
72 ; 0 for unlimited
73 #limit_request_line = 0
74
75 ; Limit the number of HTTP headers fields in a request.
76 ; By default this value is 100 and can't be larger than 32768.
77 #limit_request_fields = 32768
78
79 ; Limit the allowed size of an HTTP request header field.
80 ; Value is a positive number or 0.
81 ; Setting it to 0 will allow unlimited header field sizes.
82 #limit_request_field_size = 0
83
84 ; Timeout for graceful workers restart.
85 ; After receiving a restart signal, workers have this much time to finish
86 ; serving requests. Workers still alive after the timeout (starting from the
87 ; receipt of the restart signal) are force killed.
88 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
89 #graceful_timeout = 21600
90
91 # The number of seconds to wait for requests on a Keep-Alive connection.
92 # Generally set in the 1-5 seconds range.
93 #keepalive = 2
94
95 ; Maximum memory usage that each worker can use before it will receive a
96 ; graceful restart signal 0 = memory monitoring is disabled
97 ; Examples: 268435456 (256MB), 536870912 (512MB)
98 ; 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
99 #memory_max_usage = 0
100
101 ; How often in seconds to check for memory usage for each gunicorn worker
102 #memory_usage_check_interval = 60
103
104 ; Threshold value for which we don't recycle worker if GarbageCollection
105 ; frees up enough resources. Before each restart we try to run GC on worker
106 ; in case we get enough free memory after that, restart will not happen.
107 #memory_usage_recovery_threshold = 0.8
108
109
110 39 [app:main]
111 40 ; The %(here)s variable will be replaced with the absolute path of parent directory
112 41 ; of this file
113 42 ; Each option in the app:main can be override by an environmental variable
114 43 ;
115 44 ;To override an option:
116 45 ;
117 46 ;RC_<KeyName>
118 47 ;Everything should be uppercase, . and - should be replaced by _.
119 48 ;For example, if you have these configuration settings:
120 49 ;rc_cache.repo_object.backend = foo
121 50 ;can be overridden by
122 51 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
123 52
124 53 use = egg:rhodecode-vcsserver
125 54
126 55
127 56 ; #############
128 57 ; DEBUG OPTIONS
129 58 ; #############
130 59
131 60 # During development the we want to have the debug toolbar enabled
132 61 pyramid.includes =
133 62 pyramid_debugtoolbar
134 63
135 64 debugtoolbar.hosts = 0.0.0.0/0
136 65 debugtoolbar.exclude_prefixes =
137 66 /css
138 67 /fonts
139 68 /images
140 69 /js
141 70
142 71 ; #################
143 72 ; END DEBUG OPTIONS
144 73 ; #################
145 74
146 75 ; Pyramid default locales, we need this to be set
147 76 #pyramid.default_locale_name = en
148 77
149 78 ; default locale used by VCS systems
150 79 #locale = en_US.UTF-8
151 80
152 81 ; path to binaries for vcsserver, it should be set by the installer
153 82 ; at installation time, e.g /home/user/.rccontrol/vcsserver-1/profile/bin
154 83 ; it can also be a path to nix-build output in case of development
155 84 core.binary_dir = ""
156 85
157 86 ; Custom exception store path, defaults to TMPDIR
158 87 ; This is used to store exception from RhodeCode in shared directory
159 88 #exception_tracker.store_path =
160 89
161 90 ; #############
162 91 ; DOGPILE CACHE
163 92 ; #############
164 93
165 94 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
166 95 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
167 96 #cache_dir = %(here)s/data
168 97
169 98 ; ***************************************
170 99 ; `repo_object` cache, default file based
171 100 ; ***************************************
172 101
173 102 ; `repo_object` cache settings for vcs methods for repositories
174 103 #rc_cache.repo_object.backend = dogpile.cache.rc.file_namespace
175 104
176 105 ; cache auto-expires after N seconds
177 106 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
178 107 #rc_cache.repo_object.expiration_time = 2592000
179 108
180 109 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
181 110 #rc_cache.repo_object.arguments.filename = /tmp/vcsserver_cache_repo_object.db
182 111
183 112 ; ***********************************************************
184 113 ; `repo_object` cache with redis backend
185 114 ; recommended for larger instance, and for better performance
186 115 ; ***********************************************************
187 116
188 117 ; `repo_object` cache settings for vcs methods for repositories
189 118 #rc_cache.repo_object.backend = dogpile.cache.rc.redis_msgpack
190 119
191 120 ; cache auto-expires after N seconds
192 121 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
193 122 #rc_cache.repo_object.expiration_time = 2592000
194 123
195 124 ; redis_expiration_time needs to be greater then expiration_time
196 125 #rc_cache.repo_object.arguments.redis_expiration_time = 3592000
197 126
198 127 #rc_cache.repo_object.arguments.host = localhost
199 128 #rc_cache.repo_object.arguments.port = 6379
200 129 #rc_cache.repo_object.arguments.db = 5
201 130 #rc_cache.repo_object.arguments.socket_timeout = 30
202 131 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
203 132 #rc_cache.repo_object.arguments.distributed_lock = true
204 133
205 134 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
206 135 #rc_cache.repo_object.arguments.lock_auto_renewal = true
207 136
208 137 ; Statsd client config, this is used to send metrics to statsd
209 138 ; We recommend setting statsd_exported and scrape them using Promethues
210 139 #statsd.enabled = false
211 140 #statsd.statsd_host = 0.0.0.0
212 141 #statsd.statsd_port = 8125
213 142 #statsd.statsd_prefix =
214 143 #statsd.statsd_ipv6 = false
215 144
216 145 ; configure logging automatically at server startup set to false
217 146 ; to use the below custom logging config.
218 147 ; RC_LOGGING_FORMATTER
219 148 ; RC_LOGGING_LEVEL
220 149 ; env variables can control the settings for logging in case of autoconfigure
221 150
222 151 #logging.autoconfigure = true
223 152
224 153 ; specify your own custom logging config file to configure logging
225 154 #logging.logging_conf_file = /path/to/custom_logging.ini
226 155
227 156 ; #####################
228 157 ; LOGGING CONFIGURATION
229 158 ; #####################
230 159
231 160 [loggers]
232 161 keys = root, vcsserver
233 162
234 163 [handlers]
235 164 keys = console
236 165
237 166 [formatters]
238 167 keys = generic, json
239 168
240 169 ; #######
241 170 ; LOGGERS
242 171 ; #######
243 172 [logger_root]
244 173 level = NOTSET
245 174 handlers = console
246 175
247 176 [logger_vcsserver]
248 177 level = DEBUG
249 178 handlers =
250 179 qualname = vcsserver
251 180 propagate = 1
252 181
253 182 ; ########
254 183 ; HANDLERS
255 184 ; ########
256 185
257 186 [handler_console]
258 187 class = StreamHandler
259 188 args = (sys.stderr, )
260 189 level = DEBUG
261 190 ; To enable JSON formatted logs replace 'generic' with 'json'
262 191 ; This allows sending properly formatted logs to grafana loki or elasticsearch
263 192 formatter = generic
264 193
265 194 ; ##########
266 195 ; FORMATTERS
267 196 ; ##########
268 197
269 198 [formatter_generic]
270 199 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
271 200 datefmt = %Y-%m-%d %H:%M:%S
272 201
273 202 [formatter_json]
274 203 format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
275 204 class = vcsserver.lib._vendor.jsonlogger.JsonFormatter
@@ -1,393 +1,506 b''
1 1 """
2 2 Gunicorn config extension and hooks. This config file adds some extra settings and memory management.
3 3 Gunicorn configuration should be managed by .ini files entries of RhodeCode or VCSServer
4 4 """
5 5
6 6 import gc
7 7 import os
8 8 import sys
9 9 import math
10 10 import time
11 11 import threading
12 12 import traceback
13 13 import random
14 14 import socket
15 import dataclasses
15 16 from gunicorn.glogging import Logger
16 17
17 18
18 19 def get_workers():
19 20 import multiprocessing
20 21 return multiprocessing.cpu_count() * 2 + 1
21 22
22 # GLOBAL
23
24 bind = "127.0.0.1:10010"
25
26
27 # Error logging output for gunicorn (-) is stdout
23 28 errorlog = '-'
29
30 # Access logging output for gunicorn (-) is stdout
24 31 accesslog = '-'
25 32
26 33
27 34 # SERVER MECHANICS
28 35 # None == system temp dir
29 36 # worker_tmp_dir is recommended to be set to some tmpfs
30 37 worker_tmp_dir = None
31 38 tmp_upload_dir = None
32 39
40 # use re-use port logic
33 41 #reuse_port = True
34 42
35 43 # Custom log format
36 44 #access_log_format = (
37 45 # '%(t)s %(p)s INFO [GNCRN] %(h)-15s rqt:%(L)s %(s)s %(b)-6s "%(m)s:%(U)s %(q)s" usr:%(u)s "%(f)s" "%(a)s"')
38 46
39 47 # loki format for easier parsing in grafana
40 48 access_log_format = (
41 49 'time="%(t)s" pid=%(p)s level="INFO" type="[GNCRN]" ip="%(h)-15s" rqt="%(L)s" response_code="%(s)s" response_bytes="%(b)-6s" uri="%(m)s:%(U)s %(q)s" user=":%(u)s" user_agent="%(a)s"')
42 50
43 # self adjust workers based on CPU count
51
52 # Sets the number of process workers. More workers means more concurrent connections
53 # RhodeCode can handle at the same time. Each additional worker also it increases
54 # memory usage as each has it's own set of caches.
55 # Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
56 # than 8-10 unless for huge deployments .e.g 700-1000 users.
57 # `instance_id = *` must be set in the [app:main] section below (which is the default)
58 # when using more than 1 worker.
59 workers = 6
60
61 # self adjust workers based on CPU count, to use maximum of CPU and not overquota the resources
44 62 # workers = get_workers()
45 63
64 # Gunicorn access log level
65 loglevel = 'info'
66
67 # Process name visible in process list
68 proc_name = 'rhodecode_vcsserver'
69
70 # Type of worker class, one of `sync`, `gevent`
71 # currently `sync` is the only option allowed.
72 worker_class = 'sync'
73
74 # The maximum number of simultaneous clients. Valid only for gevent
75 worker_connections = 10
76
77 # Max number of requests that worker will handle before being gracefully restarted.
78 # Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
79 max_requests = 2000
80 max_requests_jitter = 30
81
82 # The maximum number of pending connections.
83 # Exceeding this number results in the client getting an error when attempting to connect.
84 backlog = 64
85
86 # Amount of time a worker can spend with handling a request before it
87 # gets killed and restarted. By default set to 21600 (6hrs)
88 # Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
89 timeout = 21600
90
91 # The maximum size of HTTP request line in bytes.
92 # 0 for unlimited
93 limit_request_line = 0
94
95 # Limit the number of HTTP headers fields in a request.
96 # By default this value is 100 and can't be larger than 32768.
97 limit_request_fields = 32768
98
99 # Limit the allowed size of an HTTP request header field.
100 # Value is a positive number or 0.
101 # Setting it to 0 will allow unlimited header field sizes.
102 limit_request_field_size = 0
103
104 # Timeout for graceful workers restart.
105 # After receiving a restart signal, workers have this much time to finish
106 # serving requests. Workers still alive after the timeout (starting from the
107 # receipt of the restart signal) are force killed.
108 # Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
109 graceful_timeout = 21600
110
111 # The number of seconds to wait for requests on a Keep-Alive connection.
112 # Generally set in the 1-5 seconds range.
113 keepalive = 2
114
115 # Maximum memory usage that each worker can use before it will receive a
116 # graceful restart signal 0 = memory monitoring is disabled
117 # Examples: 268435456 (256MB), 536870912 (512MB)
118 # 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
119 memory_max_usage = 0
120
121 # How often in seconds to check for memory usage for each gunicorn worker
122 memory_usage_check_interval = 60
123
124 # Threshold value for which we don't recycle worker if GarbageCollection
125 # frees up enough resources. Before each restart we try to run GC on worker
126 # in case we get enough free memory after that, restart will not happen.
127 memory_usage_recovery_threshold = 0.8
128
129
130 @dataclasses.dataclass
131 class MemoryCheckConfig:
132 max_usage: int
133 check_interval: int
134 recovery_threshold: float
135
46 136
47 137 def _get_process_rss(pid=None):
48 138 try:
49 139 import psutil
50 140 if pid:
51 141 proc = psutil.Process(pid)
52 142 else:
53 143 proc = psutil.Process()
54 144 return proc.memory_info().rss
55 145 except Exception:
56 146 return None
57 147
58 148
59 149 def _get_config(ini_path):
60 150 import configparser
61 151
62 152 try:
63 153 config = configparser.RawConfigParser()
64 154 config.read(ini_path)
65 155 return config
66 156 except Exception:
67 157 return None
68 158
69 159
70 def _time_with_offset(memory_usage_check_interval):
71 return time.time() - random.randint(0, memory_usage_check_interval/2.0)
160 def get_memory_usage_params(config=None):
161 # memory spec defaults
162 _memory_max_usage = memory_max_usage
163 _memory_usage_check_interval = memory_usage_check_interval
164 _memory_usage_recovery_threshold = memory_usage_recovery_threshold
165
166 if config:
167 ini_path = os.path.abspath(config)
168 conf = _get_config(ini_path)
169
170 section = 'server:main'
171 if conf and conf.has_section(section):
172
173 if conf.has_option(section, 'memory_max_usage'):
174 _memory_max_usage = conf.getint(section, 'memory_max_usage')
175
176 if conf.has_option(section, 'memory_usage_check_interval'):
177 _memory_usage_check_interval = conf.getint(section, 'memory_usage_check_interval')
178
179 if conf.has_option(section, 'memory_usage_recovery_threshold'):
180 _memory_usage_recovery_threshold = conf.getfloat(section, 'memory_usage_recovery_threshold')
181
182 _memory_max_usage = int(os.environ.get('RC_GUNICORN_MEMORY_MAX_USAGE', '')
183 or _memory_max_usage)
184 _memory_usage_check_interval = int(os.environ.get('RC_GUNICORN_MEMORY_USAGE_CHECK_INTERVAL', '')
185 or _memory_usage_check_interval)
186 _memory_usage_recovery_threshold = float(os.environ.get('RC_GUNICORN_MEMORY_USAGE_RECOVERY_THRESHOLD', '')
187 or _memory_usage_recovery_threshold)
188
189 return MemoryCheckConfig(_memory_max_usage, _memory_usage_check_interval, _memory_usage_recovery_threshold)
190
191
192 def _time_with_offset(check_interval):
193 return time.time() - random.randint(0, check_interval/2.0)
72 194
73 195
74 196 def pre_fork(server, worker):
75 197 pass
76 198
77 199
78 200 def post_fork(server, worker):
79 201
80 # memory spec defaults
81 _memory_max_usage = 0
82 _memory_usage_check_interval = 60
83 _memory_usage_recovery_threshold = 0.8
84
85 ini_path = os.path.abspath(server.cfg.paste)
86 conf = _get_config(ini_path)
87
88 section = 'server:main'
89 if conf and conf.has_section(section):
90
91 if conf.has_option(section, 'memory_max_usage'):
92 _memory_max_usage = conf.getint(section, 'memory_max_usage')
93
94 if conf.has_option(section, 'memory_usage_check_interval'):
95 _memory_usage_check_interval = conf.getint(section, 'memory_usage_check_interval')
96
97 if conf.has_option(section, 'memory_usage_recovery_threshold'):
98 _memory_usage_recovery_threshold = conf.getfloat(section, 'memory_usage_recovery_threshold')
202 memory_conf = get_memory_usage_params()
203 _memory_max_usage = memory_conf.max_usage
204 _memory_usage_check_interval = memory_conf.check_interval
205 _memory_usage_recovery_threshold = memory_conf.recovery_threshold
99 206
100 207 worker._memory_max_usage = int(os.environ.get('RC_GUNICORN_MEMORY_MAX_USAGE', '')
101 208 or _memory_max_usage)
102 209 worker._memory_usage_check_interval = int(os.environ.get('RC_GUNICORN_MEMORY_USAGE_CHECK_INTERVAL', '')
103 210 or _memory_usage_check_interval)
104 211 worker._memory_usage_recovery_threshold = float(os.environ.get('RC_GUNICORN_MEMORY_USAGE_RECOVERY_THRESHOLD', '')
105 212 or _memory_usage_recovery_threshold)
106 213
107 214 # register memory last check time, with some random offset so we don't recycle all
108 215 # at once
109 216 worker._last_memory_check_time = _time_with_offset(_memory_usage_check_interval)
110 217
111 218 if _memory_max_usage:
112 server.log.info("[%-10s] WORKER spawned with max memory set at %s", worker.pid,
219 server.log.info("pid=[%-10s] WORKER spawned with max memory set at %s", worker.pid,
113 220 _format_data_size(_memory_max_usage))
114 221 else:
115 server.log.info("[%-10s] WORKER spawned", worker.pid)
222 server.log.info("pid=[%-10s] WORKER spawned", worker.pid)
116 223
117 224
118 225 def pre_exec(server):
119 226 server.log.info("Forked child, re-executing.")
120 227
121 228
122 229 def on_starting(server):
123 230 server_lbl = '{} {}'.format(server.proc_name, server.address)
124 231 server.log.info("Server %s is starting.", server_lbl)
125 232
126 233
127 234 def when_ready(server):
128 235 server.log.info("Server %s is ready. Spawning workers", server)
129 236
130 237
131 238 def on_reload(server):
132 239 pass
133 240
134 241
135 242 def _format_data_size(size, unit="B", precision=1, binary=True):
136 243 """Format a number using SI units (kilo, mega, etc.).
137 244
138 245 ``size``: The number as a float or int.
139 246
140 247 ``unit``: The unit name in plural form. Examples: "bytes", "B".
141 248
142 249 ``precision``: How many digits to the right of the decimal point. Default
143 250 is 1. 0 suppresses the decimal point.
144 251
145 252 ``binary``: If false, use base-10 decimal prefixes (kilo = K = 1000).
146 253 If true, use base-2 binary prefixes (kibi = Ki = 1024).
147 254
148 255 ``full_name``: If false (default), use the prefix abbreviation ("k" or
149 256 "Ki"). If true, use the full prefix ("kilo" or "kibi"). If false,
150 257 use abbreviation ("k" or "Ki").
151 258
152 259 """
153 260
154 261 if not binary:
155 262 base = 1000
156 263 multiples = ('', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
157 264 else:
158 265 base = 1024
159 266 multiples = ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
160 267
161 268 sign = ""
162 269 if size > 0:
163 270 m = int(math.log(size, base))
164 271 elif size < 0:
165 272 sign = "-"
166 273 size = -size
167 274 m = int(math.log(size, base))
168 275 else:
169 276 m = 0
170 277 if m > 8:
171 278 m = 8
172 279
173 280 if m == 0:
174 281 precision = '%.0f'
175 282 else:
176 283 precision = '%%.%df' % precision
177 284
178 285 size = precision % (size / math.pow(base, m))
179 286
180 287 return '%s%s %s%s' % (sign, size.strip(), multiples[m], unit)
181 288
182 289
183 290 def _check_memory_usage(worker):
184 memory_max_usage = worker._memory_max_usage
185 if not memory_max_usage:
291 _memory_max_usage = worker._memory_max_usage
292 if not _memory_max_usage:
186 293 return
187 294
188 memory_usage_check_interval = worker._memory_usage_check_interval
189 memory_usage_recovery_threshold = memory_max_usage * worker._memory_usage_recovery_threshold
295 _memory_usage_check_interval = worker._memory_usage_check_interval
296 _memory_usage_recovery_threshold = memory_max_usage * worker._memory_usage_recovery_threshold
190 297
191 298 elapsed = time.time() - worker._last_memory_check_time
192 if elapsed > memory_usage_check_interval:
299 if elapsed > _memory_usage_check_interval:
193 300 mem_usage = _get_process_rss()
194 if mem_usage and mem_usage > memory_max_usage:
301 if mem_usage and mem_usage > _memory_max_usage:
195 302 worker.log.info(
196 303 "memory usage %s > %s, forcing gc",
197 _format_data_size(mem_usage), _format_data_size(memory_max_usage))
304 _format_data_size(mem_usage), _format_data_size(_memory_max_usage))
198 305 # Try to clean it up by forcing a full collection.
199 306 gc.collect()
200 307 mem_usage = _get_process_rss()
201 if mem_usage > memory_usage_recovery_threshold:
308 if mem_usage > _memory_usage_recovery_threshold:
202 309 # Didn't clean up enough, we'll have to terminate.
203 310 worker.log.warning(
204 311 "memory usage %s > %s after gc, quitting",
205 _format_data_size(mem_usage), _format_data_size(memory_max_usage))
312 _format_data_size(mem_usage), _format_data_size(_memory_max_usage))
206 313 # This will cause worker to auto-restart itself
207 314 worker.alive = False
208 315 worker._last_memory_check_time = time.time()
209 316
210 317
211 318 def worker_int(worker):
212 worker.log.info("[%-10s] worker received INT or QUIT signal", worker.pid)
319 worker.log.info("pid=[%-10s] worker received INT or QUIT signal", worker.pid)
213 320
214 321 # get traceback info, on worker crash
215 id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
322 def get_thread_id(t_id):
323 id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
324 return id2name.get(t_id, "unknown_thread_id")
325
216 326 code = []
217 for thread_id, stack in sys._current_frames().items():
327 for thread_id, stack in sys._current_frames().items(): # noqa
218 328 code.append(
219 "\n# Thread: %s(%d)" % (id2name.get(thread_id, ""), thread_id))
329 "\n# Thread: %s(%d)" % (get_thread_id(thread_id), thread_id))
220 330 for fname, lineno, name, line in traceback.extract_stack(stack):
221 331 code.append('File: "%s", line %d, in %s' % (fname, lineno, name))
222 332 if line:
223 333 code.append(" %s" % (line.strip()))
224 334 worker.log.debug("\n".join(code))
225 335
226 336
227 337 def worker_abort(worker):
228 worker.log.info("[%-10s] worker received SIGABRT signal", worker.pid)
338 worker.log.info("pid=[%-10s] worker received SIGABRT signal", worker.pid)
229 339
230 340
231 341 def worker_exit(server, worker):
232 worker.log.info("[%-10s] worker exit", worker.pid)
342 worker.log.info("pid=[%-10s] worker exit", worker.pid)
233 343
234 344
235 345 def child_exit(server, worker):
236 worker.log.info("[%-10s] worker child exit", worker.pid)
346 worker.log.info("pid=[%-10s] worker child exit", worker.pid)
237 347
238 348
239 349 def pre_request(worker, req):
240 350 worker.start_time = time.time()
241 351 worker.log.debug(
242 352 "GNCRN PRE WORKER [cnt:%s]: %s %s", worker.nr, req.method, req.path)
243 353
244 354
245 355 def post_request(worker, req, environ, resp):
246 356 total_time = time.time() - worker.start_time
247 357 # Gunicorn sometimes has problems with reading the status_code
248 358 status_code = getattr(resp, 'status_code', '')
249 359 worker.log.debug(
250 360 "GNCRN POST WORKER [cnt:%s]: %s %s resp: %s, Load Time: %.4fs",
251 361 worker.nr, req.method, req.path, status_code, total_time)
252 362 _check_memory_usage(worker)
253 363
254 364
255 365 def _filter_proxy(ip):
256 366 """
257 367 Passed in IP addresses in HEADERS can be in a special format of multiple
258 368 ips. Those comma separated IPs are passed from various proxies in the
259 369 chain of request processing. The left-most being the original client.
260 370 We only care about the first IP which came from the org. client.
261 371
262 372 :param ip: ip string from headers
263 373 """
264 374 if ',' in ip:
265 375 _ips = ip.split(',')
266 376 _first_ip = _ips[0].strip()
267 377 return _first_ip
268 378 return ip
269 379
270 380
271 381 def _filter_port(ip):
272 382 """
273 383 Removes a port from ip, there are 4 main cases to handle here.
274 384 - ipv4 eg. 127.0.0.1
275 385 - ipv6 eg. ::1
276 386 - ipv4+port eg. 127.0.0.1:8080
277 387 - ipv6+port eg. [::1]:8080
278 388
279 389 :param ip:
280 390 """
281 391 def is_ipv6(ip_addr):
282 392 if hasattr(socket, 'inet_pton'):
283 393 try:
284 394 socket.inet_pton(socket.AF_INET6, ip_addr)
285 395 except socket.error:
286 396 return False
287 397 else:
288 398 return False
289 399 return True
290 400
291 401 if ':' not in ip: # must be ipv4 pure ip
292 402 return ip
293 403
294 404 if '[' in ip and ']' in ip: # ipv6 with port
295 405 return ip.split(']')[0][1:].lower()
296 406
297 407 # must be ipv6 or ipv4 with port
298 408 if is_ipv6(ip):
299 409 return ip
300 410 else:
301 411 ip, _port = ip.split(':')[:2] # means ipv4+port
302 412 return ip
303 413
304 414
305 415 def get_ip_addr(environ):
306 416 proxy_key = 'HTTP_X_REAL_IP'
307 417 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
308 418 def_key = 'REMOTE_ADDR'
309 _filters = lambda x: _filter_port(_filter_proxy(x))
419
420 def _filters(x):
421 return _filter_port(_filter_proxy(x))
310 422
311 423 ip = environ.get(proxy_key)
312 424 if ip:
313 425 return _filters(ip)
314 426
315 427 ip = environ.get(proxy_key2)
316 428 if ip:
317 429 return _filters(ip)
318 430
319 431 ip = environ.get(def_key, '0.0.0.0')
320 432 return _filters(ip)
321 433
322 434
323 435 class RhodeCodeLogger(Logger):
324 436 """
325 437 Custom Logger that allows some customization that gunicorn doesn't allow
326 438 """
327 439
328 440 datefmt = r"%Y-%m-%d %H:%M:%S"
329 441
330 442 def __init__(self, cfg):
331 443 Logger.__init__(self, cfg)
332 444
333 445 def now(self):
334 446 """ return date in RhodeCode Log format """
335 447 now = time.time()
336 448 msecs = int((now - int(now)) * 1000)
337 449 return time.strftime(self.datefmt, time.localtime(now)) + '.{0:03d}'.format(msecs)
338 450
339 451 def atoms(self, resp, req, environ, request_time):
340 452 """ Gets atoms for log formatting.
341 453 """
342 454 status = resp.status
343 455 if isinstance(status, str):
344 456 status = status.split(None, 1)[0]
345 457 atoms = {
346 458 'h': get_ip_addr(environ),
347 459 'l': '-',
348 460 'u': self._get_user(environ) or '-',
349 461 't': self.now(),
350 462 'r': "%s %s %s" % (environ['REQUEST_METHOD'],
351 463 environ['RAW_URI'],
352 464 environ["SERVER_PROTOCOL"]),
353 465 's': status,
354 466 'm': environ.get('REQUEST_METHOD'),
355 467 'U': environ.get('PATH_INFO'),
356 468 'q': environ.get('QUERY_STRING'),
357 469 'H': environ.get('SERVER_PROTOCOL'),
358 470 'b': getattr(resp, 'sent', None) is not None and str(resp.sent) or '-',
359 471 'B': getattr(resp, 'sent', None),
360 472 'f': environ.get('HTTP_REFERER', '-'),
361 473 'a': environ.get('HTTP_USER_AGENT', '-'),
362 474 'T': request_time.seconds,
363 475 'D': (request_time.seconds * 1000000) + request_time.microseconds,
364 476 'M': (request_time.seconds * 1000) + int(request_time.microseconds/1000),
365 477 'L': "%d.%06d" % (request_time.seconds, request_time.microseconds),
366 478 'p': "<%s>" % os.getpid()
367 479 }
368 480
369 481 # add request headers
370 482 if hasattr(req, 'headers'):
371 483 req_headers = req.headers
372 484 else:
373 485 req_headers = req
374 486
375 487 if hasattr(req_headers, "items"):
376 488 req_headers = req_headers.items()
377 489
378 490 atoms.update({"{%s}i" % k.lower(): v for k, v in req_headers})
379 491
380 492 resp_headers = resp.headers
381 493 if hasattr(resp_headers, "items"):
382 494 resp_headers = resp_headers.items()
383 495
384 496 # add response headers
385 497 atoms.update({"{%s}o" % k.lower(): v for k, v in resp_headers})
386 498
387 499 # add environ variables
388 500 environ_variables = environ.items()
389 501 atoms.update({"{%s}e" % k.lower(): v for k, v in environ_variables})
390 502
391 503 return atoms
392 504
505
393 506 logger_class = RhodeCodeLogger
@@ -1,238 +1,167 b''
1 1 #
2 2
3 3 ; #################################
4 4 ; RHODECODE VCSSERVER CONFIGURATION
5 5 ; #################################
6 6
7 7 [server:main]
8 8 ; COMMON HOST/IP CONFIG
9 9 host = 127.0.0.1
10 port = 9900
10 port = 10010
11 11
12 12
13 13 ; ###########################
14 14 ; GUNICORN APPLICATION SERVER
15 15 ; ###########################
16 16
17 17 ; run with gunicorn --paste rhodecode.ini
18 18
19 19 ; Module to use, this setting shouldn't be changed
20 20 use = egg:gunicorn#main
21 21
22 ; Sets the number of process workers. More workers means more concurrent connections
23 ; RhodeCode can handle at the same time. Each additional worker also it increases
24 ; memory usage as each has it's own set of caches.
25 ; Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
26 ; than 8-10 unless for really big deployments .e.g 700-1000 users.
27 ; `instance_id = *` must be set in the [app:main] section below (which is the default)
28 ; when using more than 1 worker.
29 workers = 2
30
31 ; Gunicorn access log level
32 loglevel = info
33
34 ; Process name visible in process list
35 proc_name = rhodecode_vcsserver
36
37 ; Type of worker class, one of `sync`, `gevent`
38 ; currently `sync` is the only option allowed.
39 worker_class = sync
40
41 ; The maximum number of simultaneous clients. Valid only for gevent
42 worker_connections = 10
43
44 ; Max number of requests that worker will handle before being gracefully restarted.
45 ; Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
46 max_requests = 1000
47 max_requests_jitter = 30
48
49 ; Amount of time a worker can spend with handling a request before it
50 ; gets killed and restarted. By default set to 21600 (6hrs)
51 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
52 timeout = 21600
53
54 ; The maximum size of HTTP request line in bytes.
55 ; 0 for unlimited
56 limit_request_line = 0
57
58 ; Limit the number of HTTP headers fields in a request.
59 ; By default this value is 100 and can't be larger than 32768.
60 limit_request_fields = 32768
61
62 ; Limit the allowed size of an HTTP request header field.
63 ; Value is a positive number or 0.
64 ; Setting it to 0 will allow unlimited header field sizes.
65 limit_request_field_size = 0
66
67 ; Timeout for graceful workers restart.
68 ; After receiving a restart signal, workers have this much time to finish
69 ; serving requests. Workers still alive after the timeout (starting from the
70 ; receipt of the restart signal) are force killed.
71 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
72 graceful_timeout = 21600
73
74 # The number of seconds to wait for requests on a Keep-Alive connection.
75 # Generally set in the 1-5 seconds range.
76 keepalive = 2
77
78 ; Maximum memory usage that each worker can use before it will receive a
79 ; graceful restart signal 0 = memory monitoring is disabled
80 ; Examples: 268435456 (256MB), 536870912 (512MB)
81 ; 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
82 memory_max_usage = 0
83
84 ; How often in seconds to check for memory usage for each gunicorn worker
85 memory_usage_check_interval = 60
86
87 ; Threshold value for which we don't recycle worker if GarbageCollection
88 ; frees up enough resources. Before each restart we try to run GC on worker
89 ; in case we get enough free memory after that, restart will not happen.
90 memory_usage_recovery_threshold = 0.8
91
92
93 22 [app:main]
94 23 ; The %(here)s variable will be replaced with the absolute path of parent directory
95 24 ; of this file
96 25 ; Each option in the app:main can be override by an environmental variable
97 26 ;
98 27 ;To override an option:
99 28 ;
100 29 ;RC_<KeyName>
101 30 ;Everything should be uppercase, . and - should be replaced by _.
102 31 ;For example, if you have these configuration settings:
103 32 ;rc_cache.repo_object.backend = foo
104 33 ;can be overridden by
105 34 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
106 35
107 36 use = egg:rhodecode-vcsserver
108 37
109 38 ; Pyramid default locales, we need this to be set
110 39 #pyramid.default_locale_name = en
111 40
112 41 ; default locale used by VCS systems
113 42 #locale = en_US.UTF-8
114 43
115 44 ; path to binaries for vcsserver, it should be set by the installer
116 45 ; at installation time, e.g /home/user/.rccontrol/vcsserver-1/profile/bin
117 46 ; it can also be a path to nix-build output in case of development
118 47 core.binary_dir = ""
119 48
120 49 ; Custom exception store path, defaults to TMPDIR
121 50 ; This is used to store exception from RhodeCode in shared directory
122 51 #exception_tracker.store_path =
123 52
124 53 ; #############
125 54 ; DOGPILE CACHE
126 55 ; #############
127 56
128 57 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
129 58 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
130 59 #cache_dir = %(here)s/data
131 60
132 61 ; ***************************************
133 62 ; `repo_object` cache, default file based
134 63 ; ***************************************
135 64
136 65 ; `repo_object` cache settings for vcs methods for repositories
137 66 #rc_cache.repo_object.backend = dogpile.cache.rc.file_namespace
138 67
139 68 ; cache auto-expires after N seconds
140 69 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
141 70 #rc_cache.repo_object.expiration_time = 2592000
142 71
143 72 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
144 73 #rc_cache.repo_object.arguments.filename = /tmp/vcsserver_cache_repo_object.db
145 74
146 75 ; ***********************************************************
147 76 ; `repo_object` cache with redis backend
148 77 ; recommended for larger instance, and for better performance
149 78 ; ***********************************************************
150 79
151 80 ; `repo_object` cache settings for vcs methods for repositories
152 81 #rc_cache.repo_object.backend = dogpile.cache.rc.redis_msgpack
153 82
154 83 ; cache auto-expires after N seconds
155 84 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
156 85 #rc_cache.repo_object.expiration_time = 2592000
157 86
158 87 ; redis_expiration_time needs to be greater then expiration_time
159 88 #rc_cache.repo_object.arguments.redis_expiration_time = 3592000
160 89
161 90 #rc_cache.repo_object.arguments.host = localhost
162 91 #rc_cache.repo_object.arguments.port = 6379
163 92 #rc_cache.repo_object.arguments.db = 5
164 93 #rc_cache.repo_object.arguments.socket_timeout = 30
165 94 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
166 95 #rc_cache.repo_object.arguments.distributed_lock = true
167 96
168 97 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
169 98 #rc_cache.repo_object.arguments.lock_auto_renewal = true
170 99
171 100 ; Statsd client config, this is used to send metrics to statsd
172 101 ; We recommend setting statsd_exported and scrape them using Promethues
173 102 #statsd.enabled = false
174 103 #statsd.statsd_host = 0.0.0.0
175 104 #statsd.statsd_port = 8125
176 105 #statsd.statsd_prefix =
177 106 #statsd.statsd_ipv6 = false
178 107
179 108 ; configure logging automatically at server startup set to false
180 109 ; to use the below custom logging config.
181 110 ; RC_LOGGING_FORMATTER
182 111 ; RC_LOGGING_LEVEL
183 112 ; env variables can control the settings for logging in case of autoconfigure
184 113
185 114 #logging.autoconfigure = true
186 115
187 116 ; specify your own custom logging config file to configure logging
188 117 #logging.logging_conf_file = /path/to/custom_logging.ini
189 118
190 119 ; #####################
191 120 ; LOGGING CONFIGURATION
192 121 ; #####################
193 122
194 123 [loggers]
195 124 keys = root, vcsserver
196 125
197 126 [handlers]
198 127 keys = console
199 128
200 129 [formatters]
201 130 keys = generic, json
202 131
203 132 ; #######
204 133 ; LOGGERS
205 134 ; #######
206 135 [logger_root]
207 136 level = NOTSET
208 137 handlers = console
209 138
210 139 [logger_vcsserver]
211 140 level = INFO
212 141 handlers =
213 142 qualname = vcsserver
214 143 propagate = 1
215 144
216 145 ; ########
217 146 ; HANDLERS
218 147 ; ########
219 148
220 149 [handler_console]
221 150 class = StreamHandler
222 151 args = (sys.stderr, )
223 152 level = INFO
224 153 ; To enable JSON formatted logs replace 'generic' with 'json'
225 154 ; This allows sending properly formatted logs to grafana loki or elasticsearch
226 155 formatter = generic
227 156
228 157 ; ##########
229 158 ; FORMATTERS
230 159 ; ##########
231 160
232 161 [formatter_generic]
233 162 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
234 163 datefmt = %Y-%m-%d %H:%M:%S
235 164
236 165 [formatter_json]
237 166 format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
238 167 class = vcsserver.lib._vendor.jsonlogger.JsonFormatter
General Comments 0
You need to be logged in to leave comments. Login now