Open Trace Files¶
Zelos records data as .trz files. Save a slice of live data into one, then re-open it later with the same query(), at(), window(), and SignalFrame tools — see Query Live Data for the analysis surface.
Snippets assume an agent connected via connect(), with paths from zelos live demo:
Save a slice of live data¶
agent.export() writes a .trz to the agent host, capturing every signal that produced data in [start, end].
result = agent.export(
"/tmp/run.trz",
start="-5m",
end="now",
overwrite=True,
)
print(result.path, "ok:", result.ok)
for producer, pr in result.results.items():
print(producer, pr.ok, pr.error or "")
ExportResult carries the written path and a {producer: ExportProducerResult} map. result.ok is True only when every producer succeeded. Per-producer failures are preserved on result.results so one failed producer doesn't hide successful exports.
Localhost vs remote agents¶
- Localhost.
overwrite=FalseraisesFileExistsErrorif the path exists.overwrite=Truereplaces. - Remote agent. Use
overwrite=True;overwrite=FalseraisesValueErrorbecause the file check only works on localhost.
result = agent.export(
"/var/lib/zelos/runs/2024-05-01.trz",
start="2024-05-01T08:00:00Z",
end="2024-05-01T09:00:00Z",
overwrite=True,
)
if not result.ok:
for producer, pr in result.results.items():
if not pr.ok:
print(producer, "failed:", pr.error)
producers=("localhost",) is the default; pass producers=() to capture every connected producer.
Open a trace¶
agent.open_trace(path) returns a Trace handle. The path lives on the agent's filesystem — the SDK does not upload files.
trace = agent.open_trace("/tmp/run.trz")
print(trace.path) # resolved absolute path
print(trace.time_range.start, "→", trace.time_range.end)
print(trace.time_range.duration)
catalog = trace.signals()
print(len(catalog), "signal(s) in this trace")
time_range is a TimeRange with start, end, duration. Per-trace warnings land on catalog.warnings.
Path resolution¶
- Localhost agent. Relative paths and
~work. - Remote agent. Use absolute paths.
~and relative paths raiseConnectionTargetError.
trace = agent.open_trace("~/runs/run.trz") # OK on localhost
trace = agent.open_trace("/srv/runs/run.trz") # OK anywhere
Segments¶
Query a trace¶
trace.query() returns a SignalFrame with the same shape, metadata, and SignalSeries analysis surface as live agent.query(). See Query Live Data for series math, frame iteration, unit composition, and Arrow / pandas hand-off.
frame = trace.query(
[
"bus0/BMS_message/status.cell_voltage",
"bus0/BMS_message/status.pack_current",
],
relative_start=0,
relative_end=30,
downsample=400,
)
voltage = frame["bus0/BMS_message/status.cell_voltage"]
current = frame["bus0/BMS_message/status.pack_current"]
power = voltage * current # SignalSeries, unit "V·A"
print(power.legend, power.max())
Absolute vs relative bounds¶
Pick one shape:
- Absolute with
start/end—datetime,"now", ISO 8601, or"-30s"-style relative offsets, just like live queries. - Relative with
relative_start/relative_end— floats, in seconds fromt=0of the trace.
# First 30 seconds of the recording, regardless of wall-clock time.
frame = trace.query("bus0/*", relative_start=0, relative_end=30)
# A specific wall-clock window.
frame = trace.query(
"bus0/*",
start=trace.time_range.start,
end=trace.time_range.end,
)
You cannot mix absolute and relative bounds in the same call. Omit all four to query the full trace.
max_rows, sort, and downsample work the same as live queries — downsample=N runs the agent's M4 strategy with about N buckets across the queried time range:
frame = trace.query(
"bus0/BMS_message/status.*",
relative_start=0,
relative_end=60,
downsample=400,
max_rows=50_000,
sort="asc",
)
Read at a cursor / window snapshot¶
# Per-signal value at-or-before a cursor.
cursor = trace.time_range.start + (trace.time_range.duration / 2)
values = trace.at("bus0/BMS_message/status.*", cursor)
for path, lv in values.items():
print(path, lv.formatted())
# Opening snapshot + change stream over a 10-second window.
window = trace.window(
"bus1/inverter_status.*",
start=trace.time_range.start,
duration=10.0,
)
print(len(window.snapshot), "opening value(s),", len(window.changes), "change(s)")
at() always returns a dict[str, LatestValue]. window() returns a ReplayWindow. See Read latest values for the LatestValue field reference.
Open multiple traces together¶
agent.open_traces([...]) returns a TraceSet — one handle for querying across many traces.
traces = agent.open_traces([
"/tmp/run-2024-05-01.trz",
"/tmp/run-2024-05-02.trz",
])
print(traces.paths) # input order
print(traces.time_range.start) # earliest start across all traces
print(traces.time_range.max_duration_s) # longest single-trace duration
for t in traces.time_range.traces:
print(t.path, "starts", t.start, "for", t.duration)
Compare two runs at t = 0¶
By default, every TraceSet query runs in relative mode — each trace is aligned at t = 0, so plotting two recordings overlays them naturally regardless of when each was captured.
# Both traces' first 30 seconds, aligned.
frame = traces.query(
"bus0/BMS_message/status.cell_voltage",
relative_start=0,
relative_end=30,
)
# `frame` has one column per (trace, signal) pair.
for col in frame.columns[1:]:
print(col.trace_path, col.path)
Use wall-clock time across traces¶
Pass time_mode="absolute" to query traces by their original timestamps:
frame = traces.query(
"bus0/BMS_message/status.cell_voltage",
time_mode="absolute",
start=traces.time_range.start,
end=traces.time_range.end,
)
time_mode="relative" (default) is incompatible with start / end; time_mode="absolute" is incompatible with relative_*. The SDK raises ValueError rather than silently coercing.
traceset.at(), traceset.window(), and traceset.export() all accept the same time_mode argument with the same default.
Pick the right mode
- Relative — comparing runs: laps, test cycles, day-over-day operation.
- Absolute — incident reconstruction: correlating signals from sources that ran simultaneously.
Export a trace (or slice) to a new file¶
result = trace.export(
"/tmp/run-clipped.trz",
relative_start=10,
relative_end=70,
overwrite=True,
)
print(result.ok, result.path)
traceset.export() merges every trace in the set into one file:
result = traces.export(
"/tmp/runs-merged.trz",
relative_start=0,
relative_end=60,
overwrite=True,
)
Both accept the same bound shapes as query(). Partial failures show up on result.results exactly like agent.export(). traceset.export() defaults to time_mode="relative"; pass time_mode="absolute" with absolute bounds to merge by wall-clock time.
Closing¶
Use it when you need to free opened traces — for example, before opening a fresh batch in a long-lived process.
close_traces() is global
agent.close_traces() releases every open trace for this Agent. Coordinate if multiple parts of your code hold trace handles.
Errors¶
Trace handles raise the same exceptions as live queries (see Errors), plus two specific to traces:
from zelos_sdk.agent import AgentError, ConnectionTargetError, TraceNotFound
try:
trace = agent.open_trace("/no/such/file.trz")
except TraceNotFound:
... # path isn't on the agent's filesystem
except ConnectionTargetError:
... # remote agent, and the path was relative or `~`-prefixed
SignalNotFound, AmbiguousSignal, NoData, and QueryRangeTooLarge apply equally to trace queries.
Tips¶
- Open once, query many. Re-use the same
Tracehandle across related queries. time_mode="relative"overlays runs. Plotting two laps, two test cycles, or two days of operation?TraceSetaligns them att = 0automatically.time_mode="absolute"reconstructs incidents. When wall-clock correlation matters, passtime_mode="absolute"with absolutestart/end.- Trust column metadata. When a query spans multiple traces,
col.trace_pathtells you which file each column came from. trace.pathis resolved. Even with a relative input,trace.pathreturns the resolved absolute form on localhost — handy for logging.- Release with
agent.close_traces(). Applies to everyTraceandTraceSetopened from the sameAgent.
What's next¶
-
The same
SignalFrame/SignalSeriesanalysis surface, applied to the live store. -
Inspect schemas, execute typed Actions, and handle results.
-
Connect to an agent and run your first query in under 2 minutes.