#!/usr/bin/env python3 # Copyright (C) 2021 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Given a trace file, gives the self-time of userspace slices broken down by process, thread and thread state. """ import argparse import cmd import logging import numpy as np import pandas as pd import plotille from perfetto.batch_trace_processor.api import BatchTraceProcessor, BatchTraceProcessorConfig from perfetto.trace_processor import TraceProcessorException, TraceProcessorConfig from typing import List class TpBatchShell(cmd.Cmd): def __init__(self, files: List[str], batch_tp: BatchTraceProcessor): super().__init__() self.files = files self.batch_tp = batch_tp def do_table(self, arg: str): try: data = self.batch_tp.query_and_flatten(arg) print(data) except TraceProcessorException as ex: logging.error("Query failed: {}".format(ex)) def do_histogram(self, arg: str): try: data = self.batch_tp.query_single_result(arg) print(plotille.histogram(data)) self.print_percentiles(data) except TraceProcessorException as ex: logging.error("Query failed: {}".format(ex)) def do_vhistogram(self, arg: str): try: data = self.batch_tp.query_single_result(arg) print(plotille.hist(data)) self.print_percentiles(data) except TraceProcessorException as ex: logging.error("Query failed: {}".format(ex)) def do_count(self, arg: str): try: data = self.batch_tp.query_single_result(arg) counts = dict() for i in data: counts[i] = counts.get(i, 0) + 1 print(counts) except TraceProcessorException as ex: logging.error("Query failed: {}".format(ex)) def do_close(self, _): return True def do_quit(self, _): return True def do_EOF(self, _): print("") return True def print_percentiles(self, data): percentiles = [25, 50, 75, 95, 99, 99.9] nearest = np.percentile(data, percentiles, interpolation='nearest') logging.info("Representative traces for percentiles") for i, near in enumerate(nearest): print("{}%: {}".format(percentiles[i], self.files[data.index(near)])) def main(): parser = argparse.ArgumentParser() parser.add_argument('--shell-path', default=None) parser.add_argument('--verbose', action='store_true', default=False) parser.add_argument('--file-list', default=None) parser.add_argument('--query-file', default=None) parser.add_argument('--interactive', default=None) parser.add_argument('files', nargs='*') args = parser.parse_args() logging.basicConfig(level=logging.DEBUG) files = args.files if args.file_list: with open(args.file_list, 'r') as f: files += f.read().splitlines() if not files: logging.info("At least one file must be specified in files or file list") logging.info('Loading traces...') config = BatchTraceProcessorConfig( tp_config=TraceProcessorConfig( bin_path=args.shell_path, verbose=args.verbose, )) with BatchTraceProcessor(files, config) as batch_tp: if args.query_file: logging.info('Running query file...') with open(args.query_file, 'r') as f: queries_str = f.read() queries = [q.strip() for q in queries_str.split(";\n")] for q in queries[:-1]: batch_tp.query(q) res = batch_tp.query_and_flatten(queries[-1]) print(res.to_csv(index=False)) if args.interactive or not args.query_file: try: TpBatchShell(files, batch_tp).cmdloop() except KeyboardInterrupt: pass logging.info("Closing; please wait...") if __name__ == '__main__': exit(main())