Files
mongo/buildscripts/tracing_profiler/profile_format.py
Anna Wawrzyniak 702cbeec44 SERVER-88374 Add lightweight tracing profiler (#20624)
GitOrigin-RevId: 59f133372db2277c387499b2a38b9cb67d638876
2024-05-07 19:24:49 +00:00

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()