From 40412bf2a3097b8e672ba682145f21796b241290 Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Tue, 21 Jul 2020 23:16:40 +0200 Subject: [PATCH] BaseUtils: Added Profile decorator --- .../TheCodeLabs_BaseUtils/ProfileHelper.py | 104 ++++++++++++++++++ TheCodeLabs_BaseUtils/setup.py | 4 +- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 TheCodeLabs_BaseUtils/TheCodeLabs_BaseUtils/ProfileHelper.py diff --git a/TheCodeLabs_BaseUtils/TheCodeLabs_BaseUtils/ProfileHelper.py b/TheCodeLabs_BaseUtils/TheCodeLabs_BaseUtils/ProfileHelper.py new file mode 100644 index 0000000..66cf4e5 --- /dev/null +++ b/TheCodeLabs_BaseUtils/TheCodeLabs_BaseUtils/ProfileHelper.py @@ -0,0 +1,104 @@ +import os +import subprocess +import sys +import tempfile +import time +import webbrowser +from functools import wraps + +import psutil + + +def Profile(filePath=None): + """ + Decorator for method profiling. + + :param filePath: Optional. Absolute file path where the profiling file should be saved (timestamp will be added + automatically). If left empty the file is saved in the temp directory in a sub folder called "profiling" + :type filePath: str + + :example: + @Profile() + @Profile('C:/profiles/myProfiling.prof') + """ + if not isinstance(filePath, (type(None), str)): + raise RuntimeError('Decorator "Profile()" must used function call (with parenthesis)!') + + def ProfileDecorator(func): + @wraps(func) + def f(*args, **kwargs): + import cProfile + profFile = __determine_profile_path(filePath, callerName=func.__name__) + + prof = cProfile.Profile() + try: + return prof.runcall(func, *args, **kwargs) + finally: + __finalize(prof, profFile) + + return f + + return ProfileDecorator + + +def __finalize(prof, profFile): + print(f'Saving profiling file to: {profFile}') + prof.dump_stats(profFile) + __open_snakeviz(profFile) + + +def __determine_profile_path(filePath, callerName): + formattedDateTime = time.strftime('%Y-%m-%d_%H%M%S') + + if filePath is None: + fileDir = os.path.join(tempfile.gettempdir(), 'profiling') + filePath = f'{callerName}_{formattedDateTime}.prof' + else: + fileDir, theFileName = os.path.split(filePath) + filePath = f'{os.path.splitext(filePath)[0]}_{formattedDateTime}.prof' + + profFile = os.path.join(fileDir, filePath) + if not os.path.isdir(fileDir): + os.makedirs(fileDir) + return profFile + + +def __open_snakeviz(profFile): + snakeVizExecutable = __get_snakeviz_executable() + executable = os.path.join(os.path.dirname(sys.executable), snakeVizExecutable) + + if not os.path.isfile(executable): + raise ImportError(f'Snakeviz not installed ({executable} not found)') + + isAlreadyRunning = snakeVizExecutable in {p.name() for p in psutil.process_iter()} + if isAlreadyRunning: + browser = __get_webbrowser() + browser.open_new_tab(f'http://127.0.0.1:8080/snakeviz/{profFile}') + else: + subprocess.check_call(f'"{executable}" "{profFile}"', shell=True) + + +def __get_snakeviz_executable(): + if sys.platform == 'win32': + return 'snakeviz.exe' + return 'snakeviz' + + +def __get_webbrowser(): + webbrowser.register('firefox', None, + webbrowser.BackgroundBrowser('C://Program Files//Mozilla Firefox//firefox.exe')) + try: + return webbrowser.get('firefox') + except webbrowser.Error: + pass + + return webbrowser.get() + + +if __name__ == '__main__': + @Profile() + def test(): + os.listdir('c:') + + + test() diff --git a/TheCodeLabs_BaseUtils/setup.py b/TheCodeLabs_BaseUtils/setup.py index b2dfb19..191c7e8 100644 --- a/TheCodeLabs_BaseUtils/setup.py +++ b/TheCodeLabs_BaseUtils/setup.py @@ -3,7 +3,7 @@ from setuptools import setup setup( name='TheCodeLabs-BaseUtils', packages=['TheCodeLabs_BaseUtils'], - version='1.2.0', + version='1.3.0', license='MIT', description='Useful python classes', author='TheCodeLabs', @@ -12,6 +12,8 @@ setup( download_url='https://pypi.thecodelabs.de', keywords=[], install_requires=[ + 'psutil', + 'snakeviz' ], setup_requires=[ 'wheel' -- GitLab