summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGraham Dumpleton <Graham.Dumpleton@gmail.com>2021-02-13 13:00:44 +1100
committerGraham Dumpleton <Graham.Dumpleton@gmail.com>2021-02-13 13:00:44 +1100
commit05c83ae5b12ee2f86300f93cf0379880cc1b2e99 (patch)
treea6710cbe1dd61cda859bcd987fbbf664fc362aab /src
parenta5bf2f7064be76e69e06b901a32e0352567f7362 (diff)
parentdc77690076c3bb30ce69c64aed1467edd37e545a (diff)
downloadmod_wsgi-05c83ae5b12ee2f86300f93cf0379880cc1b2e99.tar.gz
Merge and fix Windows path issues.
Diffstat (limited to 'src')
-rw-r--r--src/server/__init__.py158
-rw-r--r--src/server/management/commands/runmodwsgi.py11
-rw-r--r--src/server/mod_wsgi.c148
-rw-r--r--src/server/wsgi_interp.c24
-rw-r--r--src/server/wsgi_logger.c4
-rw-r--r--src/server/wsgi_metrics.c657
-rw-r--r--src/server/wsgi_metrics.h9
-rw-r--r--src/server/wsgi_python.h2
-rwxr-xr-xsrc/server/wsgi_version.h6
9 files changed, 865 insertions, 154 deletions
diff --git a/src/server/__init__.py b/src/server/__init__.py
index a496d45..e3a63b7 100644
--- a/src/server/__init__.py
+++ b/src/server/__init__.py
@@ -350,6 +350,7 @@ WSGISocketRotation Off
</IfDefine>
<IfDefine !ONE_PROCESS>
+<IfDefine !EMBEDDED_MODE>
WSGIRestrictEmbedded On
<IfDefine MOD_WSGI_MULTIPROCESS>
WSGIDaemonProcess %(host)s:%(port)s \\
@@ -412,11 +413,22 @@ WSGIDaemonProcess %(host)s:%(port)s \\
server-metrics=%(server_metrics_flag)s
</IfDefine>
</IfDefine>
+</IfDefine>
WSGICallableObject '%(callable_object)s'
WSGIPassAuthorization On
WSGIMapHEADToGET %(map_head_to_get)s
+<IfDefine MOD_WSGI_DISABLE_RELOADING>
+WSGIScriptReloading Off
+</IfDefine>
+
+<IfDefine EMBEDDED_MODE>
+<IfDefine MOD_WSGI_WITH_PYTHON_PATH>
+WSGIPythonPath '%(python_path)s'
+</IfDefine>
+</IfDefine>
+
<IfDefine ONE_PROCESS>
WSGIRestrictStdin Off
<IfDefine MOD_WSGI_WITH_PYTHON_PATH>
@@ -815,11 +827,20 @@ WSGIErrorOverride On
</IfDefine>
<IfDefine !ONE_PROCESS>
+<IfDefine !EMBEDDED_MODE>
WSGIHandlerScript wsgi-handler '%(server_root)s/handler.wsgi' \\
process-group='%(host)s:%(port)s' application-group=%%{GLOBAL}
WSGIImportScript '%(server_root)s/handler.wsgi' \\
process-group='%(host)s:%(port)s' application-group=%%{GLOBAL}
</IfDefine>
+</IfDefine>
+
+<IfDefine EMBEDDED_MODE>
+WSGIHandlerScript wsgi-handler '%(server_root)s/handler.wsgi' \\
+ process-group='%%{GLOBAL}' application-group=%%{GLOBAL}
+WSGIImportScript '%(server_root)s/handler.wsgi' \\
+ process-group='%%{GLOBAL}' application-group=%%{GLOBAL}
+</IfDefine>
<IfDefine ONE_PROCESS>
<IfDefine !MOD_WSGI_MPM_ENABLE_WINNT_MODULE>
@@ -1597,6 +1618,7 @@ mount_point = '%(mount_point)s'
with_newrelic_agent = %(with_newrelic_agent)s
newrelic_config_file = '%(newrelic_config_file)s'
newrelic_environment = '%(newrelic_environment)s'
+disable_reloading = %(disable_reloading)s
reload_on_changes = %(reload_on_changes)s
debug_mode = %(debug_mode)s
enable_debugger = %(enable_debugger)s
@@ -1675,10 +1697,12 @@ handler = mod_wsgi.server.ApplicationHandler(entry_point,
debugger_startup=debugger_startup, enable_recorder=enable_recorder,
recorder_directory=recorder_directory)
-reload_required = handler.reload_required
+if not disable_reloading:
+ reload_required = handler.reload_required
+
handle_request = handler.handle_request
-if reload_on_changes and not debug_mode:
+if not disable_reloading and reload_on_changes and not debug_mode:
mod_wsgi.server.start_reloader()
"""
@@ -1874,6 +1898,8 @@ ENABLE_GDB="%(enable_gdb)s"
PROCESS_NAME="%(process_name)s"
+cd $MOD_WSGI_WORKING_DIRECTORY
+
case $ACMD in
start|stop|restart|graceful|graceful-stop)
if [ "x$ENABLE_GDB" != "xTrue" ]; then
@@ -2037,32 +2063,37 @@ option_list = (
optparse.make_option('--threads', type='int', default=5, metavar='NUMBER',
help='The number of threads in the request thread pool of '
'each process for handling requests. Defaults to 5 in each '
- 'process.'),
+ 'process. Note that if embedded mode and only prefork MPM '
+ 'is available, then processes will instead be used.'),
optparse.make_option('--max-clients', type='int', default=None,
metavar='NUMBER', help='The maximum number of simultaneous '
'client connections that will be accepted. This will default '
'to being 1.5 times the total number of threads in the '
- 'request thread pools across all process handling requests.'),
+ 'request thread pools across all process handling requests. '
+ 'Note that if embedded mode is used this will be ignored.'),
optparse.make_option('--initial-workers', type='float', default=None,
metavar='NUMBER', action='callback', callback=check_percentage,
help='The initial number of workers to create on startup '
'expressed as a percentage of the maximum number of clients. '
'The value provided should be between 0 and 1. The default is '
- 'dependent on the type of MPM being used.'),
+ 'dependent on the type of MPM being used. Note that if '
+ 'embedded mode is used, this will be ignored.'),
optparse.make_option('--minimum-spare-workers', type='float',
default=None, metavar='NUMBER', action='callback',
callback=check_percentage, help='The minimum number of spare '
'workers to maintain expressed as a percentage of the maximum '
'number of clients. The value provided should be between 0 and '
- '1. The default is dependent on the type of MPM being used.'),
+ '1. The default is dependent on the type of MPM being used. '
+ 'Note that if embedded mode is used, this will be ignored.'),
optparse.make_option('--maximum-spare-workers', type='float',
default=None, metavar='NUMBER', action='callback',
callback=check_percentage, help='The maximum number of spare '
'workers to maintain expressed as a percentage of the maximum '
'number of clients. The value provided should be between 0 and '
- '1. The default is dependent on the type of MPM being used.'),
+ '1. The default is dependent on the type of MPM being used. '
+ 'Note that if embedded mode is used, this will be ignored.'),
optparse.make_option('--limit-request-body', type='int', default=10485760,
metavar='NUMBER', help='The maximum number of bytes which are '
@@ -2249,13 +2280,21 @@ option_list = (
'only be enabled if the operating system kernel and file system '
'type where files are hosted supports it.'),
+ optparse.make_option('--disable-reloading', action='store_true',
+ default=False, help='Disables all reloading of daemon processes '
+ 'due to changes to the file containing the WSGI application '
+ 'entrypoint, or any other loaded source files. This has no '
+ 'effect when embedded mode is used as reloading is automatically '
+ 'disabled for embedded mode.'),
+
optparse.make_option('--reload-on-changes', action='store_true',
default=False, help='Flag indicating whether worker processes '
'should be automatically restarted when any Python code file '
'loaded by the WSGI application has been modified. Defaults to '
'being disabled. When reloading on any code changes is disabled, '
- 'the worker processes will still though be reloaded if the '
- 'WSGI script file itself is modified.'),
+ 'unless all reloading is also disabled, the worker processes '
+ 'will still though be reloaded if the file containing the WSGI '
+ 'application entrypoint is modified.'),
optparse.make_option('--user', default=default_run_user(),
metavar='USERNAME', help='When being run by the root user, '
@@ -2336,11 +2375,12 @@ option_list = (
help='The IP address or subnet corresponding to any trusted '
'proxy.'),
- optparse.make_option('--keep-alive-timeout', type='int', default=0,
+ optparse.make_option('--keep-alive-timeout', type='int', default=2,
metavar='SECONDS', help='The number of seconds which a client '
'connection will be kept alive to allow subsequent requests '
- 'to be made over the same connection. Defaults to 0, indicating '
- 'that keep alive connections are disabled.'),
+ 'to be made over the same connection when a keep alive '
+ 'connection is requested. Defaults to 2, indicating that keep '
+ 'alive connections are set for 2 seconds.'),
optparse.make_option('--compress-responses', action='store_true',
default=False, help='Flag indicating whether responses for '
@@ -2587,6 +2627,11 @@ option_list = (
help='Specify the name of a separate log file to be used for '
'the managed service.'),
+ optparse.make_option('--embedded-mode', action='store_true', default=False,
+ help='Flag indicating whether to run in embedded mode rather '
+ 'than the default daemon mode. Numerous daemon mode specific '
+ 'features will not operate when this mode is used.'),
+
optparse.make_option('--enable-docs', action='store_true', default=False,
help='Flag indicating whether the mod_wsgi documentation should '
'be made available at the /__wsgi__/docs sub URL.'),
@@ -2651,16 +2696,16 @@ option_list = (
'being started separately using the generated \'apachectl\' '
'script.'),
- optparse.make_option('--isatty', action='store_true', default=False,
- help='Flag indicating whether should assume being run in an '
- 'interactive terminal session. In this case Apache will not '
- 'replace this wrapper script, but will be run as a sub process.'
- 'Signals such as SIGINT, SIGTERM, SIGHUP and SIGUSR1 will be '
- 'forwarded onto Apache, but SIGWINCH will be blocked so that '
- 'resizing of a terminal session window will not cause Apache '
- 'to shutdown. This is a separate option at this time rather '
- 'than being determined automatically while the reliability of '
- 'intercepting and forwarding signals is verified.'),
+ # optparse.make_option('--isatty', action='store_true', default=False,
+ # help='Flag indicating whether should assume being run in an '
+ # 'interactive terminal session. In this case Apache will not '
+ # 'replace this wrapper script, but will be run as a sub process.'
+ # 'Signals such as SIGINT, SIGTERM, SIGHUP and SIGUSR1 will be '
+ # 'forwarded onto Apache, but SIGWINCH will be blocked so that '
+ # 'resizing of a terminal session window will not cause Apache '
+ # 'to shutdown. This is a separate option at this time rather '
+ # 'than being determined automatically while the reliability of '
+ # 'intercepting and forwarding signals is verified.'),
)
def cmd_setup_server(params):
@@ -2797,10 +2842,10 @@ def _cmd_setup_server(command, args, options):
options['auth_group_script'] = posixpath.abspath(
options['auth_group_script'])
- options['documentation_directory'] = posixpath.join(os.path.dirname(
- posixpath.dirname(__file__)), 'docs')
+ options['documentation_directory'] = os.path.join(os.path.dirname(
+ os.path.dirname(__file__)), 'docs')
options['images_directory'] = os.path.join(os.path.dirname(
- posixpath.dirname(__file__)), 'images')
+ os.path.dirname(__file__)), 'images')
if os.path.exists(posixpath.join(options['documentation_directory'],
'index.html')):
@@ -2934,6 +2979,17 @@ def _cmd_setup_server(command, args, options):
if options['python_paths'] is None:
options['python_paths'] = []
+ if options['debug_mode'] or options['embedded_mode']:
+ if options['working_directory'] not in options['python_paths']:
+ options['python_paths'].insert(0, options['working_directory'])
+
+ if options['debug_mode']:
+ options['server_mpm_variables'] = ['worker', 'prefork']
+
+ elif options['embedded_mode']:
+ if not options['server_mpm_variables']:
+ options['server_mpm_variables'] = ['worker', 'prefork']
+
# Special case to check for when being executed from shiv variant
# of a zipapp application bundle. We need to work out where the
# site packages directory is and pass it with Python module search
@@ -3015,6 +3071,9 @@ def _cmd_setup_server(command, args, options):
service_scripts.append((name, script))
options['service_scripts'] = service_scripts
+ # Node that all the below calculations are overridden if are using
+ # embedded mode.
+
max_clients = options['processes'] * options['threads']
if options['max_clients'] is not None:
@@ -3094,6 +3153,23 @@ def _cmd_setup_server(command, args, options):
int(worker_max_spare_workers * options['worker_server_limit']) *
options['worker_threads_per_child'])
+ if options['embedded_mode']:
+ max_clients = options['processes'] * options['threads']
+
+ options['prefork_max_clients'] = max_clients
+ options['prefork_server_limit'] = max_clients
+ options['prefork_start_servers'] = max_clients
+ options['prefork_min_spare_servers'] = max_clients
+ options['prefork_max_spare_servers'] = max_clients
+
+ options['worker_max_clients'] = max_clients
+ options['worker_server_limit'] = options['processes']
+ options['worker_thread_limit'] = options['threads']
+ options['worker_threads_per_child'] = options['threads']
+ options['worker_start_servers'] = options['processes']
+ options['worker_min_spare_threads'] = max_clients
+ options['worker_max_spare_threads'] = max_clients
+
options['httpd_conf'] = posixpath.join(options['server_root'], 'httpd.conf')
options['httpd_executable'] = os.environ.get('HTTPD',
@@ -3203,6 +3279,10 @@ def _cmd_setup_server(command, args, options):
else:
options['https_url'] = None
+ if options['embedded_mode']:
+ options['httpd_arguments_list'].append('-DEMBEDDED_MODE')
+ options['disable_reloading'] = True
+
if any((options['enable_debugger'], options['enable_coverage'],
options['enable_profiler'], options['enable_recorder'],
options['enable_gdb'])):
@@ -3342,6 +3422,8 @@ def _cmd_setup_server(command, args, options):
options['httpd_arguments_list'].append('-DMOD_WSGI_WITH_PYTHON_PATH')
if options['socket_prefix']:
options['httpd_arguments_list'].append('-DMOD_WSGI_WITH_SOCKET_PREFIX')
+ if options['disable_reloading']:
+ options['httpd_arguments_list'].append('-DMOD_WSGI_DISABLE_RELOADING')
if options['with_cgi']:
if os.path.exists(posixpath.join(options['modules_directory'],
@@ -3417,6 +3499,13 @@ def _cmd_setup_server(command, args, options):
print('Environ Variables :', options['server_root'] + '/envvars')
print('Control Script :', options['server_root'] + '/apachectl')
+ if options['debug_mode']:
+ print('Operating Mode : debug')
+ elif options['embedded_mode']:
+ print('Operating Mode : embedded')
+ else:
+ print('Operating Mode : daemon')
+
if options['processes'] == 1:
print('Request Capacity : %s (%s process * %s threads)' % (
options['processes']*options['threads'],
@@ -3426,17 +3515,18 @@ def _cmd_setup_server(command, args, options):
options['processes']*options['threads'],
options['processes'], options['threads']))
- print('Request Timeout : %s (seconds)' % options['request_timeout'])
+ if not options['debug_mode'] and not options['embedded_mode']:
+ print('Request Timeout : %s (seconds)' % options['request_timeout'])
- if options['startup_timeout']:
- print('Startup Timeout : %s (seconds)' % options['startup_timeout'])
+ if options['startup_timeout']:
+ print('Startup Timeout : %s (seconds)' % options['startup_timeout'])
- print('Queue Backlog : %s (connections)' % options['daemon_backlog'])
+ print('Queue Backlog : %s (connections)' % options['daemon_backlog'])
- print('Queue Timeout : %s (seconds)' % options['queue_timeout'])
+ print('Queue Timeout : %s (seconds)' % options['queue_timeout'])
- print('Server Capacity : %s (event/worker), %s (prefork)' % (
- options['worker_max_clients'], options['prefork_max_clients']))
+ print('Server Capacity : %s (event/worker), %s (prefork)' % (
+ options['worker_max_clients'], options['prefork_max_clients']))
print('Server Backlog : %s (connections)' % options['server_backlog'])
@@ -3493,7 +3583,7 @@ def cmd_start_server(params):
else:
executable = posixpath.join(config['server_root'], 'apachectl')
- if config['isatty'] and sys.stdout.isatty():
+ if sys.stdout.isatty():
process = None
def handler(signum, frame):
@@ -3652,7 +3742,7 @@ def main():
cmd_module_config(args)
elif command == 'module-location':
cmd_module_location(args)
- elif command == 'start-server':
+ elif command == 'x-start-server':
cmd_start_server(args)
else:
parser.error('Invalid command was specified.')
diff --git a/src/server/management/commands/runmodwsgi.py b/src/server/management/commands/runmodwsgi.py
index 9cb591a..f6d1392 100644
--- a/src/server/management/commands/runmodwsgi.py
+++ b/src/server/management/commands/runmodwsgi.py
@@ -73,10 +73,11 @@ class Command(BaseCommand):
module_name = '.'.join(fields[:-1])
callable_object = fields[-1]
- __import__(module_name)
-
- # script_file = inspect.getsourcefile(sys.modules[module_name])
- # args = [script_file]
+ # XXX Can't test import as loading the WSGI module may have
+ # side effects and run things that should only be run inside
+ # of the mod_wsgi process.
+ #
+ # __import__(module_name)
options['application_type'] = 'module'
options['callable_object'] = callable_object
@@ -139,7 +140,7 @@ class Command(BaseCommand):
executable = os.path.join(options['server_root'], 'apachectl')
name = executable.ljust(len(options['process_name']))
- if options['isatty'] and sys.stdout.isatty():
+ if sys.stdout.isatty():
process = None
def handler(signum, frame):
diff --git a/src/server/mod_wsgi.c b/src/server/mod_wsgi.c
index d3c6302..04e3069 100644
--- a/src/server/mod_wsgi.c
+++ b/src/server/mod_wsgi.c
@@ -3316,6 +3316,8 @@ static int Adapter_run(AdapterObject *self, PyObject *object)
/* Publish event for the end of the request. */
+ finish_time = apr_time_now();
+
if (wsgi_event_subscribers()) {
double application_time = 0.0;
double output_time = 0.0;
@@ -3360,8 +3362,6 @@ static int Adapter_run(AdapterObject *self, PyObject *object)
if (output_time < 0.0)
output_time = 0.0;
- finish_time = apr_time_now();
-
application_time = apr_time_sec((double)finish_time-self->start_time);
if (application_time < 0.0)
@@ -3422,6 +3422,18 @@ static int Adapter_run(AdapterObject *self, PyObject *object)
}
/*
+ * Record server and application time for metrics. Values
+ * are the time request first accepted by child workers,
+ * the time that the WSGI application started processing
+ * the request, and when the WSGI application finished the
+ * request.
+ */
+
+ wsgi_record_request_times(self->config->request_start,
+ self->config->queue_start, self->config->daemon_start,
+ self->start_time, finish_time);
+
+ /*
* If result indicates an internal server error, then
* replace the status line in the request object else
* that provided by the application will be what is used
@@ -3756,13 +3768,16 @@ static PyObject *wsgi_load_source(apr_pool_t *pool, request_rec *r,
if (!r || strcmp(r->filename, filename)) {
apr_finfo_t finfo;
- if (apr_stat(&finfo, filename, APR_FINFO_NORM,
- pool) != APR_SUCCESS) {
+ apr_status_t status;
+
+ Py_BEGIN_ALLOW_THREADS
+ status = apr_stat(&finfo, filename, APR_FINFO_NORM, pool);
+ Py_END_ALLOW_THREADS
+
+ if (status != APR_SUCCESS)
object = PyLong_FromLongLong(0);
- }
- else {
+ else
object = PyLong_FromLongLong(finfo.mtime);
- }
}
else {
object = PyLong_FromLongLong(r->finfo.mtime);
@@ -3825,13 +3840,16 @@ static int wsgi_reload_required(apr_pool_t *pool, request_rec *r,
if (!r || strcmp(r->filename, filename)) {
apr_finfo_t finfo;
- if (apr_stat(&finfo, filename, APR_FINFO_NORM,
- pool) != APR_SUCCESS) {
+ apr_status_t status;
+
+ Py_BEGIN_ALLOW_THREADS
+ status = apr_stat(&finfo, filename, APR_FINFO_NORM, pool);
+ Py_END_ALLOW_THREADS
+
+ if (status != APR_SUCCESS)
return 1;
- }
- else if (mtime != finfo.mtime) {
+ else if (mtime != finfo.mtime)
return 1;
- }
}
else {
if (mtime != r->finfo.mtime)
@@ -3924,21 +3942,6 @@ static int wsgi_execute_script(request_rec *r)
config = (WSGIRequestConfig *)ap_get_module_config(r->request_config,
&wsgi_module);
- /*
- * Acquire the desired python interpreter. Once this is done
- * it is safe to start manipulating python objects.
- */
-
- interp = wsgi_acquire_interpreter(config->application_group);
-
- if (!interp) {
- ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
- "mod_wsgi (pid=%d): Cannot acquire interpreter '%s'.",
- getpid(), config->application_group);
-
- return HTTP_INTERNAL_SERVER_ERROR;
- }
-
/* Setup startup timeout if first request and specified. */
#if defined(MOD_WSGI_WITH_DAEMONS)
@@ -3955,6 +3958,21 @@ static int wsgi_execute_script(request_rec *r)
#endif
/*
+ * Acquire the desired python interpreter. Once this is done
+ * it is safe to start manipulating python objects.
+ */
+
+ interp = wsgi_acquire_interpreter(config->application_group);
+
+ if (!interp) {
+ ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
+ "mod_wsgi (pid=%d): Cannot acquire interpreter '%s'.",
+ getpid(), config->application_group);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /*
* Use a lock around the check to see if the module is
* already loaded and the import of the module to prevent
* two request handlers trying to import the module at the
@@ -4098,16 +4116,18 @@ static int wsgi_execute_script(request_rec *r)
}
/*
- * When process reloading is in use need to indicate
- * that request content should now be sent through.
- * This is done by writing a special response header
- * directly out onto the appropriate network output
- * filter. The special response is picked up by
- * remote end and data will then be sent.
+ * When process reloading is in use, or a queue timeout is
+ * set, need to indicate that request content should now be
+ * sent through. This is done by writing a special response
+ * header directly out onto the appropriate network output
+ * filter. The special response is picked up by remote end
+ * and data will then be sent.
*/
#if defined(MOD_WSGI_WITH_DAEMONS)
- if (*config->process_group) {
+ if (*config->process_group && (config->script_reloading ||
+ wsgi_daemon_process->group->queue_timeout != 0)) {
+
ap_filter_t *filters;
apr_bucket_brigade *bb;
apr_bucket *b;
@@ -4115,6 +4135,8 @@ static int wsgi_execute_script(request_rec *r)
const char *data = "Status: 200 Continue\r\n\r\n";
long length = strlen(data);
+ Py_BEGIN_ALLOW_THREADS
+
filters = r->output_filters;
while (filters && filters->frec->ftype != AP_FTYPE_NETWORK) {
filters = filters->next;
@@ -4138,6 +4160,8 @@ static int wsgi_execute_script(request_rec *r)
*/
ap_pass_brigade(filters, bb);
+
+ Py_END_ALLOW_THREADS
}
#endif
@@ -4742,8 +4766,11 @@ static const char *wsgi_add_script_alias(cmd_parms *cmd, void *mconfig,
WSGIScriptFile *object = NULL;
if (!wsgi_import_list) {
- wsgi_import_list = apr_array_make(sconfig->pool, 20,
+ wsgi_import_list = apr_array_make(cmd->pool, 20,
sizeof(WSGIScriptFile));
+ apr_pool_cleanup_register(cmd->pool, &wsgi_import_list,
+ ap_pool_cleanup_set_null,
+ apr_pool_cleanup_null);
}
object = (WSGIScriptFile *)apr_array_push(wsgi_import_list);
@@ -5236,6 +5263,9 @@ static const char *wsgi_add_import_script(cmd_parms *cmd, void *mconfig,
if (!wsgi_import_list) {
wsgi_import_list = apr_array_make(cmd->pool, 20,
sizeof(WSGIScriptFile));
+ apr_pool_cleanup_register(cmd->pool, &wsgi_import_list,
+ ap_pool_cleanup_set_null,
+ apr_pool_cleanup_null);
}
object = (WSGIScriptFile *)apr_array_push(wsgi_import_list);
@@ -7889,6 +7919,9 @@ static const char *wsgi_add_daemon_process(cmd_parms *cmd, void *mconfig,
if (!wsgi_daemon_list) {
wsgi_daemon_list = apr_array_make(cmd->pool, 20,
sizeof(WSGIProcessGroup));
+ apr_pool_cleanup_register(cmd->pool, &wsgi_daemon_list,
+ ap_pool_cleanup_set_null,
+ apr_pool_cleanup_null);
}
entries = (WSGIProcessGroup *)wsgi_daemon_list->elts;
@@ -10472,8 +10505,17 @@ static int wsgi_start_process(apr_pool_t *p, WSGIDaemonProcess *daemon)
#ifdef HAVE_FORK
if (wsgi_python_initialized) {
#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7)
+#if 0
+ /*
+ * XXX Appears to be wrong to call this at this point especially
+ * since we haven't acquired the GIL. It wouldn't have been possible
+ * for any user code to have registered a Python callback to run
+ * in parent after fork either. Leave in code for now but disabled.
+ */
+
PyOS_AfterFork_Parent();
#endif
+#endif
}
#endif
@@ -12024,13 +12066,16 @@ static int wsgi_execute_remote(request_rec *r)
}
/*
- * If process reload mechanism enabled, then we need to look
- * for marker indicating it is okay to transfer content, or
- * whether process is being restarted and that we should
- * therefore create a connection to daemon process again.
+ * If process reload mechanism enabled, or a queue timeout is
+ * specified, then we need to look for marker indicating it
+ * is okay to transfer content, or whether process is being
+ * restarted and that we should therefore create a
+ * connection to daemon process again.
*/
- if (*config->process_group) {
+ if (*config->process_group && (config->script_reloading ||
+ group->queue_timeout != 0)) {
+
int retries = 0;
int maximum = (2*group->processes)+1;
@@ -12618,6 +12663,8 @@ static int wsgi_hook_daemon_handler(conn_rec *c)
int queue_timeout_occurred = 0;
+ apr_time_t daemon_start = 0;
+
#if ! (AP_MODULE_MAGIC_AT_LEAST(20120211, 37) || \
(AP_SERVER_MAJORVERSION_NUMBER == 2 && \
AP_SERVER_MINORVERSION_NUMBER <= 2 && \
@@ -12631,6 +12678,14 @@ static int wsgi_hook_daemon_handler(conn_rec *c)
return DECLINED;
/*
+ * Mark this as start of daemon process even though connection
+ * setup has already been done. Otherwise need to carry through
+ * a time value somehow.
+ */
+
+ daemon_start = apr_time_now();
+
+ /*
* Remove all input/output filters except the core filters.
* This will ensure that any SSL filters we don't want are
* removed. This is a bit of a hack. Only other option is to
@@ -13100,7 +13155,7 @@ static int wsgi_hook_daemon_handler(conn_rec *c)
config->queue_start = 0.0;
}
- config->daemon_start = apr_time_now();
+ config->daemon_start = daemon_start;
apr_table_setn(r->subprocess_env, "mod_wsgi.daemon_start",
apr_psprintf(r->pool, "%" APR_TIME_T_FMT,
@@ -14660,9 +14715,11 @@ static authn_status wsgi_check_password(request_rec *r, const char *user,
*/
#if APR_HAS_THREADS
- Py_BEGIN_ALLOW_THREADS
- apr_thread_mutex_lock(wsgi_module_lock);
- Py_END_ALLOW_THREADS
+ if (config->script_reloading) {
+ Py_BEGIN_ALLOW_THREADS
+ apr_thread_mutex_lock(wsgi_module_lock);
+ Py_END_ALLOW_THREADS
+ }
#endif
modules = PyImport_GetModuleDict();
@@ -14706,7 +14763,8 @@ static authn_status wsgi_check_password(request_rec *r, const char *user,
/* Safe now to release the module lock. */
#if APR_HAS_THREADS
- apr_thread_mutex_unlock(wsgi_module_lock);
+ if (config->script_reloading)
+ apr_thread_mutex_unlock(wsgi_module_lock);
#endif
/* Log any details of exceptions if import failed. */
diff --git a/src/server/wsgi_interp.c b/src/server/wsgi_interp.c
index bdfef88..d6cfc43 100644
--- a/src/server/wsgi_interp.c
+++ b/src/server/wsgi_interp.c
@@ -467,6 +467,16 @@ InterpreterObject *newInterpreterObject(const char *name)
self->interp = interp;
self->owner = 0;
+
+ /* Force import of threading module so that main
+ * thread attribute of module is correctly set to
+ * the main thread and not a secondary request
+ * thread.
+ */
+
+ module = PyImport_ImportModule("threading");
+
+ Py_XDECREF(module);
}
else {
/*
@@ -511,9 +521,9 @@ InterpreterObject *newInterpreterObject(const char *name)
* interpreters.
*/
-#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 4)
module = PyImport_ImportModule("threading");
+#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 4)
if (module) {
PyObject *dict = NULL;
PyObject *func = NULL;
@@ -529,9 +539,9 @@ InterpreterObject *newInterpreterObject(const char *name)
Py_DECREF(wrapper);
}
}
+#endif
Py_XDECREF(module);
-#endif
}
/*
@@ -1276,11 +1286,19 @@ InterpreterObject *newInterpreterObject(const char *name)
PyModule_AddObject(module, "process_metrics", PyCFunction_New(
&wsgi_process_metrics_method[0], NULL));
+ PyModule_AddObject(module, "request_metrics", PyCFunction_New(
+ &wsgi_request_metrics_method[0], NULL));
+
PyModule_AddObject(module, "subscribe_events", PyCFunction_New(
- &wsgi_process_events_method[0], NULL));
+ &wsgi_subscribe_events_method[0], NULL));
+
+ PyModule_AddObject(module, "subscribe_shutdown", PyCFunction_New(
+ &wsgi_subscribe_shutdown_method[0], NULL));
PyModule_AddObject(module, "event_callbacks", PyList_New(0));
+ PyModule_AddObject(module, "shutdown_callbacks", PyList_New(0));
+
PyModule_AddObject(module, "active_requests", PyDict_New());
PyModule_AddObject(module, "request_data", PyCFunction_New(
diff --git a/src/server/wsgi_logger.c b/src/server/wsgi_logger.c
index 71c7d69..df35887 100644
--- a/src/server/wsgi_logger.c
+++ b/src/server/wsgi_logger.c
@@ -223,7 +223,7 @@ static PyObject *Log_isatty(LogObject *self, PyObject *args)
return Py_False;
}
-static void Log_queue(LogObject *self, const char *msg, int len)
+static void Log_queue(LogObject *self, const char *msg, Py_ssize_t len)
{
const char *p = NULL;
const char *q = NULL;
@@ -330,7 +330,7 @@ static void Log_queue(LogObject *self, const char *msg, int len)
static PyObject *Log_write(LogObject *self, PyObject *args)
{
const char *msg = NULL;
- int len = -1;
+ Py_ssize_t len = -1;
WSGIThreadInfo *thread_info = NULL;
diff --git a/src/server/wsgi_metrics.c b/src/server/wsgi_metrics.c
index b147d54..9a26302 100644
--- a/src/server/wsgi_metrics.c
+++ b/src/server/wsgi_metrics.c
@@ -45,11 +45,12 @@ static apr_time_t wsgi_utilization_last = 0;
apr_thread_mutex_t* wsgi_monitor_lock = NULL;
-static double wsgi_utilization_time(int adjustment)
+static double wsgi_utilization_time(int adjustment,
+ apr_uint64_t* request_count)
{
apr_time_t now;
double utilization = wsgi_thread_utilization;
-
+
apr_thread_mutex_lock(wsgi_monitor_lock);
now = apr_time_now();
@@ -71,11 +72,96 @@ static double wsgi_utilization_time(int adjustment)
if (adjustment < 0)
wsgi_total_requests += -adjustment;
+ if (request_count)
+ *request_count = wsgi_total_requests;
+
apr_thread_mutex_unlock(wsgi_monitor_lock);
return utilization;
}
+static int wsgi_request_metrics_enabled = 0;
+static apr_uint64_t wsgi_sample_requests = 0;
+static double wsgi_server_time_total = 0;
+static int wsgi_server_time_buckets[16];
+static double wsgi_queue_time_total = 0;
+static int wsgi_queue_time_buckets[16];
+static double wsgi_daemon_time_total = 0;
+static int wsgi_daemon_time_buckets[16];
+static double wsgi_application_time_total = 0;
+static int wsgi_application_time_buckets[16];
+static int* wsgi_request_threads_buckets = NULL;
+
+void wsgi_record_time_in_buckets(int* buckets, double duration) {
+ int index = 0;
+ double threshold = 0.005;
+
+ while (index < 14) {
+ if (duration <= threshold)
+ {
+ buckets[index] += 1;
+ return;
+ }
+
+ threshold *= 2;
+ index += 1;
+ }
+
+ buckets[index] += 1;
+}
+
+void wsgi_record_request_times(apr_time_t request_start,
+ apr_time_t queue_start, apr_time_t daemon_start,
+ apr_time_t application_start, apr_time_t application_finish) {
+
+ double server_time = 0.0;
+ double queue_time = 0.0;
+ double daemon_time = 0.0;
+ double application_time = 0.0;
+
+ if (wsgi_request_metrics_enabled == 0)
+ return;
+
+ if (queue_start) {
+ server_time = apr_time_sec((double)(queue_start-request_start));
+ queue_time = apr_time_sec((double)(daemon_start-queue_start));
+ daemon_time = apr_time_sec((double)(application_start-daemon_start));
+ }
+ else {
+ server_time = apr_time_sec((double)(application_start-request_start));
+ daemon_time = 0;
+ queue_time = 0;
+ }
+
+ application_time = (apr_time_sec((double)(application_finish-
+ application_start)));
+
+ apr_thread_mutex_lock(wsgi_monitor_lock);
+
+ wsgi_sample_requests += 1;
+ wsgi_server_time_total += server_time;
+ wsgi_queue_time_total += queue_time;
+ wsgi_daemon_time_total += daemon_time;
+ wsgi_application_time_total += application_time;
+
+ wsgi_record_time_in_buckets(&wsgi_server_time_buckets,
+ server_time);
+
+#if defined(MOD_WSGI_WITH_DAEMONS)
+ if (wsgi_daemon_process) {
+ wsgi_record_time_in_buckets(&wsgi_queue_time_buckets,
+ queue_time);
+ wsgi_record_time_in_buckets(&wsgi_daemon_time_buckets,
+ daemon_time);
+ }
+#endif
+
+ wsgi_record_time_in_buckets(&wsgi_application_time_buckets,
+ application_time);
+
+ apr_thread_mutex_unlock(wsgi_monitor_lock);
+}
+
WSGIThreadInfo *wsgi_start_request(request_rec *r)
{
WSGIThreadInfo *thread_info;
@@ -113,7 +199,7 @@ WSGIThreadInfo *wsgi_start_request(request_rec *r)
PyErr_Clear();
#endif
- wsgi_utilization_time(1);
+ wsgi_utilization_time(1, NULL);
return thread_info;
}
@@ -127,6 +213,9 @@ void wsgi_end_request(void)
thread_info = wsgi_thread_info(0, 1);
if (thread_info) {
+ if (wsgi_request_threads_buckets)
+ wsgi_request_threads_buckets[thread_info->thread_id-1] += 1;
+
#if AP_MODULE_MAGIC_AT_LEAST(20100923,2)
module = PyImport_ImportModule("mod_wsgi");
@@ -144,6 +233,7 @@ void wsgi_end_request(void)
else
PyErr_Clear();
#endif
+
if (thread_info->log_buffer)
Py_CLEAR(thread_info->log_buffer);
@@ -154,7 +244,7 @@ void wsgi_end_request(void)
Py_CLEAR(thread_info->request_data);
}
- wsgi_utilization_time(-1);
+ wsgi_utilization_time(-1, NULL);
}
/* ------------------------------------------------------------------------- */
@@ -195,6 +285,22 @@ WSGI_STATIC_INTERNED_STRING(active_requests);
WSGI_STATIC_INTERNED_STRING(threads);
WSGI_STATIC_INTERNED_STRING(thread_id);
+WSGI_STATIC_INTERNED_STRING(sample_period);
+WSGI_STATIC_INTERNED_STRING(request_threads_maximum);
+WSGI_STATIC_INTERNED_STRING(request_threads_started);
+WSGI_STATIC_INTERNED_STRING(request_threads_active);
+WSGI_STATIC_INTERNED_STRING(capacity_utilization);
+WSGI_STATIC_INTERNED_STRING(request_throughput);
+WSGI_STATIC_INTERNED_STRING(server_time);
+WSGI_STATIC_INTERNED_STRING(queue_time);
+WSGI_STATIC_INTERNED_STRING(daemon_time);
+WSGI_STATIC_INTERNED_STRING(application_time);
+WSGI_STATIC_INTERNED_STRING(server_time_buckets);
+WSGI_STATIC_INTERNED_STRING(queue_time_buckets);
+WSGI_STATIC_INTERNED_STRING(daemon_time_buckets);
+WSGI_STATIC_INTERNED_STRING(application_time_buckets);
+WSGI_STATIC_INTERNED_STRING(request_threads_buckets);
+
static PyObject *wsgi_status_flags[SERVER_NUM_STATUS];
#define WSGI_CREATE_STATUS_FLAG(name, val) \
@@ -239,6 +345,22 @@ static void wsgi_initialize_interned_strings(void)
WSGI_CREATE_INTERNED_STRING_ID(threads);
WSGI_CREATE_INTERNED_STRING_ID(thread_id);
+ WSGI_CREATE_INTERNED_STRING_ID(sample_period);
+ WSGI_CREATE_INTERNED_STRING_ID(request_threads_maximum);
+ WSGI_CREATE_INTERNED_STRING_ID(request_threads_started);
+ WSGI_CREATE_INTERNED_STRING_ID(request_threads_active);
+ WSGI_CREATE_INTERNED_STRING_ID(capacity_utilization);
+ WSGI_CREATE_INTERNED_STRING_ID(request_throughput);
+ WSGI_CREATE_INTERNED_STRING_ID(server_time);
+ WSGI_CREATE_INTERNED_STRING_ID(queue_time);
+ WSGI_CREATE_INTERNED_STRING_ID(daemon_time);
+ WSGI_CREATE_INTERNED_STRING_ID(application_time);
+ WSGI_CREATE_INTERNED_STRING_ID(server_time_buckets);
+ WSGI_CREATE_INTERNED_STRING_ID(daemon_time_buckets);
+ WSGI_CREATE_INTERNED_STRING_ID(queue_time_buckets);
+ WSGI_CREATE_INTERNED_STRING_ID(application_time_buckets);
+ WSGI_CREATE_INTERNED_STRING_ID(request_threads_buckets);
+
WSGI_CREATE_STATUS_FLAG(SERVER_DEAD, ".");
WSGI_CREATE_STATUS_FLAG(SERVER_READY, "_");
WSGI_CREATE_STATUS_FLAG(SERVER_STARTING, "S");
@@ -257,47 +379,404 @@ static void wsgi_initialize_interned_strings(void)
/* ------------------------------------------------------------------------- */
-static PyObject *wsgi_process_metrics(void)
+static PyObject *wsgi_request_metrics(void)
{
PyObject *result = NULL;
PyObject *object = NULL;
- PyObject *thread_list = NULL;
+ apr_time_t stop_time;
+ double stop_request_busy_time = 0.0;
+ apr_uint64_t stop_request_count = 0.0;
+
+ double request_busy_time = 0.0;
+ double capacity_utilization = 0.0;
+
+ static double start_time = 0.0;
+ static double start_cpu_system_time = 0.0;
+ static double start_cpu_user_time = 0.0;
+ static double start_request_busy_time = 0.0;
+ static apr_uint64_t start_request_count = 0;
+
+ double sample_period = 0.0;
+ apr_uint64_t request_count = 0;
+ double request_throughput = 0.0;
+ double stop_cpu_system_time = 0.0;
+ double stop_cpu_user_time = 0.0;
+
+ double cpu_system_time = 0.0;
+ double cpu_user_time = 0.0;
+ double total_cpu_time = 0.0;
+
+ static int request_threads_maximum = 0;
+
+ apr_uint64_t interval_requests = 0;
+ double server_time_total = 0;
+ double server_time_avg = 0;
+ double queue_time_total = 0;
+ double queue_time_avg = 0;
+ double daemon_time_total = 0;
+ double daemon_time_avg = 0;
+ double application_time_total = 0;
+ double application_time_avg = 0;
+
WSGIThreadInfo **thread_info = NULL;
+ int request_threads_active = 0;
int i;
#ifdef HAVE_TIMES
struct tms tmsbuf;
static float tick = 0.0;
-#endif
- apr_time_t current_time;
- apr_interval_time_t running_time;
+ if (!tick) {
+#ifdef _SC_CLK_TCK
+ tick = sysconf(_SC_CLK_TCK);
+#else
+ tick = HZ;
+#endif
+ }
+#endif
if (!wsgi_interns_initialized)
wsgi_initialize_interned_strings();
-#if 0
- if (!wsgi_daemon_pool) {
- if (!wsgi_server_config->server_metrics) {
- Py_INCREF(Py_None);
+ if (!request_threads_maximum) {
+ int is_threaded = 0;
- return Py_None;
+#if defined(MOD_WSGI_WITH_DAEMONS)
+ if (wsgi_daemon_process) {
+ request_threads_maximum = wsgi_daemon_process->group->threads;
+ }
+ else {
+ ap_mpm_query(AP_MPMQ_IS_THREADED, &is_threaded);
+ if (is_threaded != AP_MPMQ_NOT_SUPPORTED) {
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &request_threads_maximum);
+ }
}
+#else
+ ap_mpm_query(AP_MPMQ_IS_THREADED, &is_threaded);
+ if (is_threaded != AP_MPMQ_NOT_SUPPORTED) {
+ ap_mpm_query(AP_MPMQ_MAX_THREADS, &request_threads_maximum);
+ }
+#endif
+
+ request_threads_maximum = ((request_threads_maximum <= 0) ? 1 :
+ request_threads_maximum);
+
+ wsgi_request_threads_buckets = (int *)apr_pcalloc(
+ wsgi_server_config->pool, request_threads_maximum*sizeof(
+ wsgi_request_threads_buckets[0]));
+ }
+
+
+ result = PyDict_New();
+
+ stop_time = apr_time_now();
+ stop_request_busy_time = wsgi_utilization_time(0, &stop_request_count);
+
+ if (!start_time) {
+ start_time = stop_time;
+ start_request_busy_time = stop_request_busy_time;
+ start_request_count = stop_request_count;
+
+#ifdef HAVE_TIMES
+ times(&tmsbuf);
+
+ start_cpu_user_time = tmsbuf.tms_utime / tick;
+ start_cpu_system_time = tmsbuf.tms_stime / tick;
+#else
+ start_cpu_user_time = 0.0;
+ start_cpu_system_time = 0.0;
+#endif
+
+ apr_thread_mutex_lock(wsgi_monitor_lock);
+
+ wsgi_sample_requests = 0;
+ wsgi_server_time_total = 0.0;
+ wsgi_queue_time_total = 0.0;
+ wsgi_daemon_time_total = 0.0;
+ wsgi_application_time_total = 0.0;
+
+ wsgi_request_metrics_enabled = 1;
+
+ apr_thread_mutex_unlock(wsgi_monitor_lock);
+
+ return result;
+ }
+
+ object = wsgi_PyInt_FromLong(getpid());
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(pid), object);
+ Py_DECREF(object);
+
+ object = PyFloat_FromDouble(apr_time_sec((double)start_time));
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(start_time), object);
+ Py_DECREF(object);
+
+ object = PyFloat_FromDouble(apr_time_sec((double)stop_time));
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(stop_time), object);
+ Py_DECREF(object);
+
+ sample_period = (apr_time_sec((double)stop_time) -
+ apr_time_sec((double)start_time));
+
+ object = PyFloat_FromDouble(sample_period);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(sample_period), object);
+ Py_DECREF(object);
+
+#ifdef HAVE_TIMES
+ times(&tmsbuf);
+
+ stop_cpu_user_time = tmsbuf.tms_utime / tick;
+ stop_cpu_system_time = tmsbuf.tms_stime / tick;
+
+ cpu_user_time = ((stop_cpu_user_time - start_cpu_user_time) /
+ sample_period);
+ cpu_system_time = ((stop_cpu_system_time - start_cpu_system_time) /
+ sample_period);
+
+ total_cpu_time += cpu_user_time;
+ total_cpu_time += cpu_system_time;
+
+ object = PyFloat_FromDouble(cpu_user_time);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(cpu_user_time), object);
+ Py_DECREF(object);
+
+ object = PyFloat_FromDouble(cpu_system_time);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(cpu_system_time), object);
+ Py_DECREF(object);
+#else
+ object = PyFloat_FromDouble(0.0);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(cpu_user_time), object);
+ Py_DECREF(object);
+
+ object = PyFloat_FromDouble(0.0);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(cpu_system_time), object);
+ Py_DECREF(object);
+#endif
+
+ object = wsgi_PyInt_FromLongLong(wsgi_get_peak_memory_RSS());
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(memory_max_rss), object);
+ Py_DECREF(object);
+
+ object = wsgi_PyInt_FromLongLong(wsgi_get_current_memory_RSS());
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(memory_rss), object);
+ Py_DECREF(object);
+
+ object = wsgi_PyInt_FromLong(request_threads_maximum);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(request_threads_maximum), object);
+ Py_DECREF(object);
+
+ object = wsgi_PyInt_FromLong(wsgi_request_threads);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(request_threads_started), object);
+ Py_DECREF(object);
+
+ thread_info = (WSGIThreadInfo **)wsgi_thread_details->elts;
+
+ request_busy_time = stop_request_busy_time - start_request_busy_time;
+
+ capacity_utilization = (request_busy_time / sample_period /
+ request_threads_maximum);
+
+ object = PyFloat_FromDouble(capacity_utilization);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(capacity_utilization), object);
+ Py_DECREF(object);
+
+ request_count = stop_request_count - start_request_count;
+
+ object = wsgi_PyInt_FromLongLong(request_count);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(request_count), object);
+ Py_DECREF(object);
+
+ request_throughput = sample_period ? request_count / sample_period : 0;
+
+ object = PyFloat_FromDouble(request_throughput);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(request_throughput), object);
+ Py_DECREF(object);
+
+ start_time = stop_time;
+ start_request_busy_time = stop_request_busy_time;
+ start_request_count = stop_request_count;
+ start_cpu_user_time = stop_cpu_user_time;
+ start_cpu_system_time = stop_cpu_system_time;
+
+ apr_thread_mutex_lock(wsgi_monitor_lock);
+
+ interval_requests = wsgi_sample_requests;
+ server_time_total = wsgi_server_time_total;
+ queue_time_total = wsgi_queue_time_total;
+ daemon_time_total = wsgi_daemon_time_total;
+ application_time_total = wsgi_application_time_total;
+
+ object = PyList_New(16);
+ for (i=0; i<16; i++) {
+ PyList_SET_ITEM(object, i, wsgi_PyInt_FromLong(
+ wsgi_server_time_buckets[i]));
+ }
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(server_time_buckets), object);
+ Py_DECREF(object);
+
+ object = PyList_New(16);
+ for (i=0; i<16; i++) {
+ PyList_SET_ITEM(object, i, wsgi_PyInt_FromLong(
+ wsgi_queue_time_buckets[i]));
+ }
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(queue_time_buckets), object);
+ Py_DECREF(object);
+
+ object = PyList_New(16);
+ for (i=0; i<16; i++) {
+ PyList_SET_ITEM(object, i, wsgi_PyInt_FromLong(
+ wsgi_daemon_time_buckets[i]));
+ }
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(daemon_time_buckets), object);
+ Py_DECREF(object);
+
+ object = PyList_New(16);
+ for (i=0; i<16; i++) {
+ PyList_SET_ITEM(object, i, wsgi_PyInt_FromLong(
+ wsgi_application_time_buckets[i]));
+ }
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(application_time_buckets), object);
+ Py_DECREF(object);
+
+ object = PyList_New(request_threads_maximum);
+ for (i=0; i<request_threads_maximum; i++) {
+ PyList_SET_ITEM(object, i, wsgi_PyInt_FromLong(
+ wsgi_request_threads_buckets[i]));
+ if (wsgi_request_threads_buckets[i])
+ request_threads_active++;
}
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(request_threads_buckets), object);
+ Py_DECREF(object);
+
+ object = wsgi_PyInt_FromLong(request_threads_active);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(request_threads_active), object);
+ Py_DECREF(object);
+
+ wsgi_sample_requests = 0;
+ wsgi_server_time_total = 0.0;
+ wsgi_queue_time_total = 0.0;
+ wsgi_daemon_time_total = 0.0;
+ wsgi_application_time_total = 0.0;
+
+ memset(&wsgi_server_time_buckets, 0,
+ sizeof(wsgi_server_time_buckets));
+ memset(&wsgi_queue_time_buckets, 0,
+ sizeof(wsgi_queue_time_buckets));
+ memset(&wsgi_daemon_time_buckets, 0,
+ sizeof(wsgi_daemon_time_buckets));
+ memset(&wsgi_application_time_buckets, 0,
+ sizeof(wsgi_application_time_buckets));
+
+ memset(wsgi_request_threads_buckets, 0, request_threads_maximum*
+ sizeof(wsgi_request_threads_buckets[0]));
+
+ apr_thread_mutex_unlock(wsgi_monitor_lock);
+
+ server_time_avg = 0;
+ queue_time_avg = 0;
+ daemon_time_avg = 0;
+ application_time_avg = 0;
+
+ if (interval_requests) {
+ server_time_avg = server_time_total / interval_requests;
+ queue_time_avg = queue_time_total / interval_requests;
+ daemon_time_avg = daemon_time_total / interval_requests;
+ application_time_avg = application_time_total / interval_requests;
+ }
+
+ object = PyFloat_FromDouble(server_time_avg);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(server_time), object);
+ Py_DECREF(object);
+
#if defined(MOD_WSGI_WITH_DAEMONS)
- else {
- if (!wsgi_daemon_process->group->server_metrics) {
- Py_INCREF(Py_None);
+ if (wsgi_daemon_process) {
+ object = PyFloat_FromDouble(queue_time_avg);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(queue_time), object);
+ Py_DECREF(object);
- return Py_None;
- }
+ object = PyFloat_FromDouble(daemon_time_avg);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(daemon_time), object);
+ Py_DECREF(object);
+ }
+ else {
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(queue_time), Py_None);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(daemon_time), Py_None);
}
+#else
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(queue_time), Py_None);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(daemon_time), Py_None);
#endif
+
+ object = PyFloat_FromDouble(application_time_avg);
+ PyDict_SetItem(result,
+ WSGI_INTERNED_STRING(application_time), object);
+ Py_DECREF(object);
+
+ return result;
+}
+
+PyMethodDef wsgi_request_metrics_method[] = {
+ { "request_metrics", (PyCFunction)wsgi_request_metrics,
+ METH_NOARGS, 0 },
+ { NULL },
+};
+
+/* ------------------------------------------------------------------------- */
+
+static PyObject *wsgi_process_metrics(void)
+{
+ PyObject *result = NULL;
+
+ PyObject *object = NULL;
+
+ PyObject *thread_list = NULL;
+ WSGIThreadInfo **thread_info = NULL;
+
+ apr_uint64_t request_count = 0;
+
+ int i;
+
+#ifdef HAVE_TIMES
+ struct tms tmsbuf;
+ static float tick = 0.0;
#endif
+ apr_time_t current_time;
+ apr_interval_time_t running_time;
+
+ if (!wsgi_interns_initialized)
+ wsgi_initialize_interned_strings();
+
result = PyDict_New();
object = wsgi_PyInt_FromLong(getpid());
@@ -305,14 +784,14 @@ static PyObject *wsgi_process_metrics(void)
WSGI_INTERNED_STRING(pid), object);
Py_DECREF(object);
- object = wsgi_PyInt_FromLongLong(wsgi_total_requests);
+ object = PyFloat_FromDouble(wsgi_utilization_time(0, &request_count));
PyDict_SetItem(result,
- WSGI_INTERNED_STRING(request_count), object);
+ WSGI_INTERNED_STRING(request_busy_time), object);
Py_DECREF(object);
- object = PyFloat_FromDouble(wsgi_utilization_time(0));
+ object = wsgi_PyInt_FromLongLong(request_count);
PyDict_SetItem(result,
- WSGI_INTERNED_STRING(request_busy_time), object);
+ WSGI_INTERNED_STRING(request_count), object);
Py_DECREF(object);
object = wsgi_PyInt_FromLongLong(wsgi_get_peak_memory_RSS());
@@ -672,82 +1151,76 @@ static PyObject *wsgi_subscribe_events(PyObject *self, PyObject *args)
return Py_None;
}
-long wsgi_event_subscribers(void)
+static PyObject *wsgi_subscribe_shutdown(PyObject *self, PyObject *args)
{
+ PyObject *callback = NULL;
+
PyObject *module = NULL;
+ if (!PyArg_ParseTuple(args, "O", &callback))
+ return NULL;
+
module = PyImport_ImportModule("mod_wsgi");
if (module) {
PyObject *dict = NULL;
PyObject *list = NULL;
- long result = 0;
-
dict = PyModule_GetDict(module);
- list = PyDict_GetItemString(dict, "event_callbacks");
+ list = PyDict_GetItemString(dict, "shutdown_callbacks");
if (list)
- result = PyList_Size(list);
+ PyList_Append(list, callback);
+ else
+ return NULL;
Py_DECREF(module);
-
- return result;
}
else
- return 0;
+ return NULL;
+
+ Py_INCREF(Py_None);
+ return Py_None;
}
-void wsgi_publish_event(const char *name, PyObject *event)
+long wsgi_event_subscribers(void)
{
- int i;
-
PyObject *module = NULL;
- PyObject *list = NULL;
module = PyImport_ImportModule("mod_wsgi");
if (module) {
PyObject *dict = NULL;
+ PyObject *list = NULL;
+
+ long result = 0;
dict = PyModule_GetDict(module);
list = PyDict_GetItemString(dict, "event_callbacks");
- Py_XINCREF(list);
+ if (list)
+ result = PyList_Size(list);
Py_DECREF(module);
- }
- else {
- Py_BEGIN_ALLOW_THREADS
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
- "mod_wsgi (pid=%d): Unable to import mod_wsgi when "
- "publishing events.", getpid());
- Py_END_ALLOW_THREADS
- PyErr_Clear();
-
- return;
+ return result;
}
+ else
+ return 0;
+}
- if (!list) {
- Py_BEGIN_ALLOW_THREADS
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
- "mod_wsgi (pid=%d): Unable to find event subscribers.",
- getpid());
- Py_END_ALLOW_THREADS
-
- PyErr_Clear();
-
- return;
- }
+void wsgi_call_callbacks(const char *name, PyObject *callbacks,
+ PyObject *event)
+{
+ int i;
- for (i=0; i<PyList_Size(list); i++) {
+ for (i=0; i<PyList_Size(callbacks); i++) {
PyObject *callback = NULL;
PyObject *res = NULL;
PyObject *args = NULL;
- callback = PyList_GetItem(list, i);
+ callback = PyList_GetItem(callbacks, i);
Py_INCREF(callback);
@@ -842,18 +1315,80 @@ void wsgi_publish_event(const char *name, PyObject *event)
Py_DECREF(callback);
Py_DECREF(args);
}
+}
+
+void wsgi_publish_event(const char *name, PyObject *event)
+{
+ PyObject *module = NULL;
+
+ PyObject *event_callbacks = NULL;
+ PyObject *shutdown_callbacks = NULL;
+
+ module = PyImport_ImportModule("mod_wsgi");
+
+ if (module) {
+ PyObject *dict = NULL;
+
+ dict = PyModule_GetDict(module);
+
+ event_callbacks = PyDict_GetItemString(dict, "event_callbacks");
+ Py_XINCREF(event_callbacks);
+
+ shutdown_callbacks = PyDict_GetItemString(dict, "shutdown_callbacks");
+ Py_XINCREF(shutdown_callbacks);
+
+ Py_DECREF(module);
+ }
+ else {
+ Py_BEGIN_ALLOW_THREADS
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
+ "mod_wsgi (pid=%d): Unable to import mod_wsgi when "
+ "publishing events.", getpid());
+ Py_END_ALLOW_THREADS
+
+ PyErr_Clear();
+
+ return;
+ }
+
+ if (!event_callbacks || !shutdown_callbacks) {
+ Py_BEGIN_ALLOW_THREADS
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, wsgi_server,
+ "mod_wsgi (pid=%d): Unable to find event subscribers.",
+ getpid());
+ Py_END_ALLOW_THREADS
+
+ PyErr_Clear();
- Py_DECREF(list);
+ Py_XDECREF(event_callbacks);
+ Py_XDECREF(shutdown_callbacks);
+
+ return;
+ }
+
+ wsgi_call_callbacks(name, event_callbacks, event);
+
+ if (strcmp(name, "process_stopping") == 0)
+ wsgi_call_callbacks(name, shutdown_callbacks, event);
+
+ Py_DECREF(event_callbacks);
+ Py_DECREF(shutdown_callbacks);
}
/* ------------------------------------------------------------------------- */
-PyMethodDef wsgi_process_events_method[] = {
+PyMethodDef wsgi_subscribe_events_method[] = {
{ "subscribe_events", (PyCFunction)wsgi_subscribe_events,
METH_VARARGS, 0 },
{ NULL },
};
+PyMethodDef wsgi_subscribe_shutdown_method[] = {
+ { "subscribe_shutdown", (PyCFunction)wsgi_subscribe_shutdown,
+ METH_VARARGS, 0 },
+ { NULL },
+};
+
/* ------------------------------------------------------------------------- */
static PyObject *wsgi_request_data(PyObject *self, PyObject *args)
diff --git a/src/server/wsgi_metrics.h b/src/server/wsgi_metrics.h
index 9d69806..233a882 100644
--- a/src/server/wsgi_metrics.h
+++ b/src/server/wsgi_metrics.h
@@ -33,17 +33,24 @@ extern int wsgi_active_requests;
extern apr_thread_mutex_t* wsgi_monitor_lock;
+extern PyMethodDef wsgi_request_metrics_method[];
+
extern PyMethodDef wsgi_process_metrics_method[];
extern WSGIThreadInfo *wsgi_start_request(request_rec *r);
extern void wsgi_end_request(void);
+extern void wsgi_record_request_times(apr_time_t request_start,
+ apr_time_t queue_start, apr_time_t daemon_start,
+ apr_time_t application_start, apr_time_t application_finish);
+
extern PyMethodDef wsgi_server_metrics_method[];
extern long wsgi_event_subscribers(void);
extern void wsgi_publish_event(const char *name, PyObject *event);
-extern PyMethodDef wsgi_process_events_method[];
+extern PyMethodDef wsgi_subscribe_events_method[];
+extern PyMethodDef wsgi_subscribe_shutdown_method[];
extern PyMethodDef wsgi_request_data_method[];
diff --git a/src/server/wsgi_python.h b/src/server/wsgi_python.h
index 0464fe6..5c10cae 100644
--- a/src/server/wsgi_python.h
+++ b/src/server/wsgi_python.h
@@ -21,6 +21,8 @@
/* ------------------------------------------------------------------------- */
+#define PY_SSIZE_T_CLEAN 1
+
#include <Python.h>
#if !defined(PY_VERSION_HEX)
diff --git a/src/server/wsgi_version.h b/src/server/wsgi_version.h
index 998ac2f..2e17984 100755
--- a/src/server/wsgi_version.h
+++ b/src/server/wsgi_version.h
@@ -24,9 +24,9 @@
/* Module version information. */
#define MOD_WSGI_MAJORVERSION_NUMBER 4
-#define MOD_WSGI_MINORVERSION_NUMBER 7
-#define MOD_WSGI_MICROVERSION_NUMBER 1
-#define MOD_WSGI_VERSION_STRING "4.7.1"
+#define MOD_WSGI_MINORVERSION_NUMBER 8
+#define MOD_WSGI_MICROVERSION_NUMBER 0
+#define MOD_WSGI_VERSION_STRING "4.8.0"
/* ------------------------------------------------------------------------- */