# Reglas de alerta

Las reglas de alerta de Grafana que se generan junto a los dashboards: con qué salta cada una, cómo instalarlas, de dónde sale cada umbral y lo que no pueden ver.

Source: https://docs.jmrp.io/mikroscope/es/dashboards/alerts/

`mikroscope dashboards gen` escribe, junto a cada dashboard, un fichero de aprovisionamiento de las
alertas unificadas de Grafana con las reglas que se desprenden de los propios contadores de fallos de
los dashboards, del registro del kernel que lee el agente y de las detecciones del colector. Todo
umbral es cero (un contador que no debería moverse), una muestra (la regla del agente en silencio),
una proporción de un techo que publicó el propio equipo o, en una regla, un múltiplo del último día
del propio equipo. Ninguno es un número compilado para un router concreto.

## Ficheros

| Fichero                                        |                                           Reglas | Lenguaje de consulta |
| ---------------------------------------------- | -----------------------------------------------: | -------------------- |
| [`dashboards/mikroscope-alerts-influxdb.yaml`](https://github.com/jmrplens/mikroscope/blob/main/dashboards/mikroscope-alerts-influxdb.yaml)   |   14 | SQL de InfluxDB 3    |
| [`dashboards/mikroscope-alerts-prometheus.yaml`](https://github.com/jmrplens/mikroscope/blob/main/dashboards/mikroscope-alerts-prometheus.yaml) | 15 | PromQL               |
| [`dashboards/mikroscope-alerts-postgres.yaml`](https://github.com/jmrplens/mikroscope/blob/main/dashboards/mikroscope-alerts-postgres.yaml)   |   10 | SQL de PostgreSQL    |

El fichero de InfluxDB tiene una regla menos porque "The sampler is slipping ticks" todavía no tiene
forma en SQL. El contador sí llega a InfluxDB —el colector lo lee del `/sampler` del agente y
escribe `mikroscope_sampler.slipped`—, así que la regla se podría escribir; no se ha escrito.

El fichero de PostgreSQL se traduce del de InfluxDB, y además deja fuera las reglas que el esquema
SQL no puede responder con la misma forma. `mikroscope-l2-loop` y `mikroscope-port-link-down` suman
un recuento por muestra de registros de puerto del kernel, y el destino SQL escribe en su lugar una
fila por registro del kernel, en `mikroscope_event`. `mikroscope-port-errors` nombra una
columna por contador del MAC, y el destino SQL escribe `mikroscope_api_ifcounter` con una fila por
contador. `mikroscope-bridge-port-dark` queda fuera por la misma razón: nombra `rx_packet`,
`tx_unicast` y `tx_broadcast` como columnas, y lee `bridge`, que esa tabla tampoco tiene. La
traducción las descarta en vez de escribir una consulta que parece correcta y responde a otra cosa.
Una prueba unitaria comprueba cada columna que lee cada consulta de alerta de PostgreSQL contra las
tablas que declara el destino SQL; lee el esquema, no una base de datos
([no ejecutadas contra PostgreSQL](https://docs.jmrp.io/mikroscope/es/about/status/#not-tested)).

Cada fichero es `apiVersion: 1` con un grupo de reglas, `mikroscope`, en una carpeta llamada
`mikroscope`, organización 1, evaluado cada minuto. Las reglas se aprovisionan en vez de ir dentro de
los dashboards, así que un operador que no quiera ninguna no copia nada. `forward --grafana`,
`dashboards publish` y `dashboards import` no instalan ninguna, y `uninstall --targets dashboard` no
quita ninguna.

## Instalar las reglas

1. Sustituye el marcador de la fuente de datos por el UID de tu fuente de datos
   (`mikroscope-<almacén>` si la creó el colector o `dashboards publish`) y escribe el fichero en el
   directorio de aprovisionamiento de Grafana. Los ficheros de aprovisionamiento no resuelven
   la entrada `${DS_MIKROSCOPE}` de un dashboard, así que la fuente de datos es un marcador literal,
   `DS_UID_PLACEHOLDER`:

   ```sh
   sed 's/DS_UID_PLACEHOLDER/<uid>/g' dashboards/mikroscope-alerts-influxdb.yaml \
     > /etc/grafana/provisioning/alerting/mikroscope-alerts-influxdb.yaml
   ```

   Usa el fichero que corresponda a la fuente de datos a la que pertenece el UID.
   `/etc/grafana/provisioning/alerting/` es la ruta que nombra la cabecera del propio fichero
   generado; el directorio es cosa de Grafana.

2. Reinicia Grafana, o pide a su API de administración que recargue los ficheros aprovisionados. El
   formato del fichero y la recarga están en la [guía de aprovisionamiento de alertas por fichero de
   Grafana](https://grafana.com/docs/grafana/latest/alerting/set-up/provision-alerting-resources/file-provisioning/).

Todas las reglas tienen la misma forma, la que escribe el propio editor de reglas de Grafana:

1. **A** — la consulta, contra tu fuente de datos, con un rango de tiempo relativo de los últimos
   600 s. Toda consulta SQL y toda consulta de Prometheus sobre un contador acota además su propia
   ventana (1, 2, 5 o 10 minutos, o 1 hora, más abajo; la regla de despertares lee además las 24
   horas anteriores a sus 10 minutos, diga lo que diga el rango de 600 s de A); las dos reglas de
   Prometheus sobre gauges, la térmica y la de conntrack, leen el último valor, y la de la cola de
   salida toma el máximo de su gauge en el último minuto.
2. **B** — reduce A a un número por serie con `last`, descartando los valores no numéricos.
3. **C** — compara B con el umbral. C es la condición de la regla.

Cada regla lleva las etiquetas `severity` (`critical` o `warning`) y `source: mikroscope`, una
anotación `summary` y `execErrState: Error`. Lo que hace Grafana después con una regla cuya consulta
falla es comportamiento de Grafana, descrito en [su propia documentación de los estados Error y No
Data](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rule-evaluation/nodata-and-error-states/)
([no probado aquí](https://docs.jmrp.io/mikroscope/es/about/status/#not-tested)).

## Reglas

Las reglas de alerta:

| Regla (uid) | Salta cuando | Umbral (C) | Severidad | `for` | Sin datos significa | Almacenes |
| --- | --- | --- | --- | --- | --- | --- |
| `mikroscope-agent-silent` | llegó al almacén menos de 1 muestra nueva en los últimos 2 minutos | < 1 | critical | 2m | Alerting | solo InfluxDB |
| `mikroscope-softnet-drops` | `softnet_stat` descartó un paquete en los últimos 5 minutos | > 0 | critical | 0s | OK | solo InfluxDB |
| `mikroscope-oom-kill` | `oom_kill` de `/proc/vmstat` se movió en los últimos 5 minutos | > 0 | critical | 0s | OK | solo InfluxDB |
| `mikroscope-detections` | cualquier detección en los últimos 5 minutos salvo `microburst` e `ipc-collapse`, que se dibujan y se guardan pero no avisan | > 0 | warning | 0s | OK | solo InfluxDB |
| `mikroscope-thermal-near-critical` | una zona al 85 % o más de su propio disparo crítico | > 0 | critical | 1m | OK | solo InfluxDB |
| `mikroscope-conntrack-near-limit` | objetos activos de `nf_conntrack` por encima de 0,8 del límite del kernel | > 0,8 | warning | 5m | OK | solo InfluxDB |
| `mikroscope-agent-oom` | el propio cgroup del agente registró un OOM kill en los últimos 5 minutos | > 0 | critical | 0s | OK | solo InfluxDB |
| `mikroscope-l2-loop` | un registro de own-address en cualquier puerto en los últimos 5 minutos | > 0 | critical | 0s | OK | los dos |
| `mikroscope-port-link-down` | un registro de link-down en cualquier puerto en los últimos 5 minutos | > 0 | warning | 0s | OK | los dos |
| `mikroscope-port-errors` | el MAC de algún puerto contó un error tipificado —desbordamiento, FCS, colisión— durante 5 minutos seguidos | > 0 | warning | 5m | OK | los dos |
| `mikroscope-bridge-port-dark` | un puerto de bridge recibió paquetes mientras el bridge no le envió ni una trama unicast ni una broadcast, durante 10 minutos | > 0 | warning | 10m | OK | los dos |
| `mikroscope-wakeup-storm` | la tasa de cambios de contexto de los últimos 10 minutos supera 4 veces su media de las 24 horas anteriores, durante 10 minutos | > 4 | warning | 10m | OK | solo InfluxDB |
| `mikroscope-egress-queue-drops` | la cola de salida de algún puerto descartó un paquete en cada uno de los últimos 10 minutos: congestión sostenida, nunca una ráfaga suelta | > 0 | warning | 10m | OK | solo InfluxDB |
| `mikroscope-ecc-failure` | la NAND informó de un fallo ECC no corregible en la última hora | > 0 | critical | 0s | OK | solo InfluxDB |
| `mikroscope-ticks-slipped` | el muestreador perdió un tick por retraso en los últimos 5 minutos | > 0 | warning | 5m | OK | solo Prometheus |

"Sin datos significa" es el `noDataState` de la regla. La regla del agente en silencio es la única en
la que el silencio es el fallo, así que la ausencia de datos la hace saltar; para todas las demás, no
tener datos es la lectura sana.

El título de cada regla, en inglés como aparece en Grafana, y debajo una traducción abreviada de su
anotación `summary` (el texto generado está en inglés):

- **mikroscope agent stopped delivering samples.** No llegaron muestras nuevas al almacén en los
  últimos dos minutos: se paró el agente, se paró el colector o se cortó el camino entre ellos. Todas
  las demás reglas están ciegas mientras esta salta.
- **Packets dropped in the kernel receive path.** `softnet_stat` descartó un paquete: una cola por
  CPU estaba llena. Pérdida inequívoca dentro del router, invisible para cualquier contador SNMP o de
  RouterOS. Cero es la lectura esperada.
- **The kernel OOM-killed a process.** `oom_kill` de `/proc/vmstat` se movió: el kernel mató un
  proceso para recuperar memoria. Qué proceso no se puede saber desde el contenedor (no hay espacio
  de nombres de PID).
- **The collector's derive stage flagged an event.** Saltó una regla de detección (counter-reset,
  agent-restart, agent-oom, reboot, link-flap, conntrack-cliff, conntrack-high, thermal-high,
  thermal-rising). La regla, la clave, el valor y el umbral están en la sección de detecciones y en
  el dashboard como anotación. `microburst` e `ipc-collapse` se dibujan y se guardan como cualquier
  detección, pero no la disparan: describen cómo lleva el tráfico un router sano, y fueron la mayoría
  de las detecciones en un día medido ([medido](https://docs.jmrp.io/mikroscope/es/about/status/#las-reglas-de-alerta)).
- **A thermal zone is within 15 % of its own critical trip.** La lectura está al 85 % o más del punto
  de disparo crítico declarado por la zona. El techo es el de la propia placa, leído de `/sys`, no un
  número compilado.
- **The connection table is above 80 % of nf_conntrack_max.** Objetos activos de `nf_conntrack` por
  encima del propio techo del kernel. Pasado el techo, el router descarta las conexiones nuevas. El
  límite es el sysctl que leyó el agente, no un número compilado.
- **The sampler is slipping ticks.** Hubo ticks que terminaron después de que tocara el siguiente. La
  cadencia no se está cumpliendo: el equipo va falto de CPU, el conjunto de fuentes es demasiado caro
  para la cadencia, o la cuota de CPU del contenedor limitó al agente (consulta el contador de
  limitación del observador).
- **mikroscope's own container was OOM-killed.** El kernel mató un proceso dentro del cgroup del
  agente: el anillo de captura y las capturas se han perdido, y todos los números de la ventana son
  sospechosos. Sube `--memory-max` o baja `RATE_HZ`, `BUFFER_S` o `CAPTURE_MB`.
- **The bridge received its own address back: a layer-2 loop signature.** El registro del kernel
  informó de `received packet on <port> with own address as source address`: una trama que envió el
  router volvió a entrar, que es el aspecto que tiene un bucle a través de un switch o un punto de
  acceso aguas abajo. La etiqueta del puerto dice de qué cable se trata. Se lee de `/dev/kmsg` con el
  agente, sin API. La forma de InfluxDB necesita un almacén que haya tenido al menos un registro de
  puerto clasificado por `kind`. El [caso real del bucle de capa 2](https://docs.jmrp.io/mikroscope/es/playbooks/loop/)
  muestra la firma.
- **A port's link went down.** El registro del kernel informó de un link-down en un puerto: un cable
  desconectado, un par que se reinició o se apagó, una renegociación. La detección de link-flap del
  colector cubre el caso repetido; esto es el suceso único. Se lee de `/dev/kmsg` con el agente, sin
  API; el puerto, su comentario y su rol están en los sucesos de puerto de la sección del registro
  del kernel.
- **A port is counting typed MAC errors.** El MAC de un puerto está contando errores tipificados:
  tramas que no pudo aceptar. El más común en una LAN conmutada es rx-overflow, la FIFO de recepción
  llenándose más rápido de lo que el chip la drena, y es una firma de microrráfaga y no de carga. Los
  errores de FCS y las colisiones significan otra cosa: cableado, dúplex, un puerto muriéndose.
  Necesita la capa de API: un contador del MAC no se ve desde dentro del contenedor. No distingue un
  error real de un reinicio del contador, así que un router que se reinicia dentro de la ventana la
  dispara una vez.
- **A bridge port is receiving but the bridge sends it nothing.** Un puerto miembro de un bridge ha
  recibido paquetes durante diez minutos mientras el bridge no le entregó ni una trama unicast ni una
  broadcast. Lo que hay detrás transmite y no recibe nada de vuelta: STP mantiene el puerto
  descartando, o el bridge aprendió todos los hosts de detrás por otro camino. RouterOS muestra el
  puerto en marcha y sin errores todo el tiempo. Un bucle de capa 2 se lee así en el puerto al que
  el bridge ha dejado de entregar ([caso real](https://docs.jmrp.io/mikroscope/es/playbooks/loop/)). Se espera que
  salte en un diseño redundante, donde un puerto alternate de RSTP descarta a propósito, y en un
  puerto con `broadcast-flood=no` u `horizon`. Necesita la capa de API.
- **The kernel is switching context four times as often as over its last day.** Algo empezó a
  despertarse muy a menudo: un proceso o un driver en espera activa, un cliente de monitorización,
  un contenedor en un bucle cerrado. Mira qué empezó cuando saltó (`/user/active`, contenedores
  nuevos, una integración nueva) y la sección de interrupciones y softirqs. Una integración de
  monitorización que consulta el router por la API es una de las causas
  ([caso real](https://docs.jmrp.io/mikroscope/es/playbooks/cpu/#cuando-los-núcleos-no-están-ocupados-pero-el-kernel-sí)).
  Un cambio deliberado, como un contenedor nuevo o un conjunto de reglas más pesado, también la
  dispara.
- **A port's egress queue has been dropping every minute for ten minutes.** La cola de salida de un
  puerto ha descartado paquetes en cada uno de los últimos diez minutos. A diferencia de las demás
  reglas de contador, el contador de esta **sí** debe moverse: descartar es como una cola llena le
  dice a quien envía que afloje. Lo que la dispara es un enlace sencillamente demasiado pequeño para
  lo que se le pide llevar, o un shaper puesto por debajo del tráfico. Necesita la capa de API.
- **The NAND reported an uncorrectable ECC failure.** `ecc_failures` subió en una partición MTD: una
  lectura que la corrección de errores no pudo arreglar, es decir, pérdida de datos en la flash.
  Cualquier incremento es un incidente.

## Consultas

- **Prometheus**

  ```text
  # mikroscope-agent-silent            (< 1)
  sum(increase(mikroscope_samples_total[2m]))
  # mikroscope-softnet-drops           (> 0)
  sum(increase(mikroscope_softnet_total{kind="dropped"}[5m]))
  # mikroscope-oom-kill                (> 0)
  sum(increase(mikroscope_vm_events_total{event="oom_kill"}[5m]))
  # mikroscope-detections              (> 0)
  sum(increase(mikroscope_collector_detections_total{rule!~"microburst|ipc-collapse"}[5m]))
  # mikroscope-thermal-near-critical   (> 0)
  count(mikroscope_thermal_celsius >= on(zone) 0.85 * mikroscope_thermal_critical_celsius)
  # mikroscope-conntrack-near-limit    (> 0.8)
  max(mikroscope_slab_active_objects{cache="nf_conntrack"} / mikroscope_slab_limit_objects{cache="nf_conntrack"})
  # mikroscope-ticks-slipped           (> 0)
  sum(increase(mikroscope_slipped_total[5m]))
  # mikroscope-agent-oom               (> 0)
  sum(increase(mikroscope_self_oom_kills_total[5m]))
  # mikroscope-l2-loop                 (> 0)
  sum(increase(mikroscope_kmsg_port_records_total{kind="own-address"}[5m]))
  # mikroscope-port-link-down          (> 0)
  sum(increase(mikroscope_kmsg_port_records_total{kind="link-down"}[5m]))
  # mikroscope-port-errors             (> 0)
  sum(increase(mikroscope_api_interface_counter_total{counter=~"rx-overflow|rx-fcs-error|rx-fragment|rx-too-short|rx-too-long|rx-jabber|tx-fcs-error|tx-late-collision|tx-excessive-collision"}[5m]))
  # mikroscope-bridge-port-dark        (> 0)
  count((sum by (interface) (increase(mikroscope_api_interface_counter_total{counter="rx-packet"}[10m])) > 0) and on (interface) (sum by (interface) (increase(mikroscope_api_interface_counter_total{counter="tx-unicast"}[10m])) == 0) and on (interface) (sum by (interface) (increase(mikroscope_api_interface_counter_total{counter="tx-broadcast"}[10m])) == 0) and on (interface) (mikroscope_api_interface_info{bridge!=""}))
  # mikroscope-wakeup-storm            (> 4)
  sum(rate(mikroscope_context_switches_total[10m])) / sum(rate(mikroscope_context_switches_total[24h] offset 10m))
  # mikroscope-egress-queue-drops      (> 0)
  max(max_over_time(mikroscope_api_interface{kind="tx_queue_drops"}[1m]))
  # mikroscope-ecc-failure             (> 0)
  sum(increase(mikroscope_mtd_ecc_failures_total[1h]))
  ```

  Las 15 leen el `/metrics` del colector, que es la única
  exposición que hay —
  [Configurar en Grafana](https://docs.jmrp.io/mikroscope/es/dashboards/import-and-check/#prometheus-un-trabajo-de-scrape)
  tiene el único trabajo de scrape. `mikroscope_slipped_total` está entre ellas: el agente es el
  dueño del ticker y lo único que puede contar un tick perdido, e informa del número por `/sampler`,
  que el colector lee cada minuto y renderiza. Así que la regla se dispara sobre una cifra de como
  mucho un minuto en vez de un scrape, que para un contador que vale 0 en un equipo sano es la misma
  alerta.

- **InfluxDB 3**

  ```sql
  -- mikroscope-agent-silent            (< 1)
  SELECT count(1) AS value FROM mikroscope_cpu WHERE time >= now() - interval '2 minutes'
  -- mikroscope-softnet-drops           (> 0)
  SELECT coalesce(sum(dropped), 0)::BIGINT AS value FROM mikroscope_softnet WHERE time >= now() - interval '5 minutes'
  -- mikroscope-oom-kill                (> 0)
  SELECT coalesce(sum(oom_kill), 0)::BIGINT AS value FROM mikroscope_vm WHERE time >= now() - interval '5 minutes'
  -- mikroscope-detections              (> 0)
  SELECT count(1) AS value FROM mikroscope_detection WHERE time >= now() - interval '5 minutes' AND rule NOT IN ('microburst', 'ipc-collapse')
  -- mikroscope-thermal-near-critical   (> 0)
  SELECT count(1) AS value FROM (SELECT zone, max(celsius) AS c, max(critical_celsius) AS crit FROM mikroscope_thermal WHERE time >= now() - interval '2 minutes' AND critical_celsius IS NOT NULL GROUP BY zone) AS zones WHERE c >= 0.85 * crit
  -- mikroscope-conntrack-near-limit    (> 0.8)
  SELECT max(active) * 1.0 / nullif(max("limit"), 0) AS value FROM mikroscope_slab WHERE time >= now() - interval '2 minutes' AND cache = 'nf_conntrack' AND "limit" IS NOT NULL
  -- mikroscope-agent-oom               (> 0)
  SELECT coalesce(sum(oom_kill), 0)::BIGINT AS value FROM mikroscope_self WHERE time >= now() - interval '5 minutes' AND oom_kill IS NOT NULL
  -- mikroscope-l2-loop                 (> 0)
  SELECT coalesce(sum(count), 0)::BIGINT AS value FROM mikroscope_kmsg WHERE time >= now() - interval '5 minutes' AND kind = 'own-address'
  -- mikroscope-port-link-down          (> 0)
  SELECT coalesce(sum(count), 0)::BIGINT AS value FROM mikroscope_kmsg WHERE time >= now() - interval '5 minutes' AND kind = 'link-down'
  -- mikroscope-port-errors             (> 0)
  SELECT coalesce(sum(v), 0)::BIGINT AS value FROM (SELECT interface, greatest(max(rx_overflow) - min(rx_overflow), 0)::BIGINT + greatest(max(rx_fcs_error) - min(rx_fcs_error), 0)::BIGINT + greatest(max(rx_fragment) - min(rx_fragment), 0)::BIGINT + greatest(max(rx_too_short) - min(rx_too_short), 0)::BIGINT + greatest(max(rx_too_long) - min(rx_too_long), 0)::BIGINT + greatest(max(rx_jabber) - min(rx_jabber), 0)::BIGINT + greatest(max(tx_fcs_error) - min(tx_fcs_error), 0)::BIGINT + greatest(max(tx_late_collision) - min(tx_late_collision), 0)::BIGINT + greatest(max(tx_excessive_collision) - min(tx_excessive_collision), 0)::BIGINT AS v FROM mikroscope_api_ifcounters WHERE time >= now() - interval '5 minutes' GROUP BY interface) AS ports
  -- mikroscope-bridge-port-dark        (> 0)
  SELECT count(*)::BIGINT AS value FROM (SELECT interface, max(rx_packet) - min(rx_packet) AS drx, max(tx_unicast) - min(tx_unicast) AS dtu, max(tx_broadcast) - min(tx_broadcast) AS dtb FROM mikroscope_api_ifcounters WHERE time >= now() - interval '10 minutes' AND bridge IS NOT NULL AND bridge <> '' GROUP BY interface) AS ports WHERE drx > 0 AND dtu = 0 AND dtb = 0
  -- mikroscope-wakeup-storm            (> 4)
  SELECT CASE WHEN base_min >= 720 THEN (now_sum / nullif(now_min * 60.0, 0)) / nullif(base_sum / (base_min * 60.0), 0) ELSE 0.0 END AS value FROM (SELECT sum(CASE WHEN time >= now() - interval '10 minutes' THEN CAST(ctxt AS BIGINT) ELSE 0 END) AS now_sum, count(DISTINCT CASE WHEN time >= now() - interval '10 minutes' THEN date_bin(interval '1 minute', time) END) AS now_min, sum(CASE WHEN time < now() - interval '10 minutes' THEN CAST(ctxt AS BIGINT) ELSE 0 END) AS base_sum, count(DISTINCT CASE WHEN time < now() - interval '10 minutes' THEN date_bin(interval '1 minute', time) END) AS base_min FROM mikroscope_stat WHERE time >= now() - interval '1450 minutes') AS w
  -- mikroscope-egress-queue-drops      (> 0)
  SELECT coalesce(max(tx_queue_drops), 0)::BIGINT AS value FROM mikroscope_api_iface WHERE time >= now() - interval '1 minute'
  -- mikroscope-ecc-failure             (> 0)
  SELECT coalesce(sum(delta), 0)::BIGINT AS value FROM (SELECT max(ecc_failures) - min(ecc_failures) AS delta FROM mikroscope_mtd WHERE time >= now() - interval '1 hour' AND ecc_failures IS NOT NULL GROUP BY "partition") AS parts
  ```

  Todo `coalesce()` sobre un agregado lleva `::BIGINT`. El destino de InfluxDB escribe sus contadores
  sin signo, así que `coalesce(sum(count), 0)` sería `coalesce(UInt64, Int64)`, un par que el plugin
  de InfluxDB de Grafana no sabe mapear: responde HTTP 200 sin marcos y sin error, Grafana lo
  interpreta como NoData, y una regla que declara `noDataState: OK` marcaría OK mientras ocurre
  justo aquello que vigila ([encontrado en Grafana](https://docs.jmrp.io/mikroscope/es/about/status/#las-reglas-de-alerta)).
  `TestCoalesceIsCastInAlertSQL` falla si una regla se deja el cast. El SQL de los paneles fija el
  mismo fallo con un cast a `BIGINT` de `greatest()` sobre un agregado
  ([`internal/dashboards/panels.go`](https://github.com/jmrplens/mikroscope/blob/main/internal/dashboards/panels.go)), y `TestGreatestIsCastForTheInfluxPlugin` falla si un
  panel se lo deja; un panel al menos falla a gritos, con un 500. La regla de conntrack lee el
  techo del slab como `"limit"`, el nombre que le da el destino de InfluxDB; la traducción a
  PostgreSQL lo convierte en `limit_objs`.

- **PostgreSQL**

  ```sql
  -- mikroscope-agent-silent            (< 1)
  SELECT count(1) AS value FROM mikroscope_cpu WHERE time >= now() - interval '2 minutes'
  -- mikroscope-softnet-drops           (> 0)
  SELECT coalesce(sum(dropped), 0)::BIGINT AS value FROM mikroscope_softnet WHERE time >= now() - interval '5 minutes'
  -- mikroscope-oom-kill                (> 0)
  SELECT coalesce(sum(oom_kill), 0)::BIGINT AS value FROM mikroscope_vm WHERE time >= now() - interval '5 minutes'
  -- mikroscope-detections              (> 0)
  SELECT count(1) AS value FROM mikroscope_detection WHERE time >= now() - interval '5 minutes' AND rule NOT IN ('microburst', 'ipc-collapse')
  -- mikroscope-thermal-near-critical   (> 0)
  SELECT count(1) AS value FROM (SELECT zone, max(celsius) AS c, max(critical_celsius) AS crit FROM mikroscope_thermal WHERE time >= now() - interval '2 minutes' AND critical_celsius IS NOT NULL GROUP BY zone) AS zones WHERE c >= 0.85 * crit
  -- mikroscope-conntrack-near-limit    (> 0.8)
  SELECT max(active_objs) * 1.0 / nullif(max("limit_objs"), 0) AS value FROM mikroscope_slab WHERE time >= now() - interval '2 minutes' AND cache = 'nf_conntrack' AND "limit_objs" IS NOT NULL
  -- mikroscope-agent-oom               (> 0)
  SELECT coalesce(sum(oom_kill), 0)::BIGINT AS value FROM mikroscope_self WHERE time >= now() - interval '5 minutes' AND oom_kill IS NOT NULL
  -- mikroscope-wakeup-storm            (> 4)
  SELECT CASE WHEN base_min >= 720 THEN (now_sum / nullif(now_min * 60.0, 0)) / nullif(base_sum / (base_min * 60.0), 0) ELSE 0.0 END AS value FROM (SELECT sum(CASE WHEN time >= now() - interval '10 minutes' THEN CAST(ctxt AS BIGINT) ELSE 0 END) AS now_sum, count(DISTINCT CASE WHEN time >= now() - interval '10 minutes' THEN date_bin(interval '1 minute', time, TIMESTAMPTZ 'epoch') END) AS now_min, sum(CASE WHEN time < now() - interval '10 minutes' THEN CAST(ctxt AS BIGINT) ELSE 0 END) AS base_sum, count(DISTINCT CASE WHEN time < now() - interval '10 minutes' THEN date_bin(interval '1 minute', time, TIMESTAMPTZ 'epoch') END) AS base_min FROM mikroscope_stat WHERE time >= now() - interval '1450 minutes') AS w
  -- mikroscope-egress-queue-drops      (> 0)
  SELECT coalesce(max(tx_queue_drops), 0)::BIGINT AS value FROM mikroscope_api_iface WHERE time >= now() - interval '1 minute'
  -- mikroscope-ecc-failure             (> 0)
  SELECT coalesce(sum(delta), 0)::BIGINT AS value FROM (SELECT max(ecc_failures) - min(ecc_failures) AS delta FROM mikroscope_mtd WHERE time >= now() - interval '1 hour' AND ecc_failures IS NOT NULL GROUP BY "partition") AS parts
  ```

## Umbrales

- **Cero, para un contador que no debería moverse.** Descartes de RX del kernel, OOM kills del
  kernel, detecciones, ticks perdidos por retraso, OOM kills del propio agente, fallos ECC no
  corregibles, errores MAC tipificados en cualquier puerto y los registros de puerto del kernel cuyo
  `kind` es `own-address` o `link-down`. Un equipo sano lee cero en cada uno.
- **Cero puertos de bridge a oscuras, durante diez minutos.** `mikroscope-bridge-port-dark` cuenta
  los puertos de bridge que recibieron paquetes mientras el bridge no les envió ni unicast ni
  broadcast; en un bridge sano el recuento es 0, y el periodo de espera de 10 minutos es lo que
  impide que un momento de calma la dispare.
- **Una muestra, para el agente en silencio.** Menos de una muestra en dos minutos es ninguna, a
  cualquier cadencia configurada.
- **Una proporción del propio disparo térmico de la placa.** El 85 % del punto de disparo crítico más
  bajo que declara cada zona, que el agente lee de `/sys/class/thermal` y envía junto a cada lectura.
  Una zona que no declara disparo crítico queda fuera de la consulta en vez de compararse con un techo
  inventado.
- **Una proporción del propio límite de conexiones del kernel.** 0.8 del límite de `nf_conntrack` que
  leyó el agente. La ocupación sale de `/proc/slabinfo`, que necesita un contenedor privilegiado.
- **Cero otra vez, para un contador que *sí* debe moverse — con el juicio en la duración.** La cola
  de salida es el único caso aquí en que la lectura sana no es exactamente cero en todos los
  equipos: descartar es como una cola llena le dice a quien envía que afloje, así que un enlace
  brevemente saturado descarta unos cuantos paquetes y está funcionando como se diseñó. Un umbral en
  paquetes por segundo sería un número que el equipo no publica, así que
  `mikroscope-egress-queue-drops` mantiene el cero y pregunta **solo por el último minuto** con un
  **periodo de espera de diez**: hacen falta diez minutos seguidos descartando para que dispare, y
  ninguna ráfaga puede lograrlo por grande que sea
  ([una ráfaga medida](https://docs.jmrp.io/mikroscope/es/about/status/#las-reglas-de-alerta)). Esta es la forma que hay que copiar
  para cualquier regla futura cuya lectura sana no sea cero.
- **Un múltiplo del último día del propio equipo.** Una tasa de cambios de contexto no tiene un valor
  sano que sirva para todas las placas, así que `mikroscope-wakeup-storm` compara el router consigo
  mismo: la tasa media de los últimos diez minutos frente a la media de las 24 horas anteriores, y
  salta por encima de 4 ([cocientes sanos y de tormenta](https://docs.jmrp.io/mikroscope/es/about/status/#las-reglas-de-alerta)).
  Cada lado se divide entre los minutos que cubre de verdad, y la regla espera a tener 12 horas de
  base, de modo que un almacén con menos de un día no parece una tormenta. La regla anuncia un
  cambio de régimen y se apaga cuando la nueva tasa pasa a ser la base.

Las reglas no alertan por squeezes de softnet: en el router medido, alrededor del 11,2 %
de las muestras por CPU llevan un squeeze, así que alertar con "squeeze > 0" te
despertaría para siempre. Lo que llega en su lugar a la alerta de detecciones es la detección
`microburst`: tres muestras de ráfaga en una CPU en 60 s. Una muestra de ráfaga es aquella en la que
una cola de softnet descartó un paquete, o agotó su presupuesto más veces que el percentil 90 móvil
de esa CPU y al menos tres, mientras el recuento de paquetes de la muestra estaba en su mediana móvil
o por debajo. Consulta [Reglas de detección](https://docs.jmrp.io/mikroscope/es/sinks/detections/).

## Límites

- **Qué proceso.** La regla de OOM dice que el kernel mató algo. Desde dentro del contenedor no hay
  espacio de nombres de PID que diga qué.
- **Qué puerto.** Toda regla de puerto se reduce a un número: las dos del registro del kernel y la
  de errores MAC suman sobre los puertos, la de la cola de salida toma el máximo y la de puerto de
  bridge a oscuras cuenta puertos. Así que una regla que salta dice que hubo una firma de bucle, un
  link-down o un puerto a oscuras, no en qué cable. El puerto está en los dos paneles de sucesos de
  puerto de la sección del registro del kernel, con su comentario y sus listas de interfaces, o en
  la sección de tráfico por interfaz.
- **Los puntos ciegos de un agente sin privilegios.** Las reglas de la tabla de conexiones y de ECC
  leen fuentes que necesitan un contenedor privilegiado. Sin él esas medidas nunca llegan al almacén,
  y en Prometheus una consulta sobre una métrica inexistente no devuelve datos — lo que estas reglas
  leen como OK.
- **Un colector sin la capa de API.** Las reglas de errores de puerto, de puerto de bridge a
  oscuras y de cola de salida leen contadores de RouterOS que solo consulta la capa de API. Con
  `--api-mode off` nada de eso llega al almacén: en Prometheus las tres reglas no leen datos y en
  PostgreSQL la de la cola de salida, la única de las tres en ese fichero, lee 0; las dos cosas son
  OK. En un almacén InfluxDB que
  nunca ha tenido esas tablas las consultas fallan y se aplica `execErrState: Error`.
- **Una tabla que el almacén nunca ha tenido.** InfluxDB 3 rechaza al planificarla una consulta que
  nombra una tabla o una columna inexistente, así que en un almacén que nunca ha tenido una
  detección, o `mikroscope_mtd` con un agente sin privilegios, la consulta de esa regla debería
  fallar y aplicarse `execErrState: Error` en lugar de OK
  ([no observado](https://docs.jmrp.io/mikroscope/es/about/status/#not-tested)).
- **Nada mientras salta la regla del agente en silencio.** Todas las reglas leen el mismo flujo —
  incluida la de ticks perdidos por retraso, cuya única consulta va contra el colector, no contra el
  agente—, así que sin muestras llegando leen cero o ningún dato, y las dos cosas son OK.

Qué formas de estas reglas se han cargado en Grafana y se han visto evaluar, y lo que no se ha
probado, está en [Probado en](https://docs.jmrp.io/mikroscope/es/about/status/#las-reglas-de-alerta).

## Véase también

- [Reglas de detección](https://docs.jmrp.io/mikroscope/es/sinks/detections/): las once reglas detrás de la alerta de
  detecciones, y lo que cada una no puede afirmar.
- [Configurar en Grafana](https://docs.jmrp.io/mikroscope/es/dashboards/import-and-check/): el UID de fuente de datos que
  necesitan estos ficheros, y el trabajo de scrape de Prometheus.
- [Dashboards](https://docs.jmrp.io/mikroscope/es/dashboards/): los paneles de cuyos contadores de fallos salen estas
  reglas.
- [Métricas de Prometheus](https://docs.jmrp.io/mikroscope/es/reference/metrics/): las familias que leen las consultas
  de Prometheus.
