mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 05:19:04 +08:00
fix: update prom rule to use range query (#4461)
This commit is contained in:
parent
3b98073ad4
commit
260d21afd0
@ -83,7 +83,7 @@ function RuleOptions({
|
|||||||
</InlineSelect>
|
</InlineSelect>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderThresholdMatchOpts = (): JSX.Element => (
|
const renderMatchOpts = (): JSX.Element => (
|
||||||
<InlineSelect
|
<InlineSelect
|
||||||
getPopupContainer={popupContainer}
|
getPopupContainer={popupContainer}
|
||||||
defaultValue={defaultMatchType}
|
defaultValue={defaultMatchType}
|
||||||
@ -98,17 +98,13 @@ function RuleOptions({
|
|||||||
</InlineSelect>
|
</InlineSelect>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderPromMatchOpts = (): JSX.Element => (
|
const onChangeEvalWindow = (value: string | unknown): void => {
|
||||||
<InlineSelect
|
const ew = (value as string) || alertDef.evalWindow;
|
||||||
getPopupContainer={popupContainer}
|
setAlertDef({
|
||||||
defaultValue={defaultMatchType}
|
...alertDef,
|
||||||
style={{ minWidth: '130px' }}
|
evalWindow: ew,
|
||||||
value={alertDef.condition?.matchType}
|
});
|
||||||
onChange={(value: string | unknown): void => handleMatchOptChange(value)}
|
};
|
||||||
>
|
|
||||||
<Select.Option value="1">{t('option_atleastonce')}</Select.Option>
|
|
||||||
</InlineSelect>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderEvalWindows = (): JSX.Element => (
|
const renderEvalWindows = (): JSX.Element => (
|
||||||
<InlineSelect
|
<InlineSelect
|
||||||
@ -116,13 +112,7 @@ function RuleOptions({
|
|||||||
defaultValue={defaultEvalWindow}
|
defaultValue={defaultEvalWindow}
|
||||||
style={{ minWidth: '120px' }}
|
style={{ minWidth: '120px' }}
|
||||||
value={alertDef.evalWindow}
|
value={alertDef.evalWindow}
|
||||||
onChange={(value: string | unknown): void => {
|
onChange={onChangeEvalWindow}
|
||||||
const ew = (value as string) || alertDef.evalWindow;
|
|
||||||
setAlertDef({
|
|
||||||
...alertDef,
|
|
||||||
evalWindow: ew,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Select.Option value="5m0s">{t('option_5min')}</Select.Option>
|
<Select.Option value="5m0s">{t('option_5min')}</Select.Option>
|
||||||
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
|
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
|
||||||
@ -133,6 +123,20 @@ function RuleOptions({
|
|||||||
</InlineSelect>
|
</InlineSelect>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderPromEvalWindows = (): JSX.Element => (
|
||||||
|
<InlineSelect
|
||||||
|
getPopupContainer={popupContainer}
|
||||||
|
defaultValue={defaultEvalWindow}
|
||||||
|
style={{ minWidth: '120px' }}
|
||||||
|
value={alertDef.evalWindow}
|
||||||
|
onChange={onChangeEvalWindow}
|
||||||
|
>
|
||||||
|
<Select.Option value="5m0s">{t('option_5min')}</Select.Option>
|
||||||
|
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
|
||||||
|
<Select.Option value="15m0s">{t('option_15min')}</Select.Option>
|
||||||
|
</InlineSelect>
|
||||||
|
);
|
||||||
|
|
||||||
const renderThresholdRuleOpts = (): JSX.Element => (
|
const renderThresholdRuleOpts = (): JSX.Element => (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Typography.Text>
|
<Typography.Text>
|
||||||
@ -147,7 +151,7 @@ function RuleOptions({
|
|||||||
onChange={onChangeSelectedQueryName}
|
onChange={onChangeSelectedQueryName}
|
||||||
/>
|
/>
|
||||||
<Typography.Text>is</Typography.Text>
|
<Typography.Text>is</Typography.Text>
|
||||||
{renderCompareOps()} {t('text_condition2')} {renderThresholdMatchOpts()}{' '}
|
{renderCompareOps()} {t('text_condition2')} {renderMatchOpts()}{' '}
|
||||||
{t('text_condition3')} {renderEvalWindows()}
|
{t('text_condition3')} {renderEvalWindows()}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -167,7 +171,8 @@ function RuleOptions({
|
|||||||
onChange={onChangeSelectedQueryName}
|
onChange={onChangeSelectedQueryName}
|
||||||
/>
|
/>
|
||||||
<Typography.Text>is</Typography.Text>
|
<Typography.Text>is</Typography.Text>
|
||||||
{renderCompareOps()} {t('text_condition2')} {renderPromMatchOpts()}
|
{renderCompareOps()} {t('text_condition2')} {renderMatchOpts()}
|
||||||
|
{t('text_condition3')} {renderPromEvalWindows()}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/prometheus/common/promlog"
|
"github.com/prometheus/common/promlog"
|
||||||
plog "github.com/prometheus/common/promlog"
|
plog "github.com/prometheus/common/promlog"
|
||||||
pconfig "github.com/prometheus/prometheus/config"
|
pconfig "github.com/prometheus/prometheus/config"
|
||||||
plabels "github.com/prometheus/prometheus/model/labels"
|
|
||||||
pql "github.com/prometheus/prometheus/promql"
|
pql "github.com/prometheus/prometheus/promql"
|
||||||
pstorage "github.com/prometheus/prometheus/storage"
|
pstorage "github.com/prometheus/prometheus/storage"
|
||||||
premote "github.com/prometheus/prometheus/storage/remote"
|
premote "github.com/prometheus/prometheus/storage/remote"
|
||||||
@ -89,8 +88,8 @@ func NewPqlEngine(config *pconfig.Config) (*PqlEngine, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PqlEngine) RunAlertQuery(ctx context.Context, qs string, t time.Time) (pql.Vector, error) {
|
func (p *PqlEngine) RunAlertQuery(ctx context.Context, qs string, start, end time.Time, interval time.Duration) (pql.Matrix, error) {
|
||||||
q, err := p.engine.NewInstantQuery(ctx, p.fanoutStorage, nil, qs, t)
|
q, err := p.engine.NewRangeQuery(ctx, p.fanoutStorage, nil, qs, start, end, interval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -101,16 +100,26 @@ func (p *PqlEngine) RunAlertQuery(ctx context.Context, qs string, t time.Time) (
|
|||||||
return nil, res.Err
|
return nil, res.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := res.Value.(type) {
|
switch typ := res.Value.(type) {
|
||||||
case pql.Vector:
|
case pql.Vector:
|
||||||
return v, nil
|
series := make([]pql.Series, 0, len(typ))
|
||||||
|
value := res.Value.(pql.Vector)
|
||||||
|
for _, smpl := range value {
|
||||||
|
series = append(series, pql.Series{
|
||||||
|
Metric: smpl.Metric,
|
||||||
|
Floats: []pql.FPoint{{T: smpl.T, F: smpl.F}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return series, nil
|
||||||
case pql.Scalar:
|
case pql.Scalar:
|
||||||
return pql.Vector{pql.Sample{
|
value := res.Value.(pql.Scalar)
|
||||||
T: v.T,
|
series := make([]pql.Series, 0, 1)
|
||||||
F: v.V,
|
series = append(series, pql.Series{
|
||||||
H: nil,
|
Floats: []pql.FPoint{{T: value.T, F: value.V}},
|
||||||
Metric: plabels.Labels{},
|
})
|
||||||
}}, nil
|
return series, nil
|
||||||
|
case pql.Matrix:
|
||||||
|
return res.Value.(pql.Matrix), nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("rule result is not a vector or scalar")
|
return nil, fmt.Errorf("rule result is not a vector or scalar")
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package rules
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -115,7 +116,9 @@ func (r *PromRule) targetVal() float64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return *r.ruleCondition.Target
|
unitConverter := converter.FromUnit(converter.Unit(r.ruleCondition.TargetUnit))
|
||||||
|
value := unitConverter.Convert(converter.Value{F: *r.ruleCondition.Target, U: converter.Unit(r.ruleCondition.TargetUnit)}, converter.Unit(r.Unit()))
|
||||||
|
return value.F
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *PromRule) Type() RuleType {
|
func (r *PromRule) Type() RuleType {
|
||||||
@ -322,14 +325,7 @@ func (r *PromRule) getPqlQuery() (string, error) {
|
|||||||
if query == "" {
|
if query == "" {
|
||||||
return query, fmt.Errorf("a promquery needs to be set for this rule to function")
|
return query, fmt.Errorf("a promquery needs to be set for this rule to function")
|
||||||
}
|
}
|
||||||
if r.ruleCondition.Target != nil && r.ruleCondition.CompareOp != CompareOpNone {
|
|
||||||
unitConverter := converter.FromUnit(converter.Unit(r.ruleCondition.TargetUnit))
|
|
||||||
value := unitConverter.Convert(converter.Value{F: *r.ruleCondition.Target, U: converter.Unit(r.ruleCondition.TargetUnit)}, converter.Unit(r.Unit()))
|
|
||||||
query = fmt.Sprintf("(%s) %s %f", query, ResolveCompareOp(r.ruleCondition.CompareOp), value.F)
|
|
||||||
return query, nil
|
return query, nil
|
||||||
} else {
|
|
||||||
return query, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -337,8 +333,26 @@ func (r *PromRule) getPqlQuery() (string, error) {
|
|||||||
return "", fmt.Errorf("invalid promql rule query")
|
return "", fmt.Errorf("invalid promql rule query")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *PromRule) matchType() MatchType {
|
||||||
|
if r.ruleCondition == nil {
|
||||||
|
return AtleastOnce
|
||||||
|
}
|
||||||
|
return r.ruleCondition.MatchType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PromRule) compareOp() CompareOp {
|
||||||
|
if r.ruleCondition == nil {
|
||||||
|
return ValueIsEq
|
||||||
|
}
|
||||||
|
return r.ruleCondition.CompareOp
|
||||||
|
}
|
||||||
|
|
||||||
func (r *PromRule) Eval(ctx context.Context, ts time.Time, queriers *Queriers) (interface{}, error) {
|
func (r *PromRule) Eval(ctx context.Context, ts time.Time, queriers *Queriers) (interface{}, error) {
|
||||||
|
|
||||||
|
start := ts.Add(-r.evalWindow)
|
||||||
|
end := ts
|
||||||
|
interval := 60 * time.Second // TODO(srikanthccv): this should be configurable
|
||||||
|
|
||||||
valueFormatter := formatter.FromUnit(r.Unit())
|
valueFormatter := formatter.FromUnit(r.Unit())
|
||||||
|
|
||||||
q, err := r.getPqlQuery()
|
q, err := r.getPqlQuery()
|
||||||
@ -346,7 +360,7 @@ func (r *PromRule) Eval(ctx context.Context, ts time.Time, queriers *Queriers) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
zap.S().Info("rule:", r.Name(), "\t evaluating promql query: ", q)
|
zap.S().Info("rule:", r.Name(), "\t evaluating promql query: ", q)
|
||||||
res, err := queriers.PqlEngine.RunAlertQuery(ctx, q, ts)
|
res, err := queriers.PqlEngine.RunAlertQuery(ctx, q, start, end, interval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.SetHealth(HealthBad)
|
r.SetHealth(HealthBad)
|
||||||
r.SetLastError(err)
|
r.SetLastError(err)
|
||||||
@ -360,16 +374,25 @@ func (r *PromRule) Eval(ctx context.Context, ts time.Time, queriers *Queriers) (
|
|||||||
|
|
||||||
var alerts = make(map[uint64]*Alert, len(res))
|
var alerts = make(map[uint64]*Alert, len(res))
|
||||||
|
|
||||||
for _, smpl := range res {
|
for _, series := range res {
|
||||||
l := make(map[string]string, len(smpl.Metric))
|
l := make(map[string]string, len(series.Metric))
|
||||||
for _, lbl := range smpl.Metric {
|
for _, lbl := range series.Metric {
|
||||||
l[lbl.Name] = lbl.Value
|
l[lbl.Name] = lbl.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(series.Floats) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
alertSmpl, shouldAlert := r.shouldAlert(series)
|
||||||
|
if !shouldAlert {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
thresholdFormatter := formatter.FromUnit(r.ruleCondition.TargetUnit)
|
thresholdFormatter := formatter.FromUnit(r.ruleCondition.TargetUnit)
|
||||||
threshold := thresholdFormatter.Format(r.targetVal(), r.ruleCondition.TargetUnit)
|
threshold := thresholdFormatter.Format(r.targetVal(), r.ruleCondition.TargetUnit)
|
||||||
|
|
||||||
tmplData := AlertTemplateData(l, valueFormatter.Format(smpl.F, r.Unit()), threshold)
|
tmplData := AlertTemplateData(l, valueFormatter.Format(alertSmpl.F, r.Unit()), threshold)
|
||||||
// Inject some convenience variables that are easier to remember for users
|
// Inject some convenience variables that are easier to remember for users
|
||||||
// who are not used to Go's templating system.
|
// who are not used to Go's templating system.
|
||||||
defs := "{{$labels := .Labels}}{{$value := .Value}}{{$threshold := .Threshold}}"
|
defs := "{{$labels := .Labels}}{{$value := .Value}}{{$threshold := .Threshold}}"
|
||||||
@ -392,7 +415,7 @@ func (r *PromRule) Eval(ctx context.Context, ts time.Time, queriers *Queriers) (
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
lb := plabels.NewBuilder(smpl.Metric).Del(plabels.MetricName)
|
lb := plabels.NewBuilder(alertSmpl.Metric).Del(plabels.MetricName)
|
||||||
|
|
||||||
for _, l := range r.labels {
|
for _, l := range r.labels {
|
||||||
lb.Set(l.Name, expand(l.Value))
|
lb.Set(l.Name, expand(l.Value))
|
||||||
@ -425,7 +448,7 @@ func (r *PromRule) Eval(ctx context.Context, ts time.Time, queriers *Queriers) (
|
|||||||
Annotations: annotations,
|
Annotations: annotations,
|
||||||
ActiveAt: ts,
|
ActiveAt: ts,
|
||||||
State: StatePending,
|
State: StatePending,
|
||||||
Value: smpl.F,
|
Value: alertSmpl.F,
|
||||||
GeneratorURL: r.GeneratorURL(),
|
GeneratorURL: r.GeneratorURL(),
|
||||||
Receivers: r.preferredChannels,
|
Receivers: r.preferredChannels,
|
||||||
}
|
}
|
||||||
@ -473,6 +496,137 @@ func (r *PromRule) Eval(ctx context.Context, ts time.Time, queriers *Queriers) (
|
|||||||
return len(r.active), nil
|
return len(r.active), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *PromRule) shouldAlert(series pql.Series) (pql.Sample, bool) {
|
||||||
|
var alertSmpl pql.Sample
|
||||||
|
var shouldAlert bool
|
||||||
|
switch r.matchType() {
|
||||||
|
case AtleastOnce:
|
||||||
|
// If any sample matches the condition, the rule is firing.
|
||||||
|
if r.compareOp() == ValueIsAbove {
|
||||||
|
for _, smpl := range series.Floats {
|
||||||
|
if smpl.F > r.targetVal() {
|
||||||
|
alertSmpl = pql.Sample{F: smpl.F, T: smpl.T, Metric: series.Metric}
|
||||||
|
shouldAlert = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsBelow {
|
||||||
|
for _, smpl := range series.Floats {
|
||||||
|
if smpl.F < r.targetVal() {
|
||||||
|
alertSmpl = pql.Sample{F: smpl.F, T: smpl.T, Metric: series.Metric}
|
||||||
|
shouldAlert = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsEq {
|
||||||
|
for _, smpl := range series.Floats {
|
||||||
|
if smpl.F == r.targetVal() {
|
||||||
|
alertSmpl = pql.Sample{F: smpl.F, T: smpl.T, Metric: series.Metric}
|
||||||
|
shouldAlert = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsNotEq {
|
||||||
|
for _, smpl := range series.Floats {
|
||||||
|
if smpl.F != r.targetVal() {
|
||||||
|
alertSmpl = pql.Sample{F: smpl.F, T: smpl.T, Metric: series.Metric}
|
||||||
|
shouldAlert = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case AllTheTimes:
|
||||||
|
// If all samples match the condition, the rule is firing.
|
||||||
|
shouldAlert = true
|
||||||
|
alertSmpl = pql.Sample{F: r.targetVal(), Metric: series.Metric}
|
||||||
|
if r.compareOp() == ValueIsAbove {
|
||||||
|
for _, smpl := range series.Floats {
|
||||||
|
if smpl.F <= r.targetVal() {
|
||||||
|
shouldAlert = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsBelow {
|
||||||
|
for _, smpl := range series.Floats {
|
||||||
|
if smpl.F >= r.targetVal() {
|
||||||
|
shouldAlert = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsEq {
|
||||||
|
for _, smpl := range series.Floats {
|
||||||
|
if smpl.F != r.targetVal() {
|
||||||
|
shouldAlert = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsNotEq {
|
||||||
|
for _, smpl := range series.Floats {
|
||||||
|
if smpl.F == r.targetVal() {
|
||||||
|
shouldAlert = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case OnAverage:
|
||||||
|
// If the average of all samples matches the condition, the rule is firing.
|
||||||
|
var sum float64
|
||||||
|
for _, smpl := range series.Floats {
|
||||||
|
if math.IsNaN(smpl.F) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sum += smpl.F
|
||||||
|
}
|
||||||
|
avg := sum / float64(len(series.Floats))
|
||||||
|
alertSmpl = pql.Sample{F: avg, Metric: series.Metric}
|
||||||
|
if r.compareOp() == ValueIsAbove {
|
||||||
|
if avg > r.targetVal() {
|
||||||
|
shouldAlert = true
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsBelow {
|
||||||
|
if avg < r.targetVal() {
|
||||||
|
shouldAlert = true
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsEq {
|
||||||
|
if avg == r.targetVal() {
|
||||||
|
shouldAlert = true
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsNotEq {
|
||||||
|
if avg != r.targetVal() {
|
||||||
|
shouldAlert = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case InTotal:
|
||||||
|
// If the sum of all samples matches the condition, the rule is firing.
|
||||||
|
var sum float64
|
||||||
|
for _, smpl := range series.Floats {
|
||||||
|
if math.IsNaN(smpl.F) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sum += smpl.F
|
||||||
|
}
|
||||||
|
alertSmpl = pql.Sample{F: sum, Metric: series.Metric}
|
||||||
|
if r.compareOp() == ValueIsAbove {
|
||||||
|
if sum > r.targetVal() {
|
||||||
|
shouldAlert = true
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsBelow {
|
||||||
|
if sum < r.targetVal() {
|
||||||
|
shouldAlert = true
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsEq {
|
||||||
|
if sum == r.targetVal() {
|
||||||
|
shouldAlert = true
|
||||||
|
}
|
||||||
|
} else if r.compareOp() == ValueIsNotEq {
|
||||||
|
if sum != r.targetVal() {
|
||||||
|
shouldAlert = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return alertSmpl, shouldAlert
|
||||||
|
}
|
||||||
|
|
||||||
func (r *PromRule) String() string {
|
func (r *PromRule) String() string {
|
||||||
|
|
||||||
ar := PostableRule{
|
ar := PostableRule{
|
||||||
|
622
pkg/query-service/rules/promrule_test.go
Normal file
622
pkg/query-service/rules/promrule_test.go
Normal file
@ -0,0 +1,622 @@
|
|||||||
|
package rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
pql "github.com/prometheus/prometheus/promql"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testLogger struct {
|
||||||
|
t *testing.T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l testLogger) Log(args ...interface{}) error {
|
||||||
|
l.t.Log(args...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPromRuleShouldAlert(t *testing.T) {
|
||||||
|
postableRule := PostableRule{
|
||||||
|
Alert: "Test Rule",
|
||||||
|
AlertType: "METRIC_BASED_ALERT",
|
||||||
|
RuleType: RuleTypeProm,
|
||||||
|
EvalWindow: Duration(5 * time.Minute),
|
||||||
|
Frequency: Duration(1 * time.Minute),
|
||||||
|
RuleCondition: &RuleCondition{
|
||||||
|
CompositeQuery: &v3.CompositeQuery{
|
||||||
|
QueryType: v3.QueryTypePromQL,
|
||||||
|
PromQueries: map[string]*v3.PromQuery{
|
||||||
|
"A": {
|
||||||
|
Query: "dummy_query", // This is not used in the test
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
values pql.Series
|
||||||
|
expectAlert bool
|
||||||
|
compareOp string
|
||||||
|
matchType string
|
||||||
|
target float64
|
||||||
|
}{
|
||||||
|
// Test cases for Equals Always
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
// Test cases for Equals Once
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
// Test cases for Greater Than Always
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "1", // Greater Than
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 1.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "1", // Greater Than
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 4.5,
|
||||||
|
},
|
||||||
|
// Test cases for Greater Than Once
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "1", // Greater Than
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 4.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 4.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "1", // Greater Than
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 4.5,
|
||||||
|
},
|
||||||
|
// Test cases for Not Equals Always
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 0.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 0.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
// Test cases for Not Equals Once
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 0.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 0.0},
|
||||||
|
{F: 1.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
{F: 1.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 0.0,
|
||||||
|
},
|
||||||
|
// Test cases for Less Than Always
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 1.5},
|
||||||
|
{F: 1.5},
|
||||||
|
{F: 1.5},
|
||||||
|
{F: 1.5},
|
||||||
|
{F: 1.5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "2", // Less Than
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "2", // Less Than
|
||||||
|
matchType: "2", // Always
|
||||||
|
target: 4,
|
||||||
|
},
|
||||||
|
// Test cases for Less Than Once
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 2.5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "2", // Less Than
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
{F: 4.5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "2", // Less Than
|
||||||
|
matchType: "1", // Once
|
||||||
|
target: 4,
|
||||||
|
},
|
||||||
|
// Test cases for OnAverage
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "3", // OnAverage
|
||||||
|
target: 6.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "3", // OnAverage
|
||||||
|
target: 4.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "3", // OnAverage
|
||||||
|
target: 4.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "3", // OnAverage
|
||||||
|
target: 6.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "1", // Greater Than
|
||||||
|
matchType: "3", // OnAverage
|
||||||
|
target: 4.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "2", // Less Than
|
||||||
|
matchType: "3", // OnAverage
|
||||||
|
target: 12.0,
|
||||||
|
},
|
||||||
|
// Test cases for InTotal
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "4", // InTotal
|
||||||
|
target: 30.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 4.0},
|
||||||
|
{F: 6.0},
|
||||||
|
{F: 8.0},
|
||||||
|
{F: 2.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "3", // Equals
|
||||||
|
matchType: "4", // InTotal
|
||||||
|
target: 20.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "4", // InTotal
|
||||||
|
target: 9.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "4", // Not Equals
|
||||||
|
matchType: "4", // InTotal
|
||||||
|
target: 10.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 10.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "1", // Greater Than
|
||||||
|
matchType: "4", // InTotal
|
||||||
|
target: 10.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 10.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "1", // Greater Than
|
||||||
|
matchType: "4", // InTotal
|
||||||
|
target: 20.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 10.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: true,
|
||||||
|
compareOp: "2", // Less Than
|
||||||
|
matchType: "4", // InTotal
|
||||||
|
target: 30.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
values: pql.Series{
|
||||||
|
Floats: []pql.FPoint{
|
||||||
|
{F: 10.0},
|
||||||
|
{F: 10.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAlert: false,
|
||||||
|
compareOp: "2", // Less Than
|
||||||
|
matchType: "4", // InTotal
|
||||||
|
target: 20.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, c := range cases {
|
||||||
|
postableRule.RuleCondition.CompareOp = CompareOp(c.compareOp)
|
||||||
|
postableRule.RuleCondition.MatchType = MatchType(c.matchType)
|
||||||
|
postableRule.RuleCondition.Target = &c.target
|
||||||
|
|
||||||
|
rule, err := NewPromRule("69", &postableRule, testLogger{t}, PromRuleOpts{})
|
||||||
|
if err != nil {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, shoulAlert := rule.shouldAlert(c.values)
|
||||||
|
assert.Equal(t, c.expectAlert, shoulAlert, "Test case %d", idx)
|
||||||
|
}
|
||||||
|
}
|
@ -1025,7 +1025,7 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time, queriers *Querie
|
|||||||
// who are not used to Go's templating system.
|
// who are not used to Go's templating system.
|
||||||
defs := "{{$labels := .Labels}}{{$value := .Value}}{{$threshold := .Threshold}}"
|
defs := "{{$labels := .Labels}}{{$value := .Value}}{{$threshold := .Threshold}}"
|
||||||
|
|
||||||
// utility function to apply go template on labels and annots
|
// utility function to apply go template on labels and annotations
|
||||||
expand := func(text string) string {
|
expand := func(text string) string {
|
||||||
|
|
||||||
tmpl := NewTemplateExpander(
|
tmpl := NewTemplateExpander(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user