##// END OF EJS Templates
gunicorn: fixed imports for configparser
marcink -
r984:107d305d python3
parent child Browse files
Show More
@@ -1,265 +1,262 b''
1 """
1 """
2 Gunicorn config extension and hooks. This config file adds some extra settings and memory management.
2 Gunicorn config extension and hooks. This config file adds some extra settings and memory management.
3 Gunicorn configuration should be managed by .ini files entries of RhodeCode or VCSServer
3 Gunicorn configuration should be managed by .ini files entries of RhodeCode or VCSServer
4 """
4 """
5
5
6 import gc
6 import gc
7 import os
7 import os
8 import sys
8 import sys
9 import math
9 import math
10 import time
10 import time
11 import threading
11 import threading
12 import traceback
12 import traceback
13 import random
13 import random
14 from gunicorn.glogging import Logger
14 from gunicorn.glogging import Logger
15
15
16
16
17 def get_workers():
17 def get_workers():
18 import multiprocessing
18 import multiprocessing
19 return multiprocessing.cpu_count() * 2 + 1
19 return multiprocessing.cpu_count() * 2 + 1
20
20
21 # GLOBAL
21 # GLOBAL
22 errorlog = '-'
22 errorlog = '-'
23 accesslog = '-'
23 accesslog = '-'
24
24
25
25
26 # SERVER MECHANICS
26 # SERVER MECHANICS
27 # None == system temp dir
27 # None == system temp dir
28 # worker_tmp_dir is recommended to be set to some tmpfs
28 # worker_tmp_dir is recommended to be set to some tmpfs
29 worker_tmp_dir = None
29 worker_tmp_dir = None
30 tmp_upload_dir = None
30 tmp_upload_dir = None
31
31
32 # Custom log format
32 # Custom log format
33 access_log_format = (
33 access_log_format = (
34 '%(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"')
34 '%(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"')
35
35
36 # self adjust workers based on CPU count
36 # self adjust workers based on CPU count
37 # workers = get_workers()
37 # workers = get_workers()
38
38
39
39
40 def _get_process_rss(pid=None):
40 def _get_process_rss(pid=None):
41 try:
41 try:
42 import psutil
42 import psutil
43 if pid:
43 if pid:
44 proc = psutil.Process(pid)
44 proc = psutil.Process(pid)
45 else:
45 else:
46 proc = psutil.Process()
46 proc = psutil.Process()
47 return proc.memory_info().rss
47 return proc.memory_info().rss
48 except Exception:
48 except Exception:
49 return None
49 return None
50
50
51
51
52 def _get_config(ini_path):
52 def _get_config(ini_path):
53 import configparser
53
54
54 try:
55 try:
55 import configparser
56 except ImportError:
57 import ConfigParser as configparser
58 try:
59 config = configparser.RawConfigParser()
56 config = configparser.RawConfigParser()
60 config.read(ini_path)
57 config.read(ini_path)
61 return config
58 return config
62 except Exception:
59 except Exception:
63 return None
60 return None
64
61
65
62
66 def _time_with_offset(memory_usage_check_interval):
63 def _time_with_offset(memory_usage_check_interval):
67 return time.time() - random.randint(0, memory_usage_check_interval/2.0)
64 return time.time() - random.randint(0, memory_usage_check_interval/2.0)
68
65
69
66
70 def pre_fork(server, worker):
67 def pre_fork(server, worker):
71 pass
68 pass
72
69
73
70
74 def post_fork(server, worker):
71 def post_fork(server, worker):
75
72
76 # memory spec defaults
73 # memory spec defaults
77 _memory_max_usage = 0
74 _memory_max_usage = 0
78 _memory_usage_check_interval = 60
75 _memory_usage_check_interval = 60
79 _memory_usage_recovery_threshold = 0.8
76 _memory_usage_recovery_threshold = 0.8
80
77
81 ini_path = os.path.abspath(server.cfg.paste)
78 ini_path = os.path.abspath(server.cfg.paste)
82 conf = _get_config(ini_path)
79 conf = _get_config(ini_path)
83
80
84 section = 'server:main'
81 section = 'server:main'
85 if conf and conf.has_section(section):
82 if conf and conf.has_section(section):
86
83
87 if conf.has_option(section, 'memory_max_usage'):
84 if conf.has_option(section, 'memory_max_usage'):
88 _memory_max_usage = conf.getint(section, 'memory_max_usage')
85 _memory_max_usage = conf.getint(section, 'memory_max_usage')
89
86
90 if conf.has_option(section, 'memory_usage_check_interval'):
87 if conf.has_option(section, 'memory_usage_check_interval'):
91 _memory_usage_check_interval = conf.getint(section, 'memory_usage_check_interval')
88 _memory_usage_check_interval = conf.getint(section, 'memory_usage_check_interval')
92
89
93 if conf.has_option(section, 'memory_usage_recovery_threshold'):
90 if conf.has_option(section, 'memory_usage_recovery_threshold'):
94 _memory_usage_recovery_threshold = conf.getfloat(section, 'memory_usage_recovery_threshold')
91 _memory_usage_recovery_threshold = conf.getfloat(section, 'memory_usage_recovery_threshold')
95
92
96 worker._memory_max_usage = _memory_max_usage
93 worker._memory_max_usage = _memory_max_usage
97 worker._memory_usage_check_interval = _memory_usage_check_interval
94 worker._memory_usage_check_interval = _memory_usage_check_interval
98 worker._memory_usage_recovery_threshold = _memory_usage_recovery_threshold
95 worker._memory_usage_recovery_threshold = _memory_usage_recovery_threshold
99
96
100 # register memory last check time, with some random offset so we don't recycle all
97 # register memory last check time, with some random offset so we don't recycle all
101 # at once
98 # at once
102 worker._last_memory_check_time = _time_with_offset(_memory_usage_check_interval)
99 worker._last_memory_check_time = _time_with_offset(_memory_usage_check_interval)
103
100
104 if _memory_max_usage:
101 if _memory_max_usage:
105 server.log.info("[%-10s] WORKER spawned with max memory set at %s", worker.pid,
102 server.log.info("[%-10s] WORKER spawned with max memory set at %s", worker.pid,
106 _format_data_size(_memory_max_usage))
103 _format_data_size(_memory_max_usage))
107 else:
104 else:
108 server.log.info("[%-10s] WORKER spawned", worker.pid)
105 server.log.info("[%-10s] WORKER spawned", worker.pid)
109
106
110
107
111 def pre_exec(server):
108 def pre_exec(server):
112 server.log.info("Forked child, re-executing.")
109 server.log.info("Forked child, re-executing.")
113
110
114
111
115 def on_starting(server):
112 def on_starting(server):
116 server_lbl = '{} {}'.format(server.proc_name, server.address)
113 server_lbl = '{} {}'.format(server.proc_name, server.address)
117 server.log.info("Server %s is starting.", server_lbl)
114 server.log.info("Server %s is starting.", server_lbl)
118
115
119
116
120 def when_ready(server):
117 def when_ready(server):
121 server.log.info("Server %s is ready. Spawning workers", server)
118 server.log.info("Server %s is ready. Spawning workers", server)
122
119
123
120
124 def on_reload(server):
121 def on_reload(server):
125 pass
122 pass
126
123
127
124
128 def _format_data_size(size, unit="B", precision=1, binary=True):
125 def _format_data_size(size, unit="B", precision=1, binary=True):
129 """Format a number using SI units (kilo, mega, etc.).
126 """Format a number using SI units (kilo, mega, etc.).
130
127
131 ``size``: The number as a float or int.
128 ``size``: The number as a float or int.
132
129
133 ``unit``: The unit name in plural form. Examples: "bytes", "B".
130 ``unit``: The unit name in plural form. Examples: "bytes", "B".
134
131
135 ``precision``: How many digits to the right of the decimal point. Default
132 ``precision``: How many digits to the right of the decimal point. Default
136 is 1. 0 suppresses the decimal point.
133 is 1. 0 suppresses the decimal point.
137
134
138 ``binary``: If false, use base-10 decimal prefixes (kilo = K = 1000).
135 ``binary``: If false, use base-10 decimal prefixes (kilo = K = 1000).
139 If true, use base-2 binary prefixes (kibi = Ki = 1024).
136 If true, use base-2 binary prefixes (kibi = Ki = 1024).
140
137
141 ``full_name``: If false (default), use the prefix abbreviation ("k" or
138 ``full_name``: If false (default), use the prefix abbreviation ("k" or
142 "Ki"). If true, use the full prefix ("kilo" or "kibi"). If false,
139 "Ki"). If true, use the full prefix ("kilo" or "kibi"). If false,
143 use abbreviation ("k" or "Ki").
140 use abbreviation ("k" or "Ki").
144
141
145 """
142 """
146
143
147 if not binary:
144 if not binary:
148 base = 1000
145 base = 1000
149 multiples = ('', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
146 multiples = ('', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
150 else:
147 else:
151 base = 1024
148 base = 1024
152 multiples = ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
149 multiples = ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
153
150
154 sign = ""
151 sign = ""
155 if size > 0:
152 if size > 0:
156 m = int(math.log(size, base))
153 m = int(math.log(size, base))
157 elif size < 0:
154 elif size < 0:
158 sign = "-"
155 sign = "-"
159 size = -size
156 size = -size
160 m = int(math.log(size, base))
157 m = int(math.log(size, base))
161 else:
158 else:
162 m = 0
159 m = 0
163 if m > 8:
160 if m > 8:
164 m = 8
161 m = 8
165
162
166 if m == 0:
163 if m == 0:
167 precision = '%.0f'
164 precision = '%.0f'
168 else:
165 else:
169 precision = '%%.%df' % precision
166 precision = '%%.%df' % precision
170
167
171 size = precision % (size / math.pow(base, m))
168 size = precision % (size / math.pow(base, m))
172
169
173 return '%s%s %s%s' % (sign, size.strip(), multiples[m], unit)
170 return '%s%s %s%s' % (sign, size.strip(), multiples[m], unit)
174
171
175
172
176 def _check_memory_usage(worker):
173 def _check_memory_usage(worker):
177 memory_max_usage = worker._memory_max_usage
174 memory_max_usage = worker._memory_max_usage
178 if not memory_max_usage:
175 if not memory_max_usage:
179 return
176 return
180
177
181 memory_usage_check_interval = worker._memory_usage_check_interval
178 memory_usage_check_interval = worker._memory_usage_check_interval
182 memory_usage_recovery_threshold = memory_max_usage * worker._memory_usage_recovery_threshold
179 memory_usage_recovery_threshold = memory_max_usage * worker._memory_usage_recovery_threshold
183
180
184 elapsed = time.time() - worker._last_memory_check_time
181 elapsed = time.time() - worker._last_memory_check_time
185 if elapsed > memory_usage_check_interval:
182 if elapsed > memory_usage_check_interval:
186 mem_usage = _get_process_rss()
183 mem_usage = _get_process_rss()
187 if mem_usage and mem_usage > memory_max_usage:
184 if mem_usage and mem_usage > memory_max_usage:
188 worker.log.info(
185 worker.log.info(
189 "memory usage %s > %s, forcing gc",
186 "memory usage %s > %s, forcing gc",
190 _format_data_size(mem_usage), _format_data_size(memory_max_usage))
187 _format_data_size(mem_usage), _format_data_size(memory_max_usage))
191 # Try to clean it up by forcing a full collection.
188 # Try to clean it up by forcing a full collection.
192 gc.collect()
189 gc.collect()
193 mem_usage = _get_process_rss()
190 mem_usage = _get_process_rss()
194 if mem_usage > memory_usage_recovery_threshold:
191 if mem_usage > memory_usage_recovery_threshold:
195 # Didn't clean up enough, we'll have to terminate.
192 # Didn't clean up enough, we'll have to terminate.
196 worker.log.warning(
193 worker.log.warning(
197 "memory usage %s > %s after gc, quitting",
194 "memory usage %s > %s after gc, quitting",
198 _format_data_size(mem_usage), _format_data_size(memory_max_usage))
195 _format_data_size(mem_usage), _format_data_size(memory_max_usage))
199 # This will cause worker to auto-restart itself
196 # This will cause worker to auto-restart itself
200 worker.alive = False
197 worker.alive = False
201 worker._last_memory_check_time = time.time()
198 worker._last_memory_check_time = time.time()
202
199
203
200
204 def worker_int(worker):
201 def worker_int(worker):
205 worker.log.info("[%-10s] worker received INT or QUIT signal", worker.pid)
202 worker.log.info("[%-10s] worker received INT or QUIT signal", worker.pid)
206
203
207 # get traceback info, on worker crash
204 # get traceback info, on worker crash
208 id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
205 id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
209 code = []
206 code = []
210 for thread_id, stack in sys._current_frames().items():
207 for thread_id, stack in sys._current_frames().items():
211 code.append(
208 code.append(
212 "\n# Thread: %s(%d)" % (id2name.get(thread_id, ""), thread_id))
209 "\n# Thread: %s(%d)" % (id2name.get(thread_id, ""), thread_id))
213 for fname, lineno, name, line in traceback.extract_stack(stack):
210 for fname, lineno, name, line in traceback.extract_stack(stack):
214 code.append('File: "%s", line %d, in %s' % (fname, lineno, name))
211 code.append('File: "%s", line %d, in %s' % (fname, lineno, name))
215 if line:
212 if line:
216 code.append(" %s" % (line.strip()))
213 code.append(" %s" % (line.strip()))
217 worker.log.debug("\n".join(code))
214 worker.log.debug("\n".join(code))
218
215
219
216
220 def worker_abort(worker):
217 def worker_abort(worker):
221 worker.log.info("[%-10s] worker received SIGABRT signal", worker.pid)
218 worker.log.info("[%-10s] worker received SIGABRT signal", worker.pid)
222
219
223
220
224 def worker_exit(server, worker):
221 def worker_exit(server, worker):
225 worker.log.info("[%-10s] worker exit", worker.pid)
222 worker.log.info("[%-10s] worker exit", worker.pid)
226
223
227
224
228 def child_exit(server, worker):
225 def child_exit(server, worker):
229 worker.log.info("[%-10s] worker child exit", worker.pid)
226 worker.log.info("[%-10s] worker child exit", worker.pid)
230
227
231
228
232 def pre_request(worker, req):
229 def pre_request(worker, req):
233 worker.start_time = time.time()
230 worker.start_time = time.time()
234 worker.log.debug(
231 worker.log.debug(
235 "GNCRN PRE WORKER [cnt:%s]: %s %s", worker.nr, req.method, req.path)
232 "GNCRN PRE WORKER [cnt:%s]: %s %s", worker.nr, req.method, req.path)
236
233
237
234
238 def post_request(worker, req, environ, resp):
235 def post_request(worker, req, environ, resp):
239 total_time = time.time() - worker.start_time
236 total_time = time.time() - worker.start_time
240 # Gunicorn sometimes has problems with reading the status_code
237 # Gunicorn sometimes has problems with reading the status_code
241 status_code = getattr(resp, 'status_code', '')
238 status_code = getattr(resp, 'status_code', '')
242 worker.log.debug(
239 worker.log.debug(
243 "GNCRN POST WORKER [cnt:%s]: %s %s resp: %s, Load Time: %.4fs",
240 "GNCRN POST WORKER [cnt:%s]: %s %s resp: %s, Load Time: %.4fs",
244 worker.nr, req.method, req.path, status_code, total_time)
241 worker.nr, req.method, req.path, status_code, total_time)
245 _check_memory_usage(worker)
242 _check_memory_usage(worker)
246
243
247
244
248 class RhodeCodeLogger(Logger):
245 class RhodeCodeLogger(Logger):
249 """
246 """
250 Custom Logger that allows some customization that gunicorn doesn't allow
247 Custom Logger that allows some customization that gunicorn doesn't allow
251 """
248 """
252
249
253 datefmt = r"%Y-%m-%d %H:%M:%S"
250 datefmt = r"%Y-%m-%d %H:%M:%S"
254
251
255 def __init__(self, cfg):
252 def __init__(self, cfg):
256 Logger.__init__(self, cfg)
253 Logger.__init__(self, cfg)
257
254
258 def now(self):
255 def now(self):
259 """ return date in RhodeCode Log format """
256 """ return date in RhodeCode Log format """
260 now = time.time()
257 now = time.time()
261 msecs = int((now - long(now)) * 1000)
258 msecs = int((now - long(now)) * 1000)
262 return time.strftime(self.datefmt, time.localtime(now)) + '.{0:03d}'.format(msecs)
259 return time.strftime(self.datefmt, time.localtime(now)) + '.{0:03d}'.format(msecs)
263
260
264
261
265 logger_class = RhodeCodeLogger
262 logger_class = RhodeCodeLogger
General Comments 0
You need to be logged in to leave comments. Login now