Steps, scales & labels
A series is identified by (metric_name, scale, labels). Two points with the same name but a different
scale or different labels go to different series, each with its own step axis. This page covers the
arguments that shape series identity and the step axis.
The step axis
Section titled “The step axis”Each series has its own step axis. The step argument to log controls it:
step value |
meaning |
|---|---|
None (default) |
auto-increment from the series’ last step |
a single int |
the step of the first point; further points in the call continue from it |
| a sequence/array | an explicit step per point (length must match value) |
metrana.log("loss", 0.5) # auto: next stepmetrana.log("loss", [a, b, c], step=100) # steps 100, 101, 102metrana.log("loss", [a, b, c], step=[10, 20, 30]) # explicit stepsAuto-increment is the common case for a single-writer series. Use explicit steps when you log out of order, resume a run, or want a metric pinned to your global training step (see Retrieving the last step).
Timestamps
Section titled “Timestamps”timestamp works exactly like step, in Unix milliseconds:
None(default) — the server stamps each point on arrival.- a single
int— applied to every point in the call. - a sequence/array — one timestamp per point (length must match
value).
import timenow_ms = int(time.time() * 1000)metrana.log("loss", loss, timestamp=now_ms)Scale, labels, and evaluation
Section titled “Scale, labels, and evaluation”These three arguments shape series identity:
The step scale — a StandardMetricScale value: "ML_STEP", "EPISODE", or "ENVIRONMENT_STEP". None
defaults to ML_STEP. Only log / log_distributed take scale; the RL helpers fix their own scale (see
RL metrics).
import metranametrana.log("custom_metric", v, scale=metrana.StandardMetricScale.ML_STEP)metrana.log("custom_metric", v, scale="ML_STEP") # strings work toolabels
Section titled “labels”A dict[str, str] that, together with the name and scale, identifies the series. Two points with the same
name but different labels form different series — useful for splitting a metric by a dimension:
metrana.log("loss", per_layer_loss, labels={"layer": "3"})metrana.log("reward", r, labels={"policy": "greedy"})evaluation
Section titled “evaluation”A shorthand that adds the evaluation label — True adds {"evaluation": "true"}, False adds
{"evaluation": "false"} (unless you already set the key in labels), and None (the default) adds
nothing. Evaluation points form a series distinct from otherwise identical training points; because both
sides of the split must share the metric’s label key set (see
One shape per metric), mark the training side with evaluation=False:
metrana.log("reward", train_reward, evaluation=False) # training seriesmetrana.log("reward", eval_reward, evaluation=True) # distinct eval seriesmetrana.log("reward_by_policy", r, labels={"policy": "greedy"}) # labelled series, own metricOne shape per metric
Section titled “One shape per metric”Labels split a metric into series, but the metric itself — the same (metric_name, scale) — keeps one
shape, fixed by the first point ever logged and enforced on every later call:
| fixed per metric | meaning |
|---|---|
| label key set | every series of the metric uses the same label keys; only the values differ |
| value kind | floats, counting/density histograms, or 2D/3D scatter plots — never a mix |
| logging mode | log (single-writer) and log_distributed (multi-writer) cannot be mixed |
| episodic mode (RL) | with episodes or without them, not both — pinned by the first call’s episode |
A divergent call raises MetranaValidationError and sends nothing; conforming series keep logging:
metrana.log("loss", a, labels={"layer": "1"}) # pins loss to the key set {layer}metrana.log("loss", b, labels={"layer": "2"}) # ok: same key, different valuemetrana.log("loss", c, labels={"rank": "0"}) # rejected: different label keymetrana.log("loss", d) # rejected: no labels on a labeled metricThe shape survives restarts: a resumed run re-learns it from the server at init(), so a divergent call
from a new process is rejected the same way. To record differently-shaped data, use a distinct metric
name or a / prefix — see Naming rules.
Retrieving the last step
Section titled “Retrieving the last step”metrana.get_last_step(metric_name, scale=None, labels=None) returns the last step logged for a series, or
None if nothing has been logged yet. Pass the same scale / labels you logged it with.
This value is seeded from the server at init(), so after a restart or resume you can continue from where
the run left off:
last = metrana.get_last_step("loss")next_step = 0 if last is None else last + 1metrana.log("loss", loss, step=next_step)This is the building block for resuming a run with explicit steps — see
Resuming & forking. The RL equivalent is
get_env_last_rl_step_and_episode.