# Esquema de los almacenes

Cada medida de InfluxDB y cada tabla SQL que escribe mikroscope, con etiquetas, campos, columnas, claves, unidades y si cada valor es un delta o un nivel.

Source: https://docs.jmrp.io/mikroscope/es/reference/measurements/

Lo que contiene una fila en el almacén: la medida de InfluxDB o la tabla SQL,
las etiquetas o columnas de clave que la identifican, los campos que lleva y sus
unidades, y si cada valor es un delta sobre el intervalo de la muestra o un
nivel. Leído de [`internal/sinks/influx.go`](https://github.com/jmrplens/mikroscope/blob/main/internal/sinks/influx.go), [`internal/sinks/device.go`](https://github.com/jmrplens/mikroscope/blob/main/internal/sinks/device.go) y del
fichero [`internal/sinks/sql.go`](https://github.com/jmrplens/mikroscope/blob/main/internal/sinks/sql.go). Los dos almacenes no guardan el mismo conjunto:
[InfluxDB frente a SQL](https://docs.jmrp.io/mikroscope/es/reference/measurements/#dónde-difieren-los-dos-almacenes) enumera las diferencias.

## Destinos que escriben

- `forward --influx` envía el line protocol de InfluxDB de abajo.
- `forward --stdout lp` y `forward --telegraf` renderizan con el mismo
  codificador, así que escriben las mismas medidas y líneas. El orden de las
  líneas de `mikroscope_api_health`, `mikroscope_softirq` y `mikroscope_slab`
  dentro de un lote no es estable: el codificador recorre mapas de Go para
  ellas, y el fichero [`internal/sinks/telegraf.go`](https://github.com/jmrplens/mikroscope/blob/main/internal/sinks/telegraf.go) registra 12 renderizados de un mapa de
  8 nombres que dieron 7 órdenes distintos.
- `forward --sql` escribe sus propias tablas de PostgreSQL, que se enumeran más
  abajo.

Loki, OTLP, Graphite y Elasticsearch dan otra forma a la misma línea de tiempo;
están en [el fichero y los demás destinos](https://docs.jmrp.io/mikroscope/es/sinks/other/).

## Convenciones

- **Cada fila lleva `host`**, el valor de `--host-tag` (`router` por defecto),
  como etiqueta en InfluxDB y como columna `host` en SQL.
- **Las marcas de tiempo son el reloj de pared del agente**, en ns para
  InfluxDB y como `TIMESTAMPTZ` para SQL. Una fila de la capa de la API se
  sella con el reloj del colector corregido por el desfase medido, así que las
  dos capas comparten la hora del agente. Los huecos y las filas de datos del
  equipo no tienen reloj propio y llevan el del colector en el momento en que
  los procesó.
- **Los contadores son deltas desde la muestra anterior**, no totales
  acumulados, y los niveles son el valor tal como se leyó. Las tablas de abajo
  dicen cuál es cuál; nunca sumes un nivel.
- **Ausente es ausente, en las medidas por fuente.** `psi`, `thermal`, `slab`,
  `flash`, `mtd`, `disk`, `perf`, `kmsg`, `buddy` e `irq` no escriben ninguna
  fila para una fuente que el kernel no tiene o que el despliegue no puede
  leer. Un techo que el equipo no publica es un campo que falta en InfluxDB y
  `NULL` en SQL, nunca 0. La excepción: en InfluxDB `mikroscope_mem`,
  `mikroscope_load`, `mikroscope_vm`, `mikroscope_vm_level`,
  `mikroscope_stat`, `mikroscope_sample` y `mikroscope_self`, y en SQL
  `mikroscope_mem`, `mikroscope_load`, `mikroscope_vm`, `mikroscope_vm_level`,
  `mikroscope_stat`, `mikroscope_sample` y `mikroscope_self`, se escriben en
  cada muestra y marcan 0 para una fuente que no se pudo leer.
- **Una dimensión, un nombre.** Un procesador es `cpu` en todas las etiquetas y
  columnas.
- **La unidad va en el nombre del campo**: `_kb`, `_khz`, `_ns`, `_us`, `_s`,
  `_ms`, `_bps`, `_pps`. Las temperaturas son `celsius` junto a
  `critical_celsius`; el tiempo ocupado de un dispositivo de bloques es `io_s`,
  convertido una sola vez desde los milisegundos del kernel.

En [line protocol](https://docs.influxdata.com/influxdb3/core/reference/line-protocol/), una `u`
final es un entero sin signo, `i` uno con signo, un número a secas un float,
`true`/`false` un booleano, y un valor entre comillas una cadena.

## Capa del kernel en InfluxDB

Un juego de filas por muestra del kernel, todas en el `wall_ns` de esa muestra.

### CPU, interrupciones y muestra

| Medida               | Etiquetas            | Campos                                                                                                                                        | Tipo                                                                              |
| -------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `mikroscope_cpu`     | `cpu`                | `user`, `nice`, `system`, `idle`, `iowait`, `irq`, `softirq`, `steal` (u, ticks de `USER_HZ`); `busy_ratio` (float, 4 decimales); `dt_ns` (i) | los ticks son deltas; `busy_ratio` es ticks ocupados sobre `dt_ns`, con tope en 1 |
| `mikroscope_cpufreq` | `cpu`                | `khz` (u); `max_khz` (u) donde el núcleo publica un techo                                                                                     | nivel, en las muestras que lo llevan: al cambiar o en el latido de 60 s           |
| `mikroscope_stat`    | ninguna              | `ctxt`, `intr`, `forks`, `irq_total`, `irq_err` (u)                                                                                           | deltas                                                                            |
| `mikroscope_softnet` | `cpu`                | `processed`, `dropped`, `time_squeeze` (u)                                                                                                    | deltas                                                                            |
| `mikroscope_softirq` | `kind`, `cpu`        | `count` (u)                                                                                                                                   | delta; solo los pares (kind, cpu) distintos de cero                               |
| `mikroscope_irq`     | `irq`, `name`        | `count` (u), sumado sobre las CPU                                                                                                             | delta; solo las líneas del top-K de la muestra                                    |
| `mikroscope_irq_cpu` | `irq`, `name`, `cpu` | `count` (u)                                                                                                                                   | delta; solo las CPU distintas de cero                                             |
| `mikroscope_sample`  | ninguna              | `seq` (u), `dt_ns` (i), `mono_ns` (i)                                                                                                         | una fila por muestra                                                              |
| `mikroscope_psi`     | ninguna              | `cpu_some_us`, `mem_some_us`, `mem_full_us`, `io_some_us`, `io_full_us` (u)                                                                   | deltas; solo en un kernel con PSI                                                 |

`busy_ratio` es la única proporción de la capa del kernel, y la calcula el
destino, no la envía el agente. Los ticks que tiene al lado son lo que hay que
dividir cuando importa la ventana: suma `user + nice + system + irq + softirq + steal`
y divide entre `dt_ns / 1e9 × 100`.

### Memoria y carga

| Medida                | Etiquetas      | Campos                                                                                                                                                                                                                                                                                  | Tipo                               |
| --------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `mikroscope_mem`      | ninguna        | `total_kb`, `free_kb`, `available_kb`, `cached_kb`, `buffers_kb`, `slab_kb`, `sreclaimable_kb`, `sunreclaim_kb`, `anon_kb`, `mapped_kb`, `dirty_kb`, `writeback_kb`, `kernel_stack_kb`, `page_tables_kb`, `committed_kb`, `commit_limit_kb`, `shmem_kb`, `active_kb`, `inactive_kb` (u) | niveles                            |
| `mikroscope_load`     | ninguna        | `load1`, `load5`, `load15` (float, 2 decimales); `running`, `threads`, `procs_blocked` (u)                                                                                                                                                                                              | niveles                            |
| `mikroscope_vm`       | ninguna        | `pgfault`, `pgmajfault`, `pgscan_kswapd`, `pgscan_direct`, `pgsteal_kswapd`, `pgsteal_direct`, `pgalloc`, `pgfree`, `allocstall`, `compact_stall`, `oom_kill`, `pswpin`, `pswpout` (u)                                                                                                  | deltas                             |
| `mikroscope_vm_level` | ninguna        | `nr_free_pages`, `nr_dirty`, `nr_writeback`, `nr_slab_reclaimable`, `nr_slab_unreclaimable` (u, páginas)                                                                                                                                                                                | niveles                            |
| `mikroscope_buddy`    | `node`, `zone` | `free_pages` (u, la suma sobre los órdenes en páginas); `order_0` … `order_N` (u, bloques libres de 2^N páginas)                                                                                                                                                                        | niveles, al cambiar o en el latido |

`mikroscope_vm` y `mikroscope_vm_level` son medidas separadas porque un delta
de `pgscan` es una tasa de eventos y `nr_dirty` es una profundidad.

### Sensores, cachés slab y flash

| Medida               | Etiquetas             | Campos                                                                                                                      | Tipo                                                                                                          |
| -------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `mikroscope_thermal` | `zone`                | `celsius` (float, 3 decimales); `critical_celsius` (float) donde la zona declara un disparo crítico                         | nivel, a la cadencia declarada de la zona                                                                     |
| `mikroscope_slab`    | `cache`               | `active` (u, objetos); `limit` (u) para las cachés con un techo publicado, hoy `nf_conntrack`                               | nivel; [requiere `privileged=yes`](https://docs.jmrp.io/mikroscope/es/limits/privileged/); se guarda al cambiar con un suelo de presupuesto                                   |
| `mikroscope_flash`   | `device`              | `page_writes`, `page_reads`, `erasures`, `gc_copies`, `gcs` (u); `bad_blocks`, `free_chunks` (u)                            | los cinco primeros deltas, los dos últimos niveles; solo cuando algo cambió                                   |
| `mikroscope_mtd`     | `device`, `partition` | `corrected_bits`, `ecc_failures`, `bad_blocks`, `bbt_blocks` (u); `bitflip_threshold`, `ecc_strength` (u) donde se publican | niveles: los recuentos del kernel desde el arranque tal como se leen, nunca diferenciados; [requiere `privileged=yes`](https://docs.jmrp.io/mikroscope/es/limits/privileged/) |
| `mikroscope_disk`    | `device`              | `reads`, `read_sectors`, `writes`, `write_sectors` (u); `io_s` (float, 3 decimales); `inflight` (u)                         | `inflight` es un nivel, el resto deltas; un dispositivo inactivo no escribe ninguna fila                      |

`limit` es una palabra reservada de SQL, así que una consulta SQL de InfluxDB 3
la pone entre comillas dobles, como hacen el panel de la tabla de conexiones
que se distribuye y la alerta `mikroscope-conntrack-near-limit`:
`max("limit")`. El panel de frecuencia de CPU
que se distribuye pone igual entre comillas dobles el campo `cluster` de
`mikroscope_device_cpufreq` ([`internal/dashboards/panels_p5.go`](https://github.com/jmrplens/mikroscope/blob/main/internal/dashboards/panels_p5.go)); ninguna
fuente del repositorio dice por qué.

### Observador, PMU y log del kernel

| Medida            | Etiquetas                                | Campos                                                                                                                                                                                         | Tipo                                                                                                                                                                                                                   |
| ----------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mikroscope_self` | ninguna                                  | `cpu_us` (u); `rss`, `cgroup_mem` (u, bytes); `cgroup_mem_max` (u) en las muestras que lo llevan; `throttled`, `throttled_us`, `oom_kill` (u) con cgroup2; `wake_ns`, `read_ns` (i) cuando la muestra lleva alguno; `resets`, `kmsg_dropped`, `seq` (u) | `cpu_us`, `throttled`, `throttled_us`, `oom_kill`, `resets`, `kmsg_dropped` son deltas; `rss`, `cgroup_mem`, `cgroup_mem_max` niveles; `wake_ns`, `read_ns` la temporización de este tick                                                                                  |
| `mikroscope_perf` | `counter`, `cpu`                         | `count` (u); `enabled_ns`, `running_ns` (u) cuando el kernel los informó                                                                                                                       | deltas; [requiere `privileged=yes`](https://docs.jmrp.io/mikroscope/es/limits/privileged/); `running_ns` por debajo de `enabled_ns` significa que el recuento está multiplexado                                                                                                        |
| `mikroscope_kmsg` | `level`, `port`, `kind`, `label`, `role` | `count` (u), registros en esta muestra                                                                                                                                                         | por (level, port, kind); un registro que no nombra ningún puerto no lleva ninguna de `port`, `kind`, `label` ni `role`; `label` y `role` solo donde el inventario los conoce; solo las combinaciones distintas de cero |

`resets` cuenta los contadores monotónicos que retrocedieron en este tick sin
un desbordamiento de 32 bits, y `kmsg_dropped` los episodios de pérdida del log del kernel, no registros: uno por un tick que llegó al tope de 64 registros, uno por cada desbordamiento del búfer circular del kernel, que puede suponer muchos registros. Los dos suelen ser 0; cualquiera de ellos
distinto de cero significa que no hay que fiarse del tick como tasa. El texto
de los registros del log del kernel no está en InfluxDB; `port` es el nombre
actual de la interfaz en RouterOS cuando el colector tiene el inventario de la
capa de la API, y si no el nombre de fábrica de la placa, o el nombre del
kernel en una placa sin tabla de puertos.

`kind` dice qué le pasó a ese puerto: `link-up`, `link-down`, `stp-blocking`,
`stp-listening`, `stp-learning`, `stp-forwarding`, `stp-disabled`,
`own-address` —el puente recibió una trama con su propia MAC como dirección de
origen, la firma del bucle de capa 2— u `other`. Un enlace que sube escribe
cuatro registros, no cuatro fallos: el link-up y las tres transiciones de STP
de su puerto del puente. `label` y `role` son el comentario de RouterOS del
puerto y sus listas de interfaces, y se etiquetan solo donde el inventario los
tiene. Como una consulta que nombra una columna que el almacén nunca ha
recibido falla al planificarse, `kind` solo se puede consultar después de
escribir el primer registro de puerto con él.

## Capa de la API en InfluxDB

Un juego por consulta a la API, sellado con la hora del agente.

| Medida                      | Etiquetas                                      | Campos                                                                                                                                                | Tipo                                                                                                                                                 |
| --------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mikroscope_api_system`     | ninguna                                        | `cpu_load` (u, porcentaje); `free_memory`, `total_memory`, `free_hdd` (u, bytes); `uptime_s` (u)                                                      | la vista de un segundo de RouterOS, tal como la informa                                                                                              |
| `mikroscope_api_core`       | `cpu`                                          | `load`, `irq`, `disk` (u, porcentaje)                                                                                                                 | tal como lo informa `/system/resource/cpu`                                                                                                           |
| `mikroscope_api_health`     | `name`                                         | `value` (float)                                                                                                                                       | tal como lo informa `/system/health`                                                                                                                 |
| `mikroscope_api_iface`      | `interface`, `label`, `type`, `role`, `bridge` | `rx_bps`, `tx_bps`, `rx_pps`, `tx_pps` (u); `rx_drops`, `tx_drops`, `tx_queue_drops`, `rx_errors`, `tx_errors` (u) solo cuando el router los devolvió | tasas instantáneas de `monitor-traffic`; las cuatro etiquetas del inventario se omiten una a una cuando están vacías                                 |
| `mikroscope_api_ifcounters` | `interface`, `label`, `type`, `role`, `bridge` | un campo por cada contador numérico que devolvió RouterOS, con `-` convertido en `_` (`rx_overflow`, `fp_rx_byte`, `link_downs` …) (u)                | acumulado desde el arranque o desde el último reinicio del puerto; todas las interfaces, en las consultas de `--counters-every`                      |
| `mikroscope_api_ifinfo`     | `interface`, `label`, `type`, `role`, `bridge` | `default_name` (string, el nombre de fábrica de un puerto físico, `""` en una interfaz que no lo tiene); `mtu` (u) solo por encima de 0               | qué es cada interfaz; se escribe una vez antes de la primera petición al kernel y otra vez en cada relectura de `--labels-every`, nunca por consulta |
| `mikroscope_api_conntrack`  | ninguna                                        | `entries` (u)                                                                                                                                         | solo en las consultas que lo pidieron, cada `--conntrack-every`                                                                                      |

Los campos de `mikroscope_api_ifcounters` varían por puerto y por placa: un
contador que un puerto no informa no es un campo de su fila. `mtu`,
`actual-mtu`, `l2mtu`, `max-l2mtu` y `sfp-shutdown-temperature` son enteros que
no cuentan nada, así que no son campos ahí; la MTU es el campo `mtu` de
`mikroscope_api_ifinfo`.

El inventario que hay detrás de `label`, `type`, `role` y `bridge` son tres
lecturas de configuración: `/interface/print`,
`/interface/list/member/print` e `/interface/bridge/port/print`. `label` es el
comentario de RouterOS, `type` el tipo propio de RouterOS (`ether`, `bridge`,
`vlan`, `pppoe-out`, `wg`, `veth`, `loopback`), `role` las listas de interfaces
a las que pertenece la interfaz, ordenadas y unidas por comas (`WAN`,
`LAN,VPN`), donde un miembro de un puente que no está en ninguna lista propia
toma las listas de su puente, y `bridge` el puente del que es puerto. Una
lectura de `/interface` que falla conserva el inventario que ya se tenía y
escribe un registro de error en su lugar; las lecturas de listas y de puentes
son de mejor esfuerzo.

`type` es lo que impide sumar dos filas, porque RouterOS cuenta cosas
distintas en interfaces distintas. Un puerto del conmutador cuenta su cable,
incluidas las tramas que el chip conmutó por hardware; un puente cuenta su lado
de la CPU; una VLAN o un PPPoE cuentan lo que la CPU envió y recibió. La fila
`ether` y la fila `bridge` son planos distintos, ninguno subconjunto del otro, y
sumarlos no cuenta nada que exista.

## Derivados y equipo en InfluxDB

| Medida                      | Etiquetas          | Campos                                                                                                                                                                                          | Sellada con                                                      |
| --------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `mikroscope_derived`        | ninguna            | `mem_pressure` (i, 0–4); `burst`, `suspect` (bool); `cycles_per_packet`, `instructions_per_packet`, `cache_misses_per_packet`, `packets_per_irq` (float, 3 decimales) cuando se pueden calcular | la hora de la muestra del kernel                                 |
| `mikroscope_derived_iface`  | `interface`        | `rx_bytes`, `fp_rx_bytes`, `tx_bytes`, `fp_tx_bytes` (u, deltas desde la consulta de contadores anterior); `fp_rx_share`, `fp_tx_share` (float, 4 decimales) cuando se movieron bytes           | la hora de la muestra de la API                                  |
| `mikroscope_detection`      | `rule`, `key`      | `value`, `threshold` (float); `seq` (u); `message` (string)                                                                                                                                     | la hora de la detección; `key` se omite cuando la regla no tiene |
| `mikroscope_trigger`        | `cause`            | `id`, `seq` (u); `value`, `threshold` (float); `field` (string)                                                                                                                                 | el reloj de pared del agente en el disparo                       |
| `mikroscope_gap`            | ninguna            | `from`, `to` (u, números de secuencia que ya no están en el anillo)                                                                                                                             | el reloj del colector                                            |
| `mikroscope_device`         | `board`, `kernel`  | `cores` (i); `privileged`, `cgroup` (bool); `sources`, `hash` (string); `conntrack_max`, `cgroup_mem_max` (u) donde se publican; `ports_from` (string) donde se conoce                          | el reloj del colector                                            |
| `mikroscope_device_thermal` | `zone`             | `critical_celsius` (float); `polling_ms` (i)                                                                                                                                                    | el reloj del colector                                            |
| `mikroscope_device_cpufreq` | `cpu`              | `cluster` (i); `min_khz`, `max_khz` (u); `governor` (string); `steps` (string, kHz separados por espacios)                                                                                      | el reloj del colector                                            |
| `mikroscope_device_cadence` | `source`, `reason` | `hz` (float)                                                                                                                                                                                    | el reloj del colector                                            |
| `mikroscope_sampler` | ninguna | `ticks`, `slipped` (u); `captures_held`, `capture_bytes`, `capture_budget_bytes` (i) y `capture_served_bytes` (u) cuando las capturas están activadas | el reloj del colector |
| `mikroscope_trigger_count` | `condition` | `fired` (u) | el reloj del colector; solo cuando las capturas están activadas |
| `mikroscope_trigger_suppressed` | `condition`, `reason` | `count` (u) | el reloj del colector; solo cuando las capturas están activadas |
| `mikroscope_capture_refused` | `reason` | `count` (u) | el reloj del colector; solo cuando las capturas están activadas |

`fp_rx_share` es la fracción del tráfico que una interfaz entrega a la CPU que
pasó por el fast path, no una fracción del cable: `fp-rx-byte` sobre
`driver-rx-byte` en un puerto del conmutador, cuyo `rx-byte` es el total del
cable, y sobre `rx-byte` en una interfaz por software, que no tiene contadores
de driver. Las tramas conmutadas por hardware no están en ninguno de los dos
números. `rx_bytes` y `tx_bytes` son los denominadores de la fracción, no
totales del cable. Mientras el `fp-tx-byte` acumulado de una interfaz sea 0, la
fracción de tx y sus deltas se retienen: `fp_tx_share` no está y `tx_bytes` y
`fp_tx_bytes` valen 0. [Valores
derivados](https://docs.jmrp.io/mikroscope/es/sinks/derive/#junto-a-cada-lectura-de-contadores) dice
lo que da la fracción en los puertos del conmutador y en las interfaces por
software.

- **Los valores derivados** se escriben junto a sus entradas y nunca en lugar de ellas, así que el
  almacén puede volver a calcularlos. `suspect` marca una muestra con un reinicio de contador, en la
  que una cifra por paquete sería una cota inferior, así que los campos por paquete se dejan fuera.
- **`board` y `kernel`** valen `unknown` cuando el agente no pudo establecerlas.
- **Las cuatro medidas `mikroscope_device*`** se escriben cuando arranca `forward`, de nuevo antes de
  un minuto después de que cambie el hash de capacidades del agente (el colector vuelve a leer
  `/healthz` una vez por minuto), y si no, se repiten cada cinco minutos para que cualquier ventana
  de un panel las contenga. Un transporte que no puede traer `/capabilities` no escribe ninguna fila
  del equipo.
- **Las cuatro últimas filas**, `mikroscope_sampler` y las tres medidas de capturas, son el
  [`/sampler`](https://docs.jmrp.io/mikroscope/es/reference/http/#get-sampler) del agente, leído cuando arranca
  `forward` y después una vez por minuto. `ticks`, `slipped`, `capture_served_bytes`, `fired` y los
  dos campos `count` son totales acumulados desde que arrancó el agente, no deltas: una tasa es la
  diferencia entre dos filas, y un reinicio del agente los devuelve a 0. `captures_held`,
  `capture_bytes` y `capture_budget_bytes` son niveles. Los campos de capturas y las tres medidas de
  capturas solo se escriben cuando las capturas están activadas (`CAPTURE_MB` por encima de 0).

Qué significa cada
valor y cada regla está en [Valores
derivados](https://docs.jmrp.io/mikroscope/es/sinks/derive/), [Reglas de
detección](https://docs.jmrp.io/mikroscope/es/sinks/detections/) y [Datos del
equipo](https://docs.jmrp.io/mikroscope/es/sinks/device-info/).

## Requisitos de InfluxDB 3 Core

- Un nodo guarda como mucho cinco bases de datos. Una escritura a una sexta
  falla con `422`; el destino espera y sigue intentándolo.
- Toda consulta tiene que estar acotada en el tiempo.
- El tipo de una columna es inmutable una vez escrito.
- Una consulta que nombra un campo que el almacén nunca ha recibido falla al
  planificarse, exactamente igual que una tabla que falta:
  `No field named limit. Valid fields are …`.
- La URL de escritura contiene `&`; entrecomíllala cuando vive en un fichero
  que cargas con `source`.

El destino envía un lote por segundo y encola hasta `--queue-seconds` ×
64 KiB de lotes, un presupuesto dimensionado para unos 1,2 KiB por muestra a
10 Hz, y después descarta el más antiguo. Un lote de más de 64 KiB, a 50 o
100 Hz o con las fuentes privileged, hace que quepan menos de
`--queue-seconds` lotes; [InfluxDB 3](https://docs.jmrp.io/mikroscope/es/sinks/influxdb/)
cubre la entrega.

## Fichero de script SQL

`--sql out.sql` escribe texto de PostgreSQL: una cabecera y luego un `INSERT`
por registro. No hay driver de base de datos; la conexión es cosa de `psql`.

```sh
mikroscope forward --sql out.sql --for 10m && psql -f out.sql
```

- La cabecera es `SET standard_conforming_strings = on;` y un
  `CREATE TABLE IF NOT EXISTS` por tabla, así que cada fichero vuelve a
  declarar el esquema sin daño. `--sql-hypertable` añade un
  `SELECT create_hypertable('<table>', 'time', if_not_exists => TRUE);` por
  tabla, con la firma documentada de TimescaleDB 2.x
  ([no ejecutado contra TimescaleDB](https://docs.jmrp.io/mikroscope/es/about/status/#not-tested)).
- La clave primaria de cada tabla empieza por `time, host`, y cada `INSERT`
  termina en `ON CONFLICT DO NOTHING`: aplicar dos veces el mismo fichero no
  hace nada, porque una fila es un instante inmutable, nunca un total que una
  pasada posterior revisa.
- `TIMESTAMPTZ` guarda microsegundos, así que los tres últimos dígitos de los
  nanosegundos del agente se pierden al redondear; dos muestras separadas por
  menos de 1 µs colisionarían en la clave.
- Un float que es NaN o infinito se escribe `NULL`. Un byte NUL en una cadena
  se descarta, y los bytes que no son UTF-8 válido pasan a ser U+FFFD, porque
  PostgreSQL rechaza las dos cosas.
- `--sql -` escribe en la salida estándar. Como este destino no tiene cola, un
  `psql` que se queda atrás bloquea el bucle de peticiones del colector en vez
  de descartar ([no medido](https://docs.jmrp.io/mikroscope/es/about/status/#not-tested)).
- El destino cuenta los eventos que escribió en el fichero, no las filas que
  guardó el servidor.

`dt_ns` está en `mikroscope_sample`, una fila por muestra, y en cada fila de
`mikroscope_cpu`. Una tasa sobre cualquier otra tabla de deltas se une con
`mikroscope_sample` (o con `mikroscope_cpu`) por `(time, host)` para obtener el
intervalo real en vez de suponer el período nominal.

## Tablas SQL del kernel

| Tabla                | Clave primaria                        | Columnas después de `time`                                                                                                                                                                                            |
| -------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mikroscope_cpu`     | `time, host, cpu`                     | `host`, `cpu`, `user_ticks`, `nice_ticks`, `system_ticks`, `idle_ticks`, `iowait_ticks`, `irq_ticks`, `softirq_ticks`, `steal_ticks` (deltas), `busy_ratio`, `dt_ns`                                                  |
| `mikroscope_cpufreq` | `time, host, cpu`                     | `host`, `cpu`, `khz`, `max_khz` (niveles, en las muestras que los llevan; `max_khz` `NULL` si no se publica) |
| `mikroscope_softnet` | `time, host, cpu`                     | `host`, `cpu`, `processed`, `dropped`, `time_squeeze` (deltas)                                                                                                                                                        |
| `mikroscope_softirq` | `time, host, kind, cpu`               | `host`, `kind`, `cpu`, `count` (delta; solo los pares (kind, cpu) distintos de cero) |
| `mikroscope_irq`     | `time, host, irq`                     | `host`, `irq`, `name`, `count` (delta, sumado sobre las CPU; líneas del top-K)                                                                                                                                        |
| `mikroscope_irq_cpu` | `time, host, irq, cpu`                | `host`, `irq`, `name`, `cpu`, `count` (delta; solo las CPU distintas de cero) |
| `mikroscope_mem`     | `time, host`                          | `host`, `free_kb`, `available_kb`, `cached_kb`, `slab_kb`, `sunreclaim_kb`, `total_kb`, `buffers_kb`, `sreclaimable_kb`, `anon_kb`, `mapped_kb`, `dirty_kb`, `writeback_kb`, `kernel_stack_kb`, `page_tables_kb`, `committed_kb`, `commit_limit_kb`, `shmem_kb`, `active_kb`, `inactive_kb` (niveles) |
| `mikroscope_load`    | `time, host`                          | `host`, `load1`, `load5`, `load15`, `running`, `threads`, `procs_blocked` (niveles)                                                                                                                                   |
| `mikroscope_stat`    | `time, host`                          | `host`, `ctxt`, `intr`, `forks`, `irq_total`, `irq_err`, `pgfault`, `pgmajfault` (deltas)                                                                                                                             |
| `mikroscope_vm`      | `time, host`                          | `host`, `pgfault`, `pgmajfault`, `pgscan_kswapd`, `pgscan_direct`, `pgsteal_kswapd`, `pgsteal_direct`, `pgalloc`, `pgfree`, `allocstall`, `compact_stall`, `oom_kill`, `pswpin`, `pswpout` (deltas) |
| `mikroscope_vm_level` | `time, host`                         | `host`, `nr_free_pages`, `nr_dirty`, `nr_writeback`, `nr_slab_reclaimable`, `nr_slab_unreclaimable` (niveles, páginas) |
| `mikroscope_self`    | `time, host`                          | `host`, `cpu_us` (delta), `rss`, `cgroup_mem` (niveles), `throttled`, `throttled_us`, `oom_kill` (deltas, `NULL` sin cgroup2), `resets`, `kmsg_dropped` (deltas), `seq`, `wake_ns`, `read_ns` (la temporización de este tick, 0 cuando la muestra no lleva ninguna) |
| `mikroscope_sample`  | `time, host`                          | `host`, `seq`, `dt_ns`, `mono_ns`: una fila por muestra |
| `mikroscope_buddy`   | `time, host, node, zone, block_order` | `host`, `node`, `zone`, `block_order`, `free_blocks` (nivel; una fila por zona y orden, y `order` está reservada)                                                                                                     |
| `mikroscope_mtd`     | `time, host, device`                  | `host`, `device`, `partition`, `corrected_bits`, `ecc_failures`, `bad_blocks`, `bbt_blocks`, `bitflip_threshold`, `ecc_strength` (niveles; umbrales `NULL` si no se publican)                                         |
| `mikroscope_psi`     | `time, host`                          | `host`, `cpu_some_us`, `mem_some_us`, `mem_full_us`, `io_some_us`, `io_full_us` (deltas)                                                                                                                              |
| `mikroscope_thermal` | `time, host, zone`                    | `host`, `zone`, `celsius`, `critical_celsius` (`NULL` si no se publica)                                                                                                                                               |
| `mikroscope_slab`    | `time, host, cache`                   | `host`, `cache`, `active_objs`, `limit_objs` (`NULL` para las cachés sin techo publicado)                                                                                                                             |
| `mikroscope_disk`    | `time, host, device`                  | `host`, `device`, `reads`, `read_sectors`, `writes`, `write_sectors`, `io_s` (deltas), `inflight` (nivel)                                                                                                             |
| `mikroscope_flash`   | `time, host, device`                  | `host`, `device`, `page_writes`, `page_reads`, `erasures`, `gc_copies`, `gcs` (deltas), `bad_blocks`, `free_chunks` (niveles)                                                                                         |
| `mikroscope_perf`    | `time, host, counter, cpu`            | `host`, `counter`, `cpu`, `count` (delta), `enabled_ns`, `running_ns` (`NULL` donde el kernel no los informó; [requiere `privileged=yes`](https://docs.jmrp.io/mikroscope/es/limits/privileged/)) |
| `mikroscope_event`   | `time, host, kernel_seq`              | `host`, `level`, `facility`, `kernel_seq`, `time_usec` (µs desde el arranque, el reloj monotónico del kernel, no el de `time`), `message`, `port`, `kind` (los dos `NULL` en un registro que no nombra ningún puerto) |

Los nombres de columna evitan tener que entrecomillar en PostgreSQL: las
columnas de ticks son `user_ticks` y compañía porque `user` está reservada. Las
columnas de contadores son `BIGINT`, ya que PostgreSQL no tiene un tipo de 64
bits sin signo y ningún delta que produzca un router en una muestra se acerca a
2^63.

## Tablas SQL: API y equipo

| Tabla                       | Clave primaria                   | Columnas después de `time`                                                                                                                                                                              |
| --------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mikroscope_api_system`     | `time, host`                     | `host`, `cpu_load`, `free_memory`, `total_memory`, `free_hdd`, `uptime_s`, `version`                                                                                                                    |
| `mikroscope_api_core`       | `time, host, cpu`                | `host`, `cpu`, `load`, `irq`, `disk`                                                                                                                                                                    |
| `mikroscope_api_health`     | `time, host, name`               | `host`, `name`, `value`                                                                                                                                                                                 |
| `mikroscope_api_iface`      | `time, host, interface`          | `host`, `interface`, `label`, `rx_bps`, `tx_bps`, `rx_pps`, `tx_pps`, `rx_drops`, `tx_drops`, `tx_queue_drops`, `rx_errors`, `tx_errors` (columnas de pérdidas `NULL` cuando el router no las devolvió) |
| `mikroscope_api_conntrack`  | `time, host`                     | `host`, `entries`: el último recuento, escrito en cada consulta a la API una vez que se ha leído uno                                                                                                    |
| `mikroscope_api_ifinfo`     | `time, host, interface`          | `host`, `interface`, `default_name`, `type`, `role`, `bridge`, `label`, `mtu` (`NULL` donde el router no da ninguno): qué es cada interfaz, una fila por interfaz y por lectura del inventario          |
| `mikroscope_api_ifcounter`  | `time, host, interface, counter` | `host`, `interface`, `counter` (el propio nombre de RouterOS, con sus guiones), `value`: una fila por contador                                                                                          |
| `mikroscope_api_error`      | `time, host, message`            | `host`, `message`: qué orden de la API falló en esa consulta y por qué                                                                                                                                  |
| `mikroscope_gap`            | `time, host, seq_from, seq_to`   | `host`, `seq_from`, `seq_to`                                                                                                                                                                            |
| `mikroscope_trigger`        | `time, host, id`                 | `host`, `id`, `cause`, `field`, `value`, `threshold`, `seq`                                                                                                                                             |
| `mikroscope_derived`        | `time, host`                     | `host`, `seq`, `mem_pressure`, `burst`, `suspect`, `cycles_per_packet`, `instructions_per_packet`, `cache_misses_per_packet`, `packets_per_irq` (`NULL` donde no se puede calcular)                     |
| `mikroscope_derived_iface`  | `time, host, interface`          | `host`, `interface`, `rx_bytes`, `fp_rx_bytes`, `tx_bytes`, `fp_tx_bytes`, `fp_rx_share`, `fp_tx_share`                                                                                                 |
| `mikroscope_detection`      | `time, host, rule, key`          | `host`, `rule`, `key` (cadena vacía cuando la regla no tiene), `seq`, `value`, `threshold`, `message`                                                                                                   |
| `mikroscope_device`         | `time, host`                     | `host`, `board`, `kernel`, `cores`, `privileged`, `cgroup`, `sources`, `conntrack_max`, `cgroup_mem_max`, `ports_from`, `hash`                                                                          |
| `mikroscope_device_thermal` | `time, host, zone`               | `host`, `zone`, `critical_celsius`, `polling_ms`                                                                                                                                                        |
| `mikroscope_device_cpufreq` | `time, host, cpu`                | `host`, `cpu`, `cluster`, `min_khz`, `max_khz`, `governor`, `steps`                                                                                                                                     |
| `mikroscope_device_cadence` | `time, host, source`             | `host`, `source`, `reason`, `hz`                                                                                                                                                                        |
| `mikroscope_sampler` | `time, host` | `host`, `ticks`, `slipped`, `captures_held`, `capture_bytes`, `capture_budget_bytes`, `capture_served_bytes` (las columnas de capturas valen 0, no `NULL`, con las capturas desactivadas) |
| `mikroscope_trigger_count` | `time, host, condition` | `host`, `condition`, `fired` |
| `mikroscope_trigger_suppressed` | `time, host, condition, reason` | `host`, `condition`, `reason`, `count` |
| `mikroscope_capture_refused` | `time, host, reason` | `host`, `reason`, `count` |

## InfluxDB frente a SQL

| Dato                              | InfluxDB                                                                                                                                   | SQL                                                                                   |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| log del kernel                    | recuentos por nivel, puerto y kind, `mikroscope_kmsg`; sin texto                                                                           | cada registro con su texto, `port` y `kind`, `mikroscope_event`; sin recuentos        |
| extras del observador             | `cgroup_mem_max` en `mikroscope_self`; `wake_ns` y `read_ns` solo en las muestras que los llevan | sin `cgroup_mem_max` en `mikroscope_self` (está en `mikroscope_device`); `wake_ns` y `read_ns` en cada fila, 0 cuando faltan |
| contadores de capturas del agente | los campos de capturas de `mikroscope_sampler` se omiten con las capturas desactivadas | las columnas de capturas de `mikroscope_sampler` se escriben como 0 con las capturas desactivadas |
| techo de slab                     | campo `limit`                                                                                                                              | columna `limit_objs`; población `active` frente a `active_objs`                       |
| listas libres                     | una fila por zona, un campo por orden                                                                                                      | una fila por zona y orden                                                             |
| qué es una interfaz               | `label`, `type`, `role` y `bridge` como etiquetas en `mikroscope_api_iface` y `mikroscope_api_ifcounters`, junto a `mikroscope_api_ifinfo` | solo `label` en `mikroscope_api_iface`; el resto, a través de `mikroscope_api_ifinfo` |
| contadores de puerto              | `mikroscope_api_ifcounters`, una fila por puerto, `_` en los nombres                                                                       | `mikroscope_api_ifcounter`, una fila por contador, nombres de RouterOS                |
| recuento de conntrack             | solo en las consultas que lo pidieron                                                                                                      | el último recuento, repetido en cada consulta después de la primera                   |
| fallos de órdenes de la API       | no se escriben                                                                                                                             | `mikroscope_api_error`                                                                |
| límites de un hueco               | `from`, `to`                                                                                                                               | `seq_from`, `seq_to`                                                                  |
| `mikroscope_api_system.version`   | no se escribe                                                                                                                              | `version`                                                                             |

El tamaño medido de este SQL frente al line protocol, y las tablas que esa
cifra no cubre, están en [Probado en](https://docs.jmrp.io/mikroscope/es/about/status/#not-tested).

## Véase también

- [InfluxDB 3](https://docs.jmrp.io/mikroscope/es/sinks/influxdb/): apuntar `forward` a InfluxDB y qué garantías de
  entrega tiene.
- [Métricas de Prometheus](https://docs.jmrp.io/mikroscope/es/reference/metrics/): los mismos datos como
  scrape.
- [Otros destinos](https://docs.jmrp.io/mikroscope/es/sinks/other/): JSONL, SQL en la práctica, Loki,
  OTLP, Graphite, Elasticsearch, Telegraf.
- [Configurar en Grafana](https://docs.jmrp.io/mikroscope/es/dashboards/import-and-check/): los paneles que consultan
  estas medidas.
