blob: 36a4d534e1c46cc23f4009e749b518594b48d966 [file] [log] [blame]
#!/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 concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from perfetto.trace_processor import TraceProcessor
from perfetto.trace_processor.api import TraceProcessorException
@dataclass
class TpArg:
shell_path: str
verbose: bool
file: str
def create_tp_args(args, files):
return [TpArg(args.shell_path, args.verbose, f) for f in files]
def create_tp(arg):
return TraceProcessor(
file_path=arg.file, bin_path=arg.shell_path, verbose=arg.verbose)
def close_tp(tp):
tp.close()
def query_single_result(tp, query):
df = tp.query(query).as_pandas_dataframe()
if len(df.index) != 1:
raise TraceProcessorException("Query should only return a single row")
if len(df.columns) != 1:
raise TraceProcessorException("Query should only return a single column")
return df.iloc[0, 0]
def query_file_and_return_last(tp, queries_str):
queries = [q.strip() for q in queries_str.split(";\n")]
return [tp.query(q).as_pandas_dataframe() for q in queries if q][-1]
def prefix_path_column(path, df):
df['trace_file_path'] = path
return df
class TpBatchShell(cmd.Cmd):
def __init__(self, executor, files, tps):
super().__init__()
self.executor = executor
self.files = files
self.tps = tps
def do_histogram(self, arg):
try:
data = list(
self.executor.map(lambda tp: query_single_result(tp, arg), self.tps))
print(plotille.histogram(data))
self.print_percentiles(data)
except TraceProcessorException as ex:
logging.error("Query failed: {}".format(ex))
def do_vhistogram(self, arg):
try:
data = list(
self.executor.map(lambda tp: query_single_result(tp, arg), self.tps))
print(plotille.hist(data))
self.print_percentiles(data)
except TraceProcessorException as ex:
logging.error("Query failed: {}".format(ex))
def do_count(self, arg):
try:
data = list(
self.executor.map(lambda tp: query_single_result(tp, arg), self.tps))
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")
executor = ThreadPoolExecutor()
logging.info('Loading traces...')
tps = [tp for tp in executor.map(create_tp, create_tp_args(args, files))]
if args.query_file:
logging.info('Running query file...')
with open(args.query_file, 'r') as f:
query = f.read()
out = list(
executor.map(lambda tp: query_file_and_return_last(tp, query), tps))
res = pd.concat(
[prefix_path_column(path, df) for (path, df) in zip(files, out)])
print(res.to_csv(index=False))
if args.interactive or not args.query_file:
try:
TpBatchShell(executor, files, tps).cmdloop()
except KeyboardInterrupt:
pass
logging.info("Closing; please wait...")
executor.map(close_tp, tps)
executor.shutdown()
if __name__ == '__main__':
exit(main())