1#!/usr/bin/env python3 2# 3# Copyright 2018 Google LLC. 4# 5# This software may be used and distributed according to the terms of the 6# GNU General Public License version 2 or any later version. 7"""Tool read primitive events from a pipe to produce a catapult trace. 8 9Usage: 10 Terminal 1: $ catapipe.py /tmp/mypipe /tmp/trace.json 11 Terminal 2: $ HGCATAPULTSERVERPIPE=/tmp/mypipe hg root 12 <ctrl-c catapipe.py in Terminal 1> 13 $ catapult/tracing/bin/trace2html /tmp/trace.json # produce /tmp/trace.html 14 <open trace.html in your browser of choice; the WASD keys are very useful> 15 (catapult is located at https://github.com/catapult-project/catapult) 16 17For now the event stream supports 18 19 START $SESSIONID ... 20 21and 22 23 END $SESSIONID ... 24 25events. Everything after the SESSIONID (which must not contain spaces) 26is used as a label for the event. Events are timestamped as of when 27they arrive in this process and are then used to produce catapult 28traces that can be loaded in Chrome's about:tracing utility. It's 29important that the event stream *into* this process stay simple, 30because we have to emit it from the shell scripts produced by 31run-tests.py. 32 33Typically you'll want to place the path to the named pipe in the 34HGCATAPULTSERVERPIPE environment variable, which both run-tests and hg 35understand. To trace *only* run-tests, use HGTESTCATAPULTSERVERPIPE instead. 36""" 37from __future__ import absolute_import, print_function 38 39import argparse 40import json 41import os 42import timeit 43 44_TYPEMAP = { 45 'START': 'B', 46 'END': 'E', 47 'COUNTER': 'C', 48} 49 50_threadmap = {} 51 52# Timeit already contains the whole logic about which timer to use based on 53# Python version and OS 54timer = timeit.default_timer 55 56 57def main(): 58 parser = argparse.ArgumentParser() 59 parser.add_argument( 60 'pipe', 61 type=str, 62 nargs=1, 63 help='Path of named pipe to create and listen on.', 64 ) 65 parser.add_argument( 66 'output', 67 default='trace.json', 68 type=str, 69 nargs='?', 70 help='Path of json file to create where the traces ' 'will be stored.', 71 ) 72 parser.add_argument( 73 '--debug', 74 default=False, 75 action='store_true', 76 help='Print useful debug messages', 77 ) 78 args = parser.parse_args() 79 fn = args.pipe[0] 80 os.mkfifo(fn) 81 try: 82 with open(fn) as f, open(args.output, 'w') as out: 83 out.write('[\n') 84 start = timer() 85 while True: 86 ev = f.readline().strip() 87 if not ev: 88 continue 89 now = timer() 90 if args.debug: 91 print(ev) 92 verb, session, label = ev.split(' ', 2) 93 if session not in _threadmap: 94 _threadmap[session] = len(_threadmap) 95 if verb == 'COUNTER': 96 amount, label = label.split(' ', 1) 97 payload_args = {'value': int(amount)} 98 else: 99 payload_args = {} 100 pid = _threadmap[session] 101 ts_micros = (now - start) * 1000000 102 out.write( 103 json.dumps( 104 { 105 "name": label, 106 "cat": "misc", 107 "ph": _TYPEMAP[verb], 108 "ts": ts_micros, 109 "pid": pid, 110 "tid": 1, 111 "args": payload_args, 112 } 113 ) 114 ) 115 out.write(',\n') 116 finally: 117 os.unlink(fn) 118 119 120if __name__ == '__main__': 121 main() 122