mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 14:18:58 +08:00
feat: add ability to configure number of required points (#5242)
This commit is contained in:
parent
a98c8db949
commit
4f76e13dbe
@ -118,6 +118,8 @@
|
||||
"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_require_min_points": "Run alert evaluation only when there are minimum of",
|
||||
"text_num_points": "data points in each result group",
|
||||
"text_alert_frequency": "Run alert every",
|
||||
"text_for": "minutes",
|
||||
"selected_query_placeholder": "Select query"
|
||||
|
@ -118,6 +118,8 @@
|
||||
"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_require_min_points": "Run alert evaluation only when there are minimum of",
|
||||
"text_num_points": "data points in each result group",
|
||||
"text_alert_frequency": "Run alert every",
|
||||
"text_for": "minutes",
|
||||
"selected_query_placeholder": "Select query"
|
||||
|
@ -323,6 +323,45 @@ function RuleOptions({
|
||||
<Typography.Text>{t('text_for')}</Typography.Text>
|
||||
</Space>
|
||||
</VerticalLine>
|
||||
|
||||
<VerticalLine>
|
||||
<Space direction="horizontal" align="center">
|
||||
<Form.Item noStyle name={['condition', 'requireMinPoints']}>
|
||||
<Checkbox
|
||||
checked={alertDef?.condition?.requireMinPoints}
|
||||
onChange={(e): void => {
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
condition: {
|
||||
...alertDef.condition,
|
||||
requireMinPoints: e.target.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Typography.Text>{t('text_require_min_points')}</Typography.Text>
|
||||
|
||||
<Form.Item noStyle name={['condition', 'requiredNumPoints']}>
|
||||
<InputNumber
|
||||
min={1}
|
||||
value={alertDef?.condition?.requiredNumPoints}
|
||||
onChange={(value): void => {
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
condition: {
|
||||
...alertDef.condition,
|
||||
requiredNumPoints: Number(value) || 0,
|
||||
},
|
||||
});
|
||||
}}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Typography.Text>{t('text_num_points')}</Typography.Text>
|
||||
</Space>
|
||||
</VerticalLine>
|
||||
</Space>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
|
@ -37,6 +37,8 @@ export interface RuleCondition {
|
||||
selectedQueryName?: string;
|
||||
alertOnAbsent?: boolean | undefined;
|
||||
absentFor?: number | undefined;
|
||||
requireMinPoints?: boolean | undefined;
|
||||
requiredNumPoints?: number | undefined;
|
||||
}
|
||||
export interface Labels {
|
||||
[key: string]: string;
|
||||
|
@ -106,16 +106,18 @@ const (
|
||||
)
|
||||
|
||||
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"`
|
||||
AlertOnAbsent bool `yaml:"alertOnAbsent,omitempty" json:"alertOnAbsent,omitempty"`
|
||||
AbsentFor uint64 `yaml:"absentFor,omitempty" json:"absentFor,omitempty"`
|
||||
MatchType MatchType `json:"matchType,omitempty"`
|
||||
TargetUnit string `json:"targetUnit,omitempty"`
|
||||
Algorithm string `json:"algorithm,omitempty"`
|
||||
Seasonality string `json:"seasonality,omitempty"`
|
||||
SelectedQuery string `json:"selectedQueryName,omitempty"`
|
||||
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"`
|
||||
AlertOnAbsent bool `yaml:"alertOnAbsent,omitempty" json:"alertOnAbsent,omitempty"`
|
||||
AbsentFor uint64 `yaml:"absentFor,omitempty" json:"absentFor,omitempty"`
|
||||
MatchType MatchType `json:"matchType,omitempty"`
|
||||
TargetUnit string `json:"targetUnit,omitempty"`
|
||||
Algorithm string `json:"algorithm,omitempty"`
|
||||
Seasonality string `json:"seasonality,omitempty"`
|
||||
SelectedQuery string `json:"selectedQueryName,omitempty"`
|
||||
RequireMinPoints bool `yaml:"requireMinPoints,omitempty" json:"requireMinPoints,omitempty"`
|
||||
RequiredNumPoints int `yaml:"requiredNumPoints,omitempty" json:"requiredNumPoints,omitempty"`
|
||||
}
|
||||
|
||||
func (rc *RuleCondition) GetSelectedQueryName() string {
|
||||
|
@ -353,6 +353,13 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
|
||||
return alertSmpl, false
|
||||
}
|
||||
|
||||
if r.ruleCondition.RequireMinPoints {
|
||||
if len(series.Points) < r.ruleCondition.RequiredNumPoints {
|
||||
zap.L().Info("not enough data points to evaluate series, skipping", zap.String("ruleid", r.ID()), zap.Int("numPoints", len(series.Points)), zap.Int("requiredPoints", r.ruleCondition.RequiredNumPoints))
|
||||
return alertSmpl, false
|
||||
}
|
||||
}
|
||||
|
||||
switch r.matchType() {
|
||||
case AtleastOnce:
|
||||
// If any sample matches the condition, the rule is firing.
|
||||
|
64
pkg/query-service/rules/base_rule_test.go
Normal file
64
pkg/query-service/rules/base_rule_test.go
Normal file
@ -0,0 +1,64 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
)
|
||||
|
||||
func TestBaseRule_RequireMinPoints(t *testing.T) {
|
||||
threshold := 1.0
|
||||
tests := []struct {
|
||||
name string
|
||||
rule *BaseRule
|
||||
shouldAlert bool
|
||||
series *v3.Series
|
||||
}{
|
||||
{
|
||||
name: "test should skip if less than min points",
|
||||
rule: &BaseRule{
|
||||
ruleCondition: &RuleCondition{
|
||||
RequireMinPoints: true,
|
||||
RequiredNumPoints: 4,
|
||||
},
|
||||
},
|
||||
series: &v3.Series{
|
||||
Points: []v3.Point{
|
||||
{Value: 1},
|
||||
{Value: 2},
|
||||
},
|
||||
},
|
||||
shouldAlert: false,
|
||||
},
|
||||
{
|
||||
name: "test should alert if more than min points",
|
||||
rule: &BaseRule{
|
||||
ruleCondition: &RuleCondition{
|
||||
RequireMinPoints: true,
|
||||
RequiredNumPoints: 4,
|
||||
CompareOp: ValueIsAbove,
|
||||
MatchType: AtleastOnce,
|
||||
Target: &threshold,
|
||||
},
|
||||
},
|
||||
series: &v3.Series{
|
||||
Points: []v3.Point{
|
||||
{Value: 1},
|
||||
{Value: 2},
|
||||
{Value: 3},
|
||||
{Value: 4},
|
||||
},
|
||||
},
|
||||
shouldAlert: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, shouldAlert := test.rule.ShouldAlert(*test.series)
|
||||
if shouldAlert != test.shouldAlert {
|
||||
t.Errorf("expected shouldAlert to be %v, got %v", test.shouldAlert, shouldAlert)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user