diff options
-rw-r--r-- | Doc/library/profile.rst | 11 | ||||
-rw-r--r-- | Lib/pstats.py | 57 | ||||
-rw-r--r-- | Lib/test/test_pstats.py | 24 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2019-08-27-03-57-25.bpo-37958.lRORI3.rst | 2 |
4 files changed, 90 insertions, 4 deletions
diff --git a/Doc/library/profile.rst b/Doc/library/profile.rst index 8d589d2..34525a9 100644 --- a/Doc/library/profile.rst +++ b/Doc/library/profile.rst @@ -525,6 +525,17 @@ Analysis of the profiler data is done using the :class:`~pstats.Stats` class. ordering are identical to the :meth:`~pstats.Stats.print_callers` method. + .. method:: get_stats_profile() + + This method returns an instance of StatsProfile, which contains a mapping + of function names to instances of FunctionProfile. Each FunctionProfile + instance holds information related to the function's profile such as how + long the function took to run, how many times it was called, etc... + + .. versionadded:: 3.9 + Added the following dataclasses: StatsProfile, FunctionProfile. + Added the following function: get_stats_profile. + .. _deterministic-profiling: What Is Deterministic Profiling? diff --git a/Lib/pstats.py b/Lib/pstats.py index 4b419a8..e781b91 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -25,11 +25,13 @@ import os import time import marshal import re + from enum import Enum from functools import cmp_to_key +from dataclasses import dataclass +from typing import Dict -__all__ = ["Stats", "SortKey"] - +__all__ = ["Stats", "SortKey", "FunctionProfile", "StatsProfile"] class SortKey(str, Enum): CALLS = 'calls', 'ncalls' @@ -52,6 +54,22 @@ class SortKey(str, Enum): return obj +@dataclass(unsafe_hash=True) +class FunctionProfile: + ncalls: int + tottime: float + percall_tottime: float + cumtime: float + percall_cumtime: float + file_name: str + line_number: int + +@dataclass(unsafe_hash=True) +class StatsProfile: + '''Class for keeping track of an item in inventory.''' + total_tt: float + func_profiles: Dict[str, FunctionProfile] + class Stats: """This class is used for creating reports from data generated by the Profile class. It is a "friend" of that class, and imports data either @@ -333,6 +351,41 @@ class Stats: return new_list, msg + def get_stats_profile(self): + """This method returns an instance of StatsProfile, which contains a mapping + of function names to instances of FunctionProfile. Each FunctionProfile + instance holds information related to the function's profile such as how + long the function took to run, how many times it was called, etc... + """ + func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys()) + if not func_list: + return StatsProfile(0, {}) + + total_tt = float(f8(self.total_tt)) + func_profiles = {} + stats_profile = StatsProfile(total_tt, func_profiles) + + for func in func_list: + cc, nc, tt, ct, callers = self.stats[func] + file_name, line_number, func_name = func + ncalls = str(nc) if nc == cc else (str(nc) + '/' + str(cc)) + tottime = float(f8(tt)) + percall_tottime = -1 if nc == 0 else float(f8(tt/nc)) + cumtime = float(f8(ct)) + percall_cumtime = -1 if cc == 0 else float(f8(ct/cc)) + func_profile = FunctionProfile( + ncalls, + tottime, # time spent in this function alone + percall_tottime, + cumtime, # time spent in the function plus all functions that this function called, + percall_cumtime, + file_name, + line_number + ) + func_profiles[func_name] = func_profile + + return stats_profile + def get_print_list(self, sel_list): width = self.max_name_len if self.fcn_list: diff --git a/Lib/test/test_pstats.py b/Lib/test/test_pstats.py index f835ce3..f3a6e58 100644 --- a/Lib/test/test_pstats.py +++ b/Lib/test/test_pstats.py @@ -1,10 +1,12 @@ import unittest + from test import support from io import StringIO -import pstats from pstats import SortKey - +import pstats +import time +import cProfile class AddCallersTestCase(unittest.TestCase): """Tests for pstats.add_callers helper.""" @@ -75,6 +77,24 @@ class StatsTestCase(unittest.TestCase): SortKey.TIME, 'calls') + def test_get_stats_profile(self): + def pass1(): pass + def pass2(): pass + def pass3(): pass + + pr = cProfile.Profile() + pr.enable() + pass1() + pass2() + pass3() + pr.create_stats() + ps = pstats.Stats(pr) + + stats_profile = ps.get_stats_profile() + funcs_called = set(stats_profile.func_profiles.keys()) + self.assertIn('pass1', funcs_called) + self.assertIn('pass2', funcs_called) + self.assertIn('pass3', funcs_called) if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2019-08-27-03-57-25.bpo-37958.lRORI3.rst b/Misc/NEWS.d/next/Library/2019-08-27-03-57-25.bpo-37958.lRORI3.rst new file mode 100644 index 0000000..d0b4d6a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-08-27-03-57-25.bpo-37958.lRORI3.rst @@ -0,0 +1,2 @@ +Added the pstats.Stats.get_profile_dict() method to return the profile +data as a StatsProfile instance. |