profiling.py 2.85 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
# -*- coding: utf-8 -*-
#
# diffoscope: in-depth comparison of files, archives, and directories
#
# Copyright © 2016 Chris Lamb <lamby@debian.org>
#
# diffoscope is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# diffoscope is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.

import sys
import time
import contextlib
import collections

25 26
_ENABLED = False

27

28 29 30 31
@contextlib.contextmanager
def profile(namespace, key):
    start = time.time()
    yield
32 33 34

    if _ENABLED:
        ProfileManager().increment(start, namespace, key)
35

Mattia Rizzolo's avatar
Mattia Rizzolo committed
36

37 38 39 40 41 42 43 44
class ProfileManager(object):
    _singleton = {}

    def __init__(self):
        self.__dict__ = self._singleton

        if not self._singleton:
            self.data = collections.defaultdict(
45 46
                lambda: collections.defaultdict(lambda: {
                    'time': 0.0,
47
                    'count': 0,
48
                }),
49 50
            )

51 52 53 54
    def setup(self, parsed_args):
        global _ENABLED
        _ENABLED = parsed_args.profile_output is not None

55 56 57 58 59 60 61
    def increment(self, start, namespace, key):
        if not isinstance(key, str):
            key = '{}.{}'.format(
                key.__class__.__module__,
                key.__class__.__name__,
            )

62
        self.data[namespace][key]['time'] += time.time() - start
63
        self.data[namespace][key]['count'] += 1
64

65 66 67 68 69 70 71 72 73
    def finish(self, parsed_args):
        from .presenters.utils import make_printer

        if parsed_args.profile_output is None:
            return

        with make_printer(parsed_args.profile_output) as fn:
            self.output(fn)

Chris Lamb's avatar
Chris Lamb committed
74
    def output(self, print_fn):
75 76
        title = "Profiling output for: {}".format(' '.join(sys.argv))

Chris Lamb's avatar
Chris Lamb committed
77 78 79 80 81
        print_fn(title)
        print_fn("=" * len(title))

        def key(x):
            return x[1]['time']
82 83

        for namespace, keys in sorted(self.data.items(), key=lambda x: x[0]):
84
            subtitle = "{} (total time: {:.3f}s)".format(
85
                namespace,
86
                sum(x['time'] for x in keys.values()),
87 88
            )

Chris Lamb's avatar
Chris Lamb committed
89
            print_fn("\n{}\n{}\n".format(subtitle, "-" * len(subtitle)))
90

Chris Lamb's avatar
Chris Lamb committed
91 92
            for value, totals in sorted(keys.items(), key=key, reverse=True):
                print_fn("  {:10.3f}s {:5d} call{}    {}".format(
93 94
                    totals['time'],
                    totals['count'],
95
                    ' ' if totals['count'] == 1 else 's',
96 97
                    value,
                ))