# `PtcRunner.TraceLog.Analyzer`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/trace_log/analyzer.ex#L1)

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)

# `trace_tree`

```elixir
@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 trace
- `trace_id`: Unique trace identifier
- `events`: Loaded events from this trace
- `summary`: Summary statistics for this trace
- `children`: List of child trace tree nodes

# `build_tree`

```elixir
@spec build_tree([map()]) :: [map()]
```

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

# `delete_tree`

```elixir
@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)

# `export_chrome_trace`

```elixir
@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 from `load_tree/1`
- `output_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 file

## Visualization

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.

# `filter`

```elixir
@spec filter(
  [map()],
  keyword()
) :: [map()]
```

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

# `format_timeline`

```elixir
@spec format_timeline([map()]) :: String.t()
```

Returns events as a formatted string timeline.

Like `print_timeline/1` but returns a string instead of printing.

# `format_tree`

```elixir
@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.

# `list_tree`

```elixir
@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", ...]

# `load`

```elixir
@spec load(String.t()) :: [map()]
```

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

# `load_tree`

```elixir
@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.stop` events with `child_trace_ids` metadata
- `tool.stop` events with `child_trace_id` metadata

Returns a tree structure where each node contains:
- `path`: File path
- `trace_id`: Trace ID
- `events`: Loaded events
- `summary`: Execution summary
- `children`: List of child trace trees

## Examples

    {:ok, tree} = Analyzer.load_tree("parent_trace.jsonl")
    length(tree.children)  #=> 28

## Options

- `: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)

# `print_timeline`

```elixir
@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)
    # ...

# `print_tree`

```elixir
@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)

# `programs`

```elixir
@spec programs([map()]) :: [String.t() | nil]
```

Returns the program sources from turn events, in load order (nil for turns
with no program, e.g. parse-failure or budget-stop turns).

# `session_turns`

```elixir
@spec session_turns([map()], String.t()) :: [map()]
```

Returns the turn events for a single session/correlation id, in load order.

# `sessions`

```elixir
@spec sessions([map()]) :: [map()]
```

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`.

# `slowest`

```elixir
@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", ...]

# `summary`

```elixir
@spec summary([map()]) :: map()
```

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

# `turn_events`

```elixir
@spec turn_events([map()]) :: [map()]
```

Returns all canonical turn events (`event == "turn"`), in load order.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
