112 lines
3.5 KiB
Python
Executable File
112 lines
3.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import copy
|
|
from dataclasses import dataclass
|
|
import json
|
|
import sys
|
|
import textwrap
|
|
from profilerlib import CallMetrics
|
|
|
|
|
|
def print_folded_visit(metrics, span_id: int, prefix: str):
|
|
span = metrics.spans[span_id]
|
|
if prefix is not None:
|
|
print(f'{prefix} {span.exclusive_nanos}')
|
|
|
|
for name, child_id in span.children.items():
|
|
new_prefix = prefix + ';' + name if prefix is not None else name
|
|
print_folded_visit(metrics, child_id, new_prefix)
|
|
|
|
|
|
def remove_empty_spans(metrics):
|
|
# Keep only metrics that have non zero count. Make sure to also keep root span.
|
|
newSpans = [s for s in metrics.spans if s.id == 0 or s.count != 0]
|
|
return CallMetrics(newSpans)
|
|
|
|
|
|
def print_folded(metrics):
|
|
metrics = copy.deepcopy(metrics)
|
|
|
|
# Print entire folded tree under the root
|
|
print_folded_visit(metrics, 0, None)
|
|
|
|
|
|
def print_tsv(metrics):
|
|
def print_tsv_line(arr):
|
|
print("\t".join([str(x) for x in arr]))
|
|
|
|
print_tsv_line(["id", "name", "parentId", "totalNanos", "netNanos", "exclusive_nanos", "count"])
|
|
for s in metrics.spans:
|
|
# Skip root span
|
|
if s.id == 0:
|
|
continue
|
|
print_tsv_line(
|
|
[s.id, s.name, s.parent_id, s.total_nanos, s.net_nanos, s.exclusive_nanos, s.count])
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(usage="usage: %(prog)s [options]",
|
|
description="Formats the profiler output",
|
|
formatter_class=argparse.RawTextHelpFormatter)
|
|
|
|
parser.add_argument("-i", "--input", dest="input", default="-",
|
|
help="I=input file name, or '-' for stdin. Defaults to stdin.")
|
|
|
|
parser.add_argument(
|
|
"-n", "--normalize-count", dest="normalize_count", default=None, help=textwrap.dedent('''\
|
|
normalizes the output by dividing the metrics by given factor:
|
|
- a number: output will be scaled and divided by that number
|
|
- a span name: output will be scaled and divided by the count value of that span
|
|
'''))
|
|
|
|
parser.add_argument(
|
|
"-f", "--format", dest="format", default=None, choices=["tsv", "folded"], required=True,
|
|
help=textwrap.dedent('''\
|
|
produces output in a given format:
|
|
- tsv: output will be formated as tsv
|
|
- folded: output will be formatted as folded flamegraph profile
|
|
'''))
|
|
|
|
parser.add_argument(
|
|
"-e",
|
|
"--keep-empty",
|
|
dest="keep_empty",
|
|
action="store_true",
|
|
default=False,
|
|
help="will keep empty spans that have count of 0",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.input == '-':
|
|
input_str = sys.stdin.read()
|
|
else:
|
|
with open(args.input, 'r') as file:
|
|
input_str = file.read()
|
|
|
|
metrics = CallMetrics.from_json(json.loads(input_str))
|
|
|
|
normalize_count = None
|
|
if args.normalize_count is not None:
|
|
if args.normalize_count.isnumeric():
|
|
normalize_count = float(args.normalize_count)
|
|
else:
|
|
span_id = metrics.find_span(args.normalize_count)
|
|
normalize_count = float(metrics.spans[span_id].count)
|
|
new_metrics = CallMetrics.new_empty()
|
|
new_metrics.add_weighted(metrics, 1.0 / normalize_count)
|
|
metrics = new_metrics
|
|
|
|
if not args.keep_empty:
|
|
metrics = remove_empty_spans(metrics)
|
|
|
|
if args.format == "folded":
|
|
print_folded(metrics)
|
|
elif args.format == "tsv":
|
|
print_tsv(metrics)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|