mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 20:16:00 +08:00
feat: add support for alerting on absent metric (#3245)
This commit is contained in:
parent
aadb962b6c
commit
3c419677e1
@ -111,5 +111,7 @@
|
|||||||
"exceptions_based_alert": "Exceptions-based Alert",
|
"exceptions_based_alert": "Exceptions-based Alert",
|
||||||
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
|
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
|
||||||
"field_unit": "Threshold unit",
|
"field_unit": "Threshold unit",
|
||||||
|
"text_alert_on_absent": "Send a notification if data is missing for",
|
||||||
|
"text_for": "minutes",
|
||||||
"selected_query_placeholder": "Select query"
|
"selected_query_placeholder": "Select query"
|
||||||
}
|
}
|
||||||
|
@ -111,5 +111,7 @@
|
|||||||
"exceptions_based_alert": "Exceptions-based Alert",
|
"exceptions_based_alert": "Exceptions-based Alert",
|
||||||
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
|
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
|
||||||
"field_unit": "Threshold unit",
|
"field_unit": "Threshold unit",
|
||||||
|
"text_alert_on_absent": "Send a notification if data is missing for",
|
||||||
|
"text_for": "minutes",
|
||||||
"selected_query_placeholder": "Select query"
|
"selected_query_placeholder": "Select query"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
Checkbox,
|
||||||
Form,
|
Form,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
InputNumberProps,
|
InputNumberProps,
|
||||||
@ -213,6 +214,7 @@ function RuleOptions({
|
|||||||
? renderPromRuleOptions()
|
? renderPromRuleOptions()
|
||||||
: renderThresholdRuleOpts()}
|
: renderThresholdRuleOpts()}
|
||||||
|
|
||||||
|
<Space direction="vertical" size="large">
|
||||||
<Space direction="horizontal" align="center">
|
<Space direction="horizontal" align="center">
|
||||||
<Form.Item noStyle name={['condition', 'target']}>
|
<Form.Item noStyle name={['condition', 'target']}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
@ -236,6 +238,43 @@ function RuleOptions({
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Space>
|
</Space>
|
||||||
|
<Space direction="horizontal" align="center">
|
||||||
|
<Form.Item noStyle name={['condition', 'alertOnAbsent']}>
|
||||||
|
<Checkbox
|
||||||
|
checked={alertDef?.condition?.alertOnAbsent}
|
||||||
|
onChange={(e): void => {
|
||||||
|
setAlertDef({
|
||||||
|
...alertDef,
|
||||||
|
condition: {
|
||||||
|
...alertDef.condition,
|
||||||
|
alertOnAbsent: e.target.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Typography.Text>{t('text_alert_on_absent')}</Typography.Text>
|
||||||
|
|
||||||
|
<Form.Item noStyle name={['condition', 'absentFor']}>
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
value={alertDef?.condition?.absentFor}
|
||||||
|
onChange={(value): void => {
|
||||||
|
setAlertDef({
|
||||||
|
...alertDef,
|
||||||
|
condition: {
|
||||||
|
...alertDef.condition,
|
||||||
|
absentFor: Number(value) || 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
onWheel={(e): void => e.currentTarget.blur()}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Typography.Text>{t('text_for')}</Typography.Text>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -32,6 +32,8 @@ export interface RuleCondition {
|
|||||||
matchType?: string;
|
matchType?: string;
|
||||||
targetUnit?: string;
|
targetUnit?: string;
|
||||||
selectedQueryName?: string;
|
selectedQueryName?: string;
|
||||||
|
alertOnAbsent?: boolean | undefined;
|
||||||
|
absentFor?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Labels {
|
export interface Labels {
|
||||||
|
@ -225,6 +225,7 @@ var TimeoutExcludedRoutes = map[string]bool{
|
|||||||
const (
|
const (
|
||||||
// AlertHelpPage is used in case default alert repo url is not set
|
// AlertHelpPage is used in case default alert repo url is not set
|
||||||
AlertHelpPage = "https://signoz.io/docs/userguide/alerts-management/#generator-url"
|
AlertHelpPage = "https://signoz.io/docs/userguide/alerts-management/#generator-url"
|
||||||
|
AlertTimeFormat = "2006-01-02 15:04:05"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetOrDefaultEnv(key string, fallback string) string {
|
func GetOrDefaultEnv(key string, fallback string) string {
|
||||||
|
@ -142,7 +142,9 @@ type RuleCondition struct {
|
|||||||
CompositeQuery *v3.CompositeQuery `json:"compositeQuery,omitempty" yaml:"compositeQuery,omitempty"`
|
CompositeQuery *v3.CompositeQuery `json:"compositeQuery,omitempty" yaml:"compositeQuery,omitempty"`
|
||||||
CompareOp CompareOp `yaml:"op,omitempty" json:"op,omitempty"`
|
CompareOp CompareOp `yaml:"op,omitempty" json:"op,omitempty"`
|
||||||
Target *float64 `yaml:"target,omitempty" json:"target,omitempty"`
|
Target *float64 `yaml:"target,omitempty" json:"target,omitempty"`
|
||||||
MatchType `json:"matchType,omitempty"`
|
AlertOnAbsent bool `yaml:"alertOnAbsent,omitempty" json:"alertOnAbsent,omitempty"`
|
||||||
|
AbsentFor time.Duration `yaml:"absentFor,omitempty" json:"absentFor,omitempty"`
|
||||||
|
MatchType MatchType `json:"matchType,omitempty"`
|
||||||
TargetUnit string `json:"targetUnit,omitempty"`
|
TargetUnit string `json:"targetUnit,omitempty"`
|
||||||
SelectedQuery string `json:"selectedQueryName,omitempty"`
|
SelectedQuery string `json:"selectedQueryName,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ type Sample struct {
|
|||||||
// Label keys as-is from the result query.
|
// Label keys as-is from the result query.
|
||||||
// The original labels are used to prepare the related{logs, traces} link in alert notification
|
// The original labels are used to prepare the related{logs, traces} link in alert notification
|
||||||
MetricOrig labels.Labels
|
MetricOrig labels.Labels
|
||||||
|
|
||||||
|
IsMissing bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Sample) String() string {
|
func (s Sample) String() string {
|
||||||
|
@ -71,6 +71,8 @@ type ThresholdRule struct {
|
|||||||
temporalityMap map[string]map[v3.Temporality]bool
|
temporalityMap map[string]map[v3.Temporality]bool
|
||||||
|
|
||||||
opts ThresholdRuleOpts
|
opts ThresholdRuleOpts
|
||||||
|
|
||||||
|
lastTimestampWithDatapoints time.Time
|
||||||
typ string
|
typ string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,6 +533,7 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer
|
|||||||
if err := rows.Scan(vars...); err != nil {
|
if err := rows.Scan(vars...); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
r.lastTimestampWithDatapoints = time.Now()
|
||||||
|
|
||||||
sample := Sample{}
|
sample := Sample{}
|
||||||
// Why do we maintain two labels sets? Alertmanager requires
|
// Why do we maintain two labels sets? Alertmanager requires
|
||||||
@ -555,8 +558,8 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer
|
|||||||
if colName == "ts" || colName == "interval" {
|
if colName == "ts" || colName == "interval" {
|
||||||
sample.Point.T = timval.Unix()
|
sample.Point.T = timval.Unix()
|
||||||
} else {
|
} else {
|
||||||
lbls.Set(colName, timval.Format("2006-01-02 15:04:05"))
|
lbls.Set(colName, timval.Format(constants.AlertTimeFormat))
|
||||||
lblsOrig.Set(columnNames[i], timval.Format("2006-01-02 15:04:05"))
|
lblsOrig.Set(columnNames[i], timval.Format(constants.AlertTimeFormat))
|
||||||
}
|
}
|
||||||
|
|
||||||
case *float64:
|
case *float64:
|
||||||
@ -709,6 +712,20 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer
|
|||||||
|
|
||||||
zap.S().Debugf("ruleid:", r.ID(), "\t resultmap(potential alerts):", len(resultMap))
|
zap.S().Debugf("ruleid:", r.ID(), "\t resultmap(potential alerts):", len(resultMap))
|
||||||
|
|
||||||
|
// if the data is missing for `For` duration then we should send alert
|
||||||
|
if r.ruleCondition.AlertOnAbsent && r.lastTimestampWithDatapoints.Add(r.Condition().AbsentFor).Before(time.Now()) {
|
||||||
|
zap.S().Debugf("ruleid:", r.ID(), "\t msg: no data found for rule condition")
|
||||||
|
lbls := labels.NewBuilder(labels.Labels{})
|
||||||
|
if !r.lastTimestampWithDatapoints.IsZero() {
|
||||||
|
lbls.Set("lastSeen", r.lastTimestampWithDatapoints.Format(constants.AlertTimeFormat))
|
||||||
|
}
|
||||||
|
result = append(result, Sample{
|
||||||
|
Metric: lbls.Labels(),
|
||||||
|
IsMissing: true,
|
||||||
|
})
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
for _, sample := range resultMap {
|
for _, sample := range resultMap {
|
||||||
// check alert rule condition before dumping results, if sendUnmatchedResults
|
// check alert rule condition before dumping results, if sendUnmatchedResults
|
||||||
// is set then add results irrespective of condition
|
// is set then add results irrespective of condition
|
||||||
@ -1177,6 +1194,11 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time, queriers *Querie
|
|||||||
|
|
||||||
annotations := make(labels.Labels, 0, len(r.annotations))
|
annotations := make(labels.Labels, 0, len(r.annotations))
|
||||||
for _, a := range r.annotations {
|
for _, a := range r.annotations {
|
||||||
|
if smpl.IsMissing {
|
||||||
|
if a.Name == labels.AlertDescriptionLabel || a.Name == labels.AlertSummaryLabel {
|
||||||
|
a.Value = labels.AlertMissingData
|
||||||
|
}
|
||||||
|
}
|
||||||
annotations = append(annotations, labels.Label{Name: normalizeLabelName(a.Name), Value: expand(a.Value)})
|
annotations = append(annotations, labels.Label{Name: normalizeLabelName(a.Name), Value: expand(a.Value)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,9 @@ const (
|
|||||||
|
|
||||||
RuleThresholdLabel = "threshold"
|
RuleThresholdLabel = "threshold"
|
||||||
AlertSummaryLabel = "summary"
|
AlertSummaryLabel = "summary"
|
||||||
|
AlertDescriptionLabel = "description"
|
||||||
|
|
||||||
|
AlertMissingData = "Missing data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Label is a key/value pair of strings.
|
// Label is a key/value pair of strings.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user