Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add pyinstrument integration to Flask API endpoints #722

Merged
merged 5 commits into from Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 4 additions & 7 deletions documentation/configuration.rst
Expand Up @@ -98,16 +98,13 @@ Default: ``"migrations/dumps"``
FLEXMEASURES_PROFILE_REQUESTS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Whether to turn on a feature which times requests made through FlexMeasures. Interesting for developers.
Whether to turn on a feature which times requests made through FlexMeasures. If `pyinstrument` is installed, the Flask API endpoints are profiled.
Nischay-Pro marked this conversation as resolved.
Show resolved Hide resolved

Default: ``False``

FLEXMEASURES_PROFILE_PYINSTRUMENT
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The profiling results are stored in the ``profile_reports`` folder in the instance directory.

Whether to turn on a feature which profiles the Flask API endpoints using `pyinstrument`. Interesting for developers.
Note: Profile reports for API endpoints are overwritten on repetition of the same request.

The profiling results are stored in the ``profile_reports`` folder in the instance directory.
Interesting for developers.

Default: ``False``

Expand Down
53 changes: 31 additions & 22 deletions flexmeasures/app.py
Expand Up @@ -99,8 +99,14 @@ def create( # noqa C901
app.config["SECURITY_PASSWORD_SALT"] = app.config["SECRET_KEY"]
if app.env not in ("documentation", "development"):
SSLify(app)
if app.config.get("FLEXMEASURES_PROFILE_PYINSTRUMENT", False):
if app.config.get("FLEXMEASURES_PROFILE_REQUESTS", False):
Nischay-Pro marked this conversation as resolved.
Show resolved Hide resolved
Path("profile_reports").mkdir(parents=True, exist_ok=True)
Nischay-Pro marked this conversation as resolved.
Show resolved Hide resolved
try:
import pyinstrument # noqa F401
except ImportError:
app.logger.warning(
"[PROFILE] pyinstrument not installed, cannot profile requests."
Nischay-Pro marked this conversation as resolved.
Show resolved Hide resolved
)

# Register database and models, including user auth security handlers

Expand Down Expand Up @@ -155,27 +161,13 @@ def create( # noqa C901
def before_request():
if app.config.get("FLEXMEASURES_PROFILE_REQUESTS", False):
g.start = time.time()
if app.config.get("FLEXMEASURES_PROFILE_PYINSTRUMENT", False):
import pyinstrument

g.profiler = pyinstrument.Profiler()
g.profiler.start()

@app.after_request
def after_request(response):
if app.config.get("FLEXMEASURES_PROFILE_PYINSTRUMENT", False):
g.profiler.stop()
output_html = g.profiler.output_html(timeline=True)
endpoint = request.endpoint
if endpoint is None:
endpoint = "unknown"
today = date.today()
profile_filename = f"pyinstrument_{endpoint}.html"
profile_output_path = Path("profile_reports", today.strftime("%Y-%m-%d"))
profile_output_path.mkdir(parents=True, exist_ok=True)
with open(os.path.join(profile_output_path, profile_filename), "w+") as f:
f.write(output_html)
return response
try:
import pyinstrument # noqa F401

g.profiler = pyinstrument.Profiler()
g.profiler.start()
except ImportError:
pass

@app.teardown_request
def teardown_request(exception=None):
Expand All @@ -185,5 +177,22 @@ def teardown_request(exception=None):
app.logger.info(
f"[PROFILE] {str(round(diff, 2)).rjust(6)} seconds to serve {request.url}."
)
if not hasattr(g, "profiler"):
return app
g.profiler.stop()
output_html = g.profiler.output_html(timeline=True)
endpoint = request.endpoint
if endpoint is None:
endpoint = "unknown"
today = date.today()
profile_filename = f"pyinstrument_{endpoint}.html"
profile_output_path = Path(
"profile_reports", today.strftime("%Y-%m-%d")
)
profile_output_path.mkdir(parents=True, exist_ok=True)
with open(
os.path.join(profile_output_path, profile_filename), "w+"
) as f:
f.write(output_html)

return app
1 change: 0 additions & 1 deletion flexmeasures/utils/config_defaults.py
Expand Up @@ -131,7 +131,6 @@ class Config(object):
FLEXMEASURES_API_SUNSET_ACTIVE: bool = False # if True, sunset endpoints return 410 (Gone) responses; if False, they return 404 (Not Found) responses or will work as before, depending on whether the current FlexMeasures version still contains the endpoint logic
FLEXMEASURES_API_SUNSET_DATE: str | None = None # e.g. 2023-05-01
FLEXMEASURES_API_SUNSET_LINK: str | None = None # e.g. https://flexmeasures.readthedocs.io/en/latest/api/introduction.html#deprecation-and-sunset
FLEXMEASURES_PROFILE_PYINSTRUMENT: bool = False


# names of settings which cannot be None
Expand Down