mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-28 07:37:56 +09:00
Centralize backup schedule calculations and shared request/form flows, encode query values, and move repository finalization into its service. Add browser CI coverage, repair stale test fixtures, and provide opt-in request and build measurement tools.
67 lines
2.4 KiB
Python
67 lines
2.4 KiB
Python
"""Opt-in request/SQL costs, without SQL text, parameters or persistent storage."""
|
|
|
|
from time import perf_counter_ns
|
|
|
|
from flask import g, has_request_context, request
|
|
from sqlalchemy import event
|
|
|
|
|
|
def register_request_profiling(app, database):
|
|
"""Attach only when enabled; normal deployments install no query listeners.
|
|
|
|
Counts SQLAlchemy statements on the request thread, including failed SQL.
|
|
Streaming body iteration and background jobs occur outside this interval.
|
|
"""
|
|
if not app.config.get('PROFILE_REQUESTS'):
|
|
return
|
|
if app.extensions.get('serverkit_request_profiling'):
|
|
return
|
|
app.extensions['serverkit_request_profiling'] = True
|
|
|
|
@app.before_request
|
|
def begin_request():
|
|
if request.path.startswith('/api/'):
|
|
g.serverkit_profile = {
|
|
'start': perf_counter_ns(), 'queries': 0, 'sql_ns': 0,
|
|
}
|
|
|
|
def before_sql(_conn, _cursor, _statement, _parameters, context, _many):
|
|
if not has_request_context():
|
|
return
|
|
state = getattr(g, 'serverkit_profile', None)
|
|
if state is not None:
|
|
state['queries'] += 1
|
|
context._serverkit_profile = (state, perf_counter_ns())
|
|
|
|
def finish_sql(context):
|
|
timing = getattr(context, '_serverkit_profile', None)
|
|
if timing is not None:
|
|
state, start = timing
|
|
state['sql_ns'] += perf_counter_ns() - start
|
|
context._serverkit_profile = None
|
|
|
|
def after_sql(_conn, _cursor, _statement, _parameters, context, _many):
|
|
finish_sql(context)
|
|
|
|
def failed_sql(error_context):
|
|
finish_sql(error_context.execution_context)
|
|
|
|
with app.app_context():
|
|
for engine in set(database.engines.values()):
|
|
event.listen(engine, 'before_cursor_execute', before_sql)
|
|
event.listen(engine, 'after_cursor_execute', after_sql)
|
|
event.listen(engine, 'handle_error', failed_sql)
|
|
|
|
@app.after_request
|
|
def expose_profile(response):
|
|
state = getattr(g, 'serverkit_profile', None)
|
|
if state is not None:
|
|
app_ms = (perf_counter_ns() - state['start']) / 1_000_000
|
|
sql_ms = state['sql_ns'] / 1_000_000
|
|
response.headers.add(
|
|
'Server-Timing',
|
|
f'app;dur={app_ms:.3f}, db;dur={sql_ms:.3f};'
|
|
f'desc="{state["queries"]} queries"',
|
|
)
|
|
return response
|