diff options
author | Andy Wingo <wingo@pobox.com> | 2025-04-11 14:10:41 +0200 |
---|---|---|
committer | Andy Wingo <wingo@pobox.com> | 2025-04-11 14:10:41 +0200 |
commit | db181e67ff482ab08cbdb0c1a88997b3a886f900 (patch) | |
tree | 6d16fbca515ae3cf506a9d16dbc89c472446e5d9 /libguile/whippet/ctf_to_json.py | |
parent | af96820e072d18c49ac03e80c6f3466d568dc77d (diff) | |
parent | f909438596f63ecd44c5b9a28385260714857361 (diff) | |
download | guile-db181e67ff482ab08cbdb0c1a88997b3a886f900.tar.gz |
Merged Whippet into libguile/whippet
Diffstat (limited to 'libguile/whippet/ctf_to_json.py')
-rwxr-xr-x | libguile/whippet/ctf_to_json.py | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/libguile/whippet/ctf_to_json.py b/libguile/whippet/ctf_to_json.py new file mode 100755 index 000000000..f6b7f429a --- /dev/null +++ b/libguile/whippet/ctf_to_json.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# Any copyright is dedicated to the Public Domain. +# https://creativecommons.org/publicdomain/zero/1.0/ +# +# Originally written by Andy Wingo <wingo@igalia.com>. + +import bt2 # From the babeltrace2 package. +import sys +import json +from enum import Enum + +# Usage: ./ctf_to_json.py ~/lttng-traces/name-of-your-trace > foo.json +# +# Convert a Common Trace Format (CTF) trace, for example as produced by +# LTTng, to the JSON-based Trace Event Format (TEF), for example as +# consumed by `chrome://tracing`, `https://ui.perfetto.dev/`, or +# `https://profiler.firefox.com`. + +# The Trace Event Format is documented here: +# +# https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview?tab=t.0 + +# By default, events are emitted as EventPhase.INSTANT. We also support +# rewriting the event stream so as to generate EventPhase.BEGIN / +# EventPhase.END events for specific named events. + +synthetic_events = { + 'gc': ['whippet:mutator_cause_gc', + 'whippet:restarting_mutators'], + 'stop-the-world': ['whippet:requesting_stop', + 'whippet:mutators_stopped'], + 'trace': ['whippet:prepare_gc', + 'whippet:restarting_mutators'], + 'mutator-stopped': ['whippet:mutator_stopping', + 'whippet:mutator_restarted'], + 'trace-roots': ['whippet:trace_roots_begin', + 'whippet:trace_roots_end'], + 'trace-check-termination': ['whippet:trace_check_termination_begin', + 'whippet:trace_check_termination_end'], + 'trace-objects': ['whippet:trace_objects_begin', + 'whippet:trace_objects_end'], + 'trace-worker': ['whippet:trace_worker_begin', + 'whippet:trace_worker_end'] +} + +class EventPhase(Enum): + BEGIN = 'B' + END = 'E' + COMPLETE = 'X' + INSTANT = 'i' + COUNTER = 'C' + NESTABLE_START = 'b' + NESTABLE_INSTANT = 'n' + NESTABLE_END = 'e' + FLOW_START = 's' + FLOW_STEP = 't' + FLOW_END = 'f' + SAMPLE = 'P' + OBJECT_CREATED = 'N' + OBJECT_SNAPSHOT = 'O' + OBJECT_DESTROYED = 'D' + METADATA = 'M' + MEMORY_DUMP_GLOBAL = 'V' + MEMORY_DUMP_PROCESS = 'V' + MARK = 'R' + CLOCK_SYNC = 'c' + CONTEXT_BEGIN = '(' + CONTEXT_END = ')' + +base_time = None +def event_us(msg): + assert(msg.default_clock_snapshot.clock_class.name == 'monotonic') + assert(msg.default_clock_snapshot.clock_class.frequency == 1e9) + global base_time + ns = msg.default_clock_snapshot.value + if base_time is None: + base_time = ns + return (ns - base_time) * 1e-3 + +def lower(x): + if isinstance(x, str) or isinstance(x, int) or isinstance(x, float): + return x + if isinstance(x, dict) or isinstance(x, bt2._StructureFieldConst): + return {lower(k):lower(v) for k, v in x.items()} + if isinstance(x, bt2._BoolValueConst) or isinstance(x, bt2._BoolFieldConst): + return bool(x) + if isinstance(x, bt2._EnumerationFieldConst): + return repr(x) + if isinstance(x, bt2._IntegerValueConst) or isinstance(x, bt2._IntegerFieldConst): + return int(x) + if isinstance(x, bt2._RealValueConst) or isinstance(x, bt2._RealFieldConst): + return float(x) + if isinstance(x, bt2._StringValueConst) or isinstance(x, bt2._StringFieldConst): + return str(x) + raise ValueError("Unexpected value from trace", x) + +# Specific Whippet events. +synthetic_begin = {} +synthetic_end = {} +for synthetic, [begin, end] in synthetic_events.items(): + synthetic_begin[begin] = [] + synthetic_end[end] = [] +for synthetic, [begin, end] in synthetic_events.items(): + synthetic_begin[begin].append(synthetic) + synthetic_end[end].append(synthetic) + +def put(str): + sys.stdout.write(str) + +need_comma = False +def print_event(ev): + global need_comma + if need_comma: + sys.stdout.write(',\n ') + else: + need_comma = True + # It appears to be faster to make a string, then print the string, + # than to call json.dump with a file object. + # json.dump(ev, sys.stdout, ensure_ascii=False, check_circular=False) + put(json.dumps(ev, ensure_ascii=False, check_circular=False)) + +def emit_event(msg, name, phase): + ev = {'name': name, + 'cat': 'whippet', + 'ph': phase.value, + 'ts': event_us(msg), + 'pid': lower(msg.event.common_context_field['vpid']), + 'tid': lower(msg.event.common_context_field['vtid']), + 'args': lower(msg.event.payload_field)} + print_event(ev) +def emit_begin_event(msg, name): + emit_event(msg, name, EventPhase.BEGIN) +def emit_end_event(msg, name): + emit_event(msg, name, EventPhase.END) + +def emit_events(msg): + emit_event(msg, msg.event.name, EventPhase.INSTANT) + for begin in synthetic_begin.get(msg.event.name, []): + emit_begin_event(msg, begin) + for end in synthetic_end.get(msg.event.name, []): + emit_end_event(msg, end) + +def ctf_to_json(path): + msg_it = bt2.TraceCollectionMessageIterator(path) + put('{\n') + put(' "traceEvents": [\n ') + for msg in msg_it: + if hasattr(msg, 'event'): + emit_events(msg) + put('\n') + put('\n ],\n') + put(' "displayTimeUnit": "ns"\n') + put('}\n') + +if len(sys.argv) != 2: + sys.stderr.write( + 'usage: ' + sys.argv[0] + ' ~/lttng-traces/name-of-your-trace\n') + sys.exit(1) +else: + ctf_to_json(sys.argv[1]) |