summaryrefslogtreecommitdiff
path: root/buildstream/_profile.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildstream/_profile.py')
-rw-r--r--buildstream/_profile.py158
1 files changed, 73 insertions, 85 deletions
diff --git a/buildstream/_profile.py b/buildstream/_profile.py
index f29e070c4..3931a4cf0 100644
--- a/buildstream/_profile.py
+++ b/buildstream/_profile.py
@@ -18,18 +18,16 @@
# Authors:
# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
# James Ennis <james.ennis@codethink.co.uk>
+# Benjamin Schubert <bschubert15@bloomberg.net>
+
+import contextlib
import cProfile
import pstats
import os
import datetime
import time
-# Track what profile topics are active
-active_topics = set()
-active_profiles = {}
-initialized = False
-
# Use the topic values here to decide what to profile
# by setting them in the BST_PROFILE environment variable.
@@ -55,99 +53,89 @@ class Topics():
ALL = 'all'
-class Profile():
- def __init__(self, topic, key, message):
- self.message = message
- self.key = topic + '-' + key
- self.start = time.time()
+class _Profile:
+ def __init__(self, key, message):
self.profiler = cProfile.Profile()
+
+ self.key = key
+ self.message = message
+
+ self.start_time = time.time()
+ filename_template = os.path.join(
+ os.getcwd(),
+ "profile-{}-{}".format(
+ datetime.datetime.fromtimestamp(self.start_time).strftime("%Y%m%dT%H%M%S"),
+ self.key.replace("/", "-").replace(".", "-")
+ )
+ )
+ self.log_filename = "{}.log".format(filename_template)
+ self.cprofile_filename = "{}.cprofile".format(filename_template)
+
+ def __enter__(self):
+ self.start()
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.stop()
+ self.save()
+
+ def start(self):
self.profiler.enable()
- def end(self):
+ def stop(self):
self.profiler.disable()
- dt = datetime.datetime.fromtimestamp(self.start)
- timestamp = dt.strftime('%Y%m%dT%H%M%S')
+ def save(self):
+ self._save_log()
+ self.profiler.dump_stats(self.cprofile_filename)
+
+ def _save_log(self):
+ heading = "\n".join([
+ "-" * 64,
+ "Profile for key: {}".format(self.key),
+ "Started at: {}".format(self.start_time),
+ "\n\t{}".format(self.message) if self.message else "",
+ "-" * 64,
+ "" # for a final new line
+ ])
+
+ with open(self.log_filename, "a") as fp:
+ fp.write(heading)
+ ps = pstats.Stats(self.profiler, stream=fp).sort_stats("cumulative")
+ ps.print_stats()
- filename = self.key.replace('/', '-')
- filename = filename.replace('.', '-')
- filename = os.path.join(os.getcwd(), 'profile-' + timestamp + '-' + filename)
- time_ = dt.strftime('%Y-%m-%d %H:%M:%S') # Human friendly format
- self.__write_log(filename + '.log', time_)
+class _Profiler:
+ def __init__(self, settings):
+ self.active_topics = set()
+ self.enabled_topics = set()
- self.__write_binary(filename + '.cprofile')
+ if settings:
+ self.enabled_topics = {
+ topic
+ for topic in settings.split(":")
+ }
- ########################################
- # Private Methods #
- ########################################
+ @contextlib.contextmanager
+ def profile(self, topic, key, message=None):
+ if not self._is_profile_enabled(topic):
+ yield
+ return
- def __write_log(self, filename, time_):
- with open(filename, "a", encoding="utf-8") as f:
- heading = '================================================================\n'
- heading += 'Profile for key: {}\n'.format(self.key)
- heading += 'Started at: {}\n'.format(time_)
- if self.message:
- heading += '\n {}'.format(self.message)
- heading += '================================================================\n'
- f.write(heading)
- ps = pstats.Stats(self.profiler, stream=f).sort_stats('cumulative')
- ps.print_stats()
+ key = "{}-{}".format(topic, key)
- def __write_binary(self, filename):
- self.profiler.dump_stats(filename)
+ assert key not in self.active_topics
+ self.active_topics.add(key)
+ profiler = _Profile(key, message)
-# profile_start()
-#
-# Start profiling for a given topic.
-#
-# Args:
-# topic (str): A topic name
-# key (str): A key for this profile run
-# message (str): An optional message to print in profile results
-#
-def profile_start(topic, key, message=None):
- if not profile_enabled(topic):
- return
+ with profiler:
+ yield
- # Start profiling and hold on to the key
- profile = Profile(topic, key, message)
- assert active_profiles.get(profile.key) is None
- active_profiles[profile.key] = profile
+ self.active_topics.remove(key)
+ def _is_profile_enabled(self, topic):
+ return topic in self.enabled_topics or Topics.ALL in self.enabled_topics
-# profile_end()
-#
-# Ends a profiling session previously
-# started with profile_start()
-#
-# Args:
-# topic (str): A topic name
-# key (str): A key for this profile run
-#
-def profile_end(topic, key):
- if not profile_enabled(topic):
- return
-
- topic_key = topic + '-' + key
- profile = active_profiles.get(topic_key)
- assert profile
- profile.end()
- del active_profiles[topic_key]
-
-
-def profile_init():
- global initialized # pylint: disable=global-statement
- if not initialized:
- setting = os.getenv('BST_PROFILE')
- if setting:
- topics = setting.split(':')
- for topic in topics:
- active_topics.add(topic)
- initialized = True
-
-
-def profile_enabled(topic):
- profile_init()
- return topic in active_topics or Topics.ALL in active_topics
+
+# Export a profiler to be used by BuildStream
+PROFILER = _Profiler(os.getenv("BST_PROFILE"))