| #!/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()) |