diff --git a/TheCodeLabs_BaseUtils/TheCodeLabs_BaseUtils/ProfileHelper.py b/TheCodeLabs_BaseUtils/TheCodeLabs_BaseUtils/ProfileHelper.py new file mode 100644 index 0000000000000000000000000000000000000000..66cf4e5543141e48cdf400f412374a8b78ac9d05 --- /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 b2dfb19b60907f1db3d5f09c3a8e3c393f402766..191c7e8f5fe026a07cee330692f388839aa4f391 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'