# InfluxDB 3

MikroTik RouterOS kernel telemetry into InfluxDB 3 over /api/v3/write_lp: URL and token, delivery and drops, every measurement, and what Core refuses.

Source: https://docs.jmrp.io/mikroscope/sinks/influxdb/

`--influx` writes the merged timeline as InfluxDB [line
protocol](https://docs.influxdata.com/influxdb3/core/reference/line-protocol/) to InfluxDB 3's
[`/api/v3/write_lp`](https://docs.influxdata.com/influxdb3/core/write-data/http-api/v3-write-lp/).
It keeps every sample at the agent's rate, and it is the sink the InfluxDB dashboard
reads. Add `--grafana <url>` and the collector also creates the InfluxDB datasource and
publishes the dashboard at start; `mikroscope dashboards publish` with the same flags does it
once, without collecting ([Publish from the
collector](https://docs.jmrp.io/mikroscope/dashboards/import-and-check/#letting-the-collector-do-it-forward---grafana)).

## URL and token

```sh
export MIKROSCOPE_INFLUX_URL=http://host:8181
export MIKROSCOPE_INFLUX_DB=mikroscope
export MIKROSCOPE_INFLUX_TOKEN=…
mikroscope forward --influx "$MIKROSCOPE_INFLUX_URL" --influx-db "$MIKROSCOPE_INFLUX_DB" --host-tag router1
```

| Flag          | Default                 | What it sets                                                                                                                |
| ------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `--influx`    | `MIKROSCOPE_INFLUX_URL` | The server. The sink assembles the write URL, `http://host:8181/api/v3/write_lp?db=mikroscope&precision=nanosecond`.        |
| `--influx-db` | `MIKROSCOPE_INFLUX_DB`  | The database.                                                                                                               |
| none          | `MIKROSCOPE_INFLUX_TOKEN` | The token, sent as `Authorization: Bearer <token>`. It is read from the variable only, never from a flag.                 |

- A server with no database is refused at start: `--influx names a server but no
  database: pass --influx-db (MIKROSCOPE_INFLUX_DB)`.
- A `--influx` that carries a path is taken verbatim as the write URL. Use that
  form for InfluxDB 2's `/api/v2/write` ([not tested](https://docs.jmrp.io/mikroscope/about/status/#not-tested)).
  `--influx-db` is then ignored, and the database `forward --grafana` describes
  is read back from the URL's `db=`.

> **Quote the URL**
>
> When a full write URL lives in a file you `source`, quote it: `&` is a shell operator, and an
> unquoted `…?db=mikroscope&precision=nanosecond` is cut at the `&`.

## Delivery

- **One batch per second.** Every event is rendered into the current batch as it
  arrives; a goroutine moves it to the queue and posts it once a second, with a 10 s
  timeout per post.
- **A bounded queue.** `--queue-seconds` (default 60) × 64 KiB. Past it the oldest
  batch is dropped and counted; the newest is never dropped.
- **Backoff.** A failed post backs off 2 s, doubling to 60 s, and logs at most one line
  per minute. A reply that is not 2xx is an error, with the first 512 bytes of the
  body in that line.
- **No silent resend.** A request whose pooled connection turns out dead is reported as
  an error and retried by the sink, rather than re-sent by the HTTP client on its own
  ([why](https://docs.jmrp.io/mikroscope/about/status/#found-and-not-fixed)).

A 10 Hz kernel sample renders to about 1.2 KiB of line protocol
([measured](https://docs.jmrp.io/mikroscope/about/status/#campaign-line-protocol-2026-09-12)). So one second of budget holds about 50
samples — about 5 s of a 10 Hz backlog — and the default 60 s holds about 5 minutes.
Sizes above 10 Hz and with today's source set are [not measured](https://docs.jmrp.io/mikroscope/about/status/#not-tested).

Counters are in batches: `written` one batch InfluxDB accepted, `dropped` one batch
evicted, `errors` one failed attempt.

## Measurements

Every measurement is named `mikroscope_<subject>` and carries `host=<--host-tag>`.
Kernel-tier rows are stamped with the agent's wall clock in nanoseconds; the table
names the exceptions. Field lists are on [Store
schema](https://docs.jmrp.io/mikroscope/reference/measurements/).

### Kernel tier

| Measurement           | Tags                                     | Carries                                                                                                                                                    |
| --------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mikroscope_cpu`      | `cpu`                                    | the tick deltas per mode, `busy_ratio`, and the sample's real interval `dt_ns`                                                                             |
| `mikroscope_cpufreq`  | `cpu`                                    | `khz`, with the core's `max_khz` on the same row where it is published                                                                                     |
| `mikroscope_softnet`  | `cpu`                                    | `processed`, `dropped`, `time_squeeze` deltas                                                                                                              |
| `mikroscope_irq`      | `irq`, `name`                            | the interrupt count summed over CPUs, for the top-K sources                                                                                                |
| `mikroscope_irq_cpu`  | `irq`, `name`, `cpu`                     | the same count per CPU, rows with a non-zero delta only                                                                                                    |
| `mikroscope_softirq`  | `kind`, `cpu`                            | softirq deltas per vector and CPU, non-zero only                                                                                                           |
| `mikroscope_sample`   | —                                        | `seq`, `dt_ns`, `mono_ns`                                                                                                                                  |
| `mikroscope_stat`     | —                                        | `/proc/stat`'s deltas: `ctxt`, `intr`, `forks`, `irq_total`, `irq_err`                                                                                     |
| `mikroscope_mem`      | —                                        | `/proc/meminfo` levels, in `_kb`                                                                                                                           |
| `mikroscope_load`     | —                                        | load averages, `running`, `threads`, `procs_blocked`                                                                                                       |
| `mikroscope_vm`       | —                                        | `/proc/vmstat` counter deltas: faults, reclaim scans and steals, stalls, `oom_kill`, swap                                                                  |
| `mikroscope_vm_level` | —                                        | `/proc/vmstat` levels: `nr_free_pages`, `nr_dirty`, `nr_writeback`, slab pages                                                                             |
| `mikroscope_buddy`    | `node`, `zone`                           | free blocks per order and `free_pages`, on the samples the free lists changed                                                                              |
| `mikroscope_self`     | —                                        | the agent's own CPU, RSS, cgroup memory and `memory.max`; its cgroup events where cgroup2 was read; `resets`; `kmsg_dropped`                               |
| `mikroscope_psi`      | —                                        | stall microseconds, only where the kernel has PSI                                                                                                         |
| `mikroscope_thermal`  | `zone`                                   | `celsius`, with the zone's `critical_celsius` where it declares one                                                                                        |
| `mikroscope_slab`     | `cache`                                  | active objects, with `limit` where the kernel publishes one (`nf_conntrack`)                                                                               |
| `mikroscope_mtd`      | `device`, `partition`                    | flash ECC counters as read, with the partition's thresholds where published                                                                                |
| `mikroscope_flash`    | `device`                                 | YAFFS page writes, reads, erasures, GC; `bad_blocks` and `free_chunks` levels                                                                              |
| `mikroscope_disk`     | `device`                                 | block-device read and write deltas, `io_s`, `inflight`                                                                                                     |
| `mikroscope_perf`     | `counter`, `cpu`                         | PMU count, with `enabled_ns` and `running_ns` on the same row                                                                                              |
| `mikroscope_kmsg`     | `level`, `port`, `kind`, `label`, `role` | a count of kernel-log records per level, not the text; a record naming a port is counted per level, port and kind, with its `label` and `role` where known |
| `mikroscope_derived`  | —                                        | the derive stage's values beside the sample                                                                                                                |

Counters are written as unsigned deltas; levels as the absolute value the kernel
reported. They are separate measurements where one source has both (`mikroscope_vm`
against `mikroscope_vm_level`), because a delta is an event rate and a level is a
depth, and one measurement invites a dashboard to sum a level or rate a gauge.

A source the deployment cannot read writes no row, never a zero row: PSI rows exist
only where the kernel has PSI, and slab, kernel-log, MTD and PMU rows need
`privileged=yes`. The
level sources the agent stores on change appear only on the samples that carry them;
see [each source at its own floor](https://docs.jmrp.io/mikroscope/limits/source-floors/).

`running_ns` below `enabled_ns` on a `mikroscope_perf` row means that count is a
multiplexed, scaled-down estimate. Kernel-log text belongs in
[Loki](https://docs.jmrp.io/mikroscope/sinks/other/#loki); what a metrics store can answer is when the
router started producing warnings, on which port, and of what kind. `kind` is the
classification of a record that names a port: `link-up`, `link-down`, `stp-blocking`
and its siblings (`listening`, `learning`, `forwarding`, `disabled`), `own-address` —
the bridge received a frame with its own MAC as source address, the layer-2 loop
signature — or `other`. A record that names no port keeps the tagless shape and carries
`level` alone. A normal link-up is followed by `stp-blocking`, `stp-learning` and
`stp-forwarding` on its bridge port: four records, not four faults.

### API tier and derived

| Measurement                 | Tags                                           | Carries                                                                                      | Clock                     |
| --------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------- |
| `mikroscope_api_system`     | —                                              | `cpu_load`, `free_memory`, `total_memory`, `free_hdd`, `uptime_s`                            | collector + skew          |
| `mikroscope_api_core`       | `cpu`                                          | RouterOS's per-core `load`, `irq`, `disk` percent                                            | collector + skew          |
| `mikroscope_api_health`     | `name`                                         | each `/system/health` value                                                                  | collector + skew          |
| `mikroscope_api_iface`      | `interface`, `label`, `type`, `role`, `bridge` | `monitor-traffic` rates, and each loss rate the router returned                              | collector + skew          |
| `mikroscope_api_ifcounters` | `interface`, `label`, `type`, `role`, `bridge` | every per-port cumulative counter the router returned, `-` folded to `_`                     | collector + skew          |
| `mikroscope_api_ifinfo`     | `interface`, `label`, `type`, `role`, `bridge` | `default_name` and `mtu`: what each interface is, at start and every `--labels-every`        | collector + skew          |
| `mikroscope_api_conntrack`  | —                                              | `entries`, when `--conntrack-every` asks                                                     | collector + skew          |
| `mikroscope_derived_iface`  | `interface`                                    | the fast-path share of what the interface hands the CPU, beside the byte deltas it came from | collector + skew          |
| `mikroscope_gap`            | —                                              | `from`, `to`: the sequence range that never arrived                                          | collector, when noticed   |
| `mikroscope_detection`      | `rule`, `key`                                  | `value`, `threshold`, `seq`, `message`                                                       | the sample that raised it |
| `mikroscope_trigger`        | `cause`                                        | `id`, `seq`, `value`, `threshold`, `field`: the capture is on the agent                      | the agent, at the fire    |
| `mikroscope_device`         | `board`, `kernel`                              | `cores`, `privileged`, `cgroup`, `sources`, `hash`, and the ceilings the board publishes     | collector                 |
| `mikroscope_device_thermal` | `zone`                                         | `critical_celsius`, `polling_ms`                                                             | collector                 |
| `mikroscope_device_cpufreq` | `cpu`                                          | `cluster`, `min_khz`, `max_khz`, `governor`, `steps`                                         | collector                 |
| `mikroscope_device_cadence` | `source`, `reason`                             | `hz`                                                                                         | collector                 |
| `mikroscope_sampler`        | —                                              | `ticks`, `slipped`, `captures_held`, `capture_bytes`, `capture_budget_bytes`, `capture_served_bytes`| collector, from `GET /sampler`|
| `mikroscope_trigger_count`  | `condition`                                    | `fired`                                                                                      | collector, from `GET /sampler`|
| `mikroscope_trigger_suppressed`| `condition`, `reason`                          | `count`                                                                                      | collector, from `GET /sampler`|
| `mikroscope_capture_refused`| `reason`                                       | `count`                                                                                      | collector, from `GET /sampler`|

Every interface row carries what that interface is: `label` is its RouterOS comment,
`type` is RouterOS's own type (`ether`, `bridge`, `vlan`, `pppoe-out`, `wg`, `veth`,
`loopback`), `role` is its interface lists sorted and comma-joined (`WAN`, `LAN,VPN` —
a bridge member in no list of its own takes its bridge's lists, which is how RouterOS
firewall rules match it), and `bridge` is the bridge it is a port of. Each is omitted
rather than sent empty, so the series key of an interface without one stays stable, and
a panel can group the wire ports (`type=ether`) apart from the bridge's CPU side
(`type=bridge`), or WAN apart from LAN. A loss field is written only when the router
returned that key.

`mikroscope_api_ifinfo` carries those five tags for every interface the collector read,
in the `--interfaces` list or not, with `default_name` (the factory name of a physical
port, an empty string for a bridge, VLAN or tunnel) as a string field and `mtu` where
the router reports one above zero. It is written once before the first kernel pull and
again on every `--labels-every` re-read, 5 minutes by default, and it is the table a
panel joins to say what an interface is. `mikroscope_api_ifcounters` carries counters
only: `mtu`, `actual-mtu`, `l2mtu`, `max-l2mtu` and `sfp-shutdown-temperature` parse as integers but
are sizes and configuration, not counts, so they are not fields there; the MTU is in
`mikroscope_api_ifinfo`.

`mikroscope_derived_iface`'s `fp_rx_share` is the fast-path share of the traffic an
interface hands the CPU: `fp-rx-byte` over `driver-rx-byte` on a switch port, whose
`rx-byte` is the wire total, and over `rx-byte` on a software interface. It is not a
share of the wire — frames the switch chip forwards in hardware are in neither number.
`fp_tx_share` is withheld, and the `tx_bytes` denominator beside it written as 0, while
the cumulative `fp-tx-byte` is 0, as it can be on every interface of a router after
hundreds of GB transmitted ([observed](https://docs.jmrp.io/mikroscope/about/status/#verified-fp-tx-zero)).

## InfluxDB 3 Core limits

- **A node holds at most five databases**, the limit [InfluxData documents for
  Core](https://docs.influxdata.com/influxdb3/core/admin/databases/). A sixth write fails
  with `422: would exceed limit of 5 databases`. The sink backs off and keeps trying, so
  the symptom is a rising `errors` count, not a failed run.
- **Every query must be time-bounded.**
- **A column's type is immutable once written.**
- **A column exists only once a row has carried it.** `mikroscope_kmsg` has no `kind`
  column until the first kernel-log record naming a port is written, and a query that
  filters on it before then fails at planning time — which is why the SQL forms of the
  layer-2 loop and link-down alert rules need a store that has already held one classified
  port record. `mikroscope_api_ifcounters` has only the counters the router has returned:
  `bridge`, `rx_packet`, `tx_unicast` and `tx_broadcast`, which the bridge-port-dark rule
  reads, exist only once the API tier has written port counters, and a typed-error column
  that the port-errors rule sums, such as `rx_jabber` or `tx_late_collision`, only if the
  router has ever reported that counter.
- **A negated tag inside an OR can return nothing, silently.**
  `NOT (port='ether2' AND kind IN (…))` over `mikroscope_kmsg` can return 0 rows where
  rows match, and so can its De Morgan form and the explicit OR; filtering
  `port='ether2'` outside the OR as well returns them
  ([observed](https://docs.jmrp.io/mikroscope/about/status/#found-and-not-fixed)). Filter positively, and check any
  empty answer with a plain `count(*)`. The shipped panels and rules do not use the
  pattern.

`forward --grafana` and `dashboards publish` build the Grafana datasource, with the sink's token,
at the address the collector writes to unless `--grafana-datasource-url` names the one Grafana
reaches. One made by hand needs two secure fields, not one ([InfluxDB 3
datasource](https://docs.jmrp.io/mikroscope/dashboards/import-and-check/#the-influxdb-3-datasource)).

The InfluxDB 3 editions and versions the sink has written to are under [Feature status](https://docs.jmrp.io/mikroscope/about/status/#what-works-end-to-end).

## See also

- [Store schema](https://docs.jmrp.io/mikroscope/reference/measurements/): every field of every
  measurement, with its unit.
- [Set up in Grafana](https://docs.jmrp.io/mikroscope/dashboards/import-and-check/): the InfluxDB dashboard, its
  datasource and the store probe.
- [Run the collector](https://docs.jmrp.io/mikroscope/sinks/): the queue, the backoff and the counters every queued sink
  shares.
- [Device info](https://docs.jmrp.io/mikroscope/sinks/device-info/): what the four `mikroscope_device*`
  measurements hold.
