[IMP] profiler: remove parameter 'path'
Even if only accessible when writing some custom code, the path option of Profiler looked a little dangerous from a security point of view, since this option allows to write the result of the collector anywhere. This shouldn't be a problem, since Profiler shoudn't be accessible inside a safe_eval() and the path param cannot be user defined, but this is still a risk. An alternative option would have been to give a file descriptor instead, so that the user must have access to `open()`, which is unlikely inside a server action. This alternative has some disadvantages, though, because the file descriptor should be given when creating the profiler, or at least before `__exit__()`, removing the possibility to add the number of entries in the filename. This commit proposes another solution: add a generic way to output the profiler as a json text. The result can thus be saved the way the developer chooses. A utility method format_path() will help to format path the same way the profiler did before this change.
This commit is contained in:
committed by
Raphael Collet
parent
3b861dfe7f
commit
73ac78f072
+38
-24
@@ -5,7 +5,6 @@ import datetime
|
||||
import gc
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
@@ -269,20 +268,18 @@ class Profiler:
|
||||
Context manager to use to start the recording of some execution.
|
||||
Will save sql and async stack trace by default.
|
||||
"""
|
||||
def __init__(self, db=..., path=None, collectors=None, profile_session=None,
|
||||
def __init__(self, collectors=None, db=..., profile_session=None,
|
||||
description=None, disable_gc=False, params=None):
|
||||
"""
|
||||
:param db: database name to use to save results.
|
||||
Will try to define database automatically by default.
|
||||
Use value ``None`` to not save results in a database.
|
||||
:param path: path to use to save result
|
||||
:param collectors: list of string and Collector object Ex: ['sql', PeriodicCollector(interval=0.2)]. Use `None` for default collectors
|
||||
:param profile_session: session description to use to reproup multiple profile. use make_session(name) for default format.
|
||||
:param description: description of the current profiler Suggestion: (route name/test method/loading module, ...)
|
||||
:param disable_gc: flag to disable gc durring profiling (usefull to avoid gc while profiling, especially during sql execution)
|
||||
:param params: parameters usable by collectors (like frame interval)
|
||||
"""
|
||||
self.path = path
|
||||
self.start_time = 0
|
||||
self.duration = 0
|
||||
self.profile_session = profile_session or make_session()
|
||||
@@ -297,7 +294,7 @@ class Profiler:
|
||||
if db is ...:
|
||||
# determine database from current thread
|
||||
db = getattr(threading.current_thread(), 'dbname', None)
|
||||
if not db and not path:
|
||||
if not db:
|
||||
# only raise if path is not given and db is not explicitely disabled
|
||||
raise Exception('Database name cannot be defined automaticaly. \n Please provide a valid/falsy dbname or path parameter')
|
||||
self.db = db
|
||||
@@ -337,25 +334,6 @@ class Profiler:
|
||||
collector.stop()
|
||||
self.duration = time.time() - self.start_time
|
||||
self._add_file_lines(self.init_stack_trace)
|
||||
if self.path:
|
||||
save_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
dirname = os.path.dirname(self.path)
|
||||
os.makedirs(dirname, exist_ok=True)
|
||||
for collector in self.collectors:
|
||||
formatted_path = self.path.format(len=len(collector.entries), time=save_time)
|
||||
row = collector.name
|
||||
description = re.sub("[^0-9a-zA-Z-]+", "_", self.description)
|
||||
path = f'{formatted_path}_{description}_{row}.json'
|
||||
_logger.warning('saving to %s', path)
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
json.dump({
|
||||
"name": self.description,
|
||||
"session": self.profile_session,
|
||||
"create_date": save_time,
|
||||
"init_stack_trace": _format_stack(self.init_stack_trace),
|
||||
"duration": self.duration,
|
||||
'result': collector.entries,
|
||||
}, f, indent=4)
|
||||
|
||||
if self.db:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
@@ -402,6 +380,42 @@ class Profiler:
|
||||
line = filelines[lineno - 1]
|
||||
stack[index] = (filename, lineno, name, line)
|
||||
|
||||
def entry_count(self):
|
||||
""" Return the total number of entries collected in this profiler. """
|
||||
return sum(len(collector.entries) for collector in self.collectors)
|
||||
|
||||
def format_path(self, path):
|
||||
"""
|
||||
Utility function to format a path for this profiler.
|
||||
This is mainly useful to uniquify a path between executions.
|
||||
"""
|
||||
return path.format(
|
||||
time=datetime.datetime.now().strftime("%Y%m%d-%H%M%S"),
|
||||
len=self.entry_count(),
|
||||
desc=re.sub("[^0-9a-zA-Z-]+", "_", self.description)
|
||||
)
|
||||
|
||||
def json(self):
|
||||
"""
|
||||
Utility function to generate a json version of this profiler.
|
||||
This is useful to write profiling entries into a file, such as::
|
||||
|
||||
with Profiler(db=None) as profiler:
|
||||
do_stuff()
|
||||
|
||||
filename = p.format_path('/home/foo/{desc}_{len}.json')
|
||||
with open(filename, 'w') as f:
|
||||
f.write(profiler.json())
|
||||
"""
|
||||
return json.dumps({
|
||||
"name": self.description,
|
||||
"session": self.profile_session,
|
||||
"create_date": datetime.datetime.now().strftime("%Y%m%d-%H%M%S"),
|
||||
"init_stack_trace": _format_stack(self.init_stack_trace),
|
||||
"duration": self.duration,
|
||||
"collectors": {collector.name: collector.entries for collector in self.collectors},
|
||||
}, indent=4)
|
||||
|
||||
|
||||
class Nested:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user