mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 18:59:10 +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.",
|
"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_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_alert_frequency": "Run alert every",
|
||||||
"text_for": "minutes",
|
"text_for": "minutes",
|
||||||
"selected_query_placeholder": "Select query"
|
"selected_query_placeholder": "Select query"
|
||||||
|
@ -118,6 +118,8 @@
|
|||||||
"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_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_alert_frequency": "Run alert every",
|
||||||
"text_for": "minutes",
|
"text_for": "minutes",
|
||||||
"selected_query_placeholder": "Select query"
|
"selected_query_placeholder": "Select query"
|
||||||
|
@ -323,6 +323,45 @@ function RuleOptions({
|
|||||||
<Typography.Text>{t('text_for')}</Typography.Text>
|
<Typography.Text>{t('text_for')}</Typography.Text>
|
||||||
</Space>
|
</Space>
|
||||||
</VerticalLine>
|
</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>
|
</Space>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
@ -37,6 +37,8 @@ export interface RuleCondition {
|
|||||||
selectedQueryName?: string;
|
selectedQueryName?: string;
|
||||||
alertOnAbsent?: boolean | undefined;
|
alertOnAbsent?: boolean | undefined;
|
||||||
absentFor?: number | undefined;
|
absentFor?: number | undefined;
|
||||||
|
requireMinPoints?: boolean | undefined;
|
||||||
|
requiredNumPoints?: number | undefined;
|
||||||
}
|
}
|
||||||
export interface Labels {
|
export interface Labels {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
|
@ -106,16 +106,18 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RuleCondition struct {
|
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"`
|
||||||
AlertOnAbsent bool `yaml:"alertOnAbsent,omitempty" json:"alertOnAbsent,omitempty"`
|
AlertOnAbsent bool `yaml:"alertOnAbsent,omitempty" json:"alertOnAbsent,omitempty"`
|
||||||
AbsentFor uint64 `yaml:"absentFor,omitempty" json:"absentFor,omitempty"`
|
AbsentFor uint64 `yaml:"absentFor,omitempty" json:"absentFor,omitempty"`
|
||||||
MatchType MatchType `json:"matchType,omitempty"`
|
MatchType MatchType `json:"matchType,omitempty"`
|
||||||
TargetUnit string `json:"targetUnit,omitempty"`
|
TargetUnit string `json:"targetUnit,omitempty"`
|
||||||
Algorithm string `json:"algorithm,omitempty"`
|
Algorithm string `json:"algorithm,omitempty"`
|
||||||
Seasonality string `json:"seasonality,omitempty"`
|
Seasonality string `json:"seasonality,omitempty"`
|
||||||
SelectedQuery string `json:"selectedQueryName,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 {
|
func (rc *RuleCondition) GetSelectedQueryName() string {
|
||||||
|
@ -353,6 +353,13 @@ func (r *BaseRule) ShouldAlert(series v3.Series) (Sample, bool) {
|
|||||||
return alertSmpl, false
|
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() {
|
switch r.matchType() {
|
||||||
case AtleastOnce:
|
case AtleastOnce:
|
||||||
// If any sample matches the condition, the rule is firing.
|
// 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