| #!/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, method='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()) |