##// END OF EJS Templates
release: merge back stable branch into default
release: merge back stable branch into default

File last commit:

r808:418e3613 default
r890:3c1446c0 merge default
Show More
gunicorn_config.py
265 lines | 7.7 KiB | text/x-python | PythonLexer
/ configs / gunicorn_config.py
config: added example gunicorn configuration
r476 """
gunicorn: updated gunicorn config based on release changes
r808 Gunicorn config extension and hooks. This config file adds some extra settings and memory management.
Gunicorn configuration should be managed by .ini files entries of RhodeCode or VCSServer
config: added example gunicorn configuration
r476 """
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 import gc
import os
import sys
gunicorn: updated gunicorn config to add memory monitoring
r761 import math
config: added example gunicorn configuration
r476 import time
import threading
import traceback
gunicorn: updated gunicorn config to add memory monitoring
r761 import random
config: added example gunicorn configuration
r476 from gunicorn.glogging import Logger
gunicorn: updated gunicorn config to add memory monitoring
r761 def get_workers():
import multiprocessing
return multiprocessing.cpu_count() * 2 + 1
config: added example gunicorn configuration
r476 # GLOBAL
errorlog = '-'
accesslog = '-'
# SERVER MECHANICS
# None == system temp dir
# worker_tmp_dir is recommended to be set to some tmpfs
worker_tmp_dir = None
tmp_upload_dir = None
# Custom log format
access_log_format = (
gunicorn: updated gunicorn config to add memory monitoring
r761 '%(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"')
config: added example gunicorn configuration
r476
# self adjust workers based on CPU count
gunicorn: updated gunicorn config to add memory monitoring
r761 # workers = get_workers()
config: added example gunicorn configuration
r476
gunicorn: updated gunicorn config to add memory monitoring
r761 def _get_process_rss(pid=None):
try:
import psutil
if pid:
proc = psutil.Process(pid)
else:
proc = psutil.Process()
return proc.memory_info().rss
except Exception:
return None
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 def _get_config(ini_path):
try:
import configparser
except ImportError:
import ConfigParser as configparser
try:
config: updated gunicorn config
r807 config = configparser.RawConfigParser()
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 config.read(ini_path)
return config
except Exception:
return None
def _time_with_offset(memory_usage_check_interval):
return time.time() - random.randint(0, memory_usage_check_interval/2.0)
config: added example gunicorn configuration
r476
def pre_fork(server, worker):
pass
gunicorn: updated gunicorn config to add memory monitoring
r761 def post_fork(server, worker):
configs: moved most of configuration back to .ini files instead of gunicorn file
r801
# memory spec defaults
_memory_max_usage = 0
_memory_usage_check_interval = 60
_memory_usage_recovery_threshold = 0.8
ini_path = os.path.abspath(server.cfg.paste)
conf = _get_config(ini_path)
config: updated gunicorn config
r807
section = 'server:main'
if conf and conf.has_section(section):
configs: moved most of configuration back to .ini files instead of gunicorn file
r801
config: updated gunicorn config
r807 if conf.has_option(section, 'memory_max_usage'):
_memory_max_usage = conf.getint(section, 'memory_max_usage')
if conf.has_option(section, 'memory_usage_check_interval'):
_memory_usage_check_interval = conf.getint(section, 'memory_usage_check_interval')
if conf.has_option(section, 'memory_usage_recovery_threshold'):
_memory_usage_recovery_threshold = conf.getfloat(section, 'memory_usage_recovery_threshold')
configs: moved most of configuration back to .ini files instead of gunicorn file
r801
worker._memory_max_usage = _memory_max_usage
worker._memory_usage_check_interval = _memory_usage_check_interval
worker._memory_usage_recovery_threshold = _memory_usage_recovery_threshold
gunicorn: updated gunicorn config to add memory monitoring
r761 # register memory last check time, with some random offset so we don't recycle all
# at once
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 worker._last_memory_check_time = _time_with_offset(_memory_usage_check_interval)
if _memory_max_usage:
server.log.info("[%-10s] WORKER spawned with max memory set at %s", worker.pid,
_format_data_size(_memory_max_usage))
else:
server.log.info("[%-10s] WORKER spawned", worker.pid)
gunicorn: updated gunicorn config to add memory monitoring
r761
config: added example gunicorn configuration
r476 def pre_exec(server):
server.log.info("Forked child, re-executing.")
def on_starting(server):
gunicorn: updated gunicorn config to add memory monitoring
r761 server_lbl = '{} {}'.format(server.proc_name, server.address)
server.log.info("Server %s is starting.", server_lbl)
config: added example gunicorn configuration
r476
def when_ready(server):
gunicorn: updated gunicorn config to add memory monitoring
r761 server.log.info("Server %s is ready. Spawning workers", server)
config: added example gunicorn configuration
r476
def on_reload(server):
pass
gunicorn: updated gunicorn config to add memory monitoring
r761 def _format_data_size(size, unit="B", precision=1, binary=True):
"""Format a number using SI units (kilo, mega, etc.).
``size``: The number as a float or int.
``unit``: The unit name in plural form. Examples: "bytes", "B".
``precision``: How many digits to the right of the decimal point. Default
is 1. 0 suppresses the decimal point.
``binary``: If false, use base-10 decimal prefixes (kilo = K = 1000).
If true, use base-2 binary prefixes (kibi = Ki = 1024).
``full_name``: If false (default), use the prefix abbreviation ("k" or
"Ki"). If true, use the full prefix ("kilo" or "kibi"). If false,
use abbreviation ("k" or "Ki").
"""
if not binary:
base = 1000
multiples = ('', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
else:
base = 1024
multiples = ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
sign = ""
if size > 0:
m = int(math.log(size, base))
elif size < 0:
sign = "-"
size = -size
m = int(math.log(size, base))
else:
m = 0
if m > 8:
m = 8
if m == 0:
precision = '%.0f'
else:
precision = '%%.%df' % precision
size = precision % (size / math.pow(base, m))
return '%s%s %s%s' % (sign, size.strip(), multiples[m], unit)
def _check_memory_usage(worker):
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 memory_max_usage = worker._memory_max_usage
if not memory_max_usage:
return
gunicorn: updated gunicorn config to add memory monitoring
r761
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 memory_usage_check_interval = worker._memory_usage_check_interval
memory_usage_recovery_threshold = memory_max_usage * worker._memory_usage_recovery_threshold
gunicorn: updated gunicorn config to add memory monitoring
r761
elapsed = time.time() - worker._last_memory_check_time
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 if elapsed > memory_usage_check_interval:
gunicorn: updated gunicorn config to add memory monitoring
r761 mem_usage = _get_process_rss()
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 if mem_usage and mem_usage > memory_max_usage:
gunicorn: updated gunicorn config to add memory monitoring
r761 worker.log.info(
"memory usage %s > %s, forcing gc",
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 _format_data_size(mem_usage), _format_data_size(memory_max_usage))
gunicorn: updated gunicorn config to add memory monitoring
r761 # Try to clean it up by forcing a full collection.
gc.collect()
mem_usage = _get_process_rss()
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 if mem_usage > memory_usage_recovery_threshold:
gunicorn: updated gunicorn config to add memory monitoring
r761 # Didn't clean up enough, we'll have to terminate.
worker.log.warning(
"memory usage %s > %s after gc, quitting",
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 _format_data_size(mem_usage), _format_data_size(memory_max_usage))
gunicorn: updated gunicorn config to add memory monitoring
r761 # This will cause worker to auto-restart itself
worker.alive = False
worker._last_memory_check_time = time.time()
config: added example gunicorn configuration
r476 def worker_int(worker):
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 worker.log.info("[%-10s] worker received INT or QUIT signal", worker.pid)
config: added example gunicorn configuration
r476
# get traceback info, on worker crash
id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
code = []
for thread_id, stack in sys._current_frames().items():
code.append(
"\n# Thread: %s(%d)" % (id2name.get(thread_id, ""), thread_id))
for fname, lineno, name, line in traceback.extract_stack(stack):
code.append('File: "%s", line %d, in %s' % (fname, lineno, name))
if line:
code.append(" %s" % (line.strip()))
worker.log.debug("\n".join(code))
def worker_abort(worker):
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 worker.log.info("[%-10s] worker received SIGABRT signal", worker.pid)
config: added example gunicorn configuration
r476
def worker_exit(server, worker):
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 worker.log.info("[%-10s] worker exit", worker.pid)
config: added example gunicorn configuration
r476
def child_exit(server, worker):
configs: moved most of configuration back to .ini files instead of gunicorn file
r801 worker.log.info("[%-10s] worker child exit", worker.pid)
config: added example gunicorn configuration
r476
def pre_request(worker, req):
worker.start_time = time.time()
worker.log.debug(
"GNCRN PRE WORKER [cnt:%s]: %s %s", worker.nr, req.method, req.path)
def post_request(worker, req, environ, resp):
total_time = time.time() - worker.start_time
gunicorn: updated gunicorn config based on release changes
r808 # Gunicorn sometimes has problems with reading the status_code
status_code = getattr(resp, 'status_code', '')
config: added example gunicorn configuration
r476 worker.log.debug(
core: added more accurate time measurements
r737 "GNCRN POST WORKER [cnt:%s]: %s %s resp: %s, Load Time: %.4fs",
gunicorn: updated gunicorn config based on release changes
r808 worker.nr, req.method, req.path, status_code, total_time)
gunicorn: updated gunicorn config to add memory monitoring
r761 _check_memory_usage(worker)
config: added example gunicorn configuration
r476
class RhodeCodeLogger(Logger):
"""
Custom Logger that allows some customization that gunicorn doesn't allow
"""
datefmt = r"%Y-%m-%d %H:%M:%S"
def __init__(self, cfg):
Logger.__init__(self, cfg)
def now(self):
""" return date in RhodeCode Log format """
now = time.time()
msecs = int((now - long(now)) * 1000)
return time.strftime(self.datefmt, time.localtime(now)) + '.{0:03d}'.format(msecs)
logger_class = RhodeCodeLogger