misc: add a record_timings utility decorator (#85102)
This commit is contained in:
parent
614c148dd3
commit
8badd75012
|
@ -2635,4 +2635,4 @@ def test_field_live_too_long(pub, freezer):
|
||||||
assert pub.loggederror_class.count() == 1
|
assert pub.loggederror_class.count() == 1
|
||||||
logged_error = pub.loggederror_class.select()[0]
|
logged_error = pub.loggederror_class.select()[0]
|
||||||
assert logged_error.summary == '/live call is taking too long'
|
assert logged_error.summary == '/live call is taking too long'
|
||||||
assert 'timings = {' in logged_error.traceback
|
assert 'timings = ' in logged_error.traceback
|
||||||
|
|
|
@ -34,6 +34,7 @@ from wcs.blocks import BlockSubWidget, BlockWidget
|
||||||
from wcs.fields import FileField
|
from wcs.fields import FileField
|
||||||
from wcs.qommon.admin.texts import TextsDirectory
|
from wcs.qommon.admin.texts import TextsDirectory
|
||||||
from wcs.qommon.upload_storage import get_storage_object
|
from wcs.qommon.upload_storage import get_storage_object
|
||||||
|
from wcs.utils import record_timings
|
||||||
from wcs.wf.editable import EditableWorkflowStatusItem
|
from wcs.wf.editable import EditableWorkflowStatusItem
|
||||||
from wcs.workflows import RedisplayFormException
|
from wcs.workflows import RedisplayFormException
|
||||||
|
|
||||||
|
@ -812,26 +813,18 @@ class FormStatusPage(Directory, FormTemplateMixin):
|
||||||
return redirect(file_url)
|
return redirect(file_url)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@record_timings(name='/live call', record_if_over=5)
|
||||||
def live_process_fields(cls, form, formdata, displayed_fields):
|
def live_process_fields(cls, form, formdata, displayed_fields):
|
||||||
if form is None:
|
if form is None:
|
||||||
return json.dumps({'result': {}})
|
return json.dumps({'result': {}})
|
||||||
|
|
||||||
t0 = time.time()
|
request = get_request()
|
||||||
timings = {'relative_start': get_request().get_duration()}
|
request.add_timing_mark('relative_start')
|
||||||
|
|
||||||
def add_timing(key):
|
|
||||||
nonlocal t0
|
|
||||||
new_t0 = time.time()
|
|
||||||
timings[key], t0 = '%.4f' % (new_t0 - t0), new_t0
|
|
||||||
|
|
||||||
def reset_timing():
|
|
||||||
nonlocal t0
|
|
||||||
t0 = time.time()
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
for field in displayed_fields:
|
for field in displayed_fields:
|
||||||
result[field.id] = {'visible': field.is_visible(formdata.data, formdata.formdef)}
|
result[field.id] = {'visible': field.is_visible(formdata.data, formdata.formdef)}
|
||||||
add_timing('visibility')
|
request.add_timing_mark('visibility')
|
||||||
|
|
||||||
modified_field_ids = get_request().form.get('modified_field_id[]') or []
|
modified_field_ids = get_request().form.get('modified_field_id[]') or []
|
||||||
modified_field_varnames = set()
|
modified_field_varnames = set()
|
||||||
|
@ -853,7 +846,7 @@ class FormStatusPage(Directory, FormTemplateMixin):
|
||||||
break
|
break
|
||||||
|
|
||||||
for field in displayed_fields:
|
for field in displayed_fields:
|
||||||
reset_timing()
|
t0 = time.time()
|
||||||
if field.key in ('item', 'items') and field.data_source:
|
if field.key in ('item', 'items') and field.data_source:
|
||||||
data_source = data_sources.get_object(field.data_source)
|
data_source = data_sources.get_object(field.data_source)
|
||||||
if data_source.type not in (
|
if data_source.type not in (
|
||||||
|
@ -876,7 +869,7 @@ class FormStatusPage(Directory, FormTemplateMixin):
|
||||||
# but reduce payload weight by removing the API URLs
|
# but reduce payload weight by removing the API URLs
|
||||||
for options in result[field.id]['items']:
|
for options in result[field.id]['items']:
|
||||||
options.pop('api', None)
|
options.pop('api', None)
|
||||||
add_timing(f'item-options-{field.id}')
|
request.add_timing_mark(f'item-options-{field.id}', relative_start=t0)
|
||||||
|
|
||||||
def get_all_field_widgets(form):
|
def get_all_field_widgets(form):
|
||||||
for widget in form.widgets:
|
for widget in form.widgets:
|
||||||
|
@ -892,7 +885,7 @@ class FormStatusPage(Directory, FormTemplateMixin):
|
||||||
block_row += 1
|
block_row += 1
|
||||||
|
|
||||||
for block, block_row, field, widget in get_all_field_widgets(form):
|
for block, block_row, field, widget in get_all_field_widgets(form):
|
||||||
reset_timing()
|
t0 = time.time()
|
||||||
if block:
|
if block:
|
||||||
try:
|
try:
|
||||||
block_data = formdata.data.get(block.id)['data'][block_row]
|
block_data = formdata.data.get(block.id)['data'][block_row]
|
||||||
|
@ -966,12 +959,9 @@ class FormStatusPage(Directory, FormTemplateMixin):
|
||||||
entry['content'] = value
|
entry['content'] = value
|
||||||
entry['locked'] = locked
|
entry['locked'] = locked
|
||||||
|
|
||||||
add_timing(f'field-block-{block.id}-row-{block_row}' if block else f'field-{field.id}')
|
request.add_timing_mark(
|
||||||
|
f'field-block-{block.id}-row-{block_row}' if block else f'field-{field.id}', relative_start=t0
|
||||||
if (get_request().get_duration() - timings['relative_start']) > 5:
|
)
|
||||||
# timings will be displayed in the traceback part of the error.
|
|
||||||
timings['total'] = get_request().get_duration()
|
|
||||||
get_publisher().record_error(_('/live call is taking too long'))
|
|
||||||
|
|
||||||
return json.dumps({'result': result})
|
return json.dumps({'result': result})
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ class HTTPRequest(quixote.http_request.HTTPRequest):
|
||||||
self.ignore_session = False
|
self.ignore_session = False
|
||||||
self.wscalls_cache = {}
|
self.wscalls_cache = {}
|
||||||
self.datasources_cache = {}
|
self.datasources_cache = {}
|
||||||
|
self.timings = []
|
||||||
# keep a copy of environment to make sure it's not reused along
|
# keep a copy of environment to make sure it's not reused along
|
||||||
# uwsgi/gunicorn processes.
|
# uwsgi/gunicorn processes.
|
||||||
self.environ = copy.copy(self.environ)
|
self.environ = copy.copy(self.environ)
|
||||||
|
@ -252,3 +253,19 @@ class HTTPRequest(quixote.http_request.HTTPRequest):
|
||||||
|
|
||||||
def get_duration(self):
|
def get_duration(self):
|
||||||
return time.time() - self.t0
|
return time.time() - self.t0
|
||||||
|
|
||||||
|
def start_timing(self, name):
|
||||||
|
self.timings.append({'name': name, 'start': time.time()})
|
||||||
|
return self.timings[-1]
|
||||||
|
|
||||||
|
def stop_timing(self, timing):
|
||||||
|
timing['end'] = time.time()
|
||||||
|
timing['duration'] = timing['end'] - timing['start']
|
||||||
|
return timing['duration']
|
||||||
|
|
||||||
|
def add_timing_mark(self, name, relative_start=None):
|
||||||
|
timestamp = time.time()
|
||||||
|
duration = timestamp - (
|
||||||
|
relative_start or self.timings[-1].get('timestamp') or self.timings[-1].get('start')
|
||||||
|
)
|
||||||
|
self.timings.append({'mark': name, 'timestamp': timestamp, 'duration': duration})
|
||||||
|
|
26
wcs/utils.py
26
wcs/utils.py
|
@ -19,9 +19,10 @@ import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import django.template.loaders.filesystem
|
import django.template.loaders.filesystem
|
||||||
|
from quixote import get_publisher, get_request
|
||||||
|
|
||||||
from .qommon import _
|
from .qommon import _
|
||||||
from .qommon.publisher import get_cfg, get_publisher
|
from .qommon.publisher import get_cfg
|
||||||
from .qommon.template import get_theme_directory
|
from .qommon.template import get_theme_directory
|
||||||
|
|
||||||
|
|
||||||
|
@ -167,3 +168,26 @@ def grep_strings(string, hit_function):
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class record_timings:
|
||||||
|
def __init__(self, name=None, record_if_over=None):
|
||||||
|
self.record_if_over = record_if_over
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __call__(self, func):
|
||||||
|
name = self.name or func.__name__
|
||||||
|
|
||||||
|
def f(*args, **kwargs):
|
||||||
|
request = get_request()
|
||||||
|
timing = request.start_timing(name=name)
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
duration = request.stop_timing(timing)
|
||||||
|
if self.record_if_over and duration > self.record_if_over:
|
||||||
|
# timings will be displayed in the traceback part of the error.
|
||||||
|
timings = request.timings # noqa pylint: disable=unused-variable
|
||||||
|
get_publisher().record_error(_('%s is taking too long') % name)
|
||||||
|
|
||||||
|
return f
|
||||||
|
|
Loading…
Reference in New Issue