Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for alerting on absent metric #3245

Merged
merged 20 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/public/locales/en-GB/alerts.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,7 @@
"exceptions_based_alert": "Exceptions-based Alert",
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
"field_unit": "Threshold unit",
"text_alert_on_absent": "Send a notification if data is missing for",
"text_for": "minutes",
"selected_query_placeholder": "Select query"
}
2 changes: 2 additions & 0 deletions frontend/public/locales/en/alerts.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,7 @@
"exceptions_based_alert": "Exceptions-based Alert",
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
"field_unit": "Threshold unit",
"text_alert_on_absent": "Send a notification if data is missing for",
"text_for": "minutes",
"selected_query_placeholder": "Select query"
}
83 changes: 61 additions & 22 deletions frontend/src/container/FormAlertRules/RuleOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Checkbox,
Form,
InputNumber,
InputNumberProps,
Expand Down Expand Up @@ -213,28 +214,66 @@ function RuleOptions({
? renderPromRuleOptions()
: renderThresholdRuleOpts()}

<Space direction="horizontal" align="center">
<Form.Item noStyle name={['condition', 'target']}>
<InputNumber
addonBefore={t('field_threshold')}
value={alertDef?.condition?.target}
onChange={onChange}
type="number"
onWheel={(e): void => e.currentTarget.blur()}
/>
</Form.Item>

<Form.Item noStyle>
<Select
getPopupContainer={popupContainer}
allowClear
showSearch
options={categorySelectOptions}
placeholder={t('field_unit')}
value={alertDef.condition.targetUnit}
onChange={onChangeAlertUnit}
/>
</Form.Item>
<Space direction="vertical" size="large">
<Space direction="horizontal" align="center">
<Form.Item noStyle name={['condition', 'target']}>
<InputNumber
addonBefore={t('field_threshold')}
value={alertDef?.condition?.target}
onChange={onChange}
type="number"
onWheel={(e): void => e.currentTarget.blur()}
/>
</Form.Item>

<Form.Item noStyle>
<Select
getPopupContainer={popupContainer}
allowClear
showSearch
options={categorySelectOptions}
placeholder={t('field_unit')}
value={alertDef.condition.targetUnit}
onChange={onChangeAlertUnit}
/>
</Form.Item>
</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>
</>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types/api/alerts/def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export interface RuleCondition {
matchType?: string;
targetUnit?: string;
selectedQueryName?: string;
alertOnAbsent?: boolean | undefined;
absentFor?: number | undefined;
}

export interface Labels {
Expand Down
3 changes: 2 additions & 1 deletion pkg/query-service/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ var TimeoutExcludedRoutes = map[string]bool{
// alert related constants
const (
// 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 {
Expand Down
8 changes: 5 additions & 3 deletions pkg/query-service/rules/alerting.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,11 @@ type RuleCondition struct {
CompositeQuery *v3.CompositeQuery `json:"compositeQuery,omitempty" yaml:"compositeQuery,omitempty"`
CompareOp CompareOp `yaml:"op,omitempty" json:"op,omitempty"`
Target *float64 `yaml:"target,omitempty" json:"target,omitempty"`
MatchType `json:"matchType,omitempty"`
TargetUnit string `json:"targetUnit,omitempty"`
SelectedQuery string `json:"selectedQueryName,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"`
SelectedQuery string `json:"selectedQueryName,omitempty"`
}

func (rc *RuleCondition) IsValid() bool {
Expand Down
2 changes: 2 additions & 0 deletions pkg/query-service/rules/resultTypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type Sample struct {
// Label keys as-is from the result query.
// The original labels are used to prepare the related{logs, traces} link in alert notification
MetricOrig labels.Labels

IsMissing bool
ankitnayan marked this conversation as resolved.
Show resolved Hide resolved
}

func (s Sample) String() string {
Expand Down
28 changes: 25 additions & 3 deletions pkg/query-service/rules/thresholdRule.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ type ThresholdRule struct {
temporalityMap map[string]map[v3.Temporality]bool

opts ThresholdRuleOpts
typ string

lastTimestampWithDatapoints time.Time
typ string
}

type ThresholdRuleOpts struct {
Expand Down Expand Up @@ -531,6 +533,7 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer
if err := rows.Scan(vars...); err != nil {
return nil, err
}
r.lastTimestampWithDatapoints = time.Now()
ankitnayan marked this conversation as resolved.
Show resolved Hide resolved

sample := Sample{}
// Why do we maintain two labels sets? Alertmanager requires
Expand All @@ -555,8 +558,8 @@ func (r *ThresholdRule) runChQuery(ctx context.Context, db clickhouse.Conn, quer
if colName == "ts" || colName == "interval" {
sample.Point.T = timval.Unix()
} else {
lbls.Set(colName, timval.Format("2006-01-02 15:04:05"))
lblsOrig.Set(columnNames[i], timval.Format("2006-01-02 15:04:05"))
lbls.Set(colName, timval.Format(constants.AlertTimeFormat))
lblsOrig.Set(columnNames[i], timval.Format(constants.AlertTimeFormat))
}

case *float64:
Expand Down Expand Up @@ -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))

// 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 {
// check alert rule condition before dumping results, if sendUnmatchedResults
// is set then add results irrespective of condition
Expand Down Expand Up @@ -1177,6 +1194,11 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time, queriers *Querie

annotations := make(labels.Labels, 0, len(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)})
}

Expand Down
7 changes: 5 additions & 2 deletions pkg/query-service/utils/labels/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ const (
AlertRuleIdLabel = "ruleId"
RuleSourceLabel = "ruleSource"

RuleThresholdLabel = "threshold"
AlertSummaryLabel = "summary"
RuleThresholdLabel = "threshold"
AlertSummaryLabel = "summary"
AlertDescriptionLabel = "description"

AlertMissingData = "Missing data"
)

// Label is a key/value pair of strings.
Expand Down
Loading