| Index: appengine/monorail/framework/profiler.py
|
| diff --git a/appengine/monorail/framework/profiler.py b/appengine/monorail/framework/profiler.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7c348914fb21ebbd55a01432c9b513a6cd88dbfb
|
| --- /dev/null
|
| +++ b/appengine/monorail/framework/profiler.py
|
| @@ -0,0 +1,92 @@
|
| +# Copyright 2016 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is govered by a BSD-style
|
| +# license that can be found in the LICENSE file or at
|
| +# https://developers.google.com/open-source/licenses/bsd
|
| +
|
| +"""A simple profiler object to track how time is spent on a request.
|
| +
|
| +The profiler is called from application code at the begining and
|
| +end of each major phase and subphase of processing. The profiler
|
| +object keeps track of how much time was spent on each phase or subphase.
|
| +
|
| +This class is useful when developers need to understand where
|
| +server-side time is being spent. It includes durations in
|
| +milliseconds, and a simple bar chart on the HTML page.
|
| +
|
| +On-page debugging and performance info is useful because it makes it easier
|
| +to explore performance interactively.
|
| +"""
|
| +
|
| +import logging
|
| +import time
|
| +
|
| +from contextlib import contextmanager
|
| +
|
| +
|
| +class Profiler(object):
|
| + """Object to record and help display request processing profiling info.
|
| +
|
| + The Profiler class holds a list of phase objects, which can hold additional
|
| + phase objects (which are subphases). Each phase or subphase represents some
|
| + meaningful part of this application's HTTP request processing.
|
| + """
|
| +
|
| + _COLORS = ['900', '090', '009', '360', '306', '036',
|
| + '630', '630', '063', '333']
|
| +
|
| + def __init__(self):
|
| + """Each request processing profile begins with an empty list of phases."""
|
| + self.top_phase = _Phase('overall profile', -1, None)
|
| + self.current_phase = self.top_phase
|
| + self.next_color = 0
|
| +
|
| + @contextmanager
|
| + def Phase(self, name='unspecified phase'):
|
| + """Context manager to automatically begin and end (sub)phases."""
|
| + color = self._COLORS[self.next_color % len(self._COLORS)]
|
| + self.next_color += 1
|
| + self.current_phase = _Phase(name, color, self.current_phase)
|
| + try:
|
| + yield
|
| + finally:
|
| + self.current_phase = self.current_phase.End()
|
| +
|
| + def LogStats(self):
|
| + """Log sufficiently-long phases and subphases, for debugging purposes."""
|
| + self.top_phase.LogStats()
|
| +
|
| +
|
| +class _Phase(object):
|
| + """A _Phase instance represents a period of time during request processing."""
|
| +
|
| + def __init__(self, name, color, parent):
|
| + """Initialize a (sub)phase with the given name and current system clock."""
|
| + self.start = time.time()
|
| + self.name = name
|
| + self.color = color
|
| + self.subphases = []
|
| + self.elapsed_seconds = None
|
| + self.ms = 'in_progress' # shown if the phase never records a finish.
|
| + self.uncategorized_ms = None
|
| + self.parent = parent
|
| + if self.parent is not None:
|
| + self.parent._RegisterSubphase(self)
|
| +
|
| + def _RegisterSubphase(self, subphase):
|
| + """Add a subphase to this phase."""
|
| + self.subphases.append(subphase)
|
| +
|
| + def End(self):
|
| + """Record the time between the start and end of this (sub)phase."""
|
| + self.elapsed_seconds = time.time() - self.start
|
| + self.ms = str(int(self.elapsed_seconds * 1000))
|
| + categorized = sum(sub.elapsed_seconds for sub in self.subphases)
|
| + self.uncategorized_ms = int((self.elapsed_seconds - categorized) * 1000)
|
| + return self.parent
|
| +
|
| + def LogStats(self):
|
| + # Phases that took longer than 30ms are interesting.
|
| + if self.elapsed_seconds > 0.03:
|
| + logging.info('%5s: %s', self.ms, self.name)
|
| + for subphase in self.subphases:
|
| + subphase.LogStats()
|
|
|