From a7c7d49363547d46bec9dbc12c57af4d7859d48a Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sun, 25 Aug 2019 20:32:32 -0400 Subject: [PATCH 1/9] Adding get_profile_dict to pstats --- Lib/pstats.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Lib/pstats.py b/Lib/pstats.py index 4b419a8ecdb6c7..eea4af4a971401 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -333,6 +333,29 @@ def eval_print_amount(self, sel, list, msg): return new_list, msg + def get_profile_dict(self): + profile_dict = {} + # TODO(will change this to avoid calling get_print_list) + width, list = self.get_print_list([]) + if list: + for func in list: + cc, nc, tt, ct, callers = self.stats[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_name = func_std_string(func) + profile_dict[func_name] = { + "ncalls": ncalls, + "tottime": tottime, # time spent in this function alone + "percall_tottime": percall_tottime, + "cumtime": cumtime, # time spent in the function plus all functions that this function called, + "percall_cumtime": percall_cumtime + } + profile_dict["total_tt"] = float(f8(self.total_tt)) + return profile_dict + def get_print_list(self, sel_list): width = self.max_name_len if self.fcn_list: From 2bdb8aba52ad470311579091f124fc5c25ada1f7 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2019 03:57:26 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2019-08-27-03-57-25.bpo-37958.lRORI3.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-08-27-03-57-25.bpo-37958.lRORI3.rst 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 00000000000000..6359e269e768f9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-08-27-03-57-25.bpo-37958.lRORI3.rst @@ -0,0 +1,3 @@ +pstats is really useful or profiling and printing the output of the execution of some block of code, but I've found on multiple occasions that sometimes I'd like to access this output directly in an easily usable dictionary on which I can further analyze or manipulate. My proposal is to add a function called get_profile_dict inside of pstats that'll automatically return this data. + +(Please note that the PR I've put up is just a first version to get some feedback on, and it needs to be updated if the community chooses to move forward with it (e.g. we shouldn't be calling get_print_list inside of get_profile_dict in production code)) \ No newline at end of file From abacfed96cee007c8d88b240d2840778b1c7a509 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Mon, 23 Sep 2019 13:07:53 -0400 Subject: [PATCH 3/9] Cleaned up the code so it's potentially prod ready --- Lib/pstats.py | 65 +++++++---- .../2019-08-27-03-57-25.bpo-37958.lRORI3.rst | 102 +++++++++++++++++- 2 files changed, 143 insertions(+), 24 deletions(-) diff --git a/Lib/pstats.py b/Lib/pstats.py index eea4af4a971401..55d8685717f1bb 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -333,28 +333,49 @@ def eval_print_amount(self, sel, list, msg): return new_list, msg - def get_profile_dict(self): - profile_dict = {} - # TODO(will change this to avoid calling get_print_list) - width, list = self.get_print_list([]) - if list: - for func in list: - cc, nc, tt, ct, callers = self.stats[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_name = func_std_string(func) - profile_dict[func_name] = { - "ncalls": ncalls, - "tottime": tottime, # time spent in this function alone - "percall_tottime": percall_tottime, - "cumtime": cumtime, # time spent in the function plus all functions that this function called, - "percall_cumtime": percall_cumtime - } - profile_dict["total_tt"] = float(f8(self.total_tt)) - return profile_dict + def get_profile_dict(self, keys_filter=None): + """ + Returns a dict where the key is a function name and the value is a dict + with the following keys: + - ncalls + - tottime + - percall_tottime + - cumtime + - percall_cumtime + - file_name + - line_number + + keys_filter can be optionally set to limit the key-value pairs in the + retrieved dict. + """ + pstats_dict = {} + func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys()) + + if not func_list: + return pstats_dict + + pstats_dict["total_tt"] = float(f8(self.total_tt)) + for func in func_list: + cc, nc, tt, ct, callers = self.stats[func] + file, line, 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_dict = { + "ncalls": ncalls, + "tottime": tottime, # time spent in this function alone + "percall_tottime": percall_tottime, + "cumtime": cumtime, # time spent in the function plus all functions that this function called, + "percall_cumtime": percall_cumtime, + "file_name": file, + "line_number": line + } + func_dict_filtered = func_dict if not keys_filter else { key: func_dict[key] for key in keys_filter } + pstats_dict[func_name] = func_dict_filtered + + return pstats_dict def get_print_list(self, sel_list): width = self.max_name_len 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 index 6359e269e768f9..3e864f66598fb4 100644 --- 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 @@ -1,3 +1,101 @@ -pstats is really useful or profiling and printing the output of the execution of some block of code, but I've found on multiple occasions that sometimes I'd like to access this output directly in an easily usable dictionary on which I can further analyze or manipulate. My proposal is to add a function called get_profile_dict inside of pstats that'll automatically return this data. +pstats is really useful or profiling and printing the output of the execution of some block of code, but I've found on multiple occasions when I'd like to access this output directly in an easily usable dictionary on which I can further analyze or manipulate. -(Please note that the PR I've put up is just a first version to get some feedback on, and it needs to be updated if the community chooses to move forward with it (e.g. we shouldn't be calling get_print_list inside of get_profile_dict in production code)) \ No newline at end of file +The proposal is to add a function called get_profile_dict inside of pstats that'll automatically return this data the data in an easily accessible dict. + +The output of the following script: + +``` +import cProfile, pstats +import pprint +from pstats import func_std_string, f8 + +def fib(n): + if n == 0: + return 0 + if n == 1: + return 1 + return fib(n-1) + fib(n-2) + +pr = cProfile.Profile() +pr.enable() +fib(5) +pr.create_stats() + +ps = pstats.Stats(pr).sort_stats('tottime', 'cumtime') + +def get_profile_dict(self, keys_filter=None): + """ + Returns a dict where the key is a function name and the value is a dict + with the following keys: + - ncalls + - tottime + - percall_tottime + - cumtime + - percall_cumtime + - file_name + - line_number + + keys_filter can be optionally set to limit the key-value pairs in the + retrieved dict. + """ + pstats_dict = {} + func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys()) + + if not func_list: + return pstats_dict + + pstats_dict["total_tt"] = float(f8(self.total_tt)) + for func in func_list: + cc, nc, tt, ct, callers = self.stats[func] + file, line, 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_dict = { + "ncalls": ncalls, + "tottime": tottime, # time spent in this function alone + "percall_tottime": percall_tottime, + "cumtime": cumtime, # time spent in the function plus all functions that this function called, + "percall_cumtime": percall_cumtime, + "file_name": file, + "line_number": line + } + func_dict_filtered = func_dict if not keys_filter else { key: func_dict[key] for key in keys_filter } + pstats_dict[func_name] = func_dict_filtered + + return pstats_dict + +pp = pprint.PrettyPrinter(depth=6) +pp.pprint(get_profile_dict(ps)) +``` + +will produce: + +``` +{"": {'cumtime': 0.0, + 'file_name': '~', + 'line_number': 0, + 'ncalls': '1', + 'percall_cumtime': 0.0, + 'percall_tottime': 0.0, + 'tottime': 0.0}, + 'create_stats': {'cumtime': 0.0, + 'file_name': '/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/cProfile.py', + 'line_number': 50, + 'ncalls': '1', + 'percall_cumtime': 0.0, + 'percall_tottime': 0.0, + 'tottime': 0.0}, + 'fib': {'cumtime': 0.0, + 'file_name': 'get_profile_dict.py', + 'line_number': 5, + 'ncalls': '15/1', + 'percall_cumtime': 0.0, + 'percall_tottime': 0.0, + 'tottime': 0.0}, + 'total_tt': 0.0} + ``` + + As an example, this can be used to generate a stacked column chart using various visualization tools which will assist in easily identifying program bottlenecks. From 858c4cdea21e1e19d0ccdf45f18eb9035def47fe Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Tue, 24 Sep 2019 10:12:44 -0400 Subject: [PATCH 4/9] Fix documentation --- Lib/pstats.py | 19 ++-- .../2019-08-27-03-57-25.bpo-37958.lRORI3.rst | 98 +------------------ 2 files changed, 9 insertions(+), 108 deletions(-) diff --git a/Lib/pstats.py b/Lib/pstats.py index 55d8685717f1bb..9a06924ffab983 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -335,18 +335,13 @@ def eval_print_amount(self, sel, list, msg): def get_profile_dict(self, keys_filter=None): """ - Returns a dict where the key is a function name and the value is a dict - with the following keys: - - ncalls - - tottime - - percall_tottime - - cumtime - - percall_cumtime - - file_name - - line_number - - keys_filter can be optionally set to limit the key-value pairs in the - retrieved dict. + Returns a dict where the key is a function name and the value is a + dict with the following keys: + ncalls, tottime, percall_tottime, cumtime, percall_cumtime, + file_name, line_number + + :param list keys_filter: Optional parameterto limit the keys returned + in the retrieved dict """ pstats_dict = {} func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys()) 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 index 3e864f66598fb4..dcde22c92130ef 100644 --- 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 @@ -2,100 +2,6 @@ pstats is really useful or profiling and printing the output of the execution of The proposal is to add a function called get_profile_dict inside of pstats that'll automatically return this data the data in an easily accessible dict. -The output of the following script: +The output of get_profile_dict is a dict pointing from function names to a dict with the following keys: ncalls, tottime, percall_tottime, cumtime, percall_cumtime, file_name, line_number. -``` -import cProfile, pstats -import pprint -from pstats import func_std_string, f8 - -def fib(n): - if n == 0: - return 0 - if n == 1: - return 1 - return fib(n-1) + fib(n-2) - -pr = cProfile.Profile() -pr.enable() -fib(5) -pr.create_stats() - -ps = pstats.Stats(pr).sort_stats('tottime', 'cumtime') - -def get_profile_dict(self, keys_filter=None): - """ - Returns a dict where the key is a function name and the value is a dict - with the following keys: - - ncalls - - tottime - - percall_tottime - - cumtime - - percall_cumtime - - file_name - - line_number - - keys_filter can be optionally set to limit the key-value pairs in the - retrieved dict. - """ - pstats_dict = {} - func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys()) - - if not func_list: - return pstats_dict - - pstats_dict["total_tt"] = float(f8(self.total_tt)) - for func in func_list: - cc, nc, tt, ct, callers = self.stats[func] - file, line, 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_dict = { - "ncalls": ncalls, - "tottime": tottime, # time spent in this function alone - "percall_tottime": percall_tottime, - "cumtime": cumtime, # time spent in the function plus all functions that this function called, - "percall_cumtime": percall_cumtime, - "file_name": file, - "line_number": line - } - func_dict_filtered = func_dict if not keys_filter else { key: func_dict[key] for key in keys_filter } - pstats_dict[func_name] = func_dict_filtered - - return pstats_dict - -pp = pprint.PrettyPrinter(depth=6) -pp.pprint(get_profile_dict(ps)) -``` - -will produce: - -``` -{"": {'cumtime': 0.0, - 'file_name': '~', - 'line_number': 0, - 'ncalls': '1', - 'percall_cumtime': 0.0, - 'percall_tottime': 0.0, - 'tottime': 0.0}, - 'create_stats': {'cumtime': 0.0, - 'file_name': '/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/cProfile.py', - 'line_number': 50, - 'ncalls': '1', - 'percall_cumtime': 0.0, - 'percall_tottime': 0.0, - 'tottime': 0.0}, - 'fib': {'cumtime': 0.0, - 'file_name': 'get_profile_dict.py', - 'line_number': 5, - 'ncalls': '15/1', - 'percall_cumtime': 0.0, - 'percall_tottime': 0.0, - 'tottime': 0.0}, - 'total_tt': 0.0} - ``` - - As an example, this can be used to generate a stacked column chart using various visualization tools which will assist in easily identifying program bottlenecks. +A specific use case of this is to generate a stacked column chart using various visualization tools which will assist in easily identifying program bottlenecks. From 448a1e506a48cc550bec1686f8ae6ac0fc6d255a Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sun, 8 Dec 2019 16:42:22 -0500 Subject: [PATCH 5/9] WIP --- Doc/library/profile.rst | 9 +++ Lib/pstats.py | 59 +++++++++++++++++++ .../2019-08-27-03-57-25.bpo-37958.lRORI3.rst | 11 ++-- 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/Doc/library/profile.rst b/Doc/library/profile.rst index 8d589d247b7747..1f388c2e1f43b2 100644 --- a/Doc/library/profile.rst +++ b/Doc/library/profile.rst @@ -525,6 +525,15 @@ 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_profile_dict(*keys) + + This method returns an instance of StatsProfile, which contains all the + a dictionary of function names to FunctionProfile objects. + + .. versionadded:: 3.9 + Added the following dataclasses: StatsProfile, FunctionProfile. + Added the following function: get_profile_dict. + .. _deterministic-profiling: What Is Deterministic Profiling? diff --git a/Lib/pstats.py b/Lib/pstats.py index 9a06924ffab983..34585843cb898f 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -25,8 +25,10 @@ import time import marshal import re + from enum import Enum from functools import cmp_to_key +from dataclasses import dataclass __all__ = ["Stats", "SortKey"] @@ -52,6 +54,63 @@ def __new__(cls, *values): 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 + function_profiles: Dict[str, FunctionProfile] + + def total_cost(self) -> float: + return self.unit_price * self.quantity_on_hand + + """ + Returns a dict where the key is a function name and the value is a + dict with the following keys: + ncalls, tottime, percall_tottime, cumtime, percall_cumtime, + file_name, line_number + + :param list keys_filter: Optional parameterto limit the keys returned + in the retrieved dict + """ + pstats_dict = {} + func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys()) + + if not func_list: + return pstats_dict + + pstats_dict["total_tt"] = float(f8(self.total_tt)) + for func in func_list: + cc, nc, tt, ct, callers = self.stats[func] + file, line, 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_dict = { + "ncalls": ncalls, + "tottime": tottime, # time spent in this function alone + "percall_tottime": percall_tottime, + "cumtime": cumtime, # time spent in the function plus all functions that this function called, + "percall_cumtime": percall_cumtime, + "file_name": file, + "line_number": line + } + func_dict_filtered = func_dict if not keys_filter else { key: func_dict[key] for key in keys_filter } + pstats_dict[func_name] = func_dict_filtered + + return pstats_dict + 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 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 index dcde22c92130ef..a3a3a9077cf9f6 100644 --- 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 @@ -1,7 +1,4 @@ -pstats is really useful or profiling and printing the output of the execution of some block of code, but I've found on multiple occasions when I'd like to access this output directly in an easily usable dictionary on which I can further analyze or manipulate. - -The proposal is to add a function called get_profile_dict inside of pstats that'll automatically return this data the data in an easily accessible dict. - -The output of get_profile_dict is a dict pointing from function names to a dict with the following keys: ncalls, tottime, percall_tottime, cumtime, percall_cumtime, file_name, line_number. - -A specific use case of this is to generate a stacked column chart using various visualization tools which will assist in easily identifying program bottlenecks. +Added the pstats.Stats.get_profile_dict() method to get retrieve the profile data as a dictionary. + + +# Reply to comments here: https://github.com/python/cpython/pull/15495/files From 21d300216b06802c817e80c902c64bbc1c4537a5 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sun, 8 Dec 2019 18:47:20 -0500 Subject: [PATCH 6/9] Reply to all comments - Move to using dataclasses - Add comments/documentation - Add tests --- Doc/library/profile.rst | 10 +- Lib/pstats.py | 95 +++++-------------- Lib/test/test_pstats.py | 25 ++++- .../2019-08-27-03-57-25.bpo-37958.lRORI3.rst | 6 +- 4 files changed, 56 insertions(+), 80 deletions(-) diff --git a/Doc/library/profile.rst b/Doc/library/profile.rst index 1f388c2e1f43b2..34525a96f55c43 100644 --- a/Doc/library/profile.rst +++ b/Doc/library/profile.rst @@ -525,14 +525,16 @@ 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_profile_dict(*keys) + .. method:: get_stats_profile() - This method returns an instance of StatsProfile, which contains all the - a dictionary of function names to FunctionProfile objects. + 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_profile_dict. + Added the following function: get_stats_profile. .. _deterministic-profiling: diff --git a/Lib/pstats.py b/Lib/pstats.py index 34585843cb898f..d187d49462909f 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -29,9 +29,9 @@ 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' @@ -68,48 +68,7 @@ class FunctionProfile: class StatsProfile: '''Class for keeping track of an item in inventory.''' total_tt: float - function_profiles: Dict[str, FunctionProfile] - - def total_cost(self) -> float: - return self.unit_price * self.quantity_on_hand - - """ - Returns a dict where the key is a function name and the value is a - dict with the following keys: - ncalls, tottime, percall_tottime, cumtime, percall_cumtime, - file_name, line_number - - :param list keys_filter: Optional parameterto limit the keys returned - in the retrieved dict - """ - pstats_dict = {} - func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys()) - - if not func_list: - return pstats_dict - - pstats_dict["total_tt"] = float(f8(self.total_tt)) - for func in func_list: - cc, nc, tt, ct, callers = self.stats[func] - file, line, 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_dict = { - "ncalls": ncalls, - "tottime": tottime, # time spent in this function alone - "percall_tottime": percall_tottime, - "cumtime": cumtime, # time spent in the function plus all functions that this function called, - "percall_cumtime": percall_cumtime, - "file_name": file, - "line_number": line - } - func_dict_filtered = func_dict if not keys_filter else { key: func_dict[key] for key in keys_filter } - pstats_dict[func_name] = func_dict_filtered - - return pstats_dict + func_profiles: Dict[str, FunctionProfile] class Stats: """This class is used for creating reports from data generated by the @@ -392,44 +351,40 @@ def eval_print_amount(self, sel, list, msg): return new_list, msg - def get_profile_dict(self, keys_filter=None): - """ - Returns a dict where the key is a function name and the value is a - dict with the following keys: - ncalls, tottime, percall_tottime, cumtime, percall_cumtime, - file_name, line_number - - :param list keys_filter: Optional parameterto limit the keys returned - in the retrieved dict + 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... """ - pstats_dict = {} func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys()) - if not func_list: return pstats_dict - pstats_dict["total_tt"] = float(f8(self.total_tt)) + 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, line, func_name = 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_dict = { - "ncalls": ncalls, - "tottime": tottime, # time spent in this function alone - "percall_tottime": percall_tottime, - "cumtime": cumtime, # time spent in the function plus all functions that this function called, - "percall_cumtime": percall_cumtime, - "file_name": file, - "line_number": line - } - func_dict_filtered = func_dict if not keys_filter else { key: func_dict[key] for key in keys_filter } - pstats_dict[func_name] = func_dict_filtered - - return pstats_dict + 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 diff --git a/Lib/test/test_pstats.py b/Lib/test/test_pstats.py index f835ce309a60e1..eeaac035f7c4bd 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,25 @@ def test_sort_starts_mix(self): 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 = stats_profile.func_profiles.keys() + self.assertTrue( + all(pass_func in funcs_called for pass_func in ['pass1', 'pass2', 'pass3']), + "stats profile did not contain the expected function calls" + ) 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 index a3a3a9077cf9f6..01008634ff35dd 100644 --- 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 @@ -1,4 +1,2 @@ -Added the pstats.Stats.get_profile_dict() method to get retrieve the profile data as a dictionary. - - -# Reply to comments here: https://github.com/python/cpython/pull/15495/files +Added the pstats.Stats.get_profile_dict() method to get retrieve the profile +data as a StatsProfile object. From 04792f44b5c720da97cef94e490b160bc7b4beef Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Wed, 8 Jan 2020 22:15:04 -0500 Subject: [PATCH 7/9] Follow up on comments - Return correct default value - Make tests output better errors with assertIn --- Lib/pstats.py | 2 +- Lib/test/test_pstats.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/pstats.py b/Lib/pstats.py index d187d49462909f..e781b91c6052cf 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -359,7 +359,7 @@ def get_stats_profile(self): """ func_list = self.fcn_list[:] if self.fcn_list else list(self.stats.keys()) if not func_list: - return pstats_dict + return StatsProfile(0, {}) total_tt = float(f8(self.total_tt)) func_profiles = {} diff --git a/Lib/test/test_pstats.py b/Lib/test/test_pstats.py index eeaac035f7c4bd..f3a6e586c3bccb 100644 --- a/Lib/test/test_pstats.py +++ b/Lib/test/test_pstats.py @@ -91,11 +91,10 @@ def pass3(): pass ps = pstats.Stats(pr) stats_profile = ps.get_stats_profile() - funcs_called = stats_profile.func_profiles.keys() - self.assertTrue( - all(pass_func in funcs_called for pass_func in ['pass1', 'pass2', 'pass3']), - "stats profile did not contain the expected function calls" - ) + 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() From b0a8bab36fdb36b578504bbff58a2af0e2cfb131 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Wed, 15 Jan 2020 14:32:09 -0800 Subject: [PATCH 8/9] adjust news entry wording --- .../next/Library/2019-08-27-03-57-25.bpo-37958.lRORI3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 01008634ff35dd..634ba84b0e6c15 100644 --- 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 @@ -1,2 +1,2 @@ -Added the pstats.Stats.get_profile_dict() method to get retrieve the profile -data as a StatsProfile object. +Added the pstats.Stats.get_profile_dict() method to reture the profile +data as a StatsProfile instance. From 4818b0e1b9553758e579f9a1ab8b4785f14a7d10 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Wed, 15 Jan 2020 14:32:35 -0800 Subject: [PATCH 9/9] fix my news typo :) --- .../next/Library/2019-08-27-03-57-25.bpo-37958.lRORI3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 634ba84b0e6c15..d0b4d6adca4b98 100644 --- 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 @@ -1,2 +1,2 @@ -Added the pstats.Stats.get_profile_dict() method to reture the profile +Added the pstats.Stats.get_profile_dict() method to return the profile data as a StatsProfile instance.