~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# Copyright 2009 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Profile the test layers."""

__metaclass__ = type
__all__ = ['profiled', 'setup_profiling']

import atexit
import cPickle as pickle
import os
import tempfile
import time

_profile_stats_filename = os.environ.get('lp_layer_profile_filename', None)
_profiling_setup_time = None

def profiled(func):
    """Decorator that automatically profiles invocations of the method."""
    def profiled_func(cls, *args, **kw):
        global _profile_stats_filename
        if _profile_stats_filename is not None:
            start_time = time.time()
            try:
                return func(cls, *args, **kw)
            finally:
                _update_profile_stats(cls, func, time.time() - start_time)
        else:
            return func(cls, *args, **kw)
    return profiled_func


def setup_profiling():
    """Initialize our profiling information.

    This cannot be done on module load as this information should only
    be initialized in the top testrunner process.
    """
    global _profile_stats_filename
    global _profiling_setup_time

    _profiling_setup_time = time.time()

    outf, _profile_stats_filename = tempfile.mkstemp(
            '.pickle', 'lp_layer_prof')
    os.close(outf)

    outf = open(_profile_stats_filename, 'wb')
    pickle.dump({}, outf, pickle.HIGHEST_PROTOCOL)
    outf.close()

    atexit.register(os.remove, _profile_stats_filename)

    # Store filename in the environment so subprocesses can find it.
    os.environ['lp_layer_profile_filename'] = _profile_stats_filename


def _update_profile_stats(cls, func, duration):
    """Update the profile statistics with new information about a method call.
    """
    global _profile_stats_filename

    key = '%s.%s' % (cls.__name__, func.__name__)

    # Load stats from disk. We can't store in RAM as it needs to persist
    # across processes.
    stats = pickle.load(open(_profile_stats_filename, 'rb'))
    hits, total_duration = stats.setdefault(key, (0, 0))

    # Update stats
    stats[key] = (hits + 1, total_duration + duration)

    # Dump stats back to disk, making sure we flush
    outf = open(_profile_stats_filename, 'wb')
    pickle.dump(stats, outf, pickle.HIGHEST_PROTOCOL)
    outf.close() # and flush


def report_profile_stats():
    """Print a report about our collected statistics to stdout."""
    stats = pickle.load(open(_profile_stats_filename, 'rb'))

    print
    print 'Test suite profiling information'
    print '================================'

    total_profiled_duration = 0.0
    for key, value in sorted(stats.items()):
        hits, duration = value
        total_profiled_duration += duration
        if duration < 0.1:
            duration = 'negligible time'
        else:
            duration = '%0.1fs' % duration
        print '%-45s %4d calls taking %s.' % (
                key[:45], hits, duration)

    print
    print "Total duration of profiled methods %0.1f seconds." % (
            total_profiled_duration)

    global _profiling_setup_time
    print
    print "Total duration of test run %0.1f seconds." % (
            time.time() - _profiling_setup_time)