diff options
| author | Graham Dumpleton <Graham.Dumpleton@gmail.com> | 2021-02-13 13:00:44 +1100 |
|---|---|---|
| committer | Graham Dumpleton <Graham.Dumpleton@gmail.com> | 2021-02-13 13:00:44 +1100 |
| commit | 05c83ae5b12ee2f86300f93cf0379880cc1b2e99 (patch) | |
| tree | a6710cbe1dd61cda859bcd987fbbf664fc362aab /src | |
| parent | a5bf2f7064be76e69e06b901a32e0352567f7362 (diff) | |
| parent | dc77690076c3bb30ce69c64aed1467edd37e545a (diff) | |
| download | mod_wsgi-05c83ae5b12ee2f86300f93cf0379880cc1b2e99.tar.gz | |
Merge and fix Windows path issues.
Diffstat (limited to 'src')
| -rw-r--r-- | src/server/__init__.py | 158 | ||||
| -rw-r--r-- | src/server/management/commands/runmodwsgi.py | 11 | ||||
| -rw-r--r-- | src/server/mod_wsgi.c | 148 | ||||
| -rw-r--r-- | src/server/wsgi_interp.c | 24 | ||||
| -rw-r--r-- | src/server/wsgi_logger.c | 4 | ||||
| -rw-r--r-- | src/server/wsgi_metrics.c | 657 | ||||
| -rw-r--r-- | src/server/wsgi_metrics.h | 9 | ||||
| -rw-r--r-- | src/server/wsgi_python.h | 2 | ||||
| -rwxr-xr-x | src/server/wsgi_version.h | 6 |
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" /* ------------------------------------------------------------------------- */ |
