feat: Introduces sample window size for metric alerting (#4498)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Amir Raminfar
2026-03-02 09:28:54 -08:00
committed by GitHub
parent 0991df5554
commit 93d9c88424
31 changed files with 188 additions and 5 deletions
@@ -53,6 +53,8 @@
<template v-if="alert.metricExpression">
<span>{{ $t("notifications.alert.metric-filter") }}</span>
<code class="bg-base-200 text-base-content rounded px-2 py-0.5 font-mono">{{ alert.metricExpression }}</code>
<span>{{ $t("notifications.alert.sample-window") }}</span>
<span>{{ formatDuration(alert.sampleWindow || 15, locale || undefined) }}</span>
<span>{{ $t("notifications.alert.cooldown") }}</span>
<span>{{ formatDuration(alert.cooldown || 300, locale || undefined) }}</span>
</template>
@@ -23,6 +23,18 @@
</p>
</fieldset>
<fieldset class="fieldset">
<legend class="fieldset-legend text-lg">{{ $t("notifications.alert-form.sample-window-label") }}</legend>
<input v-model.number="sampleWindow" type="range" min="15" max="300" step="15" class="range range-primary" />
<p class="text-base-content/50 mt-1 text-xs">
{{
$t("notifications.alert-form.sample-window-hint", {
duration: formatDuration(sampleWindow, locale || undefined),
})
}}
</p>
</fieldset>
<fieldset class="fieldset">
<legend class="fieldset-legend text-lg">{{ $t("notifications.alert-form.cooldown-label") }}</legend>
<input v-model.number="cooldown" type="range" min="10" max="3600" step="10" class="range range-primary" />
@@ -46,6 +58,7 @@ const props = defineProps<{
const metricExpression = ref(props.alert?.metricExpression ?? props.prefill?.metricExpression ?? "");
const metricError = ref<string | null>(null);
const sampleWindow = ref(props.alert?.sampleWindow ?? 15);
const cooldown = ref(props.alert?.cooldown ?? 300);
const canSave = computed(() => !!metricExpression.value.trim() && !metricError.value);
@@ -53,6 +66,7 @@ const typeFields = computed(() => ({
metricExpression: metricExpression.value,
logExpression: "",
cooldown: cooldown.value,
sampleWindow: sampleWindow.value,
}));
defineExpose({ canSave, typeFields });
+2
View File
@@ -6,6 +6,7 @@ export interface NotificationRule {
logExpression: string;
metricExpression?: string;
cooldown?: number;
sampleWindow?: number;
triggerCount: number;
triggeredContainers: number;
lastTriggeredAt: string | null;
@@ -30,6 +31,7 @@ export interface NotificationRuleInput {
containerExpression: string;
metricExpression?: string;
cooldown?: number;
sampleWindow?: number;
}
export interface PreviewResult {
+3
View File
@@ -515,6 +515,9 @@ func (c *Client) UpdateNotificationConfig(ctx context.Context, subscriptions []t
DispatcherId: int32(sub.DispatcherID),
LogExpression: sub.LogExpression,
ContainerExpression: sub.ContainerExpression,
MetricExpression: sub.MetricExpression,
Cooldown: int32(sub.Cooldown),
SampleWindow: int32(sub.SampleWindow),
}
}
+29 -2
View File
@@ -828,6 +828,9 @@ type NotificationSubscription struct {
DispatcherId int32 `protobuf:"varint,4,opt,name=dispatcherId,proto3" json:"dispatcherId,omitempty"`
LogExpression string `protobuf:"bytes,5,opt,name=logExpression,proto3" json:"logExpression,omitempty"`
ContainerExpression string `protobuf:"bytes,6,opt,name=containerExpression,proto3" json:"containerExpression,omitempty"`
MetricExpression string `protobuf:"bytes,7,opt,name=metricExpression,proto3" json:"metricExpression,omitempty"`
Cooldown int32 `protobuf:"varint,8,opt,name=cooldown,proto3" json:"cooldown,omitempty"`
SampleWindow int32 `protobuf:"varint,9,opt,name=sampleWindow,proto3" json:"sampleWindow,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -904,6 +907,27 @@ func (x *NotificationSubscription) GetContainerExpression() string {
return ""
}
func (x *NotificationSubscription) GetMetricExpression() string {
if x != nil {
return x.MetricExpression
}
return ""
}
func (x *NotificationSubscription) GetCooldown() int32 {
if x != nil {
return x.Cooldown
}
return 0
}
func (x *NotificationSubscription) GetSampleWindow() int32 {
if x != nil {
return x.SampleWindow
}
return 0
}
type NotificationDispatcher struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
@@ -1058,14 +1082,17 @@ const file_types_proto_rawDesc = "" +
"\rdockerVersion\x18\f \x01(\tR\rdockerVersion\x1a9\n" +
"\vLabelsEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd4\x01\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc0\x02\n" +
"\x18NotificationSubscription\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x18\n" +
"\aenabled\x18\x03 \x01(\bR\aenabled\x12\"\n" +
"\fdispatcherId\x18\x04 \x01(\x05R\fdispatcherId\x12$\n" +
"\rlogExpression\x18\x05 \x01(\tR\rlogExpression\x120\n" +
"\x13containerExpression\x18\x06 \x01(\tR\x13containerExpression\"~\n" +
"\x13containerExpression\x18\x06 \x01(\tR\x13containerExpression\x12*\n" +
"\x10metricExpression\x18\a \x01(\tR\x10metricExpression\x12\x1a\n" +
"\bcooldown\x18\b \x01(\x05R\bcooldown\x12\"\n" +
"\fsampleWindow\x18\t \x01(\x05R\fsampleWindow\"~\n" +
"\x16NotificationDispatcher\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" +
+3
View File
@@ -414,6 +414,9 @@ func (s *server) UpdateNotificationConfig(ctx context.Context, req *pb.UpdateNot
DispatcherID: int(sub.DispatcherId),
LogExpression: sub.LogExpression,
ContainerExpression: sub.ContainerExpression,
MetricExpression: sub.MetricExpression,
Cooldown: int(sub.Cooldown),
SampleWindow: int(sub.SampleWindow),
}
}
+6
View File
@@ -6,6 +6,7 @@ import (
"time"
"github.com/amir20/dozzle/internal/notification/dispatcher"
"github.com/amir20/dozzle/internal/utils"
"github.com/amir20/dozzle/types"
"github.com/puzpuzpuz/xsync/v4"
"github.com/rs/zerolog/log"
@@ -46,6 +47,7 @@ func (m *Manager) LoadConfig(r io.Reader) error {
ContainerExpression: sub.ContainerExpression,
MetricExpression: sub.MetricExpression,
Cooldown: sub.Cooldown,
SampleWindow: sub.SampleWindow,
}
}
@@ -99,6 +101,7 @@ func (m *Manager) HandleNotificationConfig(subscriptions []types.SubscriptionCon
ContainerExpression: sub.ContainerExpression,
MetricExpression: sub.MetricExpression,
Cooldown: sub.Cooldown,
SampleWindow: sub.SampleWindow,
}
if err := m.loadSubscription(s); err != nil {
return fmt.Errorf("failed to load subscription %s: %w", sub.Name, err)
@@ -151,6 +154,9 @@ func (m *Manager) loadSubscription(sub *Subscription) error {
if sub.MetricCooldowns == nil {
sub.MetricCooldowns = xsync.NewMap[string, time.Time]()
}
if sub.MetricSampleBuffers == nil {
sub.MetricSampleBuffers = xsync.NewMap[string, *utils.RingBuffer[bool]]()
}
m.subscriptions.Store(sub.ID, sub)
log.Debug().Str("name", sub.Name).Int("id", sub.ID).Msg("Loaded subscription")
+10
View File
@@ -9,6 +9,7 @@ import (
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/notification/dispatcher"
"github.com/amir20/dozzle/internal/utils"
"github.com/amir20/dozzle/types"
"github.com/expr-lang/expr"
"github.com/puzpuzpuz/xsync/v4"
@@ -79,6 +80,7 @@ func (m *Manager) AddSubscription(sub *Subscription) error {
sub.ID = int(m.subscriptionCounter.Add(1))
sub.Enabled = true
sub.MetricCooldowns = xsync.NewMap[string, time.Time]()
sub.MetricSampleBuffers = xsync.NewMap[string, *utils.RingBuffer[bool]]()
if err := sub.CompileExpressions(); err != nil {
return err
@@ -104,6 +106,7 @@ func (m *Manager) RemoveSubscription(id int) {
// ReplaceSubscription replaces a subscription with new data
func (m *Manager) ReplaceSubscription(sub *Subscription) error {
sub.MetricCooldowns = xsync.NewMap[string, time.Time]()
sub.MetricSampleBuffers = xsync.NewMap[string, *utils.RingBuffer[bool]]()
if err := sub.CompileExpressions(); err != nil {
return err
@@ -146,7 +149,9 @@ func (m *Manager) UpdateSubscription(id int, updates map[string]any) error {
MetricExpression: sub.MetricExpression,
MetricProgram: sub.MetricProgram,
Cooldown: sub.Cooldown,
SampleWindow: sub.SampleWindow,
MetricCooldowns: sub.MetricCooldowns,
MetricSampleBuffers: sub.MetricSampleBuffers,
}
// Apply updates to the clone
@@ -208,6 +213,11 @@ func (m *Manager) UpdateSubscription(id int, updates map[string]any) error {
if cd, ok := value.(int); ok {
updated.Cooldown = cd
}
case "sampleWindow":
if sw, ok := value.(int); ok {
updated.SampleWindow = sw
updated.MetricSampleBuffers = xsync.NewMap[string, *utils.RingBuffer[bool]]()
}
}
}
+4 -2
View File
@@ -133,8 +133,9 @@ func (m *Manager) processStatEvent(event *ContainerStatEvent) {
return true
}
// Check metric expression
if !sub.MatchesMetric(notificationStat) {
// Evaluate metric expression and record in sample window
matched := sub.MatchesMetric(notificationStat)
if !sub.RecordMetricSample(event.Stat.ID, matched) {
return true
}
@@ -171,6 +172,7 @@ func (m *Manager) processStatEvent(event *ContainerStatEvent) {
MetricExpression: sub.MetricExpression,
ContainerExpression: sub.ContainerExpression,
Cooldown: sub.Cooldown,
SampleWindow: sub.SampleWindow,
},
Timestamp: time.Now(),
}
+50 -1
View File
@@ -7,6 +7,7 @@ import (
"time"
"github.com/amir20/dozzle/internal/container"
"github.com/amir20/dozzle/internal/utils"
"github.com/amir20/dozzle/types"
"github.com/expr-lang/expr"
"github.com/expr-lang/expr/vm"
@@ -91,7 +92,8 @@ type Subscription struct {
LogExpression string `json:"logExpression" yaml:"logExpression"`
ContainerExpression string `json:"containerExpression" yaml:"containerExpression"`
MetricExpression string `json:"metricExpression,omitempty" yaml:"metricExpression,omitempty"`
Cooldown int `json:"cooldown,omitempty" yaml:"cooldown,omitempty"` // seconds between metric notifications, default 300
Cooldown int `json:"cooldown,omitempty" yaml:"cooldown,omitempty"` // seconds between metric notifications, default 300
SampleWindow int `json:"sampleWindow,omitempty" yaml:"sampleWindow,omitempty"` // seconds of samples to evaluate, default 15
// Compiled filter expressions
LogProgram *vm.Program `json:"-" yaml:"-"` // Compiled log filter expression
@@ -105,6 +107,9 @@ type Subscription struct {
// Per-container cooldown tracking for metric alerts (containerID -> last triggered time)
MetricCooldowns *xsync.Map[string, time.Time] `json:"-" yaml:"-"`
// Per-container sample buffers for windowed metric evaluation (containerID -> ring buffer of match results)
MetricSampleBuffers *xsync.Map[string, *utils.RingBuffer[bool]] `json:"-" yaml:"-"`
}
// TriggeredContainersCount returns the number of unique containers that triggered this subscription
@@ -259,3 +264,47 @@ func (s *Subscription) IsMetricCooldownActive(containerID string) bool {
func (s *Subscription) SetMetricCooldown(containerID string) {
s.MetricCooldowns.Store(containerID, time.Now())
}
// GetSampleWindowSeconds returns the sample window in seconds, clamped to [1, 300], defaulting to 15
func (s *Subscription) GetSampleWindowSeconds() int {
if s.SampleWindow <= 0 {
return 15
}
if s.SampleWindow < 1 {
return 1
}
if s.SampleWindow > 300 {
return 300
}
return s.SampleWindow
}
// RecordMetricSample records a metric evaluation result and returns true if the window threshold is met.
// The alert fires when the buffer is full and >=80% of samples matched.
func (s *Subscription) RecordMetricSample(containerID string, matched bool) bool {
windowSize := s.GetSampleWindowSeconds()
// For window size of 1, just return the match result directly
if windowSize <= 1 {
return matched
}
buf, _ := s.MetricSampleBuffers.LoadOrCompute(containerID, func() (*utils.RingBuffer[bool], bool) {
return utils.NewRingBuffer[bool](windowSize), false
})
buf.Push(matched)
if buf.Len() < windowSize {
return false
}
trueCount := 0
for _, v := range buf.Data() {
if v {
trueCount++
}
}
return float64(trueCount)/float64(buf.Len()) >= 0.8
}
@@ -253,6 +253,7 @@ func (m *MultiHostService) broadcastNotificationConfig() {
ContainerExpression: sub.ContainerExpression,
MetricExpression: sub.MetricExpression,
Cooldown: sub.Cooldown,
SampleWindow: sub.SampleWindow,
}
}
+9
View File
@@ -29,6 +29,7 @@ type NotificationRuleResponse struct {
LogExpression string `json:"logExpression"`
MetricExpression string `json:"metricExpression,omitempty"`
Cooldown int `json:"cooldown,omitempty"`
SampleWindow int `json:"sampleWindow,omitempty"`
TriggerCount int64 `json:"triggerCount"`
TriggeredContainers int `json:"triggeredContainers"`
LastTriggeredAt *time.Time `json:"lastTriggeredAt"`
@@ -53,6 +54,7 @@ type NotificationRuleInput struct {
ContainerExpression string `json:"containerExpression"`
MetricExpression string `json:"metricExpression,omitempty"`
Cooldown int `json:"cooldown,omitempty"`
SampleWindow int `json:"sampleWindow,omitempty"`
}
type NotificationRuleUpdateInput struct {
@@ -63,6 +65,7 @@ type NotificationRuleUpdateInput struct {
ContainerExpression *string `json:"containerExpression,omitempty"`
MetricExpression *string `json:"metricExpression,omitempty"`
Cooldown *int `json:"cooldown,omitempty"`
SampleWindow *int `json:"sampleWindow,omitempty"`
}
type DispatcherInput struct {
@@ -123,6 +126,7 @@ func subscriptionToResponse(sub *notification.Subscription, dispatchers []notifi
ContainerExpression: sub.ContainerExpression,
MetricExpression: sub.MetricExpression,
Cooldown: sub.Cooldown,
SampleWindow: sub.SampleWindow,
TriggerCount: sub.TriggerCount.Load(),
LastTriggeredAt: lastTriggeredAt,
TriggeredContainers: sub.TriggeredContainersCount(),
@@ -210,6 +214,7 @@ func (h *handler) createNotificationRule(w http.ResponseWriter, r *http.Request)
ContainerExpression: input.ContainerExpression,
MetricExpression: input.MetricExpression,
Cooldown: input.Cooldown,
SampleWindow: input.SampleWindow,
}
if err := h.hostService.AddSubscription(sub); err != nil {
@@ -242,6 +247,7 @@ func (h *handler) replaceNotificationRule(w http.ResponseWriter, r *http.Request
ContainerExpression: input.ContainerExpression,
MetricExpression: input.MetricExpression,
Cooldown: input.Cooldown,
SampleWindow: input.SampleWindow,
}
if err := h.hostService.ReplaceSubscription(sub); err != nil {
@@ -287,6 +293,9 @@ func (h *handler) updateNotificationRule(w http.ResponseWriter, r *http.Request)
if input.Cooldown != nil {
updates["cooldown"] = *input.Cooldown
}
if input.SampleWindow != nil {
updates["sampleWindow"] = *input.SampleWindow
}
if err := h.hostService.UpdateSubscription(id, updates); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
+3
View File
@@ -179,6 +179,7 @@ notifications:
log-filter: Log filter
metric-filter: Metrik
cooldown: Afkøling
sample-window: Prøvevindue
destination: Destination
dispatcher-deleted: Dispatcher slettet
containers-count: "{count} containere"
@@ -211,6 +212,8 @@ notifications:
metric-filter: Metrikudtryk
expression-valid: Udtryk er gyldigt
metric-fields-hint: "Tilgængelige felter: {fields}"
sample-window-label: Prøvevindue
sample-window-hint: "Alarm udløses, når betingelsen er opfyldt i {duration}"
cooldown-label: Afkøling
cooldown-hint: "{duration} mellem alarmer pr. container"
alert-type: Alarmtype
+3
View File
@@ -179,6 +179,7 @@ notifications:
log-filter: Log-Filter
metric-filter: Metrik
cooldown: Abklingzeit
sample-window: Zeitfenster
destination: Ziel
dispatcher-deleted: Dispatcher gelöscht
containers-count: "{count} Container"
@@ -211,6 +212,8 @@ notifications:
metric-filter: Metrik-Ausdruck
expression-valid: Ausdruck ist gültig
metric-fields-hint: "Verfügbare Felder: {fields}"
sample-window-label: Zeitfenster
sample-window-hint: "Alarm wird ausgelöst, wenn Bedingung für {duration} erfüllt ist"
cooldown-label: Abklingzeit
cooldown-hint: "{duration} zwischen Alarmen pro Container"
alert-type: Alarmtyp
+3
View File
@@ -188,6 +188,7 @@ notifications:
log-filter: Log filter
metric-filter: Metric
cooldown: Cooldown
sample-window: Sample window
destination: Destination
dispatcher-deleted: Dispatcher deleted
containers-count: "{count} containers"
@@ -220,6 +221,8 @@ notifications:
metric-filter: Metric Expression
expression-valid: Expression is valid
metric-fields-hint: "Available fields: {fields}"
sample-window-label: Sample Window
sample-window-hint: "Alert triggers when condition is met for {duration}"
cooldown-label: Cooldown
cooldown-hint: "{duration} between alerts per container"
alert-type: Alert Type
+3
View File
@@ -179,6 +179,7 @@ notifications:
log-filter: Filtro de registro
metric-filter: Métrica
cooldown: Tiempo de espera
sample-window: Ventana de muestreo
destination: Destino
dispatcher-deleted: Dispatcher eliminado
containers-count: "{count} contenedores"
@@ -211,6 +212,8 @@ notifications:
metric-filter: Expresión de Métrica
expression-valid: La expresión es válida
metric-fields-hint: "Campos disponibles: {fields}"
sample-window-label: Ventana de muestreo
sample-window-hint: "La alerta se activa cuando la condición se cumple durante {duration}"
cooldown-label: Tiempo de espera
cooldown-hint: "{duration} entre alertas por contenedor"
alert-type: Tipo de alerta
+3
View File
@@ -179,6 +179,7 @@ notifications:
log-filter: Filtre de journal
metric-filter: Métrique
cooldown: Temps d'attente
sample-window: Fenêtre d'échantillonnage
destination: Destination
dispatcher-deleted: Dispatcher supprimé
containers-count: "{count} conteneurs"
@@ -211,6 +212,8 @@ notifications:
metric-filter: Expression de Métrique
expression-valid: L'expression est valide
metric-fields-hint: "Champs disponibles : {fields}"
sample-window-label: Fenêtre d'échantillonnage
sample-window-hint: "L'alerte se déclenche lorsque la condition est remplie pendant {duration}"
cooldown-label: Temps d'attente
cooldown-hint: "{duration} entre les alertes par conteneur"
alert-type: Type d'alerte
+3
View File
@@ -191,6 +191,7 @@ notifications:
log-filter: Filter log
metric-filter: Metrik
cooldown: Jeda
sample-window: Jendela sampel
destination: Tujuan
dispatcher-deleted: Dispatcher dihapus
containers-count: "{count} kontainer"
@@ -223,6 +224,8 @@ notifications:
metric-filter: Ekspresi Metrik
expression-valid: Ekspresi valid
metric-fields-hint: "Field tersedia: {fields}"
sample-window-label: Jendela Sampel
sample-window-hint: "Peringatan dipicu ketika kondisi terpenuhi selama {duration}"
cooldown-label: Jeda
cooldown-hint: "{duration} antara peringatan per kontainer"
alert-type: Jenis Peringatan
+3
View File
@@ -179,6 +179,7 @@ notifications:
log-filter: Filtro log
metric-filter: Metrica
cooldown: Tempo di attesa
sample-window: Finestra di campionamento
destination: Destinazione
dispatcher-deleted: Dispatcher eliminato
containers-count: "{count} container"
@@ -211,6 +212,8 @@ notifications:
metric-filter: Espressione Metrica
expression-valid: L'espressione è valida
metric-fields-hint: "Campi disponibili: {fields}"
sample-window-label: Finestra di campionamento
sample-window-hint: "L'avviso si attiva quando la condizione è soddisfatta per {duration}"
cooldown-label: Tempo di attesa
cooldown-hint: "{duration} tra gli avvisi per container"
alert-type: Tipo di avviso
+3
View File
@@ -182,6 +182,7 @@ notifications:
log-filter: 로그 필터
metric-filter: 메트릭
cooldown: 대기 시간
sample-window: 샘플 창
destination: 목적지
dispatcher-deleted: 디스패처 삭제됨
containers-count: "컨테이너 {count}개"
@@ -214,6 +215,8 @@ notifications:
metric-filter: 메트릭 표현식
expression-valid: 표현식이 유효합니다
metric-fields-hint: "사용 가능한 필드: {fields}"
sample-window-label: 샘플 창
sample-window-hint: "조건이 {duration} 동안 충족되면 알림이 트리거됩니다"
cooldown-label: 대기 시간
cooldown-hint: "컨테이너당 알림 간격 {duration}"
alert-type: 알림 유형
+3
View File
@@ -180,6 +180,7 @@ notifications:
log-filter: Logfilter
metric-filter: Metriek
cooldown: Wachttijd
sample-window: Steekproefvenster
destination: Bestemming
dispatcher-deleted: Dispatcher verwijderd
containers-count: "{count} containers"
@@ -212,6 +213,8 @@ notifications:
metric-filter: Metriekexpressie
expression-valid: Expressie is geldig
metric-fields-hint: "Beschikbare velden: {fields}"
sample-window-label: Steekproefvenster
sample-window-hint: "Waarschuwing wordt geactiveerd wanneer de voorwaarde gedurende {duration} is voldaan"
cooldown-label: Wachttijd
cooldown-hint: "{duration} tussen waarschuwingen per container"
alert-type: Waarschuwingstype
+3
View File
@@ -186,6 +186,7 @@ notifications:
log-filter: Filtr logów
metric-filter: Metryka
cooldown: Czas oczekiwania
sample-window: Okno próbkowania
destination: Miejsce docelowe
dispatcher-deleted: Dispatcher usunięty
containers-count: "{count} kontenerów"
@@ -218,6 +219,8 @@ notifications:
metric-filter: Wyrażenie metryki
expression-valid: Wyrażenie jest poprawne
metric-fields-hint: "Dostępne pola: {fields}"
sample-window-label: Okno próbkowania
sample-window-hint: "Alert jest wyzwalany, gdy warunek jest spełniony przez {duration}"
cooldown-label: Czas oczekiwania
cooldown-hint: "{duration} między alertami na kontener"
alert-type: Typ alertu
+3
View File
@@ -188,6 +188,7 @@ notifications:
log-filter: Filtro de registo
metric-filter: Métrica
cooldown: Tempo de espera
sample-window: Janela de amostragem
destination: Destino
dispatcher-deleted: Dispatcher eliminado
containers-count: "{count} contentores"
@@ -220,6 +221,8 @@ notifications:
metric-filter: Expressão de Métrica
expression-valid: A expressão é válida
metric-fields-hint: "Campos disponíveis: {fields}"
sample-window-label: Janela de amostragem
sample-window-hint: "O alerta é ativado quando a condição é cumprida durante {duration}"
cooldown-label: Tempo de espera
cooldown-hint: "{duration} entre alertas por contentor"
alert-type: Tipo de alerta
+3
View File
@@ -178,6 +178,7 @@ notifications:
log-filter: Filtro de log
metric-filter: Métrica
cooldown: Tempo de espera
sample-window: Janela de amostragem
destination: Destino
dispatcher-deleted: Dispatcher excluído
containers-count: "{count} containers"
@@ -210,6 +211,8 @@ notifications:
metric-filter: Expressão de Métrica
expression-valid: A expressão é válida
metric-fields-hint: "Campos disponíveis: {fields}"
sample-window-label: Janela de amostragem
sample-window-hint: "O alerta é acionado quando a condição é atendida por {duration}"
cooldown-label: Tempo de espera
cooldown-hint: "{duration} entre alertas por container"
alert-type: Tipo de alerta
+3
View File
@@ -179,6 +179,7 @@ notifications:
log-filter: Фильтр логов
metric-filter: Метрика
cooldown: Период ожидания
sample-window: Окно выборки
destination: Назначение
dispatcher-deleted: Диспетчер удалён
containers-count: "{count} контейнеров"
@@ -211,6 +212,8 @@ notifications:
metric-filter: Выражение метрики
expression-valid: Выражение корректно
metric-fields-hint: "Доступные поля: {fields}"
sample-window-label: Окно выборки
sample-window-hint: "Оповещение срабатывает, когда условие выполняется в течение {duration}"
cooldown-label: Период ожидания
cooldown-hint: "{duration} между оповещениями на контейнер"
alert-type: Тип оповещения
+3
View File
@@ -184,6 +184,7 @@ notifications:
log-filter: Filter dnevnika
metric-filter: Metrika
cooldown: Čas mirovanja
sample-window: Vzorčno okno
destination: Cilj
dispatcher-deleted: Pošiljatelj izbrisan
containers-count: "{count} zabojnikov"
@@ -216,6 +217,8 @@ notifications:
metric-filter: Izraz metrike
expression-valid: Izraz je veljaven
metric-fields-hint: "Razpoložljiva polja: {fields}"
sample-window-label: Vzorčno okno
sample-window-hint: "Opozorilo se sproži, ko je pogoj izpolnjen za {duration}"
cooldown-label: Čas mirovanja
cooldown-hint: "{duration} med opozorili na zabojnik"
alert-type: Vrsta opozorila
+3
View File
@@ -179,6 +179,7 @@ notifications:
log-filter: Günlük filtresi
metric-filter: Metrik
cooldown: Bekleme süresi
sample-window: Örnek penceresi
destination: Hedef
dispatcher-deleted: Dağıtıcı silindi
containers-count: "{count} konteyner"
@@ -211,6 +212,8 @@ notifications:
metric-filter: Metrik İfadesi
expression-valid: İfade geçerli
metric-fields-hint: "Kullanılabilir alanlar: {fields}"
sample-window-label: Örnek Penceresi
sample-window-hint: "Koşul {duration} boyunca karşılandığında uyarı tetiklenir"
cooldown-label: Bekleme süresi
cooldown-hint: "Konteyner başına uyarılar arası {duration}"
alert-type: Uyarı Türü
+3
View File
@@ -182,6 +182,7 @@ notifications:
log-filter: 日誌篩選器
metric-filter: 指標
cooldown: 冷卻時間
sample-window: 取樣視窗
destination: 目標
dispatcher-deleted: 發送器已刪除
containers-count: "{count} 個容器"
@@ -214,6 +215,8 @@ notifications:
metric-filter: 指標表達式
expression-valid: 表達式有效
metric-fields-hint: "可用欄位:{fields}"
sample-window-label: 取樣視窗
sample-window-hint: "當條件在 {duration} 內滿足時觸發警報"
cooldown-label: 冷卻時間
cooldown-hint: "每個容器警報間隔 {duration}"
alert-type: 警報類型
+3
View File
@@ -179,6 +179,7 @@ notifications:
log-filter: 日志筛选器
metric-filter: 指标
cooldown: 冷却时间
sample-window: 采样窗口
destination: 目标
dispatcher-deleted: 发送器已删除
containers-count: "{count} 个容器"
@@ -211,6 +212,8 @@ notifications:
metric-filter: 指标表达式
expression-valid: 表达式有效
metric-fields-hint: "可用字段:{fields}"
sample-window-label: 采样窗口
sample-window-hint: "当条件在 {duration} 内满足时触发警报"
cooldown-label: 冷却时间
cooldown-hint: "每个容器警报间隔 {duration}"
alert-type: 警报类型
+3
View File
@@ -100,6 +100,9 @@ message NotificationSubscription {
int32 dispatcherId = 4;
string logExpression = 5;
string containerExpression = 6;
string metricExpression = 7;
int32 cooldown = 8;
int32 sampleWindow = 9;
}
message NotificationDispatcher {
+1
View File
@@ -61,6 +61,7 @@ type SubscriptionConfig struct {
ContainerExpression string `json:"containerExpression"`
MetricExpression string `json:"metricExpression,omitempty"`
Cooldown int `json:"cooldown,omitempty"`
SampleWindow int `json:"sampleWindow,omitempty"`
}
// DispatcherConfig represents a notification dispatcher configuration