Offline analysis of trace log files.
Provides functions to load, filter, summarize, and visualize trace data
captured by PtcRunner.TraceLog.
Example
events = TraceLog.Analyzer.load("trace.jsonl")
summary = TraceLog.Analyzer.summary(events)
# Filter to specific event types
llm_events = TraceLog.Analyzer.filter(events, type: "llm")
# Find slowest operations
slowest = TraceLog.Analyzer.slowest(events, 5)
# Print timeline
TraceLog.Analyzer.print_timeline(events)
Summary
Types
A trace tree node representing a trace file and its children.
Functions
Builds a span hierarchy tree from events.
Deletes all trace files in the tree.
Exports a trace tree to Chrome DevTools Trace Event format.
Filters events by various criteria.
Returns events as a formatted string timeline.
Returns the trace tree as a formatted string.
Returns a flat list of all file paths in the trace tree.
Loads events from a JSONL trace file.
Loads a trace file and recursively loads all child traces.
Prints an ASCII timeline visualization of events.
Prints an ASCII visualization of the trace tree hierarchy.
Returns the program sources from turn events, in load order (nil for turns with no program, e.g. parse-failure or budget-stop turns).
Returns the turn events for a single session/correlation id, in load order.
Groups turn events into per-session summaries, keyed by correlation id (the
session_id for session-driven turns, the agent_id for SubAgent turns).
Returns the N slowest events by duration.
Creates a summary of the trace execution.
Returns all canonical turn events (event == "turn"), in load order.
Types
@type trace_tree() :: %{ path: String.t(), trace_id: String.t() | nil, events: [map()], summary: map(), children: [trace_tree()] }
A trace tree node representing a trace file and its children.
Fields:
path: File path to this tracetrace_id: Unique trace identifierevents: Loaded events from this tracesummary: Summary statistics for this tracechildren: List of child trace tree nodes
Functions
Builds a span hierarchy tree from events.
Groups events by span_id and constructs parent-child relationships using parent_span_id.
Examples
tree = Analyzer.build_tree(events)
# Returns nested structure with children
@spec delete_tree(trace_tree()) :: {:ok, non_neg_integer()} | {:error, term()}
Deletes all trace files in the tree.
Returns {:ok, deleted_count} on success, {:error, reason} on failure.
Examples
{:ok, tree} = Analyzer.load_tree("parent.jsonl")
{:ok, 29} = Analyzer.delete_tree(tree)
@spec export_chrome_trace(trace_tree(), String.t()) :: :ok | {:error, term()}
Exports a trace tree to Chrome DevTools Trace Event format.
The output can be opened in Chrome DevTools (Performance panel → Load profile) or at chrome://tracing for flame chart visualization.
Parameters
tree- A trace tree fromload_tree/1output_path- Path to write the JSON file (e.g., "trace.json")
Examples
{:ok, tree} = Analyzer.load_tree("rlm_trace.jsonl")
:ok = Analyzer.export_chrome_trace(tree, "rlm_trace.json")
# Then open Chrome DevTools → Performance → Load profile → select rlm_trace.json
# Or navigate to chrome://tracing and load the fileVisualization
The flame chart shows:
- Horizontal axis: Time (wider = longer duration)
- Vertical stacking: Nested calls (children below parents)
- Colors: Different categories (turns, tools, pmap)
Click any span to see details including arguments and results.
Filters events by various criteria.
Options
:type- Event type prefix (e.g., "llm", "tool", "run"):span_id- Filter by span ID:min_duration_ms- Minimum duration in milliseconds
Examples
# All LLM events
llm_events = Analyzer.filter(events, type: "llm")
# All events taking > 100ms
slow = Analyzer.filter(events, min_duration_ms: 100)
# Events in a specific span
span_events = Analyzer.filter(events, span_id: "abc123")
Returns events as a formatted string timeline.
Like print_timeline/1 but returns a string instead of printing.
@spec format_tree(trace_tree()) :: String.t()
Returns the trace tree as a formatted string.
Like print_tree/1 but returns a string instead of printing.
@spec list_tree(trace_tree()) :: [String.t()]
Returns a flat list of all file paths in the trace tree.
Useful for cleanup operations.
Examples
{:ok, tree} = Analyzer.load_tree("parent.jsonl")
paths = Analyzer.list_tree(tree)
#=> ["parent.jsonl", "child1.jsonl", "child2.jsonl", ...]
Loads events from a JSONL trace file.
Returns a list of event maps in chronological order.
Examples
events = Analyzer.load("trace.jsonl")
length(events) #=> 42
@spec load_tree( String.t(), keyword() ) :: {:ok, trace_tree()} | {:error, term()}
Loads a trace file and recursively loads all child traces.
Child traces are discovered from:
pmap.stopevents withchild_trace_idsmetadatatool.stopevents withchild_trace_idmetadata
Returns a tree structure where each node contains:
path: File pathtrace_id: Trace IDevents: Loaded eventssummary: Execution summarychildren: List of child trace trees
Examples
{:ok, tree} = Analyzer.load_tree("parent_trace.jsonl")
length(tree.children) #=> 28Options
:base_dir- Directory to search for child trace files (defaults to same directory as parent):_seen- Internal option for cycle detection (do not set manually)
@spec print_timeline([map()]) :: :ok
Prints an ASCII timeline visualization of events.
Shows the sequence of events with timing information.
Examples
Analyzer.print_timeline(events)
# Outputs:
# [0ms] run.start
# [10ms] llm.start
# [150ms] llm.stop (140ms)
# ...
@spec print_tree(trace_tree()) :: :ok
Prints an ASCII visualization of the trace tree hierarchy.
Shows execution times and nested structure for debugging and analysis.
Examples
{:ok, tree} = Analyzer.load_tree("parent.jsonl")
Analyzer.print_tree(tree)
# Output:
# ├─ [1234ms] parent (trace-abc123)
# │ ├─ [100ms] worker-1 (trace-def456)
# │ ├─ [120ms] worker-2 (trace-ghi789)
# │ └─ [95ms] worker-3 (trace-jkl012)
Returns the program sources from turn events, in load order (nil for turns with no program, e.g. parse-failure or budget-stop turns).
Returns the turn events for a single session/correlation id, in load order.
Groups turn events into per-session summaries, keyed by correlation id (the
session_id for session-driven turns, the agent_id for SubAgent turns).
Each summary reports the driver, total turns, committed/failed counts, and
total tool calls — enough to answer "what did my previous sessions do, and
where did they waste turns?" before drilling into session_turns/2.
@spec slowest([map()], pos_integer()) :: [map()]
Returns the N slowest events by duration.
Only includes events that have a duration_ms field (typically stop events).
Examples
slowest = Analyzer.slowest(events, 5)
Enum.map(slowest, & &1["event"]) #=> ["llm.stop", "tool.stop", ...]
Creates a summary of the trace execution.
Extracts key metrics from the trace including duration, turns, token counts, and call counts for LLM and tool operations.
Examples
summary = Analyzer.summary(events)
summary.duration_ms #=> 1234
summary.turns #=> 3
summary.llm_calls #=> 3
summary.tool_calls #=> 5
Returns all canonical turn events (event == "turn"), in load order.