monitor.parsers

Metric parsers — convert raw command output (CommandResult.value) to numeric values.

Built-in parsers cover the most common Linux host metrics. To add support for a custom command, subclass MetricParser and override parse():

class MyAppParser(MetricParser):
    chart = "Connections"
    unit = ""
    command = "ss -s | grep estab"

    def parse(self, output: str, *, ctx: ParseContext) -> dict[str, MetricDataPoint]: ...

To associate custom parsers with a specific host, call register_host_parsers() from an init module listed in .otto/settings.toml:

from otto.monitor.parsers import DEFAULT_PARSERS, TopCpuParser, register_host_parsers
from my_repo.parsers import NvidiaGpuParser

register_host_parsers(
    "gpu-01",
    {
        **DEFAULT_PARSERS,
        "nvidia-smi --query-gpu=utilization.gpu ...": NvidiaGpuParser(),
    },
)

Hosts with no registered parsers fall back to DEFAULT_PARSERS.

To add or override a parser for every monitored host instead of one host, call register_parsers() the same way:

from otto.monitor.parsers import register_parsers
from my_repo.parsers import UptimeParser

register_parsers([UptimeParser()])

Project-level entries merge over DEFAULT_PARSERS by command string (a command matching a default overrides it; a new command extends the set), and yield to any per-host registration for that host.

class otto.monitor.parsers.MetricDataPoint(value: float, meta: dict[str, Any] | None = None)

Bases: NamedTuple

A single data point returned by MetricParser.parse().

value : float

The numeric measurement for this tick.

meta : dict[str, Any] | None

Optional supplementary data forwarded to the dashboard as hover text (e.g. {'used': '4.2 GB', 'total': '16 GB'} for memory).

class otto.monitor.parsers.ParseContext(core_count: int = 1, ts: datetime | None = None)

Bases: object

Tick-local input to MetricParser.parse — extensible without signature breaks.

core_count : int = 1

Number of CPU cores on the target host for this tick. Most parsers ignore this; TopCpuParser uses it to normalize per-process CPU%.

ts : datetime | None = None

Collection timestamp for this tick. Rate parsers feed it to their RateTracker; None (bare construction in tests) means the parser falls back to datetime.now(tz=timezone.utc).

class otto.monitor.parsers.TimedSample(ts: datetime | None, series: dict[str, MetricDataPoint])

Bases: NamedTuple

One timestamped batch of series points produced by MetricParser.parse_tick().

ts : datetime | None

Data-carried timestamp for every point in series; None means “stamp with the collector’s tick time” (what plain parse() output gets).

series : dict[str, MetricDataPoint]

Series label → data point, exactly as parse() returns.

class otto.monitor.parsers.LogEvent(ts: datetime, fields: dict[str, str])

Bases: NamedTuple

One columnar log-event row produced by MetricParser.parse_tick().

A table row, not a chart point — rendered by kind="table" dashboard tabs. Deliberately separate from MonitorEvent (the global, low-volume chart-marker annotation system): log events are per-host, high-volume, columnar data.

ts : datetime

Data-carried timestamp of the row.

fields : dict[str, str]

Column → value; the schema is declared by the parser’s table_columns.

class otto.monitor.parsers.TickResult(samples: list[TimedSample], events: list[LogEvent])

Bases: NamedTuple

Everything a parser produced for one tick: timed samples and/or log events.

samples : list[TimedSample]

Alias for field number 0

events : list[LogEvent]

Alias for field number 1

otto.monitor.parsers.human_readable(value: float, precision: int = 1) str

Format a byte count as a human-readable string using binary prefixes (df -h style).

Parameters:
  • value – The value in bytes.

  • precision – Maximum number of decimal places in the output (trailing zeros are stripped).

>>> human_readable(0)
'0 B'
>>> human_readable(1024)
'1 K'
>>> human_readable(1536)
'1.5 K'
>>> human_readable(1073741824)
'1 G'
class otto.monitor.parsers.MetricParser

Bases: ABC

Base class for metric parsers.

Subclass this and set the class attributes, then override parse() to extract a single numeric value from the raw command output string.

y_title : str

Y-axis title shown to the left of the chart, e.g. ‘CPU’.

unit : str

Unit suffix for chart annotations, e.g. ‘%’, ‘MB’, ‘GB’. Empty string for dimensionless values.

command : str

The exact shell command whose output this parser handles.

tab : str = 'metrics'

Dashboard tab id this metric belongs to, e.g. ‘cpu’. Defaults to ‘metrics’.

tab_label : str = 'Metrics'

Human-readable label for the tab button, e.g. ‘CPU’. Defaults to ‘Metrics’.

chart : str

Chart group id. Series with the same chart value share one Plotly chart. Single-series parsers set this to their series label; multi-series parsers set it to a shared group name (e.g. 'Load').

interval : float | None = None

Collection interval override in seconds for this parser’s command. None means the collector’s global --interval (the default). Honored by run()’s per-interval scheduling.

table_columns : list[str] | None = None

Table columns for log-event parsers — their tab renders as a table (TabSpec.kind == "table") instead of charts. None (the default) for chart parsers. Table parsers must declare their own tab id; sharing a tab with chart parsers raises in get_meta_model().

abstract parse(output: str, *, ctx: ParseContext) dict[str, MetricDataPoint]

Convert raw command output into one or more labelled data points.

Parameters:
  • output – The full stdout+stderr string returned by the remote command.

  • ctx – Tick-local input (e.g. the target host’s core count).

Returns:

A dict mapping series label → MetricDataPoint for each data point produced this tick. Return an empty dict if parsing fails. Single-series parsers return {self.chart: MetricDataPoint(value)}. Multi-series parsers may return multiple entries in the dict. Log-sourced parsers — which override parse_tick() — implement this as a trivial return {}.

parse_tick(output: str, *, ctx: ParseContext) TickResult

Convert one tick’s command output into timed samples and/or log events.

The collector always calls this, never parse() directly. The default wraps parse() as a single untimed sample, so existing parsers behave exactly as before. Log-sourced parsers (see otto.monitor.log_sourced) override this to emit data-carried timestamps — multiple samples per read, ascending — and/or columnar LogEvent rows.

class otto.monitor.parsers.TopCpuParser(top_n: int = 5, delay: float = 0.5)

Bases: MetricParser

Parse overall and per-process CPU usage from top -d{delay} -bn2 output.

Runs two top iterations separated by delay seconds so that per-process %CPU values reflect activity during that interval (the first iteration has no baseline and is discarded). Overall CPU usage and per-process traces share one chart.

Parameters:
  • top_n – Maximum number of processes to include per collection tick.

  • delay – Seconds between top iterations (controls accuracy vs latency trade-off).

y_title : str = 'Usage %'

Y-axis title shown to the left of the chart, e.g. ‘CPU’.

unit : str = '%'

Unit suffix for chart annotations, e.g. ‘%’, ‘MB’, ‘GB’. Empty string for dimensionless values.

tab : str = 'cpu'

Dashboard tab id this metric belongs to, e.g. ‘cpu’. Defaults to ‘metrics’.

tab_label : str = 'CPU'

Human-readable label for the tab button, e.g. ‘CPU’. Defaults to ‘Metrics’.

chart : str = 'CPU'

Chart group id. Series with the same chart value share one Plotly chart. Single-series parsers set this to their series label; multi-series parsers set it to a shared group name (e.g. 'Load').

property command : str
parse(output: str, *, ctx: ParseContext) dict[str, MetricDataPoint]

Convert raw command output into one or more labelled data points.

Parameters:
  • output – The full stdout+stderr string returned by the remote command.

  • ctx – Tick-local input (e.g. the target host’s core count).

Returns:

A dict mapping series label → MetricDataPoint for each data point produced this tick. Return an empty dict if parsing fails. Single-series parsers return {self.chart: MetricDataPoint(value)}. Multi-series parsers may return multiple entries in the dict. Log-sourced parsers — which override parse_tick() — implement this as a trivial return {}.

class otto.monitor.parsers.MemParser

Bases: MetricParser

Parse memory and swap usage % from free -b output.

Reads the ‘Mem:’ and ‘Swap:’ lines and computes used/total as percentages.

y_title : str = 'Memory'

Y-axis title shown to the left of the chart, e.g. ‘CPU’.

unit : str = '%'

Unit suffix for chart annotations, e.g. ‘%’, ‘MB’, ‘GB’. Empty string for dimensionless values.

command : str = 'free -b'

The exact shell command whose output this parser handles.

tab : str = 'memory'

Dashboard tab id this metric belongs to, e.g. ‘cpu’. Defaults to ‘metrics’.

tab_label : str = 'Memory'

Human-readable label for the tab button, e.g. ‘CPU’. Defaults to ‘Metrics’.

chart : str = 'Memory Usage'

Chart group id. Series with the same chart value share one Plotly chart. Single-series parsers set this to their series label; multi-series parsers set it to a shared group name (e.g. 'Load').

parse(output: str, *, ctx: ParseContext) dict[str, MetricDataPoint]

Convert raw command output into one or more labelled data points.

Parameters:
  • output – The full stdout+stderr string returned by the remote command.

  • ctx – Tick-local input (e.g. the target host’s core count).

Returns:

A dict mapping series label → MetricDataPoint for each data point produced this tick. Return an empty dict if parsing fails. Single-series parsers return {self.chart: MetricDataPoint(value)}. Multi-series parsers may return multiple entries in the dict. Log-sourced parsers — which override parse_tick() — implement this as a trivial return {}.

class otto.monitor.parsers.DiskParser

Bases: MetricParser

Parse root filesystem usage % from df -h / output.

Reads the data row (second line) and extracts the Use% column. parse_meta() returns the already human-readable Size/Used strings from df -h.

y_title : str = 'Disk'

Y-axis title shown to the left of the chart, e.g. ‘CPU’.

unit : str = '%'

Unit suffix for chart annotations, e.g. ‘%’, ‘MB’, ‘GB’. Empty string for dimensionless values.

command : str = 'df -h'

The exact shell command whose output this parser handles.

tab : str = 'disk'

Dashboard tab id this metric belongs to, e.g. ‘cpu’. Defaults to ‘metrics’.

tab_label : str = 'Disk'

Human-readable label for the tab button, e.g. ‘CPU’. Defaults to ‘Metrics’.

chart : str = 'Disk Usage'

Chart group id. Series with the same chart value share one Plotly chart. Single-series parsers set this to their series label; multi-series parsers set it to a shared group name (e.g. 'Load').

parse(output: str, *, ctx: ParseContext) dict[str, MetricDataPoint]

Convert raw command output into one or more labelled data points.

Parameters:
  • output – The full stdout+stderr string returned by the remote command.

  • ctx – Tick-local input (e.g. the target host’s core count).

Returns:

A dict mapping series label → MetricDataPoint for each data point produced this tick. Return an empty dict if parsing fails. Single-series parsers return {self.chart: MetricDataPoint(value)}. Multi-series parsers may return multiple entries in the dict. Log-sourced parsers — which override parse_tick() — implement this as a trivial return {}.

class otto.monitor.parsers.LoadParser

Bases: MetricParser

Parse all three load averages from cat /proc/loadavg.

Output format: ‘0.52 0.58 0.59 1/432 12345’ Returns 1-minute, 5-minute, and 15-minute load averages as separate series.

y_title : str = 'Load'

Y-axis title shown to the left of the chart, e.g. ‘CPU’.

unit : str = ''

Unit suffix for chart annotations, e.g. ‘%’, ‘MB’, ‘GB’. Empty string for dimensionless values.

command : str = 'cat /proc/loadavg'

The exact shell command whose output this parser handles.

tab : str = 'cpu'

Dashboard tab id this metric belongs to, e.g. ‘cpu’. Defaults to ‘metrics’.

tab_label : str = 'CPU'

Human-readable label for the tab button, e.g. ‘CPU’. Defaults to ‘Metrics’.

chart : str = 'Load'

Chart group id. Series with the same chart value share one Plotly chart. Single-series parsers set this to their series label; multi-series parsers set it to a shared group name (e.g. 'Load').

parse(output: str, *, ctx: ParseContext) dict[str, MetricDataPoint]

Convert raw command output into one or more labelled data points.

Parameters:
  • output – The full stdout+stderr string returned by the remote command.

  • ctx – Tick-local input (e.g. the target host’s core count).

Returns:

A dict mapping series label → MetricDataPoint for each data point produced this tick. Return an empty dict if parsing fails. Single-series parsers return {self.chart: MetricDataPoint(value)}. Multi-series parsers may return multiple entries in the dict. Log-sourced parsers — which override parse_tick() — implement this as a trivial return {}.

class otto.monitor.parsers.NetDevParser

Bases: MetricParser

Per-interface network throughput from /proc/net/dev counter deltas.

Emits rx <iface> / tx <iface> byte rates; packet/error/drop rates ride each series’ hover meta. The loopback interface is skipped. First tick per interface is the rate baseline and emits nothing; a counter reset (reboot) skips one tick and re-baselines (see otto.monitor.rates).

y_title : str = 'Throughput'

Y-axis title shown to the left of the chart, e.g. ‘CPU’.

unit : str = 'B/s'

Unit suffix for chart annotations, e.g. ‘%’, ‘MB’, ‘GB’. Empty string for dimensionless values.

command : str = 'cat /proc/net/dev'

The exact shell command whose output this parser handles.

tab : str = 'network'

Dashboard tab id this metric belongs to, e.g. ‘cpu’. Defaults to ‘metrics’.

tab_label : str = 'Network'

Human-readable label for the tab button, e.g. ‘CPU’. Defaults to ‘Metrics’.

chart : str = 'Network I/O'

Chart group id. Series with the same chart value share one Plotly chart. Single-series parsers set this to their series label; multi-series parsers set it to a shared group name (e.g. 'Load').

parse(output: str, *, ctx: ParseContext) dict[str, MetricDataPoint]

Convert raw command output into one or more labelled data points.

Parameters:
  • output – The full stdout+stderr string returned by the remote command.

  • ctx – Tick-local input (e.g. the target host’s core count).

Returns:

A dict mapping series label → MetricDataPoint for each data point produced this tick. Return an empty dict if parsing fails. Single-series parsers return {self.chart: MetricDataPoint(value)}. Multi-series parsers may return multiple entries in the dict. Log-sourced parsers — which override parse_tick() — implement this as a trivial return {}.

class otto.monitor.parsers.SocketsParser

Bases: MetricParser

TCP socket-state counts from the TCP: summary line of ss -s.

Hosts without ss produce a shell error the parser cannot match — the series simply never appears (and the collector warns once; see parser-health warnings). Swap in a netstat-based parser per host via register_host_parsers() if needed.

y_title : str = 'Sockets'

Y-axis title shown to the left of the chart, e.g. ‘CPU’.

unit : str = ''

Unit suffix for chart annotations, e.g. ‘%’, ‘MB’, ‘GB’. Empty string for dimensionless values.

command : str = 'ss -s'

The exact shell command whose output this parser handles.

tab : str = 'network'

Dashboard tab id this metric belongs to, e.g. ‘cpu’. Defaults to ‘metrics’.

tab_label : str = 'Network'

Human-readable label for the tab button, e.g. ‘CPU’. Defaults to ‘Metrics’.

chart : str = 'Sockets'

Chart group id. Series with the same chart value share one Plotly chart. Single-series parsers set this to their series label; multi-series parsers set it to a shared group name (e.g. 'Load').

parse(output: str, *, ctx: ParseContext) dict[str, MetricDataPoint]

Convert raw command output into one or more labelled data points.

Parameters:
  • output – The full stdout+stderr string returned by the remote command.

  • ctx – Tick-local input (e.g. the target host’s core count).

Returns:

A dict mapping series label → MetricDataPoint for each data point produced this tick. Return an empty dict if parsing fails. Single-series parsers return {self.chart: MetricDataPoint(value)}. Multi-series parsers may return multiple entries in the dict. Log-sourced parsers — which override parse_tick() — implement this as a trivial return {}.

class otto.monitor.parsers.DiskIoParser

Bases: MetricParser

Per-device read/write throughput from /proc/diskstats sector deltas.

Whole devices only: partitions (sda1, nvme0n1p2, mmcblk0p1) and virtual/noise devices (loop*, ram*, dm-*, zram*, sr*) are skipped so charts show physical disk activity once.

y_title : str = 'Disk I/O'

Y-axis title shown to the left of the chart, e.g. ‘CPU’.

unit : str = 'B/s'

Unit suffix for chart annotations, e.g. ‘%’, ‘MB’, ‘GB’. Empty string for dimensionless values.

command : str = 'cat /proc/diskstats'

The exact shell command whose output this parser handles.

tab : str = 'disk'

Dashboard tab id this metric belongs to, e.g. ‘cpu’. Defaults to ‘metrics’.

tab_label : str = 'Disk'

Human-readable label for the tab button, e.g. ‘CPU’. Defaults to ‘Metrics’.

chart : str = 'Disk I/O'

Chart group id. Series with the same chart value share one Plotly chart. Single-series parsers set this to their series label; multi-series parsers set it to a shared group name (e.g. 'Load').

parse(output: str, *, ctx: ParseContext) dict[str, MetricDataPoint]

Convert raw command output into one or more labelled data points.

Parameters:
  • output – The full stdout+stderr string returned by the remote command.

  • ctx – Tick-local input (e.g. the target host’s core count).

Returns:

A dict mapping series label → MetricDataPoint for each data point produced this tick. Return an empty dict if parsing fails. Single-series parsers return {self.chart: MetricDataPoint(value)}. Multi-series parsers may return multiple entries in the dict. Log-sourced parsers — which override parse_tick() — implement this as a trivial return {}.

class otto.monitor.parsers.PerCoreCpuParser

Bases: MetricParser

Per-core busy % from /proc/stat jiffies deltas.

Far cheaper than a second top run: busy% = 100 x (1 - Δ(idle+iowait) / Δtotal) per cpuN line. The aggregate cpu line is skipped — TopCpuParser already charts overall CPU. Jiffies ratios need no wall clock (time cancels), so state is plain previous counters.

y_title : str = 'Usage %'

Y-axis title shown to the left of the chart, e.g. ‘CPU’.

unit : str = '%'

Unit suffix for chart annotations, e.g. ‘%’, ‘MB’, ‘GB’. Empty string for dimensionless values.

command : str = 'cat /proc/stat'

The exact shell command whose output this parser handles.

tab : str = 'cpu'

Dashboard tab id this metric belongs to, e.g. ‘cpu’. Defaults to ‘metrics’.

tab_label : str = 'CPU'

Human-readable label for the tab button, e.g. ‘CPU’. Defaults to ‘Metrics’.

chart : str = 'Per-core CPU'

Chart group id. Series with the same chart value share one Plotly chart. Single-series parsers set this to their series label; multi-series parsers set it to a shared group name (e.g. 'Load').

parse(output: str, *, ctx: ParseContext) dict[str, MetricDataPoint]

Convert raw command output into one or more labelled data points.

Parameters:
  • output – The full stdout+stderr string returned by the remote command.

  • ctx – Tick-local input (e.g. the target host’s core count).

Returns:

A dict mapping series label → MetricDataPoint for each data point produced this tick. Return an empty dict if parsing fails. Single-series parsers return {self.chart: MetricDataPoint(value)}. Multi-series parsers may return multiple entries in the dict. Log-sourced parsers — which override parse_tick() — implement this as a trivial return {}.

class otto.monitor.parsers.ProcCountParser

Bases: MetricParser

Process counts: runnable/total from loadavg field 4, blocked from /proc/stat.

Cats both files in one command — the command string doubles as the parser registry key, so it must differ from LoadParser’s cat /proc/loadavg and PerCoreCpuParser’s cat /proc/stat; reading both also gets procs_blocked for free.

y_title : str = 'Count'

Y-axis title shown to the left of the chart, e.g. ‘CPU’.

unit : str = ''

Unit suffix for chart annotations, e.g. ‘%’, ‘MB’, ‘GB’. Empty string for dimensionless values.

command : str = 'cat /proc/loadavg /proc/stat'

The exact shell command whose output this parser handles.

tab : str = 'cpu'

Dashboard tab id this metric belongs to, e.g. ‘cpu’. Defaults to ‘metrics’.

tab_label : str = 'CPU'

Human-readable label for the tab button, e.g. ‘CPU’. Defaults to ‘Metrics’.

chart : str = 'Processes'

Chart group id. Series with the same chart value share one Plotly chart. Single-series parsers set this to their series label; multi-series parsers set it to a shared group name (e.g. 'Load').

parse(output: str, *, ctx: ParseContext) dict[str, MetricDataPoint]

Convert raw command output into one or more labelled data points.

Parameters:
  • output – The full stdout+stderr string returned by the remote command.

  • ctx – Tick-local input (e.g. the target host’s core count).

Returns:

A dict mapping series label → MetricDataPoint for each data point produced this tick. Return an empty dict if parsing fails. Single-series parsers return {self.chart: MetricDataPoint(value)}. Multi-series parsers may return multiple entries in the dict. Log-sourced parsers — which override parse_tick() — implement this as a trivial return {}.

otto.monitor.parsers.register_host_parsers(host_id: str | Pattern[str], parsers: dict[str, MetricParser]) None

Associate a custom parser dict with a host ID or a host-ID pattern.

A plain string is an exact host ID (the key in lab.hosts) — this is a total replacement for that host and may be re-registered freely. A compiled re.Pattern scopes the dict to every host whose id fullmatch``es one registration covers a family of hosts (e.g. ``re.compile(r"busybox-.*")). Precedence: exact id > pattern > project-level > defaults; two patterns matching the same host raise at resolution time. Call from an init module listed in .otto/settings.toml.

Hosts with no registered parsers automatically fall back to DEFAULT_PARSERS.

otto.monitor.parsers.register_parsers(parsers: Sequence[MetricParser]) None

Register project-level parsers that apply to every monitored host.

Call from an init module (listed in .otto/settings.toml). Each parser’s command becomes its key: a command matching a DEFAULT_PARSERS entry overrides that built-in; a new command extends the set. Per-host registrations (register_host_parsers) take total precedence for their host. Registering the same command twice raises.

otto.monitor.parsers.default_catalog() dict[str, MetricParser]

Return the parser catalog used when no per-host registration applies.

DEFAULT_PARSERS extended/overridden by project-level registrations.

otto.monitor.parsers.get_host_parsers(host_id: str) dict[str, MetricParser]

Return the parser dict for host_id: exact > pattern > project-level > defaults.

An exact registration wins outright for its host_id — it is a total replacement, not merged with anything else, and shadows any pattern. Otherwise a single pattern registration whose fullmatch matches wins the same way; two or more matching patterns raise (no import-order-dependent silent winner). With neither, project-level parsers (see register_parsers()) are merged over DEFAULT_PARSERS. Non-raising for unregistered hosts by design: no registration at all is normal.