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