~ztk-steering-group/zope.testrunner/trunk

« back to all changes in this revision

Viewing changes to src/zope/testrunner/profiling.py

  • Committer: mgedmin
  • Date: 2013-02-11 20:02:43 UTC
  • Revision ID: svn-v4:62d5b8a3-27da-0310-9561-8e5933582275:zope.testrunner/trunk:129304
Moved to github

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
##############################################################################
2
 
#
3
 
# Copyright (c) 2004-2008 Zope Foundation and Contributors.
4
 
# All Rights Reserved.
5
 
#
6
 
# This software is subject to the provisions of the Zope Public License,
7
 
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8
 
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
 
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
 
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
 
# FOR A PARTICULAR PURPOSE.
12
 
#
13
 
##############################################################################
14
 
"""Profiler support for the test runner
15
 
"""
16
 
 
17
 
import os
18
 
import glob
19
 
import sys
20
 
import tempfile
21
 
import zope.testrunner.feature
22
 
 
23
 
available_profilers = {}
24
 
 
25
 
 
26
 
try:
27
 
    import cProfile
28
 
    import pstats
29
 
except ImportError:
30
 
    pass
31
 
else:
32
 
    class CProfiler(object):
33
 
        """cProfiler"""
34
 
        def __init__(self, filepath):
35
 
            self.filepath = filepath
36
 
            self.profiler = cProfile.Profile()
37
 
            self.enable = self.profiler.enable
38
 
            self.disable = self.profiler.disable
39
 
 
40
 
        def finish(self):
41
 
            self.profiler.dump_stats(self.filepath)
42
 
 
43
 
        def loadStats(self, prof_glob):
44
 
            stats = None
45
 
            for file_name in glob.glob(prof_glob):
46
 
                if stats is None:
47
 
                    stats = pstats.Stats(file_name)
48
 
                else:
49
 
                    stats.add(file_name)
50
 
            return stats
51
 
 
52
 
    available_profilers['cProfile'] = CProfiler
53
 
 
54
 
 
55
 
# some Linux distributions don't include the profiler, which hotshot uses
56
 
if not sys.hexversion >= 0x02060000:
57
 
    # Hotshot is not maintained any longer in 2.6. It does not support 
58
 
    # merging to hotshot files. Thus we won't use it in python2.6 and
59
 
    # onwards
60
 
    try:
61
 
        import hotshot
62
 
        import hotshot.stats
63
 
    except ImportError:
64
 
        pass
65
 
    else:
66
 
        class HotshotProfiler(object):
67
 
            """hotshot interface"""
68
 
 
69
 
            def __init__(self, filepath):
70
 
                self.profiler = hotshot.Profile(filepath)
71
 
                self.enable = self.profiler.start
72
 
                self.disable = self.profiler.stop
73
 
 
74
 
            def finish(self):
75
 
                self.profiler.close()
76
 
 
77
 
            def loadStats(self, prof_glob):
78
 
                stats = None
79
 
                for file_name in glob.glob(prof_glob):
80
 
                    loaded = hotshot.stats.load(file_name)
81
 
                    if stats is None:
82
 
                        stats = loaded
83
 
                    else:
84
 
                        stats.add(loaded)
85
 
                return stats
86
 
 
87
 
        available_profilers['hotshot'] = HotshotProfiler
88
 
 
89
 
 
90
 
class Profiling(zope.testrunner.feature.Feature):
91
 
 
92
 
    def __init__(self, runner):
93
 
        super(Profiling, self).__init__(runner)
94
 
 
95
 
        if (self.runner.options.profile
96
 
            and sys.version_info[:3] <= (2,4,1)
97
 
            and __debug__):
98
 
            self.runner.options.output.error(
99
 
                'Because of a bug in Python < 2.4.1, profiling '
100
 
                'during tests requires the -O option be passed to '
101
 
                'Python (not the test runner).')
102
 
            sys.exit()
103
 
 
104
 
        self.active = bool(self.runner.options.profile)
105
 
        self.profiler = self.runner.options.profile
106
 
 
107
 
    def global_setup(self):
108
 
        self.prof_prefix = 'tests_profile.'
109
 
        self.prof_suffix = '.prof'
110
 
        self.prof_glob = os.path.join(self.runner.options.prof_dir,
111
 
                                      self.prof_prefix + '*' + self.prof_suffix)
112
 
        # if we are going to be profiling, and this isn't a subprocess,
113
 
        # clean up any stale results files
114
 
        if not self.runner.options.resume_layer:
115
 
            for file_name in glob.glob(self.prof_glob):
116
 
                os.unlink(file_name)
117
 
        # set up the output file
118
 
        self.oshandle, self.file_path = tempfile.mkstemp(
119
 
            self.prof_suffix, self.prof_prefix, self.runner.options.prof_dir)
120
 
        self.profiler = available_profilers[self.runner.options.profile](self.file_path)
121
 
 
122
 
        # Need to do this rebinding to support the stack-frame annoyance with
123
 
        # hotshot.
124
 
        self.late_setup = self.profiler.enable
125
 
        self.early_teardown = self.profiler.disable
126
 
 
127
 
    def global_teardown(self):
128
 
        self.profiler.finish()
129
 
        # We must explicitly close the handle mkstemp returned, else on
130
 
        # Windows this dies the next time around just above due to an
131
 
        # attempt to unlink a still-open file.
132
 
        os.close(self.oshandle)
133
 
        if not self.runner.options.resume_layer:
134
 
            self.profiler_stats = self.profiler.loadStats(self.prof_glob)
135
 
            self.profiler_stats.sort_stats('cumulative', 'calls')
136
 
 
137
 
    def report(self):
138
 
        if not self.runner.options.resume_layer:
139
 
            self.runner.options.output.profiler_stats(self.profiler_stats)