diff --git a/_unittests/ut_pycode/test_profiling.py b/_unittests/ut_pycode/test_profiling.py index 211c1663..fa87aa9a 100644 --- a/_unittests/ut_pycode/test_profiling.py +++ b/_unittests/ut_pycode/test_profiling.py @@ -6,7 +6,11 @@ import unittest import warnings import time -from pstats import SortKey +try: + from pstats import SortKey +except ImportError: + # python < 3.7 + from pyquickhelper.pycode.profiling import SortKey import pandas from pyquickhelper.pycode import ExtTestCase from pyquickhelper.pandashelper import df2rst @@ -119,6 +123,8 @@ def simple(): self.assertIn('"start_time"', res) self.assertNotEmpty(ps) + @unittest.skipIf(sys.version_info[:2] < (3, 7), + reason="not supported") def test_profile_graph(self): calls = [0] diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 135719a1..376f53ac 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,6 +6,8 @@ jobs: matrix: Python39: python.version: '3.9' + Python36: + python.version: '3.6' maxParallel: 3 steps: diff --git a/requirements.txt b/requirements.txt index 12d901a8..5efdfd36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -51,9 +51,8 @@ sphinx-gallery sphinxcontrib-imagesvg sphinx_rtd_theme tabulate -thebe tqdm -traitlets>=5.0 +traitlets unify virtualenv wheel diff --git a/src/pyquickhelper/pycode/profiling.py b/src/pyquickhelper/pycode/profiling.py index 786e73fb..07e40b30 100644 --- a/src/pyquickhelper/pycode/profiling.py +++ b/src/pyquickhelper/pycode/profiling.py @@ -9,7 +9,17 @@ import os import site import cProfile -from pstats import SortKey, Stats +from pstats import Stats + +try: + from pstarts import SortKey +except ImportError: # pragma: no cover + # Python < 3.7 + + class SortKey: + LINE = 'line' + CUMULATIVE = 'cumulative' + TIME = 'time' class ProfileNode: @@ -53,9 +63,7 @@ def add_calls_to(self, pnode, time_elements): @staticmethod def _key(filename, line, fct): - key = "%s:%d" % (filename, line) - if key == "~:0": - key += ":%s" % fct + key = "%s:%d:%s" % (filename, line, fct) return key @property @@ -68,11 +76,13 @@ def get_root(self): "Returns the root of the graph." done = set() - def _get_root(node): + def _get_root(node, stor=None): + if stor is not None: + stor.append(node) if len(node.called_by) == 0: return node if len(node.called_by) == 1: - return _get_root(node.called_by[0]) + return _get_root(node.called_by[0], stor=stor) res = None for ct in node.called_by: k = id(node), id(ct) @@ -81,12 +91,20 @@ def _get_root(node): res = ct break if res is None: - raise RuntimeError( # pragma: no cover - "All paths have been explored and no entry point was found.") + # All paths have been explored and no entry point was found. + # Choosing the most consuming function. + return None done.add((id(node), id(res))) - return _get_root(res) + return _get_root(res, stor=stor) - return _get_root(self) + root = _get_root(self) + if root is None: + candidates = [] + _get_root(self, stor=candidates) + tall = [(n.tall, n) for n in candidates] + tall.sort() + root = tall[-1][-1] + return root def __repr__(self): "usual" @@ -537,7 +555,7 @@ def better_name(row): return ps, res if as_df: raise ValueError( # pragma: no cover - "as_df is not a compatible option with pyinst_format") + "as_df is not a compatible option with pyinst_format.") try: from pyinstrument import Profiler