| #!/usr/bin/env python3 |
| # |
| # Copyright (C) 2017 Codethink Limited |
| # |
| # This program is free software; you can redistribute it and/or |
| # modify it under the terms of the GNU Lesser General Public |
| # License as published by the Free Software Foundation; either |
| # version 2 of the License, or (at your option) any later version. |
| # |
| # This library is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # Lesser General Public License for more details. |
| # |
| # You should have received a copy of the GNU Lesser General Public |
| # License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| # |
| # Authors: |
| # Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> |
| |
| import cProfile |
| import pstats |
| import os |
| import datetime |
| import time |
| |
| # Track what profile topics are active |
| active_topics = {} |
| active_profiles = {} |
| initialized = False |
| |
| |
| # Use the topic values here to decide what to profile |
| # by setting them in the BST_PROFILE environment variable. |
| # |
| # Multiple topics can be set with the ':' separator. |
| # |
| # E.g.: |
| # |
| # BST_PROFILE=circ-dep-check:sort-deps bst <command> <args> |
| # |
| # The special 'all' value will enable all profiles |
| class Topics(): |
| CIRCULAR_CHECK = 'circ-dep-check' |
| SORT_DEPENDENCIES = 'sort-deps' |
| LOAD_LOADER = 'load-loader' |
| LOAD_CONTEXT = 'load-context' |
| LOAD_PROJECT = 'load-project' |
| LOAD_PIPELINE = 'load-pipeline' |
| SHOW = 'show' |
| ALL = 'all' |
| |
| |
| class Profile(): |
| def __init__(self, topic, key, message): |
| self.message = message |
| self.key = topic + '-' + key |
| self.start = time.time() |
| self.profiler = cProfile.Profile() |
| self.profiler.enable() |
| |
| def end(self): |
| self.profiler.disable() |
| |
| filename = self.key.replace('/', '-') |
| filename = filename.replace('.', '-') |
| filename = os.path.join(os.getcwd(), 'profile-' + filename + '.log') |
| |
| with open(filename, "a", encoding="utf-8") as f: |
| |
| dt = datetime.datetime.fromtimestamp(self.start) |
| time_ = dt.strftime('%Y-%m-%d %H:%M:%S') |
| |
| 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() |
| |
| |
| # 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 |
| |
| # 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 |
| |
| |
| # 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[topic] = True |
| initialized = True |
| |
| |
| def profile_enabled(topic): |
| profile_init() |
| if active_topics.get(topic): |
| return True |
| if active_topics.get(Topics.ALL): |
| return True |
| return False |