Coverage for diffoscope/profiling.py: 94%
47 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-07 13:38 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-07 13:38 +0000
1#
2# diffoscope: in-depth comparison of files, archives, and directories
3#
4# Copyright © 2016-2017, 2019-2022 Chris Lamb <lamby@debian.org>
5#
6# diffoscope is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# diffoscope is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with diffoscope. If not, see <https://www.gnu.org/licenses/>.
19import sys
20import time
21import contextlib
22import collections
24from .utils import format_class
25from .logging import setup_logging
27_ENABLED = False
30@contextlib.contextmanager
31def profile(namespace, key):
32 start = time.time()
33 yield
35 if _ENABLED:
36 ProfileManager().increment(start, namespace, key)
39class ProfileManager:
40 _singleton = {}
42 def __init__(self):
43 self.__dict__ = self._singleton
45 if not self._singleton:
46 self.data = collections.defaultdict(
47 lambda: collections.defaultdict(
48 lambda: {"time": 0.0, "count": 0}
49 )
50 )
52 def setup(self, parsed_args):
53 global _ENABLED
54 _ENABLED = parsed_args.profile_output is not None or parsed_args.debug
56 def increment(self, start, namespace, key):
57 if not isinstance(key, str):
58 key = format_class(key.__class__)
60 self.data[namespace][key]["time"] += time.time() - start
61 self.data[namespace][key]["count"] += 1
63 def finish(self, parsed_args=None):
64 from .presenters.utils import make_printer
66 # We are being called in the TERM handler so we don't have access to
67 # parsed_args. Print the profiling output to stderr if we have been
68 # collecting it.
69 if parsed_args is None:
70 if _ENABLED:
71 self.output(lambda x: print(x, file=sys.stderr))
72 return
74 # Include profiling in --debug output if --profile is not set.
75 if parsed_args.profile_output is None:
76 with setup_logging(parsed_args.debug, None) as logger:
77 self.output(lambda x: logger.debug(x.strip("\n")))
78 return
80 with make_printer(parsed_args.profile_output) as fn:
81 self.output(fn)
83 def output(self, print_fn):
84 title = "# Profiling output for: {}".format(" ".join(sys.argv))
86 print_fn(title)
88 def key(x):
89 return x[1]["time"]
91 for namespace, keys in sorted(self.data.items(), key=lambda x: x[0]):
92 print_fn(
93 "\n## {} (total time: {:.3f}s)".format(
94 namespace, sum(x["time"] for x in keys.values())
95 )
96 )
98 for value, totals in sorted(keys.items(), key=key, reverse=True):
99 print_fn(
100 " {:10.3f}s {:6d} call{} {}".format(
101 totals["time"],
102 totals["count"],
103 " " if totals["count"] == 1 else "s",
104 value,
105 )
106 )