summaryrefslogtreecommitdiff
path: root/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl')
-rw-r--r--chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/__init__.py7
-rw-r--r--chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/decorators.py87
-rw-r--r--chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/decorators_test.py62
-rw-r--r--chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/log.py183
-rw-r--r--chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/log_io_test.py50
-rw-r--r--chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/meta_class.py16
-rw-r--r--chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/multiprocessing_shim.py92
-rw-r--r--chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/parsed_trace_events.py98
-rw-r--r--chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/trace_test.py48
9 files changed, 643 insertions, 0 deletions
diff --git a/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/__init__.py b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/__init__.py
new file mode 100644
index 00000000000..d250e0312e4
--- /dev/null
+++ b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/__init__.py
@@ -0,0 +1,7 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+from log import *
+from decorators import *
+from meta_class import *
+import multiprocessing_shim
diff --git a/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/decorators.py b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/decorators.py
new file mode 100644
index 00000000000..dc753f1f61b
--- /dev/null
+++ b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/decorators.py
@@ -0,0 +1,87 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import contextlib
+import inspect
+import time
+import functools
+
+import log
+from py_trace_event import trace_time
+
+
+@contextlib.contextmanager
+def trace(name, **kwargs):
+ category = "python"
+ start = trace_time.Now()
+ args_to_log = {key: repr(value) for key, value in kwargs.iteritems()}
+ log.add_trace_event("B", start, category, name, args_to_log)
+ try:
+ yield
+ finally:
+ end = trace_time.Now()
+ log.add_trace_event("E", end, category, name)
+
+def traced(*args):
+ def get_wrapper(func):
+ if inspect.isgeneratorfunction(func):
+ raise Exception("Can not trace generators.")
+
+ category = "python"
+
+ arg_spec = inspect.getargspec(func)
+ is_method = arg_spec.args and arg_spec.args[0] == "self"
+
+ def arg_spec_tuple(name):
+ arg_index = arg_spec.args.index(name)
+ defaults_length = len(arg_spec.defaults) if arg_spec.defaults else 0
+ default_index = arg_index + defaults_length - len(arg_spec.args)
+ if default_index >= 0:
+ default = arg_spec.defaults[default_index]
+ else:
+ default = None
+ return (name, arg_index, default)
+
+ args_to_log = map(arg_spec_tuple, arg_names)
+
+ @functools.wraps(func)
+ def traced_function(*args, **kwargs):
+ # Everything outside traced_function is done at decoration-time.
+ # Everything inside traced_function is done at run-time and must be fast.
+ if not log._enabled: # This check must be at run-time.
+ return func(*args, **kwargs)
+
+ def get_arg_value(name, index, default):
+ if name in kwargs:
+ return kwargs[name]
+ elif index < len(args):
+ return args[index]
+ else:
+ return default
+
+ if is_method:
+ name = "%s.%s" % (args[0].__class__.__name__, func.__name__)
+ else:
+ name = "%s.%s" % (func.__module__, func.__name__)
+
+ # Be sure to repr before calling func. Argument values may change.
+ arg_values = {
+ name: repr(get_arg_value(name, index, default))
+ for name, index, default in args_to_log}
+
+ start = trace_time.Now()
+ log.add_trace_event("B", start, category, name, arg_values)
+ try:
+ return func(*args, **kwargs)
+ finally:
+ end = trace_time.Now()
+ log.add_trace_event("E", end, category, name)
+ return traced_function
+
+ no_decorator_arguments = len(args) == 1 and callable(args[0])
+ if no_decorator_arguments:
+ arg_names = ()
+ return get_wrapper(args[0])
+ else:
+ arg_names = args
+ return get_wrapper
diff --git a/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/decorators_test.py b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/decorators_test.py
new file mode 100644
index 00000000000..5bb13ad8859
--- /dev/null
+++ b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/decorators_test.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import decorators
+import logging
+import unittest
+
+from trace_test import TraceTest
+#from .trace_test import TraceTest
+
+def generator():
+ yield 1
+ yield 2
+
+class DecoratorTests(unittest.TestCase):
+ def test_tracing_object_fails(self):
+ self.assertRaises(Exception, lambda: decorators.trace(1))
+ self.assertRaises(Exception, lambda: decorators.trace(""))
+ self.assertRaises(Exception, lambda: decorators.trace([]))
+
+ def test_tracing_generators_fail(self):
+ self.assertRaises(Exception, lambda: decorators.trace(generator))
+
+class ClassToTest(object):
+ @decorators.traced
+ def method1(self):
+ return 1
+
+ @decorators.traced
+ def method2(self):
+ return 1
+
+@decorators.traced
+def traced_func():
+ return 1
+
+class DecoratorTests(TraceTest):
+ def _get_decorated_method_name(self, f):
+ res = self.go(f)
+ events = res.findEventsOnThread(res.findThreadIds()[0])
+
+ # Sanity checks.
+ self.assertEquals(2, len(events))
+ self.assertEquals(events[0]["name"], events[1]["name"])
+ return events[1]["name"]
+
+
+ def test_func_names_work(self):
+ self.assertEquals('__main__.traced_func',
+ self._get_decorated_method_name(traced_func))
+
+ def test_method_names_work(self):
+ ctt = ClassToTest()
+ self.assertEquals('ClassToTest.method1',
+ self._get_decorated_method_name(ctt.method1))
+ self.assertEquals('ClassToTest.method2',
+ self._get_decorated_method_name(ctt.method2))
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.DEBUG)
+ unittest.main(verbosity=2)
diff --git a/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/log.py b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/log.py
new file mode 100644
index 00000000000..2d69f083af0
--- /dev/null
+++ b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/log.py
@@ -0,0 +1,183 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import atexit
+import json
+import os
+import sys
+import time
+import threading
+
+from py_trace_event import trace_time
+
+from py_utils import lock
+
+
+_lock = threading.Lock()
+
+_enabled = False
+_log_file = None
+
+_cur_events = [] # events that have yet to be buffered
+
+_tls = threading.local() # tls used to detect forking/etc
+_atexit_regsitered_for_pid = None
+
+_control_allowed = True
+
+
+class TraceException(Exception):
+ pass
+
+def _note(msg, *args):
+ pass
+# print "%i: %s" % (os.getpid(), msg)
+
+
+def _locked(fn):
+ def locked_fn(*args,**kwargs):
+ _lock.acquire()
+ try:
+ ret = fn(*args,**kwargs)
+ finally:
+ _lock.release()
+ return ret
+ return locked_fn
+
+def _disallow_tracing_control():
+ global _control_allowed
+ _control_allowed = False
+
+def trace_enable(log_file=None):
+ _trace_enable(log_file)
+
+@_locked
+def _trace_enable(log_file=None):
+ global _enabled
+ if _enabled:
+ raise TraceException("Already enabled")
+ if not _control_allowed:
+ raise TraceException("Tracing control not allowed in child processes.")
+ _enabled = True
+ global _log_file
+ if log_file == None:
+ if sys.argv[0] == '':
+ n = 'trace_event'
+ else:
+ n = sys.argv[0]
+ log_file = open("%s.json" % n, "ab", False)
+ _note("trace_event: tracelog name is %s.json" % n)
+ elif isinstance(log_file, basestring):
+ _note("trace_event: tracelog name is %s" % log_file)
+ log_file = open("%s" % log_file, "ab", False)
+ elif not hasattr(log_file, 'fileno'):
+ raise TraceException(
+ "Log file must be None, a string, or file-like object with a fileno()")
+
+ _log_file = log_file
+ with lock.FileLock(_log_file, lock.LOCK_EX):
+ _log_file.seek(0, os.SEEK_END)
+
+ lastpos = _log_file.tell()
+ creator = lastpos == 0
+ if creator:
+ _note("trace_event: Opened new tracelog, lastpos=%i", lastpos)
+ _log_file.write('[')
+
+ tid = threading.current_thread().ident
+ if not tid:
+ tid = os.getpid()
+ x = {"ph": "M", "category": "process_argv",
+ "pid": os.getpid(), "tid": threading.current_thread().ident,
+ "ts": trace_time.Now(),
+ "name": "process_argv", "args": {"argv": sys.argv}}
+ _log_file.write("%s\n" % json.dumps(x))
+ else:
+ _note("trace_event: Opened existing tracelog")
+ _log_file.flush()
+
+@_locked
+def trace_flush():
+ if _enabled:
+ _flush()
+
+@_locked
+def trace_disable():
+ global _enabled
+ if not _control_allowed:
+ raise TraceException("Tracing control not allowed in child processes.")
+ if not _enabled:
+ return
+ _enabled = False
+ _flush(close=True)
+
+def _flush(close=False):
+ global _log_file
+ with lock.FileLock(_log_file, lock.LOCK_EX):
+ _log_file.seek(0, os.SEEK_END)
+ if len(_cur_events):
+ _log_file.write(",\n")
+ _log_file.write(",\n".join([json.dumps(e) for e in _cur_events]))
+ del _cur_events[:]
+
+ if close:
+ # We might not be the only process writing to this logfile. So,
+ # we will simply close the file rather than writign the trailing ] that
+ # it technically requires. The trace viewer understands that this may
+ # happen and will insert a trailing ] during loading.
+ pass
+ _log_file.flush()
+
+ if close:
+ _note("trace_event: Closed")
+ _log_file.close()
+ _log_file = None
+ else:
+ _note("trace_event: Flushed")
+
+@_locked
+def trace_is_enabled():
+ return _enabled
+
+@_locked
+def add_trace_event(ph, ts, category, name, args=None):
+ global _enabled
+ if not _enabled:
+ return
+ if not hasattr(_tls, 'pid') or _tls.pid != os.getpid():
+ _tls.pid = os.getpid()
+ global _atexit_regsitered_for_pid
+ if _tls.pid != _atexit_regsitered_for_pid:
+ _atexit_regsitered_for_pid = _tls.pid
+ atexit.register(_trace_disable_atexit)
+ _tls.pid = os.getpid()
+ del _cur_events[:] # we forked, clear the event buffer!
+ tid = threading.current_thread().ident
+ if not tid:
+ tid = os.getpid()
+ _tls.tid = tid
+
+ _cur_events.append({"ph": ph,
+ "category": category,
+ "pid": _tls.pid,
+ "tid": _tls.tid,
+ "ts": ts,
+ "name": name,
+ "args": args or {}});
+
+def trace_begin(name, args=None):
+ add_trace_event("B", trace_time.Now(), "python", name, args)
+
+def trace_end(name, args=None):
+ add_trace_event("E", trace_time.Now(), "python", name, args)
+
+def trace_set_thread_name(thread_name):
+ add_trace_event("M", trace_time.Now(), "__metadata", "thread_name",
+ {"name": thread_name})
+
+def _trace_disable_atexit():
+ trace_disable()
+
+def is_tracing_controllable():
+ global _control_allowed
+ return _control_allowed
diff --git a/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/log_io_test.py b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/log_io_test.py
new file mode 100644
index 00000000000..99a062115bc
--- /dev/null
+++ b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/log_io_test.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import logging
+import os
+import sys
+import tempfile
+import unittest
+
+from log import *
+from parsed_trace_events import *
+
+
+class LogIOTest(unittest.TestCase):
+ def test_enable_with_file(self):
+ file = tempfile.NamedTemporaryFile()
+ trace_enable(open(file.name, 'w+'))
+ trace_disable()
+ e = ParsedTraceEvents(trace_filename = file.name)
+ file.close()
+ self.assertTrue(len(e) > 0)
+
+ def test_enable_with_filename(self):
+ file = tempfile.NamedTemporaryFile()
+ trace_enable(file.name)
+ trace_disable()
+ e = ParsedTraceEvents(trace_filename = file.name)
+ file.close()
+ self.assertTrue(len(e) > 0)
+
+ def test_enable_with_implicit_filename(self):
+ expected_filename = "%s.json" % sys.argv[0]
+ def do_work():
+ file = tempfile.NamedTemporaryFile()
+ trace_enable()
+ trace_disable()
+ e = ParsedTraceEvents(trace_filename = expected_filename)
+ file.close()
+ self.assertTrue(len(e) > 0)
+ try:
+ do_work()
+ finally:
+ if os.path.exists(expected_filename):
+ os.unlink(expected_filename)
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.DEBUG)
+ unittest.main(verbosity=2)
+
diff --git a/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/meta_class.py b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/meta_class.py
new file mode 100644
index 00000000000..4ede79bd338
--- /dev/null
+++ b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/meta_class.py
@@ -0,0 +1,16 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import types
+
+from py_trace_event.trace_event_impl import decorators
+
+
+class TracedMetaClass(type):
+ def __new__(cls, name, bases, attrs):
+ for attr_name, attr_value in attrs.iteritems():
+ if isinstance(attr_value, types.FunctionType):
+ attrs[attr_name] = decorators.traced(attr_value)
+
+ return super(TracedMetaClass, cls).__new__(cls, name, bases, attrs)
diff --git a/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/multiprocessing_shim.py b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/multiprocessing_shim.py
new file mode 100644
index 00000000000..9796bdf9665
--- /dev/null
+++ b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/multiprocessing_shim.py
@@ -0,0 +1,92 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import multiprocessing
+import log
+import time
+
+
+_RealProcess = multiprocessing.Process
+__all__ = []
+
+
+class ProcessSubclass(_RealProcess):
+ def __init__(self, shim, *args, **kwards):
+ _RealProcess.__init__(self, *args, **kwards)
+ self._shim = shim
+
+ def run(self,*args,**kwargs):
+ log._disallow_tracing_control()
+ try:
+ r = _RealProcess.run(self, *args, **kwargs)
+ finally:
+ if log.trace_is_enabled():
+ log.trace_flush() # todo, reduce need for this...
+ return r
+
+class ProcessShim():
+ def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
+ self._proc = ProcessSubclass(self, group, target, name, args, kwargs)
+ # hint to testing code that the shimming worked
+ self._shimmed_by_trace_event = True
+
+ def run(self):
+ self._proc.run()
+
+ def start(self):
+ self._proc.start()
+
+ def terminate(self):
+ if log.trace_is_enabled():
+ # give the flush a chance to finish --> TODO: find some other way.
+ time.sleep(0.25)
+ self._proc.terminate()
+
+ def join(self, timeout=None):
+ self._proc.join( timeout)
+
+ def is_alive(self):
+ return self._proc.is_alive()
+
+ @property
+ def name(self):
+ return self._proc.name
+
+ @name.setter
+ def name(self, name):
+ self._proc.name = name
+
+ @property
+ def daemon(self):
+ return self._proc.daemon
+
+ @daemon.setter
+ def daemon(self, daemonic):
+ self._proc.daemon = daemonic
+
+ @property
+ def authkey(self):
+ return self._proc._authkey
+
+ @authkey.setter
+ def authkey(self, authkey):
+ self._proc.authkey = AuthenticationString(authkey)
+
+ @property
+ def exitcode(self):
+ return self._proc.exitcode
+
+ @property
+ def ident(self):
+ return self._proc.ident
+
+ @property
+ def pid(self):
+ return self._proc.pid
+
+ def __repr__(self):
+ return self._proc.__repr__()
+
+# Monkeypatch in our process replacement.
+if multiprocessing.Process != ProcessShim:
+ multiprocessing.Process = ProcessShim
diff --git a/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/parsed_trace_events.py b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/parsed_trace_events.py
new file mode 100644
index 00000000000..fdc7514542d
--- /dev/null
+++ b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/parsed_trace_events.py
@@ -0,0 +1,98 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import math
+import json
+
+
+class ParsedTraceEvents(object):
+ def __init__(self, events = None, trace_filename = None):
+ """
+ Utility class for filtering and manipulating trace data.
+
+ events -- An iterable object containing trace events
+ trace_filename -- A file object that contains a complete trace.
+
+ """
+ if trace_filename and events:
+ raise Exception("Provide either a trace file or event list")
+ if not trace_filename and events == None:
+ raise Exception("Provide either a trace file or event list")
+
+ if trace_filename:
+ f = open(trace_filename, 'r')
+ t = f.read()
+ f.close()
+
+ # If the event data begins with a [, then we know it should end with a ].
+ # The reason we check for this is because some tracing implementations
+ # cannot guarantee that a ']' gets written to the trace file. So, we are
+ # forgiving and if this is obviously the case, we fix it up before
+ # throwing the string at JSON.parse.
+ if t[0] == '[':
+ n = len(t);
+ if t[n - 1] != ']' and t[n - 1] != '\n':
+ t = t + ']'
+ elif t[n - 2] != ']' and t[n - 1] == '\n':
+ t = t + ']'
+ elif t[n - 3] != ']' and t[n - 2] == '\r' and t[n - 1] == '\n':
+ t = t + ']'
+
+ try:
+ events = json.loads(t)
+ except ValueError:
+ raise Exception("Corrupt trace, did not parse. Value: %s" % t)
+
+ if 'traceEvents' in events:
+ events = events['traceEvents']
+
+ if not hasattr(events, '__iter__'):
+ raise Exception, 'events must be iteraable.'
+ self.events = events
+ self.pids = None
+ self.tids = None
+
+ def __len__(self):
+ return len(self.events)
+
+ def __getitem__(self, i):
+ return self.events[i]
+
+ def __setitem__(self, i, v):
+ self.events[i] = v
+
+ def __repr__(self):
+ return "[%s]" % ",\n ".join([repr(e) for e in self.events])
+
+ def findProcessIds(self):
+ if self.pids:
+ return self.pids
+ pids = set()
+ for e in self.events:
+ if "pid" in e and e["pid"]:
+ pids.add(e["pid"])
+ self.pids = list(pids)
+ return self.pids
+
+ def findThreadIds(self):
+ if self.tids:
+ return self.tids
+ tids = set()
+ for e in self.events:
+ if "tid" in e and e["tid"]:
+ tids.add(e["tid"])
+ self.tids = list(tids)
+ return self.tids
+
+ def findEventsOnProcess(self, pid):
+ return ParsedTraceEvents([e for e in self.events if e["pid"] == pid])
+
+ def findEventsOnThread(self, tid):
+ return ParsedTraceEvents(
+ [e for e in self.events if e["ph"] != "M" and e["tid"] == tid])
+
+ def findByPhase(self, ph):
+ return ParsedTraceEvents([e for e in self.events if e["ph"] == ph])
+
+ def findByName(self, n):
+ return ParsedTraceEvents([e for e in self.events if e["name"] == n])
diff --git a/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/trace_test.py b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/trace_test.py
new file mode 100644
index 00000000000..7047e0eae86
--- /dev/null
+++ b/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/trace_test.py
@@ -0,0 +1,48 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import tempfile
+import unittest
+
+#from .log import *
+#from .parsed_trace_events import *
+
+from log import *
+from parsed_trace_events import *
+
+class TraceTest(unittest.TestCase):
+ def __init__(self, *args):
+ """
+ Infrastructure for running tests of the tracing system.
+
+ Does not actually run any tests. Look at subclasses for those.
+ """
+ unittest.TestCase.__init__(self, *args)
+ self._file = None
+
+ def go(self, cb):
+ """
+ Enables tracing, runs the provided callback, and if successful, returns a
+ TraceEvents object with the results.
+ """
+ self._file = tempfile.NamedTemporaryFile()
+ trace_enable(open(self._file.name, 'a+'))
+
+ try:
+ cb()
+ finally:
+ trace_disable()
+ e = ParsedTraceEvents(trace_filename = self._file.name)
+ self._file.close()
+ self._file = None
+ return e
+
+ @property
+ def trace_filename(self):
+ return self._file.name
+
+ def tearDown(self):
+ if trace_is_enabled():
+ trace_disable()
+ if self._file:
+ self._file.close()