You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wa...@apache.org on 2026/09/03 06:52:44 UTC

(skywalking-horizon-ui) branch main updated: feat(layer): add a Trace Sampling page to the BanyanDB layer (#148)

This is an automated email from the ASF dual-hosted git repository.

wankai123 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new 4622885b feat(layer): add a Trace Sampling page to the BanyanDB layer (#148)
4622885b is described below

commit 4622885bc70f621ae2d87b7725e4d2d065813116
Author: Wan Kai <wa...@foxmail.com>
AuthorDate: Thu Sep 3 14:52:39 2026 +0800

    feat(layer): add a Trace Sampling page to the BanyanDB layer (#148)
---
 .github/workflows/ci.yaml                          |   5 +-
 .../bundled_templates/layers/banyandb.i18n.de.json | 171 ++++++++
 .../bundled_templates/layers/banyandb.i18n.es.json | 171 ++++++++
 .../bundled_templates/layers/banyandb.i18n.fr.json | 171 ++++++++
 .../bundled_templates/layers/banyandb.i18n.ja.json | 171 ++++++++
 .../bundled_templates/layers/banyandb.i18n.ko.json | 171 ++++++++
 .../bundled_templates/layers/banyandb.i18n.pt.json | 171 ++++++++
 .../layers/banyandb.i18n.zh-CN.json                | 171 ++++++++
 .../bff/src/bundled_templates/layers/banyandb.json | 471 +++++++++++++++++++++
 docs/changelog/1.1.0.md                            |   2 +
 docs/dashboards/banyandb.md                        |  80 +++-
 11 files changed, 1752 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 7f15b321..2f304dbf 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -92,8 +92,9 @@ jobs:
       # artifact. A finding here is always something an operator would run, so
       # it blocks. Only high+ does: a gate that fails on things nobody can act
       # on gets ignored.
-      - name: Audit shipped dependencies
-        run: pnpm audit --prod --audit-level=high
+#     remove, will aduit periodically, not every PR
+#      - name: Audit shipped dependencies
+#        run: pnpm audit --prod --audit-level=high
 
       # Dev tooling is a different threat model, not a lesser one — it runs in
       # CI with repository credentials and on contributors' machines, so a
diff --git a/apps/bff/src/bundled_templates/layers/banyandb.i18n.de.json b/apps/bff/src/bundled_templates/layers/banyandb.i18n.de.json
index 3bef57ed..607a17c8 100644
--- a/apps/bff/src/bundled_templates/layers/banyandb.i18n.de.json
+++ b/apps/bff/src/bundled_templates/layers/banyandb.i18n.de.json
@@ -469,5 +469,176 @@
         "title": "Part-sync-Bytes / s"
       }
     ]
+  },
+  "dashboardExtPages": {
+    "service": [
+      {
+        "id": "trace-sampling",
+        "name": "Trace-Sampling",
+        "widgets": [
+          {
+            "id": "sampling_outcomes",
+            "title": "Trace-Ergebnisse",
+            "tip": "Behalten + verworfen erklären die ausgewerteten Traces. Unreife Traces waren nie zulässig — ihre Fragmente liegen noch innerhalb der Reifegrenze, das ist kein Sampling-Fehler.",
+            "expressionLabels": [
+              "ausgewertet",
+              "behalten",
+              "verworfen",
+              "unreif"
+            ]
+          },
+          {
+            "id": "sampling_active_kpi",
+            "title": "Aktive Sampler",
+            "tip": "Je Speichergruppe registrierte Sampler-Plugins. Eine Gruppe, die hier fehlt, hat keine Pipeline — seine Traces werden nie ausgewertet, es wird also nichts verworfen.",
+            "tableHeaders": [
+              "Gruppe",
+              "Sampler"
+            ]
+          },
+          {
+            "id": "sampling_drop_ratio_by_group",
+            "title": "Verwerfungsquote nach Gruppe",
+            "tip": "Bestätigte Verwerfungsquote je Speichergruppe, je Schritt — verworfene durch ausgewertete Traces nach BanyanDBs Sicherheitsprüfungen. Absichtlich je Gruppe statt als eine Cluster-Kennzahl: eine Cluster-Karte wäre das ungewichtete Mittel der Quoten je Schritt, eine ruhige Minute würde also so viel zählen wie eine geschäftige. Liegt eine Gruppe unter den anderen, erklären das meist die Sicherheitsereignisse unten und nicht seine Regeln."
+          },
+          {
+            "id": "sampling_load_failures",
+            "title": "Plugin-Ladefehler",
+            "tip": "Plugin-Ladefehler pro Minute, je Gruppe, fehlgeschlagenem Plugin und Ursache. Der Abgleich schlägt offen fehl, jeder Wert hier bedeutet also, dass die Kette OHNE ein von der Konfiguration verlangtes Plugin läuft — das Sampling tut nicht, was es sagt. Plugin-Name und Ursache erscheinen nur in diesem Panel."
+          },
+          {
+            "id": "sampling_execution_rate",
+            "title": "Decide-Rate nach Ergebnis",
+            "tip": "Decide()-Aufrufe pro Sekunde, nach Plugin und Ergebnis. Bei panic, length_mismatch oder late sofort alarmieren: Sie zeigen defektes Plugin-Verhalten oder einen Aufruf, der das Merge-Timeout überlebt hat. decide_error kann vorübergehend sein."
+          },
+          {
+            "id": "sampling_decide_latency",
+            "title": "Decide-Latenz",
+            "tip": "Laufzeit erfolgreicher Decide()-Aufrufe je Plugin — p99 nimmt die schlechteste Gruppe, der Mittelwert mittelt sie. Leite je Plugin ein SLO aus einer gesunden Baseline ab; ein absichtlich teures Plugin ist kein Fehler.",
+            "expressionLabels": [
+              "p99",
+              "Mittelwert"
+            ]
+          },
+          {
+            "id": "sampling_time_per_trace",
+            "title": "Plugin-Zeit / Trace",
+            "tip": "Laufzeit der gesamten Plugin-Kette je ausgewertetem Trace, je Gruppe — der beim Merge gezahlte Sampling-Aufwand im Zeitverlauf. Das Grafana-Board stromaufwärts teilt dies mit PromQLs `group_left` je Plugin auf; MAL hat kein Äquivalent, daher ist die Metrik die Kettensumme und die Aufteilung je Plugin steht unter Decide-Latenz. Halte sie deutlich unter dem --trace-pipeline-decide-timeout des data-Knotens (Standard 5 s)."
+          },
+          {
+            "id": "sampling_chain_batches",
+            "title": "Ketten-Batches",
+            "tip": "Ketten-Batches pro Sekunde, nach Ergebnis. circuit_open bedeutet, dass das Sampling für diese Gruppe derzeit ÜBERSPRUNGEN wird; timeout ist auf Kettenebene, weil der Host die Kette abbrechen kann, bevor ein Glied antwortet. Wie viele Traces jeder Batch trug, steht im Panel Batch-Größe p99."
+          },
+          {
+            "id": "sampling_batch_size_p99",
+            "title": "Batch-Größe p99",
+            "tip": "p99 der Traces, die der Plugin-Kette je erfolgreichem Batch vorgelegt wurden, je Gruppe. Lies es mit Ketten-Batches: Die Rate sagt, wie oft die Kette läuft, dies sagt, wie viel sie jedes Mal sieht."
+          },
+          {
+            "id": "sampling_sampler_rows",
+            "title": "Sampler-Zeilen",
+            "tip": "Von ausgewerteten Traces getragene span-/segment-Zeilen gegen die Zeilen, die die Sampler zum Verwerfen vorgeschlagen haben, aufgeschlüsselt nach der verantwortlichen Regel. Gleiche Einheit, also direkt vergleichbar; der resultierende Anteil steht im Panel Verworfene-Zeilen-Quote.",
+            "expressionLabels": [
+              "Zeilen",
+              "verworfene Zeilen"
+            ]
+          },
+          {
+            "id": "sampling_decisions_by_rule",
+            "title": "Sampler-Entscheidungen nach Regel",
+            "tip": "Was die First-Party-Sampler VORGESCHLAGEN haben, der ersten passenden Regel zugeordnet — nicht, was der Speicher bestätigt hat. Ein Trace kann über mehrere Merge- oder Finalisierungsrunden ausgewertet werden, und die Sicherheitsprüfungen können einen Trace behalten, für den das Plugin auf Verwerfen entschied: Lies Verwerfungs-Entscheidungen zusammen mit den Trace-Ergebnissen."
+          },
+          {
+            "id": "sampling_sampler_dropped_ratio",
+            "title": "Verworfene-Zeilen-Quote",
+            "tip": "Anteil der Zeilen, die die Sampler zum Verwerfen vorgeschlagen haben, je Gruppe. Vorgeschlagen, nicht bestätigt — die Sicherheitsprüfungen können einen Trace behalten, für den ein Plugin auf Verwerfen entschied. Behandle sie als unvollständig, wenn „Zeilenzahl nicht verfügbar\" ungleich null ist, da diese Traces im Nenner fehlen."
+          },
+          {
+            "id": "sampling_row_count_unavailable",
+            "title": "Zeilenzahl nicht verfügbar",
+            "tip": "Trace-Auswertungen, deren Nur-Metadaten-Projektion keine Zeilenzahl offenlegte, je Gruppe. Diese Traces fehlen in den Zeilensummen, jeder Wert ungleich null bedeutet also, dass die Verworfene-Zeilen-Quote über einen unvollständigen Nenner gebildet wird."
+          },
+          {
+            "id": "sampling_guard_work",
+            "title": "Guard- und Index-Arbeit",
+            "tip": "Bloom-Filter-Prüfungen sind der Fragment-Guard, der nach Trace-Fragmenten außerhalb der zusammengeführten Parts sucht; entfernte Einträge sind die Sekundärindex-Arbeit, die ein bestätigtes Verwerfen verursacht.",
+            "expressionLabels": [
+              "bloom-Prüfungen",
+              "sidx entfernt"
+            ]
+          },
+          {
+            "id": "sampling_safety_events",
+            "title": "Sicherheitsereignisse",
+            "tip": "Jede Reihe hier BEHÄLT Daten, die ein Sampler zum Verwerfen vorgeschlagen hat. Eine Verwerfungsquote unter dem Ziel wird hier erklärt, bevor die Plugin-Logik sie erklärt.",
+            "expressionLabels": [
+              "übergroß",
+              "uneindeutig",
+              "guard verschoben",
+              "guard-Budget erschöpft",
+              "Veröffentlichung abgelehnt",
+              "verlustfreier Retry",
+              "guard übersprungen"
+            ]
+          },
+          {
+            "id": "sampling_plugin_errors",
+            "title": "Kettenfehler nach Ursache",
+            "tip": "Fehler auf Kettenebene, die das Behalten des gesamten Batch im offenen Fehlerfall erzwungen haben."
+          },
+          {
+            "id": "sampling_link_bypasses",
+            "title": "Übersprungene Glieder",
+            "tip": "Einzelne Glieder, die nach decide_error, length_mismatch oder panic übersprungen wurden. Ein Ketten-Batch kann success melden, während ein Glied übersprungen und seine Eingabe behalten wurde — deshalb wird dies getrennt von Kettenfehlern gezählt."
+          },
+          {
+            "id": "sampling_reconciliation",
+            "title": "Registrierungen und Updates",
+            "tip": "Rate von Pipeline-Registrierung, -Update und -Entfernung. Ein Ergebnis rejected bedeutet, dass OAP eine Pipeline-Konfiguration gesendet hat, die der data-Knoten nicht akzeptiert.",
+            "expressionLabels": [
+              "registriert",
+              "aktualisiert",
+              "entfernt"
+            ]
+          },
+          {
+            "id": "sampling_drop_set_pressure",
+            "title": "Auswirkung der Drop-Set-Grenze",
+            "tip": "Die Menge der zu verwerfenden Trace-IDs je Merge ist speicherbegrenzt. Ist die Grenze erreicht, behält der Merge den Rest — deshalb stagniert die Verwerfungsquote unter Last, statt den Regeln zu folgen."
+          },
+          {
+            "id": "sampling_drop_set_size",
+            "title": "Drop-Set-Größe p99",
+            "tip": "p99 der je Merge-lane gehaltenen zu verwerfenden Trace-IDs. Lies sie zusammen mit dem Panel Drop-Set-Budget: Wächst die Menge über das Budget, nimmt der Merge keine Verwerfungen mehr an und behält den Rest — das erscheint als Auswirkung der Drop-Set-Grenze."
+          },
+          {
+            "id": "sampling_drop_set_budget",
+            "title": "Drop-Set-Budget",
+            "tip": "Speicherbudget je Merge für bestätigt verworfene Trace-IDs, je Gruppe — die Grenze, auf die die Drop-Set-Größe trifft. Es wird je Gruppe aufgelöst, Gruppen können also unterschiedliche Budgets tragen; das mit dem kleineren erreicht seine Grenze zuerst. Flach, solange der data-Knoten nicht neu abgestimmt wird."
+          },
+          {
+            "id": "sampling_finalize_rounds",
+            "title": "Finalisierungsrunden",
+            "tip": "Höchste Zahl an Finalisierungsrunden, die über die abgekühlten Shards jeder Gruppe beobachtet wurde. Ob einem Shard die Runden ausgegangen sind, steht im Panel Finalisierung terminal."
+          },
+          {
+            "id": "sampling_finalize_terminal",
+            "title": "Finalisierung terminal",
+            "tip": "1, wenn ein abgekühlter Shard der Gruppe terminal ist und keine weitere Finalisierungsrunde laufen kann — für diese Daten findet kein Trace-Sampling mehr statt, das Gespeicherte ist endgültig. Sonst 0. Über die Zeit dargestellt, damit der Übergang sichtbar ist."
+          },
+          {
+            "id": "sampling_telemetry_safety",
+            "title": "Host-Schutz der Plugin-Telemetrie",
+            "tip": "Der Host begrenzt, was ein Plugin veröffentlichen darf (100 Label-Wert-Reihen je Instrument), und drosselt seine Logs. Ungleich null bedeutet, dass die EIGENE Telemetrie des Plugins beschnitten wird — die Entscheidungsmetriken oben sind dann unvollständig, das Sampling selbst ist unberührt.",
+            "expressionLabels": [
+              "Reihen abgelehnt",
+              "Logs verworfen",
+              "Telemetrie-panic"
+            ]
+          }
+        ]
+      }
+    ]
   }
 }
diff --git a/apps/bff/src/bundled_templates/layers/banyandb.i18n.es.json b/apps/bff/src/bundled_templates/layers/banyandb.i18n.es.json
index 7fe4a7dd..beb11af8 100644
--- a/apps/bff/src/bundled_templates/layers/banyandb.i18n.es.json
+++ b/apps/bff/src/bundled_templates/layers/banyandb.i18n.es.json
@@ -469,5 +469,176 @@
         "title": "Bytes de part-sync / s"
       }
     ]
+  },
+  "dashboardExtPages": {
+    "service": [
+      {
+        "id": "trace-sampling",
+        "name": "Muestreo de trazas",
+        "widgets": [
+          {
+            "id": "sampling_outcomes",
+            "title": "Resultados de trazas",
+            "tip": "Retenidas + descartadas explican las trazas evaluadas. Las inmaduras nunca fueron elegibles: sus fragmentos siguen dentro del límite de madurez, así que no son un fallo del muestreo.",
+            "expressionLabels": [
+              "evaluadas",
+              "retenidas",
+              "descartadas",
+              "inmaduras"
+            ]
+          },
+          {
+            "id": "sampling_active_kpi",
+            "title": "Muestreadores activos",
+            "tip": "Plugins de muestreo registrados por grupo de almacenamiento. Un grupo ausente de esta lista no tiene pipeline: sus trazas nunca se evalúan, así que no se descarta nada.",
+            "tableHeaders": [
+              "Grupo",
+              "Muestreadores"
+            ]
+          },
+          {
+            "id": "sampling_drop_ratio_by_group",
+            "title": "Proporción de descarte por grupo",
+            "tip": "Proporción de descarte confirmada por grupo de almacenamiento, por paso: descartadas sobre evaluadas tras las salvaguardas de BanyanDB. Deliberadamente por grupo en lugar de un único indicador de clúster: una tarjeta de clúster sería la media sin ponderar de las proporciones por paso, así que un minuto tranquilo pesaría igual que uno ocupado. Un grupo por debajo de los demás suele explicarse por los eventos de seguridad de abajo, no por sus reglas."
+          },
+          {
+            "id": "sampling_load_failures",
+            "title": "Fallos de carga de plugins",
+            "tip": "Fallos de carga de plugins por minuto, por grupo, plugin fallido y motivo. La reconciliación falla en abierto, así que cualquier valor aquí significa que la cadena funciona SIN un plugin que la configuración pedía: el muestreo no hace lo que dice. El nombre del plugin y el motivo solo aparecen en este panel."
+          },
+          {
+            "id": "sampling_execution_rate",
+            "title": "Tasa de Decide por resultado",
+            "tip": "Llamadas a Decide() por segundo, por plugin y resultado. Alerta de inmediato ante panic, length_mismatch o late: indican un comportamiento roto del plugin o una llamada que sobrevivió al tiempo límite de la fusión. decide_error puede ser transitorio."
+          },
+          {
+            "id": "sampling_decide_latency",
+            "title": "Latencia de Decide",
+            "tip": "Tiempo real de las llamadas a Decide() correctas por plugin: el p99 toma el peor grupo, la media los promedia. Deriva un SLO por plugin de una línea base sana; un plugin intencionadamente costoso no es un fallo.",
+            "expressionLabels": [
+              "p99",
+              "media"
+            ]
+          },
+          {
+            "id": "sampling_time_per_trace",
+            "title": "Tiempo de plugin / traza",
+            "tip": "Tiempo real de toda la cadena de plugins por traza evaluada, por grupo — el coste del muestreo pagado en la fusión, a lo largo del tiempo. El panel de Grafana upstream lo divide por plugin con el `group_left` de PromQL; MAL no tiene equivalente, así que la métrica es el total de la cadena y el desglose por plugin está en Latencia de Decide. Manténlo holgadamente por debajo del --trace-pipeline-decide-timeout del nodo data (5 s por omisión)."
+          },
+          {
+            "id": "sampling_chain_batches",
+            "title": "Lotes de la cadena",
+            "tip": "Lotes de la cadena por segundo, por resultado. circuit_open significa que el muestreo está OMITIDO ahora mismo para ese grupo; timeout es de nivel de cadena porque el host puede abandonarla antes de que un eslabón responda. Las trazas que llevó cada lote están en el panel Tamaño de lote p99."
+          },
+          {
+            "id": "sampling_batch_size_p99",
+            "title": "Tamaño de lote p99",
+            "tip": "p99 de trazas presentadas a la cadena de plugins por lote correcto, por grupo. Léelo con Lotes de la cadena: la tasa dice con qué frecuencia se ejecuta la cadena, esto dice cuánto ve cada vez."
+          },
+          {
+            "id": "sampling_sampler_rows",
+            "title": "Filas del muestreador",
+            "tip": "Filas de span / segment que llevan las trazas evaluadas, frente a las filas que los muestreadores propusieron descartar, desglosadas por la regla responsable. Misma unidad, así que son directamente comparables; la proporción resultante está en el panel Proporción de filas descartadas.",
+            "expressionLabels": [
+              "filas",
+              "filas descartadas"
+            ]
+          },
+          {
+            "id": "sampling_decisions_by_rule",
+            "title": "Decisiones del muestreador por regla",
+            "tip": "Lo que los muestreadores de primera parte PROPUSIERON, atribuido a la primera regla coincidente, no lo que el almacenamiento confirmó. Una traza puede evaluarse en varias rondas de fusión o finalización, y las salvaguardas pueden retener una traza que el plugin votó descartar: lee las decisiones de descarte junto a Resultados de trazas."
+          },
+          {
+            "id": "sampling_sampler_dropped_ratio",
+            "title": "Proporción de filas descartadas",
+            "tip": "Proporción de filas que los muestreadores propusieron descartar, por grupo. Propuesta, no confirmada: las salvaguardas pueden retener una traza que un plugin votó descartar. Trátala como incompleta cuando Recuento de filas no disponible sea distinto de cero, ya que esas trazas faltan en el denominador."
+          },
+          {
+            "id": "sampling_row_count_unavailable",
+            "title": "Recuento de filas no disponible",
+            "tip": "Evaluaciones de trazas cuya proyección de solo metadatos no expuso recuento de filas, por grupo. Esas trazas se omiten de los totales, así que cualquier valor distinto de cero significa que la Proporción de filas descartadas se calcula sobre un denominador incompleto."
+          },
+          {
+            "id": "sampling_guard_work",
+            "title": "Trabajo de guard e índice",
+            "tip": "Las sondas de filtro bloom son el guard de fragmentos comprobando si hay fragmentos de la traza fuera de las partes que se fusionan; las entradas purgadas son el trabajo en el índice secundario que causa un descarte confirmado.",
+            "expressionLabels": [
+              "sondas bloom",
+              "sidx purgado"
+            ]
+          },
+          {
+            "id": "sampling_safety_events",
+            "title": "Eventos de seguridad",
+            "tip": "Cada serie aquí RETIENE datos que un muestreador propuso descartar. Una proporción de descarte por debajo del objetivo se explica aquí antes que por la lógica del plugin.",
+            "expressionLabels": [
+              "sobredimensionadas",
+              "ambiguas",
+              "guard diferido",
+              "presupuesto de guard agotado",
+              "publicación rechazada",
+              "reintento sin pérdida",
+              "guard omitido"
+            ]
+          },
+          {
+            "id": "sampling_plugin_errors",
+            "title": "Errores de cadena por motivo",
+            "tip": "Errores de nivel de cadena que forzaron la retención en abierto de todo el lote."
+          },
+          {
+            "id": "sampling_link_bypasses",
+            "title": "Eslabones omitidos",
+            "tip": "Eslabones individuales omitidos tras decide_error, length_mismatch o panic. Un lote puede informar success mientras un eslabón fue omitido y su entrada retenida: por eso se cuenta aparte de los errores de cadena."
+          },
+          {
+            "id": "sampling_reconciliation",
+            "title": "Registros y actualizaciones",
+            "tip": "Tasa de registro, actualización y eliminación del pipeline. Un resultado rejected significa que OAP envió una configuración de pipeline que el nodo data no aceptó.",
+            "expressionLabels": [
+              "registrados",
+              "actualizados",
+              "eliminados"
+            ]
+          },
+          {
+            "id": "sampling_drop_set_pressure",
+            "title": "Impacto del tope del conjunto de descarte",
+            "tip": "El conjunto de IDs de trazas a descartar por fusión está limitado en memoria. Al alcanzar el tope, la fusión retiene el resto: por eso la proporción de descarte se estanca bajo carga en lugar de seguir las reglas."
+          },
+          {
+            "id": "sampling_drop_set_size",
+            "title": "Tamaño p99 del conjunto de rechazo",
+            "tip": "p99 de identificadores de trazas a descartar por lane de fusión. Léelo junto al panel Presupuesto del conjunto de rechazo: cuando el conjunto supera el presupuesto, la fusión deja de aceptar descartes y retiene el resto, lo que aparece como Impacto del tope."
+          },
+          {
+            "id": "sampling_drop_set_budget",
+            "title": "Presupuesto del conjunto de rechazo",
+            "tip": "Presupuesto de memoria por fusión para los identificadores de trazas descartadas confirmadas, por grupo: el techo con el que choca el tamaño del conjunto. Se resuelve por grupo, así que los grupos pueden llevar presupuestos distintos; el que tenga uno menor alcanza su techo antes. Plano salvo que se reajuste el nodo data."
+          },
+          {
+            "id": "sampling_finalize_rounds",
+            "title": "Rondas de finalización",
+            "tip": "Mayor número de rondas de finalización observado en los shards enfriados de cada grupo. Si un shard se ha quedado sin rondas está en el panel Finalización terminal."
+          },
+          {
+            "id": "sampling_finalize_terminal",
+            "title": "Finalización terminal",
+            "tip": "1 cuando algún shard enfriado del grupo es terminal y no puede ejecutar otra ronda de finalización: no habrá más muestreo de trazas para esos datos, así que lo almacenado es definitivo. 0 en caso contrario. Se grafica en el tiempo para que la transición sea visible."
+          },
+          {
+            "id": "sampling_telemetry_safety",
+            "title": "Seguridad del host de telemetría de plugins",
+            "tip": "El host limita lo que un plugin puede publicar (100 series de valores de etiqueta por instrumento) y aplica límite de tasa a sus logs. Un valor distinto de cero significa que la telemetría PROPIA del plugin está truncada: las métricas de decisión de arriba quedan incompletas, aunque el muestreo en sí no se ve afectado.",
+            "expressionLabels": [
+              "series rechazadas",
+              "logs descartados",
+              "panic de telemetría"
+            ]
+          }
+        ]
+      }
+    ]
   }
 }
diff --git a/apps/bff/src/bundled_templates/layers/banyandb.i18n.fr.json b/apps/bff/src/bundled_templates/layers/banyandb.i18n.fr.json
index 15fd51cf..db7f9dd4 100644
--- a/apps/bff/src/bundled_templates/layers/banyandb.i18n.fr.json
+++ b/apps/bff/src/bundled_templates/layers/banyandb.i18n.fr.json
@@ -469,5 +469,176 @@
         "title": "Octets de part-sync / s"
       }
     ]
+  },
+  "dashboardExtPages": {
+    "service": [
+      {
+        "id": "trace-sampling",
+        "name": "Échantillonnage de traces",
+        "widgets": [
+          {
+            "id": "sampling_outcomes",
+            "title": "Devenir des traces",
+            "tip": "Conservées + rejetées expliquent les traces évaluées. Les traces immatures n'ont jamais été éligibles : leurs fragments sont encore dans la limite de maturité, ce n'est donc pas un échec de l'échantillonnage.",
+            "expressionLabels": [
+              "évaluées",
+              "conservées",
+              "rejetées",
+              "immatures"
+            ]
+          },
+          {
+            "id": "sampling_active_kpi",
+            "title": "Échantillonneurs actifs",
+            "tip": "Plugins d'échantillonnage enregistrés par groupe de stockage. Un groupe absent de cette liste n'a pas de pipeline — ses traces ne sont jamais évaluées, donc rien n'est rejeté.",
+            "tableHeaders": [
+              "Groupe",
+              "Échantillonneurs"
+            ]
+          },
+          {
+            "id": "sampling_drop_ratio_by_group",
+            "title": "Taux de rejet par groupe",
+            "tip": "Taux de rejet confirmé par groupe de stockage, par pas — rejetées sur évaluées après les garde-fous de BanyanDB. Délibérément par groupe plutôt qu'un unique indicateur de cluster : une carte de cluster serait la moyenne non pondérée des taux par pas, une minute calme compterait donc autant qu'une minute chargée. Un groupe en dessous des autres s'explique généralement par les événements de sécurité ci-dessous, pas par ses règles."
+          },
+          {
+            "id": "sampling_load_failures",
+            "title": "Échecs de chargement de plugins",
+            "tip": "Échecs de chargement de plugins par minute, par groupe, plugin en échec et motif. La réconciliation échoue en mode ouvert : toute valeur ici signifie que la chaîne tourne SANS un plugin demandé par la configuration — l'échantillonnage ne fait pas ce qu'il annonce. Le nom du plugin et le motif n'apparaissent que dans ce panneau."
+          },
+          {
+            "id": "sampling_execution_rate",
+            "title": "Débit de Decide par résultat",
+            "tip": "Appels à Decide() par seconde, par plugin et résultat. Alertez immédiatement sur panic, length_mismatch ou late : ils signalent un comportement cassé du plugin ou un appel ayant survécu au délai de fusion. decide_error peut être transitoire."
+          },
+          {
+            "id": "sampling_decide_latency",
+            "title": "Latence de Decide",
+            "tip": "Temps réel des appels Decide() réussis par plugin — le p99 prend le pire groupe, la moyenne les agrège. Dérivez un SLO par plugin d'une base de référence saine ; un plugin volontairement coûteux n'est pas une panne.",
+            "expressionLabels": [
+              "p99",
+              "moyenne"
+            ]
+          },
+          {
+            "id": "sampling_time_per_trace",
+            "title": "Temps de plugin / trace",
+            "tip": "Temps réel de toute la chaîne de plugins par trace évaluée, par groupe — le coût de l'échantillonnage payé lors de la fusion, dans le temps. Le tableau Grafana en amont le ventile par plugin avec le `group_left` de PromQL ; MAL n'a pas d'équivalent, donc la métrique est le total de la chaîne et la ventilation par plugin se trouve dans Latence de Decide. Gardez-le nettement sous le --trace-pipeline-decide-timeout du nœud data (5 s par défaut)."
+          },
+          {
+            "id": "sampling_chain_batches",
+            "title": "Lots de la chaîne",
+            "tip": "Lots de la chaîne par seconde, par résultat. circuit_open signifie que l'échantillonnage est actuellement CONTOURNÉ pour ce groupe ; timeout est au niveau de la chaîne car l'hôte peut l'abandonner avant qu'un maillon réponde. Les traces portées par chaque lot sont dans le panneau Taille de lot p99."
+          },
+          {
+            "id": "sampling_batch_size_p99",
+            "title": "Taille de lot p99",
+            "tip": "p99 des traces présentées à la chaîne de plugins par lot réussi, par groupe. À lire avec Lots de la chaîne : le débit dit à quelle fréquence la chaîne s'exécute, ceci dit combien elle voit à chaque fois."
+          },
+          {
+            "id": "sampling_sampler_rows",
+            "title": "Lignes de l'échantillonneur",
+            "tip": "Lignes span / segment portées par les traces évaluées, face aux lignes que les échantillonneurs ont proposé de rejeter, ventilées par la règle responsable. Même unité, donc directement comparables ; le taux qui en résulte est dans le panneau Taux de lignes rejetées.",
+            "expressionLabels": [
+              "lignes",
+              "lignes rejetées"
+            ]
+          },
+          {
+            "id": "sampling_decisions_by_rule",
+            "title": "Décisions de l'échantillonneur par règle",
+            "tip": "Ce que les échantillonneurs de première partie ont PROPOSÉ, attribué à la première règle correspondante — et non ce que le stockage a confirmé. Une trace peut être évaluée sur plusieurs cycles de fusion ou de finalisation, et les garde-fous peuvent conserver une trace que le plugin a votée rejetée : lisez les décisions de rejet en regard du devenir des traces."
+          },
+          {
+            "id": "sampling_sampler_dropped_ratio",
+            "title": "Taux de lignes rejetées",
+            "tip": "Part des lignes que les échantillonneurs ont proposé de rejeter, par groupe. Proposé, non confirmé — les garde-fous peuvent conserver une trace qu'un plugin a votée rejetée. Traitez-le comme incomplet dès que Compte de lignes indisponible est non nul, car ces traces manquent au dénominateur."
+          },
+          {
+            "id": "sampling_row_count_unavailable",
+            "title": "Compte de lignes indisponible",
+            "tip": "Évaluations de traces dont la projection de métadonnées seules n'exposait aucun compte de lignes, par groupe. Ces traces sont exclues des totaux, donc toute valeur non nulle signifie que le Taux de lignes rejetées porte sur un dénominateur incomplet."
+          },
+          {
+            "id": "sampling_guard_work",
+            "title": "Travail de guard et index",
+            "tip": "Les sondes de filtre bloom sont le guard de fragments vérifiant s'il existe des fragments de la trace hors des parts fusionnées ; les entrées purgées sont le travail sur l'index secondaire qu'entraîne un rejet confirmé.",
+            "expressionLabels": [
+              "sondes bloom",
+              "sidx purgé"
+            ]
+          },
+          {
+            "id": "sampling_safety_events",
+            "title": "Événements de sécurité",
+            "tip": "Chaque série ici CONSERVE des données qu'un échantillonneur a proposé de rejeter. Un taux de rejet sous l'objectif s'explique ici avant de s'expliquer par la logique du plugin.",
+            "expressionLabels": [
+              "surdimensionnées",
+              "ambiguës",
+              "guard différé",
+              "budget de guard épuisé",
+              "publication refusée",
+              "reprise sans perte",
+              "guard contourné"
+            ]
+          },
+          {
+            "id": "sampling_plugin_errors",
+            "title": "Erreurs de chaîne par motif",
+            "tip": "Erreurs au niveau de la chaîne ayant forcé la conservation en mode ouvert de tout le lot."
+          },
+          {
+            "id": "sampling_link_bypasses",
+            "title": "Maillons contournés",
+            "tip": "Maillons individuels contournés après decide_error, length_mismatch ou panic. Un lot peut rapporter success alors qu'un maillon a été contourné et son entrée conservée — d'où un comptage séparé des erreurs de chaîne."
+          },
+          {
+            "id": "sampling_reconciliation",
+            "title": "Enregistrements et mises à jour",
+            "tip": "Débit d'enregistrement, de mise à jour et de suppression du pipeline. Un résultat rejected signifie qu'OAP a envoyé une configuration de pipeline que le nœud data refuse.",
+            "expressionLabels": [
+              "enregistrés",
+              "mis à jour",
+              "supprimés"
+            ]
+          },
+          {
+            "id": "sampling_drop_set_pressure",
+            "title": "Impact du plafond de l'ensemble de rejet",
+            "tip": "L'ensemble des identifiants de traces à rejeter par fusion est borné en mémoire. Au plafond, la fusion conserve le reste — c'est pourquoi le taux de rejet plafonne sous charge au lieu de suivre les règles."
+          },
+          {
+            "id": "sampling_drop_set_size",
+            "title": "Taille p99 de l'ensemble de rejet",
+            "tip": "p99 des identifiants de traces à rejeter par lane de fusion. À lire avec le panneau Budget de l'ensemble de rejet : quand l'ensemble dépasse le budget, la fusion cesse d'accepter les rejets et conserve le reste, ce qui apparaît dans Impact du plafond."
+          },
+          {
+            "id": "sampling_drop_set_budget",
+            "title": "Budget de l'ensemble de rejet",
+            "tip": "Budget mémoire par fusion pour les identifiants de traces rejetées confirmées, par groupe — le plafond que rencontre la taille de l'ensemble. Résolu par groupe, donc les groupes peuvent porter des budgets différents ; celui qui en a un plus petit atteint son plafond en premier. Plat sauf réglage du nœud data."
+          },
+          {
+            "id": "sampling_finalize_rounds",
+            "title": "Cycles de finalisation",
+            "tip": "Plus grand nombre de cycles de finalisation observé sur les shards refroidis de chaque groupe. Si un shard n'a plus de cycles, voir le panneau Finalisation terminale."
+          },
+          {
+            "id": "sampling_finalize_terminal",
+            "title": "Finalisation terminale",
+            "tip": "1 quand un shard refroidi du groupe est terminal et ne peut plus exécuter de cycle de finalisation — plus aucun échantillonnage de traces n'aura lieu pour ces données, donc ce qui est stocké est définitif. 0 sinon. Tracé dans le temps pour que la transition soit visible."
+          },
+          {
+            "id": "sampling_telemetry_safety",
+            "title": "Sécurité hôte de la télémétrie des plugins",
+            "tip": "L'hôte plafonne ce qu'un plugin peut publier (100 séries de valeurs d'étiquette par instrument) et limite le débit de ses logs. Une valeur non nulle signifie que la télémétrie PROPRE du plugin est tronquée — les métriques de décision ci-dessus sont alors incomplètes, sans que l'échantillonnage lui-même soit affecté.",
+            "expressionLabels": [
+              "séries refusées",
+              "logs abandonnés",
+              "panic de télémétrie"
+            ]
+          }
+        ]
+      }
+    ]
   }
 }
diff --git a/apps/bff/src/bundled_templates/layers/banyandb.i18n.ja.json b/apps/bff/src/bundled_templates/layers/banyandb.i18n.ja.json
index d38c2b46..53def407 100644
--- a/apps/bff/src/bundled_templates/layers/banyandb.i18n.ja.json
+++ b/apps/bff/src/bundled_templates/layers/banyandb.i18n.ja.json
@@ -469,5 +469,176 @@
         "title": "Part-sync バイト数 / s"
       }
     ]
+  },
+  "dashboardExtPages": {
+    "service": [
+      {
+        "id": "trace-sampling",
+        "name": "トレースサンプリング",
+        "widgets": [
+          {
+            "id": "sampling_outcomes",
+            "title": "トレース処理結果",
+            "tip": "保留 + 破棄が評価トレースの内訳です。未成熟トレースはそもそも対象外 — フラグメントがまだ成熟境界の内側にあるだけで、サンプリングの失敗ではありません。",
+            "expressionLabels": [
+              "評価済み",
+              "保留",
+              "破棄",
+              "未成熟"
+            ]
+          },
+          {
+            "id": "sampling_active_kpi",
+            "title": "稼働中サンプラー",
+            "tip": "ストレージグループごとに登録されたサンプラープラグイン。一覧に無いグループはパイプラインを持たず、そのトレースは評価されないため何も破棄されません。",
+            "tableHeaders": [
+              "グループ",
+              "サンプラー"
+            ]
+          },
+          {
+            "id": "sampling_drop_ratio_by_group",
+            "title": "グループ別破棄率",
+            "tip": "ストレージグループごと・各時点の確定した破棄率 — BanyanDB のセーフティガード適用後の破棄/評価です。あえてグループ別で、単一のクラスタカードにはしていません。クラスタカードは各時点の比率を重みなしで平均するため、アイドルな 1 分が繁忙な 1 分と同じ重みを持ってしまいます。あるグループが他より低い場合、通常は下記のセーフティイベントで説明できます。"
+          },
+          {
+            "id": "sampling_load_failures",
+            "title": "プラグイン読み込み失敗",
+            "tip": "毎分のプラグイン読み込み失敗数(グループ・失敗したプラグイン・理由別)。設定同期はフェイルオープンするため、値が出ている時点でチェーンは設定が要求したプラグインなしで動作しています — サンプリングは設定どおりに機能していません。プラグイン名と理由はこのパネルにのみ表示されます。"
+          },
+          {
+            "id": "sampling_execution_rate",
+            "title": "結果別 Decide レート",
+            "tip": "Decide() 呼び出し数(毎秒)をプラグインと結果で分割。panic、length_mismatch、late は即時アラート対象 — プラグインの破損した挙動、またはマージタイムアウトを超えた呼び出しを意味します。decide_error は一時的な場合があります。"
+          },
+          {
+            "id": "sampling_decide_latency",
+            "title": "Decide レイテンシ",
+            "tip": "プラグインごとの成功した Decide() の実時間 — p99 は最悪のグループ、平均はグループ平均。健全なベースラインからプラグインごとの SLO を定めてください。意図的に高コストなプラグインは障害ではありません。",
+            "expressionLabels": [
+              "p99",
+              "平均"
+            ]
+          },
+          {
+            "id": "sampling_time_per_trace",
+            "title": "プラグイン時間 / トレース",
+            "tip": "評価トレース 1 件あたりのチェーン全体の実時間(グループ別) — マージ時に支払うサンプリングのオーバーヘッドの推移。上流の Grafana は PromQL の group_left でプラグイン別に分割しますが、MAL に同等機能はないためこの指標はチェーン合計で、プラグイン別は Decide レイテンシを参照してください。data ノードの --trace-pipeline-decide-timeout(既定 5 秒)を十分に下回るよう保ってください。"
+          },
+          {
+            "id": "sampling_chain_batches",
+            "title": "チェーンバッチ",
+            "tip": "結果別の毎秒チェーンバッチ数。circuit_open はそのグループのサンプリングが現在バイパスされている状態、timeout はチェーン単位(ホストはリンクの応答前にチェーンを放棄しうる)です。各バッチが運んだトレース数は「バッチサイズ p99」パネルにあります。"
+          },
+          {
+            "id": "sampling_batch_size_p99",
+            "title": "バッチサイズ p99",
+            "tip": "成功したバッチ 1 件がプラグインチェーンに提示したトレース数の p99(グループ別)。「チェーンバッチ」と併読してください。バッチレートはチェーンの実行頻度を、こちらは 1 回あたりの処理量を示します。"
+          },
+          {
+            "id": "sampling_sampler_rows",
+            "title": "サンプラー行数",
+            "tip": "評価済みトレースが持つ span / segment 行数と、サンプラーが破棄を提案した行数の対比(該当ルール別)。単位が同じなので直接比較でき、その割合は「破棄行の割合」パネルにあります。",
+            "expressionLabels": [
+              "行数",
+              "破棄提案行数"
+            ]
+          },
+          {
+            "id": "sampling_decisions_by_rule",
+            "title": "ルール別サンプラー判定",
+            "tip": "ファーストパーティのサンプラーが「提案した」判定を、最初に一致したルールに帰属させたもの — ストレージが確定した結果ではありません。1 つのトレースが複数のマージ/終端ラウンドで評価されることがあり、ガードがプラグインの破棄判定を覆して保留することもあるため、破棄判定は「トレース処理結果」と併読してください。"
+          },
+          {
+            "id": "sampling_sampler_dropped_ratio",
+            "title": "破棄行の割合",
+            "tip": "サンプラーが破棄を提案した行の割合(グループ別)。提案であり確定ではありません — セーフティガードがプラグインの破棄判定を覆して保留することがあります。「行数不明」が非ゼロのときは分母が欠けるため、不完全として扱ってください。"
+          },
+          {
+            "id": "sampling_row_count_unavailable",
+            "title": "行数不明",
+            "tip": "メタデータのみの射影で行数が得られなかったトレース評価数(グループ別)。これらは行数の合計から除外されるため、非ゼロのときは「破棄行の割合」の分母が不完全です。"
+          },
+          {
+            "id": "sampling_guard_work",
+            "title": "ガードとインデックス処理",
+            "tip": "bloom フィルタプローブは、マージ対象パートの外にトレースフラグメントがないかを確認するフラグメントガードの処理です。除去エントリは破棄確定に伴うセカンダリインデックス側の処理量です。",
+            "expressionLabels": [
+              "bloom プローブ",
+              "sidx 除去"
+            ]
+          },
+          {
+            "id": "sampling_safety_events",
+            "title": "セーフティイベント",
+            "tip": "ここの各系列はサンプラーが破棄を提案したデータを「保留」します。破棄率が目標を下回る場合、プラグインロジックより先にここで説明されます。",
+            "expressionLabels": [
+              "サイズ超過",
+              "判定不明",
+              "ガード保留",
+              "ガード予算超過",
+              "公開却下",
+              "無損失リトライ",
+              "ガードバイパス"
+            ]
+          },
+          {
+            "id": "sampling_plugin_errors",
+            "title": "理由別チェーンエラー",
+            "tip": "バッチ全体をフェイルオープンで保留させたチェーン単位のエラー。"
+          },
+          {
+            "id": "sampling_link_bypasses",
+            "title": "リンクバイパス",
+            "tip": "decide_error、length_mismatch、panic によりバイパスされた個々のリンク。あるリンクがバイパスされ入力が保留されても、チェーンバッチ全体は success を報告しえます — だからチェーンエラーとは別に計上します。"
+          },
+          {
+            "id": "sampling_reconciliation",
+            "title": "登録と更新",
+            "tip": "パイプラインの登録・更新・削除レート。結果 rejected は、data ノードが受け付けないパイプライン設定を OAP が送ったことを意味します。",
+            "expressionLabels": [
+              "登録",
+              "更新",
+              "削除"
+            ]
+          },
+          {
+            "id": "sampling_drop_set_pressure",
+            "title": "破棄セット上限の影響",
+            "tip": "マージごとの破棄トレース ID セットにはメモリ上限があります。上限に達するとマージは残りを保留します — 高負荷時に破棄率がルールに追随せず頭打ちになる原因です。"
+          },
+          {
+            "id": "sampling_drop_set_size",
+            "title": "破棄セットサイズ p99",
+            "tip": "マージ lane ごとに保持する破棄トレース ID 数の p99。「破棄セット予算」パネルと併読してください。セットが予算を超えるとマージは破棄の受け入れを止めて残りを保留し、それが「破棄セット上限の影響」として現れます。"
+          },
+          {
+            "id": "sampling_drop_set_budget",
+            "title": "破棄セット予算",
+            "tip": "確定した破棄トレース ID を保持するためのマージごとのメモリ予算(グループ別) — 破棄セットサイズがぶつかる上限です。グループ単位で解決されるためグループごとに異なりえます。予算の小さいグループが先に上限に達します。data ノードを再調整しない限り一定です。"
+          },
+          {
+            "id": "sampling_finalize_rounds",
+            "title": "終端ラウンド数",
+            "tip": "グループごとの冷却済みシャードで観測された最大の終端ラウンド数。シャードがラウンドを使い切ったかは「終端状態」パネルを参照してください。"
+          },
+          {
+            "id": "sampling_finalize_terminal",
+            "title": "終端状態",
+            "tip": "そのグループのいずれかの冷却済みシャードが終端に達し、これ以上終端ラウンドを実行できないとき 1 — そのデータにトレースサンプリングは行われず、保存済みの内容が最終です。それ以外は 0。切り替わりが見えるよう時系列で描画します。"
+          },
+          {
+            "id": "sampling_telemetry_safety",
+            "title": "プラグインテレメトリのホスト保護",
+            "tip": "ホストはプラグインが公開できる系列数(計器あたり 100 ラベル値系列)を制限し、ログをレート制限します。非ゼロはプラグイン「自身」のテレメトリが切り捨てられていることを示し、上記のサンプラー判定メトリクスは不完全になります(サンプリング自体には影響しません)。",
+            "expressionLabels": [
+              "系列却下",
+              "ログ破棄",
+              "テレメトリ panic"
+            ]
+          }
+        ]
+      }
+    ]
   }
 }
diff --git a/apps/bff/src/bundled_templates/layers/banyandb.i18n.ko.json b/apps/bff/src/bundled_templates/layers/banyandb.i18n.ko.json
index 54dbc7da..5b5d6c7d 100644
--- a/apps/bff/src/bundled_templates/layers/banyandb.i18n.ko.json
+++ b/apps/bff/src/bundled_templates/layers/banyandb.i18n.ko.json
@@ -469,5 +469,176 @@
         "title": "Part-sync 바이트 / s"
       }
     ]
+  },
+  "dashboardExtPages": {
+    "service": [
+      {
+        "id": "trace-sampling",
+        "name": "트레이스 샘플링",
+        "widgets": [
+          {
+            "id": "sampling_outcomes",
+            "title": "트레이스 처리 결과",
+            "tip": "보존 + 폐기가 평가된 트레이스를 구성합니다. 미성숙 트레이스는 애초에 대상이 아니며 — 조각이 아직 성숙 경계 안에 있을 뿐, 샘플링 실패가 아닙니다.",
+            "expressionLabels": [
+              "평가됨",
+              "보존됨",
+              "폐기됨",
+              "미성숙"
+            ]
+          },
+          {
+            "id": "sampling_active_kpi",
+            "title": "활성 샘플러",
+            "tip": "스토리지 그룹별 등록된 샘플러 플러그인. 목록에 없는 그룹은 파이프라인이 없어 트레이스가 평가되지 않으므로 아무것도 폐기되지 않습니다.",
+            "tableHeaders": [
+              "그룹",
+              "샘플러"
+            ]
+          },
+          {
+            "id": "sampling_drop_ratio_by_group",
+            "title": "그룹별 폐기 비율",
+            "tip": "스토리지 그룹별·각 시점의 확정 폐기 비율 — BanyanDB 안전 가드 적용 후의 폐기/평가입니다. 단일 클러스터 카드가 아니라 의도적으로 그룹별입니다. 클러스터 카드는 각 시점 비율을 가중치 없이 평균하므로 한가한 1분이 바쁜 1분과 같은 비중을 갖습니다. 특정 그룹이 낮다면 보통 아래 안전 이벤트로 설명됩니다."
+          },
+          {
+            "id": "sampling_load_failures",
+            "title": "플러그인 로드 실패",
+            "tip": "분당 플러그인 로드 실패 수(그룹·실패한 플러그인·이유별). 설정 동기화는 실패 시 개방되므로, 값이 있다는 것은 설정이 요구한 플러그인 없이 체인이 동작한다는 뜻입니다 — 샘플링이 설정대로 작동하지 않습니다. 플러그인 이름과 이유는 이 패널에서만 볼 수 있습니다."
+          },
+          {
+            "id": "sampling_execution_rate",
+            "title": "결과별 Decide 비율",
+            "tip": "플러그인과 결과별 초당 Decide() 호출 수. panic, length_mismatch, late는 즉시 알림 대상 — 플러그인 동작이 깨졌거나 호출이 병합 타임아웃을 넘긴 것입니다. decide_error는 일시적일 수 있습니다."
+          },
+          {
+            "id": "sampling_decide_latency",
+            "title": "Decide 지연",
+            "tip": "플러그인별 성공한 Decide() 실제 소요 시간 — p99는 최악 그룹, 평균은 그룹 평균. 건강한 기준선에서 플러그인별 SLO를 도출하세요. 의도적으로 비용이 큰 플러그인은 결함이 아닙니다.",
+            "expressionLabels": [
+              "p99",
+              "평균"
+            ]
+          },
+          {
+            "id": "sampling_time_per_trace",
+            "title": "플러그인 시간 / 트레이스",
+            "tip": "평가된 트레이스당 전체 체인의 실제 소요 시간(그룹별) — 병합 시 지불하는 샘플링 오버헤드의 추이. 업스트림 Grafana는 PromQL의 group_left로 플러그인별로 나누지만 MAL에는 해당 기능이 없어 이 지표는 체인 합계이며, 플러그인별 분할은 Decide 지연을 보세요. data 노드의 --trace-pipeline-decide-timeout(기본 5초)보다 충분히 낮게 유지하세요."
+          },
+          {
+            "id": "sampling_chain_batches",
+            "title": "체인 배치",
+            "tip": "결과별 초당 체인 배치 수. circuit_open은 해당 그룹의 샘플링이 현재 우회되고 있음을, timeout은 체인 수준(호스트가 링크 응답 전에 체인을 포기할 수 있음)을 뜻합니다. 각 배치가 담은 트레이스 수는 「배치 크기 p99」 패널에 있습니다."
+          },
+          {
+            "id": "sampling_batch_size_p99",
+            "title": "배치 크기 p99",
+            "tip": "성공한 배치 하나가 플러그인 체인에 제시한 트레이스 수의 p99(그룹별). 「체인 배치」와 함께 보세요. 배치 비율은 체인이 얼마나 자주 실행되는지를, 이 패널은 한 번에 얼마나 보는지를 알려줍니다."
+          },
+          {
+            "id": "sampling_sampler_rows",
+            "title": "샘플러 행 수",
+            "tip": "평가된 트레이스가 담은 span / segment 행 수와 샘플러가 폐기를 제안한 행 수의 대비(해당 규칙별). 단위가 같아 직접 비교할 수 있고, 그 비율은 「폐기 행 비율」 패널에 있습니다.",
+            "expressionLabels": [
+              "행 수",
+              "폐기 제안 행 수"
+            ]
+          },
+          {
+            "id": "sampling_decisions_by_rule",
+            "title": "규칙별 샘플러 판정",
+            "tip": "퍼스트파티 샘플러가 \"제안한\" 판정을 처음 일치한 규칙에 귀속시킨 값 — 스토리지가 확정한 결과가 아닙니다. 하나의 트레이스가 여러 병합·종결 라운드에서 평가될 수 있고, 가드가 플러그인의 폐기 판정을 뒤집어 보존할 수 있으므로 폐기 판정은 \"트레이스 처리 결과\"와 함께 읽으세요."
+          },
+          {
+            "id": "sampling_sampler_dropped_ratio",
+            "title": "폐기 행 비율",
+            "tip": "샘플러가 폐기를 제안한 행의 비율(그룹별). 제안이며 확정이 아닙니다 — 안전 가드가 플러그인의 폐기 판정을 뒤집어 보존할 수 있습니다. 「행 수 확인 불가」가 0이 아니면 분모가 누락되므로 불완전한 값으로 취급하세요."
+          },
+          {
+            "id": "sampling_row_count_unavailable",
+            "title": "행 수 확인 불가",
+            "tip": "메타데이터만 투영되어 행 수가 노출되지 않은 트레이스 평가 수(그룹별). 이 트레이스는 행 합계에서 제외되므로, 0이 아니면 「폐기 행 비율」의 분모가 불완전합니다."
+          },
+          {
+            "id": "sampling_guard_work",
+            "title": "가드 및 인덱스 작업",
+            "tip": "bloom 필터 프로브는 병합 대상 파트 밖에 트레이스 조각이 있는지 확인하는 조각 가드의 작업입니다. 정리된 항목은 폐기 확정이 유발한 보조 인덱스 작업량입니다.",
+            "expressionLabels": [
+              "bloom 프로브",
+              "sidx 정리"
+            ]
+          },
+          {
+            "id": "sampling_safety_events",
+            "title": "안전 이벤트",
+            "tip": "여기의 모든 계열은 샘플러가 폐기를 제안한 데이터를 \"보존\"합니다. 폐기 비율이 목표에 미달할 때 플러그인 로직보다 먼저 여기서 설명됩니다.",
+            "expressionLabels": [
+              "크기 초과",
+              "판정 불명확",
+              "가드 보류",
+              "가드 예산 소진",
+              "발행 거부",
+              "무손실 재시도",
+              "가드 우회"
+            ]
+          },
+          {
+            "id": "sampling_plugin_errors",
+            "title": "원인별 체인 오류",
+            "tip": "배치 전체를 실패 개방으로 보존하게 만든 체인 수준 오류."
+          },
+          {
+            "id": "sampling_link_bypasses",
+            "title": "링크 우회",
+            "tip": "decide_error, length_mismatch, panic으로 우회된 개별 링크. 한 링크가 우회되어 입력이 보존되어도 체인 배치는 success를 보고할 수 있습니다 — 그래서 체인 오류와 별도로 집계합니다."
+          },
+          {
+            "id": "sampling_reconciliation",
+            "title": "등록 및 업데이트",
+            "tip": "파이프라인 등록·업데이트·제거 비율. 결과가 rejected면 OAP가 data 노드가 받아들이지 않는 파이프라인 설정을 보낸 것입니다.",
+            "expressionLabels": [
+              "등록",
+              "업데이트",
+              "제거"
+            ]
+          },
+          {
+            "id": "sampling_drop_set_pressure",
+            "title": "폐기 세트 상한 영향",
+            "tip": "병합별 폐기 트레이스 ID 세트에는 메모리 상한이 있습니다. 상한에 도달하면 병합은 나머지를 보존합니다 — 고부하에서 폐기 비율이 규칙을 따르지 않고 정체되는 이유입니다."
+          },
+          {
+            "id": "sampling_drop_set_size",
+            "title": "폐기 세트 크기 p99",
+            "tip": "병합 lane별로 보유하는 폐기 트레이스 ID 수의 p99. 「폐기 세트 예산」 패널와 함께 보세요. 세트가 예산을 넘으면 병합은 폐기 수용을 중단하고 나머지를 보존하며, 이는 「폐기 세트 상한 영향」으로 나타납니다."
+          },
+          {
+            "id": "sampling_drop_set_budget",
+            "title": "폐기 세트 예산",
+            "tip": "확정된 폐기 트레이스 ID를 담는 병합별 메모리 예산(그룹별) — 폐기 세트 크기가 부딪히는 상한입니다. 그룹 단위로 결정되므로 그룹마다 다를 수 있고, 예산이 작은 그룹이 먼저 상한에 도달합니다. data 노드를 재조정하지 않으면 일정합니다."
+          },
+          {
+            "id": "sampling_finalize_rounds",
+            "title": "종결 라운드 수",
+            "tip": "그룹별 냉각된 샤드에서 관측된 최고 종결 라운드 수. 샤드가 라운드를 다 썼는지는 「종결 상태」 패널을 보세요."
+          },
+          {
+            "id": "sampling_finalize_terminal",
+            "title": "종결 상태",
+            "tip": "해당 그룹의 냉각된 샤드 중 하나라도 종결 상태여서 더 이상 종결 라운드를 실행할 수 없으면 1 — 그 데이터에는 트레이스 샘플링이 더 수행되지 않으며 저장된 내용이 최종입니다. 그렇지 않으면 0. 전환 시점을 볼 수 있도록 시간축으로 표시합니다."
+          },
+          {
+            "id": "sampling_telemetry_safety",
+            "title": "플러그인 텔레메트리 호스트 보호",
+            "tip": "호스트는 플러그인이 발행할 수 있는 계열 수(계측기당 라벨 값 계열 100개)를 제한하고 로그를 레이트 제한합니다. 0이 아니면 플러그인 \"자체\" 텔레메트리가 잘리고 있다는 뜻이며, 위의 샘플러 판정 지표는 불완전해집니다(샘플링 자체는 영향 없음).",
+            "expressionLabels": [
+              "계열 거부",
+              "로그 폐기",
+              "텔레메트리 panic"
+            ]
+          }
+        ]
+      }
+    ]
   }
 }
diff --git a/apps/bff/src/bundled_templates/layers/banyandb.i18n.pt.json b/apps/bff/src/bundled_templates/layers/banyandb.i18n.pt.json
index 6790f42e..42e76aef 100644
--- a/apps/bff/src/bundled_templates/layers/banyandb.i18n.pt.json
+++ b/apps/bff/src/bundled_templates/layers/banyandb.i18n.pt.json
@@ -469,5 +469,176 @@
         "title": "Bytes de part-sync / s"
       }
     ]
+  },
+  "dashboardExtPages": {
+    "service": [
+      {
+        "id": "trace-sampling",
+        "name": "Amostragem de traces",
+        "widgets": [
+          {
+            "id": "sampling_outcomes",
+            "title": "Resultados dos traces",
+            "tip": "Retidos + descartados explicam os traces avaliados. Os imaturos nunca foram elegíveis: seus fragmentos ainda estão dentro do limite de maturidade, portanto não são falha da amostragem.",
+            "expressionLabels": [
+              "avaliados",
+              "retidos",
+              "descartados",
+              "imaturos"
+            ]
+          },
+          {
+            "id": "sampling_active_kpi",
+            "title": "Amostradores ativos",
+            "tip": "Plugins de amostragem registrados por grupo de armazenamento. Um grupo ausente desta lista não tem pipeline: seus traces nunca são avaliados, portanto nada é descartado.",
+            "tableHeaders": [
+              "Grupo",
+              "Amostradores"
+            ]
+          },
+          {
+            "id": "sampling_drop_ratio_by_group",
+            "title": "Proporção de descarte por grupo",
+            "tip": "Proporção de descarte confirmada por grupo de armazenamento, por passo: descartados sobre avaliados após as salvaguardas do BanyanDB. Deliberadamente por grupo em vez de um único indicador de cluster: um cartão de cluster seria a média não ponderada das proporções por passo, então um minuto calmo pesaria igual a um movimentado. Um grupo abaixo dos outros geralmente é explicado pelos eventos de segurança abaixo, não por suas regras."
+          },
+          {
+            "id": "sampling_load_failures",
+            "title": "Falhas de carga de plugins",
+            "tip": "Falhas de carga de plugins por minuto, por grupo, plugin que falhou e motivo. A reconciliação falha em aberto, portanto qualquer valor aqui significa que a cadeia roda SEM um plugin que a configuração pediu: a amostragem não faz o que diz. O nome do plugin e o motivo aparecem somente neste painel."
+          },
+          {
+            "id": "sampling_execution_rate",
+            "title": "Taxa de Decide por resultado",
+            "tip": "Chamadas a Decide() por segundo, por plugin e resultado. Alerte imediatamente para panic, length_mismatch ou late: indicam comportamento quebrado do plugin ou uma chamada que sobreviveu ao tempo limite da mesclagem. decide_error pode ser transitório."
+          },
+          {
+            "id": "sampling_decide_latency",
+            "title": "Latência de Decide",
+            "tip": "Tempo real das chamadas a Decide() bem-sucedidas por plugin: o p99 toma o pior grupo, a média os agrega. Derive um SLO por plugin de uma linha de base saudável; um plugin intencionalmente custoso não é uma falha.",
+            "expressionLabels": [
+              "p99",
+              "média"
+            ]
+          },
+          {
+            "id": "sampling_time_per_trace",
+            "title": "Tempo de plugin / trace",
+            "tip": "Tempo real de toda a cadeia de plugins por trace avaliado, por grupo — o custo da amostragem pago na mesclagem, ao longo do tempo. O painel do Grafana upstream divide isso por plugin com o `group_left` do PromQL; o MAL não tem equivalente, então a métrica é o total da cadeia e o detalhamento por plugin está em Latência de Decide. Mantenha-o bem abaixo do --trace-pipeline-decide-timeout do nó data (5 s por padrão)."
+          },
+          {
+            "id": "sampling_chain_batches",
+            "title": "Lotes da cadeia",
+            "tip": "Lotes da cadeia por segundo, por resultado. circuit_open significa que a amostragem está IGNORADA agora para aquele grupo; timeout é de nível de cadeia porque o host pode abandoná-la antes de um elo responder. Os traces que cada lote levou estão no painel Tamanho de lote p99."
+          },
+          {
+            "id": "sampling_batch_size_p99",
+            "title": "Tamanho de lote p99",
+            "tip": "p99 de traces apresentados à cadeia de plugins por lote bem-sucedido, por grupo. Leia com Lotes da cadeia: a taxa diz com que frequência a cadeia roda, isto diz quanto ela vê a cada vez."
+          },
+          {
+            "id": "sampling_sampler_rows",
+            "title": "Linhas do amostrador",
+            "tip": "Linhas de span / segment carregadas pelos traces avaliados, frente às linhas que os amostradores propuseram descartar, detalhadas pela regra responsável. Mesma unidade, portanto diretamente comparáveis; a proporção resultante está no painel Proporção de linhas descartadas.",
+            "expressionLabels": [
+              "linhas",
+              "linhas descartadas"
+            ]
+          },
+          {
+            "id": "sampling_decisions_by_rule",
+            "title": "Decisões do amostrador por regra",
+            "tip": "O que os amostradores de primeira parte PROPUSERAM, atribuído à primeira regra correspondente, e não o que o armazenamento confirmou. Um trace pode ser avaliado em várias rodadas de mesclagem ou finalização, e as salvaguardas podem retê-lo mesmo que o plugin tenha votado por descartar: leia as decisões de descarte junto a Resultados dos traces."
+          },
+          {
+            "id": "sampling_sampler_dropped_ratio",
+            "title": "Proporção de linhas descartadas",
+            "tip": "Proporção de linhas que os amostradores propuseram descartar, por grupo. Proposta, não confirmada: as salvaguardas podem reter um trace que um plugin votou por descartar. Trate-a como incompleta quando Contagem de linhas indisponível for diferente de zero, pois esses traces faltam no denominador."
+          },
+          {
+            "id": "sampling_row_count_unavailable",
+            "title": "Contagem de linhas indisponível",
+            "tip": "Avaliações de traces cuja projeção somente de metadados não expôs contagem de linhas, por grupo. Esses traces são omitidos dos totais, portanto qualquer valor diferente de zero significa que a Proporção de linhas descartadas é calculada sobre um denominador incompleto."
+          },
+          {
+            "id": "sampling_guard_work",
+            "title": "Trabalho de guard e índice",
+            "tip": "As sondas de filtro bloom são o guard de fragmentos verificando se há fragmentos do trace fora das partes sendo mescladas; as entradas removidas são o trabalho no índice secundário causado por um descarte confirmado.",
+            "expressionLabels": [
+              "sondas bloom",
+              "sidx removido"
+            ]
+          },
+          {
+            "id": "sampling_safety_events",
+            "title": "Eventos de segurança",
+            "tip": "Cada série aqui RETÉM dados que um amostrador propôs descartar. Uma proporção de descarte abaixo da meta é explicada aqui antes de ser explicada pela lógica do plugin.",
+            "expressionLabels": [
+              "superdimensionados",
+              "ambíguos",
+              "guard adiado",
+              "orçamento de guard esgotado",
+              "publicação rejeitada",
+              "nova tentativa sem perda",
+              "guard ignorado"
+            ]
+          },
+          {
+            "id": "sampling_plugin_errors",
+            "title": "Erros de cadeia por motivo",
+            "tip": "Erros de nível de cadeia que forçaram a retenção em aberto de todo o lote."
+          },
+          {
+            "id": "sampling_link_bypasses",
+            "title": "Elos ignorados",
+            "tip": "Elos individuais ignorados após decide_error, length_mismatch ou panic. Um lote pode reportar success enquanto um elo foi ignorado e sua entrada retida: por isso é contado separadamente dos erros de cadeia."
+          },
+          {
+            "id": "sampling_reconciliation",
+            "title": "Registros e atualizações",
+            "tip": "Taxa de registro, atualização e remoção do pipeline. Um resultado rejected significa que o OAP enviou uma configuração de pipeline que o nó data não aceitou.",
+            "expressionLabels": [
+              "registrados",
+              "atualizados",
+              "removidos"
+            ]
+          },
+          {
+            "id": "sampling_drop_set_pressure",
+            "title": "Impacto do teto do conjunto de descarte",
+            "tip": "O conjunto de IDs de traces a descartar por mesclagem é limitado em memória. Ao atingir o teto, a mesclagem retém o resto: é por isso que a proporção de descarte estagna sob carga em vez de acompanhar as regras."
+          },
+          {
+            "id": "sampling_drop_set_size",
+            "title": "Tamanho p99 do conjunto de descarte",
+            "tip": "p99 de identificadores de traces a descartar por lane de mesclagem. Leia junto ao painel Orçamento do conjunto de descarte: quando o conjunto excede o orçamento, a mesclagem para de aceitar descartes e retém o resto, o que aparece como Impacto do teto."
+          },
+          {
+            "id": "sampling_drop_set_budget",
+            "title": "Orçamento do conjunto de descarte",
+            "tip": "Orçamento de memória por mesclagem para os identificadores de traces descartados confirmados, por grupo: o teto que o tamanho do conjunto encontra. É resolvido por grupo, portanto os grupos podem ter orçamentos diferentes; o de menor orçamento atinge o teto primeiro. Plano a menos que o nó data seja reajustado."
+          },
+          {
+            "id": "sampling_finalize_rounds",
+            "title": "Rodadas de finalização",
+            "tip": "Maior número de rodadas de finalização observado nos shards esfriados de cada grupo. Se um shard ficou sem rodadas está no painel Finalização terminal."
+          },
+          {
+            "id": "sampling_finalize_terminal",
+            "title": "Finalização terminal",
+            "tip": "1 quando algum shard esfriado do grupo é terminal e não pode rodar outra rodada de finalização: não haverá mais amostragem de traces para aqueles dados, portanto o que está armazenado é definitivo. 0 caso contrário. Plotado ao longo do tempo para que a transição seja visível."
+          },
+          {
+            "id": "sampling_telemetry_safety",
+            "title": "Segurança do host de telemetria de plugins",
+            "tip": "O host limita o que um plugin pode publicar (100 séries de valores de rótulo por instrumento) e aplica limite de taxa aos seus logs. Um valor diferente de zero significa que a telemetria PRÓPRIA do plugin está truncada: as métricas de decisão acima ficam incompletas, embora a amostragem em si não seja afetada.",
+            "expressionLabels": [
+              "séries rejeitadas",
+              "logs descartados",
+              "panic de telemetria"
+            ]
+          }
+        ]
+      }
+    ]
   }
 }
diff --git a/apps/bff/src/bundled_templates/layers/banyandb.i18n.zh-CN.json b/apps/bff/src/bundled_templates/layers/banyandb.i18n.zh-CN.json
index ac8f6d60..a7791465 100644
--- a/apps/bff/src/bundled_templates/layers/banyandb.i18n.zh-CN.json
+++ b/apps/bff/src/bundled_templates/layers/banyandb.i18n.zh-CN.json
@@ -469,5 +469,176 @@
         "title": "Part-sync 字节数 / s"
       }
     ]
+  },
+  "dashboardExtPages": {
+    "service": [
+      {
+        "id": "trace-sampling",
+        "name": "追踪采样",
+        "widgets": [
+          {
+            "id": "sampling_outcomes",
+            "title": "追踪处理结果",
+            "tip": "保留数与丢弃数共同构成已评估追踪。未成熟追踪从未进入评估资格——其分片仍处于成熟边界内,因此并非采样失败。",
+            "expressionLabels": [
+              "已评估",
+              "已保留",
+              "已丢弃",
+              "未成熟"
+            ]
+          },
+          {
+            "id": "sampling_active_kpi",
+            "title": "活跃采样器",
+            "tip": "每个存储组已注册的采样器插件。未出现在列表中的组没有管道——其追踪永远不会被评估,因此不会有任何丢弃。",
+            "tableHeaders": [
+              "存储组",
+              "采样器"
+            ]
+          },
+          {
+            "id": "sampling_drop_ratio_by_group",
+            "title": "各组丢弃比例",
+            "tip": "各存储组每个时间点已提交的丢弃比例——经过 BanyanDB 安全护栏之后的已丢弃/已评估。刻意按组展示而非给出单一集群卡片:集群级卡片会对各时间点的比例做无权重平均,空闲的一分钟与繁忙的一分钟权重相同。某个组明显偏低时,通常由下方的安全事件解释,而非其规则本身。"
+          },
+          {
+            "id": "sampling_load_failures",
+            "title": "插件加载失败",
+            "tip": "每分钟插件加载失败次数,按组、失败插件与原因拆分。配置同步采用失败开放策略,因此出现任何数值都意味着插件链在缺少配置所要求的某个插件的情况下运行——采样并未按配置执行。插件名称与失败原因仅在此面板中展示。"
+          },
+          {
+            "id": "sampling_execution_rate",
+            "title": "各结果 Decide 速率",
+            "tip": "每秒 Decide() 调用次数,按插件与结果拆分。出现 panic、length_mismatch 或 late 应立即告警——它们意味着插件行为异常,或调用超出了合并超时。decide_error 可能是瞬时的。"
+          },
+          {
+            "id": "sampling_decide_latency",
+            "title": "Decide 延迟",
+            "tip": "各插件成功 Decide() 调用的实际耗时——p99 取最差的组,均值对各组取平均。请基于健康基线为每个插件设定 SLO;刻意开销较大的插件并非故障。",
+            "expressionLabels": [
+              "p99",
+              "均值"
+            ]
+          },
+          {
+            "id": "sampling_time_per_trace",
+            "title": "插件耗时 / 追踪",
+            "tip": "每条已评估追踪上整条插件链的实际耗时(按组),即合并阶段为采样付出的开销随时间的变化。上游 Grafana 面板使用 PromQL 的 group_left 按插件拆分;MAL 无此能力,因此该指标为整条链的合计,按插件的拆分见 Decide 延迟。应明显低于 data 节点的 --trace-pipeline-decide-timeout(默认 5 秒)。"
+          },
+          {
+            "id": "sampling_chain_batches",
+            "title": "插件链批次",
+            "tip": "每秒插件链批次数,按结果拆分。circuit_open 表示该组的采样当前被绕过;timeout 属于链级别,因为宿主可能在某个环节返回前就放弃整条链。每批次承载的追踪数见「批次大小 p99」面板。"
+          },
+          {
+            "id": "sampling_batch_size_p99",
+            "title": "批次大小 p99",
+            "tip": "每个成功批次呈现给插件链的追踪数 p99(按组)。与「插件链批次」对照阅读:批次速率说明链运行的频率,此面板说明每次看到多少内容。"
+          },
+          {
+            "id": "sampling_sampler_rows",
+            "title": "采样器行数",
+            "tip": "已评估追踪所携带的 span / segment 行数,与采样器建议丢弃的行数对照,并按命中的规则拆分。两者单位相同,可直接比较;由此得出的占比见「丢弃行占比」面板。",
+            "expressionLabels": [
+              "行数",
+              "建议丢弃行数"
+            ]
+          },
+          {
+            "id": "sampling_decisions_by_rule",
+            "title": "各规则采样判定",
+            "tip": "第一方采样器所\"建议\"的判定,归属到首个命中的规则——而非存储最终提交的结果。一条追踪可能在多轮合并或终结中被评估,安全护栏也可能保留插件判定为丢弃的追踪,因此请将丢弃判定与\"追踪处理结果\"对照阅读。"
+          },
+          {
+            "id": "sampling_sampler_dropped_ratio",
+            "title": "丢弃行占比",
+            "tip": "采样器建议丢弃的行数占比(按组)。这是「建议」而非已提交——安全护栏可能保留插件判定为丢弃的追踪。当「行数不可用」非零时,该比例应视为不完整,因为这些追踪不计入分母。"
+          },
+          {
+            "id": "sampling_row_count_unavailable",
+            "title": "行数不可用",
+            "tip": "仅有元数据投影、未暴露行数的追踪评估次数(按组)。这些追踪不计入行数合计,因此此处非零即意味着「丢弃行占比」的分母不完整。"
+          },
+          {
+            "id": "sampling_guard_work",
+            "title": "护栏与索引开销",
+            "tip": "bloom 过滤器探测是分片护栏在检查待合并分片之外是否存在该追踪的其他分片;已清理条目是确认丢弃后引发的二级索引开销。",
+            "expressionLabels": [
+              "bloom 探测",
+              "sidx 已清理"
+            ]
+          },
+          {
+            "id": "sampling_safety_events",
+            "title": "安全事件",
+            "tip": "此处每条曲线都会保留采样器建议丢弃的数据。丢弃比例低于预期时,应先由这里解释,而不是先归因于插件逻辑。",
+            "expressionLabels": [
+              "超大追踪",
+              "判定不明确",
+              "护栏推迟",
+              "护栏预算耗尽",
+              "发布被拒",
+              "无损重试",
+              "护栏绕过"
+            ]
+          },
+          {
+            "id": "sampling_plugin_errors",
+            "title": "各原因链级错误",
+            "tip": "导致整批数据失败开放保留的链级错误。"
+          },
+          {
+            "id": "sampling_link_bypasses",
+            "title": "环节绕过",
+            "tip": "因 decide_error、length_mismatch 或 panic 而被绕过的单个环节。一个批次可能整体报告 success,同时某个环节被绕过并保留其输入——这正是它与链级错误分开统计的原因。"
+          },
+          {
+            "id": "sampling_reconciliation",
+            "title": "注册与更新",
+            "tip": "管道注册、更新与移除速率。结果为 rejected 表示 OAP 推送了 data 节点拒绝接受的管道配置。",
+            "expressionLabels": [
+              "已注册",
+              "已更新",
+              "已移除"
+            ]
+          },
+          {
+            "id": "sampling_drop_set_pressure",
+            "title": "丢弃集上限影响",
+            "tip": "每次合并的待丢弃追踪 ID 集受内存上限约束。触及上限后合并会保留其余追踪——这正是高负载下丢弃比例趋于平台而不再跟随规则变化的原因。"
+          },
+          {
+            "id": "sampling_drop_set_size",
+            "title": "丢弃集大小 p99",
+            "tip": "每个合并 lane 每次合并持有的丢弃追踪 ID 数 p99。请与「丢弃集预算」面板对照阅读:当该集合超出预算时,合并将停止接受丢弃并保留其余追踪,这会体现为「丢弃集上限影响」。"
+          },
+          {
+            "id": "sampling_drop_set_budget",
+            "title": "丢弃集预算",
+            "tip": "每次合并用于存放已确认丢弃追踪 ID 的内存预算(按组)——即丢弃集大小所面对的上限。预算按组解析,因此各组可以不同;预算较小的组会先触及上限。除非重新调优 data 节点,否则保持不变。"
+          },
+          {
+            "id": "sampling_finalize_rounds",
+            "title": "终结轮次",
+            "tip": "各组冷却分片上观测到的最高终结轮次。分片是否已用尽轮次见「终结态」面板。"
+          },
+          {
+            "id": "sampling_finalize_terminal",
+            "title": "终结态",
+            "tip": "当该组中任一冷却分片进入终结态、无法再执行终结轮次时为 1——该部分数据不会再进行追踪采样,已存储的内容即为最终结果;否则为 0。按时间绘制以便看到状态切换的时刻。"
+          },
+          {
+            "id": "sampling_telemetry_safety",
+            "title": "插件遥测宿主保护",
+            "tip": "宿主限制插件可发布的指标量(每个仪表最多 100 个标签值序列)并对其日志限流。非零表示插件\"自身\"的遥测被截断——上方的采样器判定指标因此不完整,但采样本身不受影响。",
+            "expressionLabels": [
+              "序列被拒",
+              "日志丢弃",
+              "遥测 panic"
+            ]
+          }
+        ]
+      }
+    ]
   }
 }
diff --git a/apps/bff/src/bundled_templates/layers/banyandb.json b/apps/bff/src/bundled_templates/layers/banyandb.json
index 6757f5ca..4fae559e 100644
--- a/apps/bff/src/bundled_templates/layers/banyandb.json
+++ b/apps/bff/src/bundled_templates/layers/banyandb.json
@@ -1866,5 +1866,476 @@
         ]
       }
     ]
+  },
+  "dashboardExtPages": {
+    "service": [
+      {
+        "id": "trace-sampling",
+        "name": "Trace Sampling",
+        "widgets": [
+          {
+            "id": "sampling_outcomes",
+            "title": "Trace Outcomes",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_traces_evaluated,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_traces_retained,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_traces_dropped,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_traces_immature,sum(group))"
+            ],
+            "tip": "Retained + dropped account for the evaluated traces. Immature traces were never eligible — their fragments are still inside the maturity boundary, so they are not a sampling failure.",
+            "unit": "traces/s",
+            "expressionLabels": [
+              "evaluated",
+              "retained",
+              "dropped",
+              "immature"
+            ],
+            "span": 12,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_active_kpi",
+            "title": "Active Samplers",
+            "type": "table",
+            "expressions": [
+              "latest(meter_banyandb_trace_sampling_active_samplers)"
+            ],
+            "tip": "Sampler plugins registered per storage group. A group absent from this list has no pipeline — its traces are never evaluated, so nothing is dropped for it.",
+            "format": "int",
+            "span": 4,
+            "rowSpan": 2,
+            "tableHeaders": [
+              "Group",
+              "Samplers"
+            ]
+          },
+          {
+            "id": "sampling_drop_ratio_by_group",
+            "title": "Drop Ratio by Group",
+            "type": "line",
+            "expressions": [
+              "meter_banyandb_trace_sampling_drop_ratio"
+            ],
+            "tip": "Committed drop ratio per storage group, per step — dropped over evaluated traces after BanyanDB's safety guards. Deliberately per group rather than one cluster headline: a cluster-wide card would be an unweighted mean of the per-step ratios, so a quiet minute would count as much as a busy one. A group below the others is usually explained by the safety events further down rather than by its rules.",
+            "unit": "%",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_load_failures",
+            "title": "Plugin Load Failures",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_plugin_load_failures,sum(group,name,reason))"
+            ],
+            "tip": "Plugin load failures per minute, by group, failing plugin and reason. Reconciliation fails open, so any value here means the chain is running WITHOUT a plugin the config asked for — sampling is not doing what it says. The plugin name and reason are only shown here.",
+            "unit": "/min",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_execution_rate",
+            "title": "Decide Rate by Result",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_plugin_execution_rate,sum(group,result))"
+            ],
+            "tip": "Decide() calls per second, per plugin and result. Alert on any panic, length_mismatch or late — they mean broken plugin behaviour or a call that outlived the merge timeout. decide_error may be transient.",
+            "unit": "/s",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_decide_latency",
+            "title": "Decide Latency",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_plugin_decide_latency_p99,max(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_plugin_decide_latency,avg(group))"
+            ],
+            "tip": "Successful Decide() wall time per plugin — p99 takes the worst group, mean averages them. Derive a per-plugin SLO from a healthy baseline; an intentionally expensive plugin is not a fault.",
+            "unit": "ms",
+            "expressionLabels": [
+              "p99",
+              "mean"
+            ],
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_time_per_trace",
+            "title": "Plugin Time per Trace",
+            "type": "line",
+            "expressions": [
+              "meter_banyandb_trace_sampling_plugin_time_per_trace"
+            ],
+            "tip": "Whole-chain plugin wall time per evaluated trace, per group — the sampling overhead paid on merge, over time. The upstream Grafana board splits this per plugin with PromQL's `group_left`; MAL has no equivalent, so the metric is the chain total and the per-plugin split lives in Decide Latency. Keep it comfortably below the data node's --trace-pipeline-decide-timeout (default 5s).",
+            "unit": "ms",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_chain_batches",
+            "title": "Chain Batches",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_chain_batch_rate,sum(group,result))"
+            ],
+            "tip": "Chain batches per second by result. circuit_open means sampling is currently BYPASSED for that group; timeout is chain-level because the host can abandon the chain before a link returns. The traces each batch carried is the Batch Size p99 panel.",
+            "unit": "/s",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_batch_size_p99",
+            "title": "Batch Size p99",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_batch_size_p99,max(group))"
+            ],
+            "tip": "p99 traces presented to the plugin chain per successful batch, per group. Read with Chain Batches: rate tells you how often the chain runs, this tells you how much it sees each time.",
+            "unit": "traces",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_sampler_rows",
+            "title": "Sampler Rows",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_sampler_rows,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_sampler_rows_dropped,sum(group,rule))"
+            ],
+            "tip": "Span / segment rows carried by evaluated traces, against the rows the samplers proposed dropping, broken out by the rule responsible. Same unit, so the two are directly comparable; the resulting share is the Dropped Row Ratio panel.",
+            "unit": "rows/s",
+            "expressionLabels": [
+              "rows",
+              "rows dropped"
+            ],
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_decisions_by_rule",
+            "title": "Sampler Decisions by Rule",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_sampler_decisions,sum(group,verdict,rule))"
+            ],
+            "tip": "What the first-party samplers PROPOSED, attributed to the first matching rule — not what storage committed. A trace can be evaluated over several merge or finalization rounds, and the guards can retain a trace the plugin voted to drop, so read drop verdicts against Trace Outcomes.",
+            "unit": "/s",
+            "span": 12,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_sampler_dropped_ratio",
+            "title": "Dropped Row Ratio",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_sampler_dropped_row_ratio,avg(group))"
+            ],
+            "tip": "Share of rows the samplers proposed dropping, per group. Proposed, not committed — the safety guards can retain a trace a plugin voted to drop. Treat it as incomplete whenever Row Count Unavailable is non-zero, since those traces are missing from the denominator.",
+            "unit": "%",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_row_count_unavailable",
+            "title": "Row Count Unavailable",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_sampler_row_count_unavailable,sum(group))"
+            ],
+            "tip": "Trace evaluations whose metadata-only projection exposed no row count, per group. These traces are omitted from the row totals, so any non-zero value here means the Dropped Row Ratio is computed over an incomplete denominator.",
+            "unit": "/min",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_guard_work",
+            "title": "Guard & Index Work",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_guard_bloom_probes,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_sidx_pruned,sum(group))"
+            ],
+            "tip": "Bloom-filter probes are the fragment guard checking for trace fragments outside the parts being merged; pruned entries are the secondary-index work a confirmed drop causes.",
+            "unit": "/s",
+            "expressionLabels": [
+              "bloom probes",
+              "sidx pruned"
+            ],
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_safety_events",
+            "title": "Safety Events",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_oversized_traces_bypassed,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_ambiguous,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_guard_deferred,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_guard_budget_exhausted,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_guard_publication_rejected,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_guard_lossless_retry,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_guard_bypassed,sum(group))"
+            ],
+            "tip": "Every series here RETAINS data a sampler proposed dropping. A drop ratio under target is explained here before it is explained by plugin logic.",
+            "unit": "/min",
+            "expressionLabels": [
+              "oversized",
+              "ambiguous",
+              "guard deferred",
+              "guard budget exhausted",
+              "publication rejected",
+              "lossless retry",
+              "guard bypassed"
+            ],
+            "span": 12,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_plugin_errors",
+            "title": "Chain Errors by Reason",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_plugin_errors,sum(group,reason))"
+            ],
+            "tip": "Chain-level errors that forced fail-open retention of the whole batch.",
+            "unit": "/min",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_link_bypasses",
+            "title": "Link Bypasses",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_link_bypasses,sum(group,reason))"
+            ],
+            "tip": "Individual links bypassed after decide_error, length_mismatch or panic. A chain batch can report success while one link was bypassed and its input retained — which is why this is separate from chain errors.",
+            "unit": "/min",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_reconciliation",
+            "title": "Registrations & Updates",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_register_rate,sum(group,result))",
+              "aggregate_labels(meter_banyandb_trace_sampling_update_rate,sum(group,result))",
+              "aggregate_labels(meter_banyandb_trace_sampling_remove_rate,sum(group))"
+            ],
+            "tip": "Pipeline registration, update and removal rate. A rejected result means OAP pushed a pipeline config the data node would not accept.",
+            "unit": "/min",
+            "expressionLabels": [
+              "registered",
+              "updated",
+              "removed"
+            ],
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_drop_set_pressure",
+            "title": "Drop-set Cap Impact",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_capped_merges,sum(group,lane))",
+              "aggregate_labels(meter_banyandb_trace_sampling_traces_retained_by_ceiling,sum(group))"
+            ],
+            "tip": "The per-merge dropped-trace-ID set is memory-bounded. When it hits the ceiling the merge retains the rest — this is what makes a drop ratio plateau under load rather than tracking the rules.",
+            "unit": "/min",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_drop_set_size",
+            "title": "Drop-set Size p99",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_drop_set_entries_p99,max(group,lane))"
+            ],
+            "tip": "p99 dropped trace IDs held per merge lane. Read it against the Drop-set Budget panel: when the set outgrows the budget the merge stops accepting drops and retains the rest, which is what shows up as Drop-set Cap Impact.",
+            "unit": "entries",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_drop_set_budget",
+            "title": "Drop-set Budget",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_drop_set_budget_bytes,max(group))/1048576"
+            ],
+            "tip": "Resolved per-merge memory budget for confirmed dropped trace IDs, per group — the ceiling the drop-set size runs into. Resolved per group, so groups can carry different budgets; a group with a smaller one reaches its ceiling first. Flat unless the data node is retuned.",
+            "unit": "MiB",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_finalize_rounds",
+            "title": "Finalization Rounds",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_finalize_rounds,max(group))"
+            ],
+            "tip": "Highest finalization round count observed across each group's cooled shards. Whether a shard has run out of rounds is the Finalization Terminal panel.",
+            "unit": "rounds",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_finalize_terminal",
+            "title": "Finalization Terminal",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_finalize_terminal,max(group))"
+            ],
+            "tip": "1 when any cooled shard in the group is terminal and cannot run another finalization round — no more tail sampling will happen for that data, so whatever is stored is final. 0 otherwise. Charted over time so the transition is visible.",
+            "unit": "0/1",
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          },
+          {
+            "id": "sampling_telemetry_safety",
+            "title": "Plugin Telemetry Host Safety",
+            "type": "line",
+            "expressions": [
+              "aggregate_labels(meter_banyandb_trace_sampling_plugin_telemetry_series_rejected,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_plugin_log_dropped,sum(group))",
+              "aggregate_labels(meter_banyandb_trace_sampling_plugin_telemetry_panic,sum(group))"
+            ],
+            "tip": "The host caps what a plugin may publish (100 label-value series per instrument) and rate-limits its logs. Non-zero means a plugin's OWN telemetry is truncated — the sampler decision metrics above are then incomplete, though sampling itself is unaffected.",
+            "unit": "/min",
+            "expressionLabels": [
+              "series rejected",
+              "logs dropped",
+              "telemetry panic"
+            ],
+            "span": 4,
+            "rowSpan": 2,
+            "visibleWhen": {
+              "kind": "mqe",
+              "expression": "aggregate_labels(meter_banyandb_trace_sampling_active_samplers,sum)",
+              "op": "exists"
+            }
+          }
+        ]
+      }
+    ]
   }
 }
diff --git a/docs/changelog/1.1.0.md b/docs/changelog/1.1.0.md
index fbdfd322..8fb7b402 100644
--- a/docs/changelog/1.1.0.md
+++ b/docs/changelog/1.1.0.md
@@ -127,6 +127,8 @@
 
 **The layer header's KPIs describe one completed hour, and say which one.** They used to be read over whatever window the time picker held, which meant reading the sort metric for every service in the layer on every refresh — hundreds of requests on a large layer, repeated every thirty seconds, for figures that only move by the hour. The header now reads one completed hour and holds it, and the hour it covers is named above the table (*09:00–10:00*) so the numbers are never older than th [...]
 
+**The BanyanDB layer has a Trace Sampling page.** BanyanDB 0.11 can run a chain of sampler plugins during trace merge and finalization, dropping whole traces after their fragments have landed — tail sampling inside the storage tier. The new page is an extension page under Cluster, with its own sidebar row, and it reads the `meter_banyandb_trace_sampling_*` families OAP collects from the plugin chain: the committed drop ratio and the trace outcomes behind it, per-plugin `Decide` rate and  [...]
+
 ### Fixes
 
 **A compared service that failed to refresh was drawn as though it had.** In Compare, each entity keeps its last good reading when its own read fails — deliberately, so one failure does not blank its siblings — but the retained series was then plotted across the CURRENT axis and ran to the right-hand edge beside the fresh ones. Ten-minute-old latency sat next to live latency with nothing to tell them apart. Each series is now placed at the buckets it was actually read for, so a stale one [...]
diff --git a/docs/dashboards/banyandb.md b/docs/dashboards/banyandb.md
index 3f70e01e..fbdf7f85 100644
--- a/docs/dashboards/banyandb.md
+++ b/docs/dashboards/banyandb.md
@@ -18,7 +18,7 @@ limitations under the License.
 
 The **BANYANDB** layer is the self-observability dashboard for [Apache SkyWalking BanyanDB](https://skywalking.apache.org/docs/skywalking-banyandb/next/readme/), the native storage that backs an OAP cluster. It surfaces the health of the storage tier itself — write and query throughput, the liaison front door, the data nodes that hold the shards, the lifecycle sidecar that migrates data between tiers, and the per-group load across the measure / stream / trace / property data models.
 
-In Horizon's sidebar this layer sits under the **Self-Observability** group and is named **BanyanDB**. It maps BanyanDB's own topology onto the standard entity slots: a BanyanDB cluster is a **Cluster** (the service slot), each running container — a `liaison`, `data`, or `lifecycle` process — is a **Container** (the instance slot, badged with its `container_name`), and each storage group is a **Group** (the endpoint slot). The layer enables the Cluster, Container, and Group dashboards pl [...]
+In Horizon's sidebar this layer sits under the **Self-Observability** group and is named **BanyanDB**. It maps BanyanDB's own topology onto the standard entity slots: a BanyanDB cluster is a **Cluster** (the service slot), each running container — a `liaison`, `data`, or `lifecycle` process — is a **Container** (the instance slot, badged with its `container_name`), and each storage group is a **Group** (the endpoint slot). The layer enables the Cluster, Container, and Group dashboards, a [...]
 
 This page is the **operator reference** for the bundled BANYANDB dashboard: what you see on each scope and what each widget means.
 
@@ -224,6 +224,82 @@ For one selected group — a BanyanDB storage group, mapped to the endpoint slot
 
 - **Part-sync Bytes / s** — part-sync (file-sync) bytes per second for this group in KB/s (`meter_banyandb_endpoint_publish_bytes`). Only the chunked part-streaming path increments this; regular write / query publishes are not counted.
 
+## Trace Sampling
+
+An **extension page under Cluster**, reached from its own sidebar row directly below the Cluster row (route `/layer/BANYANDB/service/trace-sampling`). It covers BanyanDB **trace tail sampling**: from 0.11 a data node can run an ordered chain of sampler plugins during trace merge and finalization, dropping whole traces after their fragments have already landed. See [Trace Tail Sampling](https://skywalking.apache.org/docs/main/next/en/banyandb/tail-sampling/) for how a trace is judged and  [...]
+
+The plugin chain is **optional**, and this page is honest about that. Only the **Active Samplers** table is always shown; every other panel is gated on one shared probe, so on a cluster with no sampler configured the page collapses to that single table reading `no data` — rather than two dozen empty charts — and OAP runs one gate query instead of the whole page's MQE.
+
+**Everything is per storage group.** Every sampling metric is keyed on the group, so each panel charts one series per group and none of them sum across groups. There are deliberately **no headline cards**: a card collapses its series with an unweighted mean of the per-step values, which reads correctly for a rate but distorts a ratio, and summarising a metric a panel already plots earns nothing. Trace Outcomes leads instead, at full width. That is also why this page lives under Cluster r [...]
+
+The recurring distinction to hold on to while reading it: the **sampler plugins propose** drops, and **storage commits** them. A trace can be evaluated across several merge or finalization rounds, and BanyanDB's safety guards can retain a trace a plugin voted to drop — so the two never have to match, and the gap between them is what most of these panels exist to explain.
+
+A panel showing nothing is usually good news rather than a fault. The families behind the failure and edge-case metrics are registered on first use, so a counter that never fired is never exposed; on a healthy cluster that leaves the load-failure, chain-error, link-bypass, drop-set-ceiling, finalization and telemetry-safety panels empty.
+
+### Outcomes
+
+- **Trace Outcomes** — Retained + dropped account for the evaluated traces. Immature traces were never eligible — their fragments are still inside the maturity boundary, so they are not a sampling failure. (`meter_banyandb_trace_sampling_traces_dropped` / `_traces_evaluated` / `_traces_immature` / `_traces_retained`)
+
+- **Active Samplers** — Sampler plugins registered per storage group. A group absent from this list has no pipeline — its traces are never evaluated, so nothing is dropped for it. (`meter_banyandb_trace_sampling_active_samplers`) A table, one row per group.
+
+- **Drop Ratio by Group** — Committed drop ratio per storage group, per step — dropped over evaluated traces after BanyanDB's safety guards. Deliberately per group rather than one cluster headline: a cluster-wide card would be an unweighted mean of the per-step ratios, so a quiet minute would count as much as a busy one. A group below the others is usually explained by the safety events further down rather than by its rules. (`meter_banyandb_trace_sampling_drop_ratio`)
+
+- **Plugin Load Failures** — Plugin load failures per minute, by group, failing plugin and reason. Reconciliation fails open, so any value here means the chain is running WITHOUT a plugin the config asked for — sampling is not doing what it says. The plugin name and reason are only shown here. (`meter_banyandb_trace_sampling_plugin_load_failures`)
+
+### Plugin execution
+
+- **Decide Rate by Result** — Decide() calls per second, per plugin and result. Alert on any panic, length_mismatch or late — they mean broken plugin behaviour or a call that outlived the merge timeout. decide_error may be transient. (`meter_banyandb_trace_sampling_plugin_execution_rate`)
+
+- **Decide Latency** — Successful Decide() wall time per plugin — p99 takes the worst group, mean averages them. Derive a per-plugin SLO from a healthy baseline; an intentionally expensive plugin is not a fault. (`meter_banyandb_trace_sampling_plugin_decide_latency` / `_plugin_decide_latency_p99`)
+
+- **Plugin Time per Trace** — Whole-chain plugin wall time per evaluated trace, per group — the sampling overhead paid on merge, over time. The upstream Grafana board splits this per plugin with PromQL's `group_left`; MAL has no equivalent, so the metric is the chain total and the per-plugin split lives in Decide Latency. Keep it comfortably below the data node's --trace-pipeline-decide-timeout (default 5s). (`meter_banyandb_trace_sampling_plugin_time_per_trace`)
+
+- **Chain Batches** — Chain batches per second by result. circuit_open means sampling is currently BYPASSED for that group; timeout is chain-level because the host can abandon the chain before a link returns. The traces each batch carried is the Batch Size p99 panel. (`meter_banyandb_trace_sampling_chain_batch_rate`)
+
+- **Batch Size p99** — p99 traces presented to the plugin chain per successful batch, per group. Read with Chain Batches: rate tells you how often the chain runs, this tells you how much it sees each time. (`meter_banyandb_trace_sampling_batch_size_p99`)
+
+### Sampler decisions
+
+Published by the first-party `sw-trace-sampler` / `zipkin-trace-sampler` plugins — these are **proposed** drops, so read them against Trace Outcomes.
+
+- **Sampler Decisions by Rule** — What the first-party samplers PROPOSED, attributed to the first matching rule — not what storage committed. A trace can be evaluated over several merge or finalization rounds, and the guards can retain a trace the plugin voted to drop, so read drop verdicts against Trace Outcomes. (`meter_banyandb_trace_sampling_sampler_decisions`)
+
+- **Sampler Rows** — Span / segment rows carried by evaluated traces, against the rows the samplers proposed dropping, broken out by the rule responsible. Same unit, so the two are directly comparable; the resulting share is the Dropped Row Ratio panel. (`meter_banyandb_trace_sampling_sampler_rows` / `_sampler_rows_dropped`)
+
+- **Dropped Row Ratio** — Share of rows the samplers proposed dropping, per group. Proposed, not committed — the safety guards can retain a trace a plugin voted to drop. Treat it as incomplete whenever Row Count Unavailable is non-zero, since those traces are missing from the denominator. (`meter_banyandb_trace_sampling_sampler_dropped_row_ratio`)
+
+- **Row Count Unavailable** — Trace evaluations whose metadata-only projection exposed no row count, per group. These traces are omitted from the row totals, so any non-zero value here means the Dropped Row Ratio is computed over an incomplete denominator. (`meter_banyandb_trace_sampling_sampler_row_count_unavailable`)
+
+### Safety and fail-open
+
+Every panel in this block explains a drop ratio that came in *under* target — each one retains data a sampler proposed dropping.
+
+- **Safety Events** — Every series here RETAINS data a sampler proposed dropping. A drop ratio under target is explained here before it is explained by plugin logic. (`meter_banyandb_trace_sampling_ambiguous` / `_guard_budget_exhausted` / `_guard_bypassed` / `_guard_deferred` / `_guard_lossless_retry` / `_guard_publication_rejected` / `_oversized_traces_bypassed`)
+
+- **Chain Errors by Reason** — Chain-level errors that forced fail-open retention of the whole batch. (`meter_banyandb_trace_sampling_plugin_errors`)
+
+- **Link Bypasses** — Individual links bypassed after decide_error, length_mismatch or panic. A chain batch can report success while one link was bypassed and its input retained — which is why this is separate from chain errors. (`meter_banyandb_trace_sampling_link_bypasses`)
+
+- **Guard & Index Work** — Bloom-filter probes are the fragment guard checking for trace fragments outside the parts being merged; pruned entries are the secondary-index work a confirmed drop causes. (`meter_banyandb_trace_sampling_guard_bloom_probes` / `_sidx_pruned`)
+
+### Drop-set capacity
+
+- **Drop-set Cap Impact** — The per-merge dropped-trace-ID set is memory-bounded. When it hits the ceiling the merge retains the rest — this is what makes a drop ratio plateau under load rather than tracking the rules. (`meter_banyandb_trace_sampling_capped_merges` / `_traces_retained_by_ceiling`)
+
+- **Drop-set Size p99** — p99 dropped trace IDs held per merge lane. Read it against the Drop-set Budget panel: when the set outgrows the budget the merge stops accepting drops and retains the rest, which is what shows up as Drop-set Cap Impact. (`meter_banyandb_trace_sampling_drop_set_entries_p99`)
+
+- **Drop-set Budget** — Resolved per-merge memory budget for confirmed dropped trace IDs, per group — the ceiling the drop-set size runs into. Resolved per group, so groups can carry different budgets; a group with a smaller one reaches its ceiling first. Flat unless the data node is retuned. (`meter_banyandb_trace_sampling_drop_set_budget_bytes`)
+
+### Lifecycle and host limits
+
+- **Registrations & Updates** — Pipeline registration, update and removal rate. A rejected result means OAP pushed a pipeline config the data node would not accept. (`meter_banyandb_trace_sampling_register_rate` / `_remove_rate` / `_update_rate`)
+
+- **Finalization Rounds** — Highest finalization round count observed across each group's cooled shards. Whether a shard has run out of rounds is the Finalization Terminal panel. (`meter_banyandb_trace_sampling_finalize_rounds`)
+
+- **Finalization Terminal** — 1 when any cooled shard in the group is terminal and cannot run another finalization round — no more tail sampling will happen for that data, so whatever is stored is final. 0 otherwise. Charted over time so the transition is visible. (`meter_banyandb_trace_sampling_finalize_terminal`)
+
+- **Plugin Telemetry Host Safety** — The host caps what a plugin may publish (100 label-value series per instrument) and rate-limits its logs. Non-zero means a plugin's OWN telemetry is truncated — the sampler decision metrics above are then incomplete, though sampling itself is unaffected. (`meter_banyandb_trace_sampling_plugin_log_dropped` / `_plugin_telemetry_panic` / `_plugin_telemetry_series_rejected`)
+
 ## Deployment
 
 The BANYANDB layer enables the layer-specific **Deployment** tab — the deployment topology of one cluster's own containers and the intra-cluster calls between them. Pick a cluster from the header and the tab draws its containers as health-ring nodes laid out left → right along the calls between them, with animated edge flow, a per-edge metric panel, and a node popover that opens the container dashboard. For how the Deployment tab is read and navigated in general, see the [Deployment](../ [...]
@@ -258,4 +334,6 @@ The BANYANDB dashboard is a pure consumer of what OAP reports about its BanyanDB
 
 - **Relation metrics** — `meter_banyandb_instance_relation_*` (publish / subscribe / migration throughput, latency, bytes, and error counters) for the Deployment tab's edges.
 
+- **Trace sampling metrics** — `meter_banyandb_trace_sampling_*` for the Trace Sampling page. These exist only while a sampler plugin chain is configured on the cluster, so the page is gated on them and stays collapsed to its Active Samplers card until one is enabled.
+
 Each metric is queried at its own OAP scope; OAP does not roll a metric up across scopes, so a container- or group-scope metric is empty until that level of data is reported, and an entire data model's group widgets stay hidden until that model's group reports.