1
##############################################################################
3
# Copyright (c) 2004-2008 Zope Foundation and Contributors.
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.
13
##############################################################################
14
"""Profiler support for the test runner
21
import zope.testrunner.feature
23
available_profilers = {}
32
class CProfiler(object):
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
41
self.profiler.dump_stats(self.filepath)
43
def loadStats(self, prof_glob):
45
for file_name in glob.glob(prof_glob):
47
stats = pstats.Stats(file_name)
52
available_profilers['cProfile'] = CProfiler
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
66
class HotshotProfiler(object):
67
"""hotshot interface"""
69
def __init__(self, filepath):
70
self.profiler = hotshot.Profile(filepath)
71
self.enable = self.profiler.start
72
self.disable = self.profiler.stop
77
def loadStats(self, prof_glob):
79
for file_name in glob.glob(prof_glob):
80
loaded = hotshot.stats.load(file_name)
87
available_profilers['hotshot'] = HotshotProfiler
90
class Profiling(zope.testrunner.feature.Feature):
92
def __init__(self, runner):
93
super(Profiling, self).__init__(runner)
95
if (self.runner.options.profile
96
and sys.version_info[:3] <= (2,4,1)
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).')
104
self.active = bool(self.runner.options.profile)
105
self.profiler = self.runner.options.profile
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):
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)
122
# Need to do this rebinding to support the stack-frame annoyance with
124
self.late_setup = self.profiler.enable
125
self.early_teardown = self.profiler.disable
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')
138
if not self.runner.options.resume_layer:
139
self.runner.options.output.profiler_stats(self.profiler_stats)