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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
__author__ = 'calvin'
import cProfile
import logging
from pyperform import StringIO
import os
import pstats
import sys
import threading
Thread = threading.Thread # Start off using threading.Thread until changed
BaseThread = threading.Thread # Store the Thread class from the threading module before monkey-patching
profiled_thread_enabled = False
logged_thread_enabled = True
def enable_thread_profiling(profile_dir, exception_callback=None):
"""
Monkey-patch the threading.Thread class with our own ProfiledThread. Any subsequent imports of threading.Thread
will reference ProfiledThread instead.
"""
global profiled_thread_enabled, Thread
if os.path.isdir(profile_dir):
ProfiledThread.profile_dir = profile_dir
else:
raise OSError('%s does not exist' % profile_dir)
ProfiledThread.exception_callback = exception_callback
Thread = threading.Thread = ProfiledThread
profiled_thread_enabled = True
def enable_thread_logging(exception_callback=None):
"""
Monkey-patch the threading.Thread class with our own LoggedThread. Any subsequent imports of threading.Thread
will reference LoggedThread instead.
"""
global logged_thread_enabled, Thread
LoggedThread.exception_callback = exception_callback
Thread = threading.Thread = LoggedThread
logged_thread_enabled = True
class ProfiledThread(BaseThread):
"""
A Thread that contains it's own profiler. When the SSI_App closes, all profiles are combined and printed
to a single .profile.
"""
profile_dir = None
exception_callback = None
def run(self):
profiler = cProfile.Profile()
try:
logging.debug('ProfiledThread: Starting ProfiledThread {}'.format(self.name))
profiler.runcall(BaseThread.run, self)
except Exception as e:
logging.error('ProfiledThread: Error encountered in Thread {name}'.format(name=self.name))
logging.error(e)
if ProfiledThread.exception_callback:
e_type, e_value, last_traceback = sys.exc_info()
ProfiledThread.exception_callback(e_type, e_value, last_traceback)
finally:
if ProfiledThread.profile_dir is None:
logging.warning('ProfiledThread: profile_dir is not specified. '
'Profile \'{}\' will not be saved.'.format(self.name))
return
self.print_stats(profiler)
def print_stats(self, profiler):
filename = os.path.join(self.profile_dir, self.name)
s = StringIO.StringIO()
sortby = 'cumulative'
ps = pstats.Stats(profiler, stream=s)
# Take out directory names
ps.strip_dirs()
# Sort
ps.sort_stats(sortby)
# Print to the stream
ps.print_stats()
stats_file = filename + '.stats'
profile_file = filename + '.profile'
# Create the stats file
ps.dump_stats(stats_file)
# Create a readable .profile file
with open(profile_file, 'w') as f:
f.write(s.getvalue())
@staticmethod
def combine_profiles(profile_dir, outfile, sortby='cumulative'):
s = StringIO.StringIO()
stat_files = [f for f in os.listdir(profile_dir) if os.path.isfile(os.path.join(profile_dir, f))
and f.endswith('.stats')]
ps = pstats.Stats(os.path.join(profile_dir, stat_files[0]), stream=s)
if len(stat_files) > 1:
for stat in stat_files[1:]:
ps.add(os.path.join(profile_dir, stat))
profile_name = os.path.join(profile_dir, '{}.profile'.format(outfile.replace('.profile', '')))
with open(profile_name, 'w') as f:
ps.strip_dirs()
ps.sort_stats(sortby)
ps.print_stats()
f.write(s.getvalue())
class LoggedThread(BaseThread):
exception_callback = None
def run(self):
logging.debug('LoggedThread: Starting LoggedThread {}'.format(self.name))
try:
super(LoggedThread, self).run()
except Exception as e:
logging.error('LoggedThread: Error encountered in Thread {name}'.format(name=self.name))
logging.error(e)
if LoggedThread.exception_callback:
e_type, e_value, last_traceback = sys.exc_info()
LoggedThread.exception_callback(e_type, e_value, last_traceback)
|