Packages

Secure BEAM sandbox runtime for LLM code mode and MCP aggregation. Run concurrent LLM/tool clients safely while agents orchestrate approved tools, call upstream MCP/OpenAPI servers, and transform data.

Current section

Files

Jump to
ptc_runner lib ptc_runner tracer timeline.ex
Raw

lib/ptc_runner/tracer/timeline.ex

defmodule PtcRunner.Tracer.Timeline do
@moduledoc """
Text-based timeline visualization for execution traces.
Renders traces as ASCII timelines showing relative timing of events.
Each entry is displayed as a bar proportional to its duration within
the total execution time.
## Example
iex> tracer = PtcRunner.Tracer.new()
iex> tracer = PtcRunner.Tracer.add_entry(tracer, %{type: :llm_call, data: %{duration_ms: 150}})
iex> tracer = PtcRunner.Tracer.add_entry(tracer, %{type: :tool_call, data: %{duration_ms: 30}})
iex> tracer = PtcRunner.Tracer.finalize(tracer)
iex> output = PtcRunner.Tracer.Timeline.render(tracer)
iex> output =~ "Timeline:"
true
Note: Entries should have `duration_ms` in their data map for accurate
bar rendering. Entries without duration default to 1ms for display.
"""
alias PtcRunner.Tracer
@bar_width 60
@doc """
Render a timeline visualization as a string.
## Examples
iex> tracer = PtcRunner.Tracer.new() |> PtcRunner.Tracer.finalize()
iex> output = PtcRunner.Tracer.Timeline.render(tracer)
iex> output =~ "no entries"
true
"""
@spec render(Tracer.t()) :: String.t()
def render(%Tracer{} = tracer) do
entries = Tracer.entries(tracer)
if entries == [] do
"Timeline: #{short_id(tracer.trace_id)} (no entries)"
else
render_with_entries(tracer, entries)
end
end
defp render_with_entries(tracer, entries) do
total = Tracer.total_duration(tracer)
start_time = tracer.started_at
header = "Timeline: #{short_id(tracer.trace_id)} (total: #{total}ms)"
separator = String.duplicate("=", @bar_width + 20)
lines =
Enum.map(entries, fn entry ->
render_entry(entry, start_time, total)
end)
Enum.join([header, separator] ++ lines ++ [separator], "\n")
end
defp render_entry(entry, start_time, total) do
offset_ms = DateTime.diff(entry.timestamp, start_time, :millisecond)
duration_ms = Map.get(entry.data, :duration_ms, 1)
bar_start = if total > 0, do: trunc(offset_ms / total * @bar_width), else: 0
bar_length = if total > 0, do: max(1, trunc(duration_ms / total * @bar_width)), else: 1
bar = String.duplicate(" ", bar_start) <> String.duplicate("#", bar_length)
"#{pad_ms(offset_ms)} |#{pad_bar(bar)}| #{entry.type} (#{duration_ms}ms)"
end
defp short_id(trace_id), do: String.slice(trace_id, 0, 8) <> "..."
defp pad_ms(ms) do
ms_str = "#{ms}ms"
String.pad_leading(ms_str, 7)
end
defp pad_bar(bar), do: String.pad_trailing(bar, @bar_width)
end