1#!/usr/bin/env python3 2# Copyright (C) 2021 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15""" Given a trace file, gives the self-time of userspace slices broken 16down by process, thread and thread state. 17""" 18 19import argparse 20import cmd 21import logging 22import numpy as np 23import pandas as pd 24import plotille 25 26from perfetto.batch_trace_processor.api import BatchTraceProcessor, BatchTraceProcessorConfig 27from perfetto.trace_processor import TraceProcessorException, TraceProcessorConfig 28from typing import List 29 30 31class TpBatchShell(cmd.Cmd): 32 33 def __init__(self, files: List[str], batch_tp: BatchTraceProcessor): 34 super().__init__() 35 self.files = files 36 self.batch_tp = batch_tp 37 38 def do_table(self, arg: str): 39 try: 40 data = self.batch_tp.query_and_flatten(arg) 41 print(data) 42 except TraceProcessorException as ex: 43 logging.error("Query failed: {}".format(ex)) 44 45 def do_histogram(self, arg: str): 46 try: 47 data = self.batch_tp.query_single_result(arg) 48 print(plotille.histogram(data)) 49 self.print_percentiles(data) 50 except TraceProcessorException as ex: 51 logging.error("Query failed: {}".format(ex)) 52 53 def do_vhistogram(self, arg: str): 54 try: 55 data = self.batch_tp.query_single_result(arg) 56 print(plotille.hist(data)) 57 self.print_percentiles(data) 58 except TraceProcessorException as ex: 59 logging.error("Query failed: {}".format(ex)) 60 61 def do_count(self, arg: str): 62 try: 63 data = self.batch_tp.query_single_result(arg) 64 counts = dict() 65 for i in data: 66 counts[i] = counts.get(i, 0) + 1 67 print(counts) 68 except TraceProcessorException as ex: 69 logging.error("Query failed: {}".format(ex)) 70 71 def do_close(self, _): 72 return True 73 74 def do_quit(self, _): 75 return True 76 77 def do_EOF(self, _): 78 print("") 79 return True 80 81 def print_percentiles(self, data): 82 percentiles = [25, 50, 75, 95, 99, 99.9] 83 nearest = np.percentile(data, percentiles, interpolation='nearest') 84 logging.info("Representative traces for percentiles") 85 for i, near in enumerate(nearest): 86 print("{}%: {}".format(percentiles[i], self.files[data.index(near)])) 87 88 89def main(): 90 parser = argparse.ArgumentParser() 91 parser.add_argument('--shell-path', default=None) 92 parser.add_argument('--verbose', action='store_true', default=False) 93 parser.add_argument('--file-list', default=None) 94 parser.add_argument('--query-file', default=None) 95 parser.add_argument('--interactive', default=None) 96 parser.add_argument('files', nargs='*') 97 args = parser.parse_args() 98 99 logging.basicConfig(level=logging.DEBUG) 100 101 files = args.files 102 if args.file_list: 103 with open(args.file_list, 'r') as f: 104 files += f.read().splitlines() 105 106 if not files: 107 logging.info("At least one file must be specified in files or file list") 108 109 logging.info('Loading traces...') 110 config = BatchTraceProcessorConfig( 111 tp_config=TraceProcessorConfig( 112 bin_path=args.shell_path, 113 verbose=args.verbose, 114 )) 115 116 with BatchTraceProcessor(files, config) as batch_tp: 117 if args.query_file: 118 logging.info('Running query file...') 119 120 with open(args.query_file, 'r') as f: 121 queries_str = f.read() 122 123 queries = [q.strip() for q in queries_str.split(";\n")] 124 for q in queries[:-1]: 125 batch_tp.query(q) 126 127 res = batch_tp.query_and_flatten(queries[-1]) 128 print(res.to_csv(index=False)) 129 130 if args.interactive or not args.query_file: 131 try: 132 TpBatchShell(files, batch_tp).cmdloop() 133 except KeyboardInterrupt: 134 pass 135 136 logging.info("Closing; please wait...") 137 138 139if __name__ == '__main__': 140 exit(main()) 141