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