• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4import atexit
5import json
6import os
7import sys
8import time
9import threading
10
11from py_trace_event import trace_time
12
13from py_utils import lock
14
15
16_lock = threading.Lock()
17
18_enabled = False
19_log_file = None
20
21_cur_events = [] # events that have yet to be buffered
22
23_tls = threading.local() # tls used to detect forking/etc
24_atexit_regsitered_for_pid = None
25
26_control_allowed = True
27
28
29class TraceException(Exception):
30  pass
31
32def _note(msg, *args):
33  pass
34#  print "%i: %s" % (os.getpid(), msg)
35
36
37def _locked(fn):
38  def locked_fn(*args,**kwargs):
39    _lock.acquire()
40    try:
41      ret = fn(*args,**kwargs)
42    finally:
43      _lock.release()
44    return ret
45  return locked_fn
46
47def _disallow_tracing_control():
48  global _control_allowed
49  _control_allowed = False
50
51def trace_enable(log_file=None):
52  _trace_enable(log_file)
53
54@_locked
55def _trace_enable(log_file=None):
56  global _enabled
57  if _enabled:
58    raise TraceException("Already enabled")
59  if not _control_allowed:
60    raise TraceException("Tracing control not allowed in child processes.")
61  _enabled = True
62  global _log_file
63  if log_file == None:
64    if sys.argv[0] == '':
65      n = 'trace_event'
66    else:
67      n = sys.argv[0]
68    log_file = open("%s.json" % n, "ab", False)
69    _note("trace_event: tracelog name is %s.json" % n)
70  elif isinstance(log_file, basestring):
71    _note("trace_event: tracelog name is %s" % log_file)
72    log_file = open("%s" % log_file, "ab", False)
73  elif not hasattr(log_file, 'fileno'):
74    raise TraceException(
75        "Log file must be None, a string, or file-like object with a fileno()")
76
77  _log_file = log_file
78  with lock.FileLock(_log_file, lock.LOCK_EX):
79    _log_file.seek(0, os.SEEK_END)
80
81    lastpos = _log_file.tell()
82    creator = lastpos == 0
83    if creator:
84      _note("trace_event: Opened new tracelog, lastpos=%i", lastpos)
85      _log_file.write('[')
86
87      tid = threading.current_thread().ident
88      if not tid:
89        tid = os.getpid()
90      x = {"ph": "M", "category": "process_argv",
91           "pid": os.getpid(), "tid": threading.current_thread().ident,
92           "ts": trace_time.Now(),
93           "name": "process_argv", "args": {"argv": sys.argv}}
94      _log_file.write("%s\n" % json.dumps(x))
95    else:
96      _note("trace_event: Opened existing tracelog")
97    _log_file.flush()
98
99@_locked
100def trace_flush():
101  if _enabled:
102    _flush()
103
104@_locked
105def trace_disable():
106  global _enabled
107  if not _control_allowed:
108    raise TraceException("Tracing control not allowed in child processes.")
109  if not _enabled:
110    return
111  _enabled = False
112  _flush(close=True)
113
114def _flush(close=False):
115  global _log_file
116  with lock.FileLock(_log_file, lock.LOCK_EX):
117    _log_file.seek(0, os.SEEK_END)
118    if len(_cur_events):
119      _log_file.write(",\n")
120      _log_file.write(",\n".join([json.dumps(e) for e in _cur_events]))
121      del _cur_events[:]
122
123    if close:
124      # We might not be the only process writing to this logfile. So,
125      # we will simply close the file rather than writign the trailing ] that
126      # it technically requires. The trace viewer understands that this may
127      # happen and will insert a trailing ] during loading.
128      pass
129    _log_file.flush()
130
131  if close:
132    _note("trace_event: Closed")
133    _log_file.close()
134    _log_file = None
135  else:
136    _note("trace_event: Flushed")
137
138@_locked
139def trace_is_enabled():
140  return _enabled
141
142@_locked
143def add_trace_event(ph, ts, category, name, args=None):
144  global _enabled
145  if not _enabled:
146    return
147  if not hasattr(_tls, 'pid') or _tls.pid != os.getpid():
148    _tls.pid = os.getpid()
149    global _atexit_regsitered_for_pid
150    if _tls.pid != _atexit_regsitered_for_pid:
151      _atexit_regsitered_for_pid = _tls.pid
152      atexit.register(_trace_disable_atexit)
153      _tls.pid = os.getpid()
154      del _cur_events[:] # we forked, clear the event buffer!
155    tid = threading.current_thread().ident
156    if not tid:
157      tid = os.getpid()
158    _tls.tid = tid
159
160  _cur_events.append({"ph": ph,
161                      "category": category,
162                      "pid": _tls.pid,
163                      "tid": _tls.tid,
164                      "ts": ts,
165                      "name": name,
166                      "args": args or {}});
167
168def trace_begin(name, args=None):
169  add_trace_event("B", trace_time.Now(), "python", name, args)
170
171def trace_end(name, args=None):
172  add_trace_event("E", trace_time.Now(), "python", name, args)
173
174def trace_set_thread_name(thread_name):
175  add_trace_event("M", trace_time.Now(), "__metadata", "thread_name",
176                  {"name": thread_name})
177
178def _trace_disable_atexit():
179  trace_disable()
180
181def is_tracing_controllable():
182  global _control_allowed
183  return _control_allowed
184