Files
zeekay b9f9cdf918 feat: bump o11y fork to latest SigNoz — full re-sync, builds green (§4c retained)
Re-sync the entire SigNoz-derived Go tree to a clean fork of latest SigNoz
main (upstream 3e6339019), eliminating the version-skew that broke
`go build ./...` (mixed-era packages, ~14 imported-but-absent packages,
partial signoz->o11y / clickhouse->datastore rebrands, self-import cycle,
duplicate package decls).

- pkg/ + cmd/ wholesale replaced with latest SigNoz; imports rewritten
  github.com/SigNoz/signoz/ -> github.com/hanzoai/o11y/.
- Adopt SigNoz's CURRENT structure (resourcefilter->telemetryresourcefilter,
  integrationtypes->cloudintegrationtypes, roletypes/grammar/times folded in
  upstream); old paths dropped.
- Fork deps repathed to hanzoai + bumped: signoz-otel-collector v0.144.6,
  clickhouse-go-mock v0.14.1, govaluate v0.1.0; expr-lang/expr => hanzoai/expr
  v1.17.8 (replace). go.mod reconciled from latest SigNoz; go.sum re-tidied.
- ee/ + cmd/enterprise stripped — o11y stays MIT-only; community edition
  imports no ee packages.
- Preserved o11y additions: module path github.com/hanzoai/o11y, mount.go
  (cloud.Register/zip mount adapter, HIP-0106), NOTICE + LICENSE (SigNoz §4c
  attribution retained; upstream pkg files are headerless per SigNoz convention).
- Reverted o11y cosmetic renames (o11y*/datastore*) and experimental originals
  (authz/iamauthz, billing, zapreceiver, zapmetricreceiver) to SigNoz canonical
  to kill the skew; snapshots retained in git history for re-layering.

go build ./... = exit 0. go vet clean except benign "unreachable code" in
generated ANTLR parser files (inherited from upstream).
2026-07-02 14:20:32 -07:00

229 lines
6.9 KiB
Go

package rules
import (
"math"
"time"
"github.com/hanzoai/o11y/pkg/types/metrictypes"
qbtypes "github.com/hanzoai/o11y/pkg/types/querybuildertypes/querybuildertypesv5"
ruletypes "github.com/hanzoai/o11y/pkg/types/ruletypes"
"github.com/hanzoai/o11y/pkg/types/telemetrytypes"
"github.com/hanzoai/o11y/pkg/valuer"
)
// ThresholdRuleTestCase defines test case structure for threshold rule test notifications
type ThresholdRuleTestCase struct {
Name string
Values [][]any
ExpectAlerts int
ExpectValue float64
}
// PromRuleTestValue represents a single value point in a PromQL rule test case
type PromRuleTestValue struct {
Offset time.Duration // offset from baseTime (negative = in the past)
Value float64
}
// PromRuleTestCase defines test case structure for PromQL rule test notifications
type PromRuleTestCase struct {
Name string
Values []PromRuleTestValue
ExpectAlerts int
ExpectValue float64
}
// ThresholdRuleAtLeastOnceValueAbove creates a PostableRule for threshold rule test notifications
func ThresholdRuleAtLeastOnceValueAbove(target float64, recovery *float64) ruletypes.PostableRule {
return ruletypes.PostableRule{
AlertName: "test-alert",
AlertType: ruletypes.AlertTypeMetric,
RuleType: ruletypes.RuleTypeThreshold,
Evaluation: &ruletypes.EvaluationEnvelope{Kind: ruletypes.RollingEvaluation, Spec: ruletypes.RollingWindow{
EvalWindow: valuer.MustParseTextDuration("5m"),
Frequency: valuer.MustParseTextDuration("1m"),
}},
Labels: map[string]string{
"service.name": "frontend",
},
Annotations: map[string]string{
"value": "{{$value}}",
},
Version: "v5",
RuleCondition: &ruletypes.RuleCondition{
MatchType: ruletypes.AtleastOnce,
CompareOperator: ruletypes.ValueIsAbove,
Target: &target,
CompositeQuery: &ruletypes.AlertCompositeQuery{
QueryType: ruletypes.QueryTypeBuilder,
Queries: []qbtypes.QueryEnvelope{
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "A",
StepInterval: qbtypes.Step{Duration: 60 * time.Second},
Signal: telemetrytypes.SignalMetrics,
Aggregations: []qbtypes.MetricAggregation{
{
MetricName: "probe_success",
TimeAggregation: metrictypes.TimeAggregationAvg,
SpaceAggregation: metrictypes.SpaceAggregationAvg,
},
},
},
},
},
},
Thresholds: &ruletypes.RuleThresholdData{
Kind: ruletypes.BasicThresholdKind,
Spec: ruletypes.BasicRuleThresholds{
{
Name: "primary",
TargetValue: &target,
RecoveryTarget: recovery,
MatchType: ruletypes.AtleastOnce,
CompareOperator: ruletypes.ValueIsAbove,
},
},
},
},
NotificationSettings: &ruletypes.NotificationSettings{},
}
}
// BuildPromAtLeastOnceValueAbove creates a PostableRule for PromQL rule test notifications
func BuildPromAtLeastOnceValueAbove(target float64, recovery *float64) ruletypes.PostableRule {
return ruletypes.PostableRule{
AlertName: "test-prom-alert",
AlertType: ruletypes.AlertTypeMetric,
RuleType: ruletypes.RuleTypeProm,
Evaluation: &ruletypes.EvaluationEnvelope{Kind: ruletypes.RollingEvaluation, Spec: ruletypes.RollingWindow{
EvalWindow: valuer.MustParseTextDuration("5m"),
Frequency: valuer.MustParseTextDuration("1m"),
}},
Labels: map[string]string{
"service.name": "frontend",
},
Annotations: map[string]string{
"value": "{{$value}}",
},
Version: "v5",
RuleCondition: &ruletypes.RuleCondition{
MatchType: ruletypes.AtleastOnce,
SelectedQuery: "A",
CompareOperator: ruletypes.ValueIsAbove,
Target: &target,
CompositeQuery: &ruletypes.AlertCompositeQuery{
QueryType: ruletypes.QueryTypePromQL,
PanelType: ruletypes.PanelTypeGraph,
Queries: []qbtypes.QueryEnvelope{
{
Type: qbtypes.QueryTypePromQL,
Spec: qbtypes.PromQuery{
Name: "A",
Query: "{\"test_metric\"}",
Disabled: false,
Stats: false,
},
},
},
},
Thresholds: &ruletypes.RuleThresholdData{
Kind: ruletypes.BasicThresholdKind,
Spec: ruletypes.BasicRuleThresholds{
{
Name: "primary",
TargetValue: &target,
RecoveryTarget: recovery,
MatchType: ruletypes.AtleastOnce,
CompareOperator: ruletypes.ValueIsAbove,
Channels: []string{"slack"},
},
},
},
},
NotificationSettings: &ruletypes.NotificationSettings{},
}
}
var (
// TcTestNotiSendUnmatchedThresholdRule contains test cases for threshold rule test notifications
TcTestNotiSendUnmatchedThresholdRule = []ThresholdRuleTestCase{
{
Name: "return first valid point in case of test notification",
Values: [][]any{
{float64(3), "attr", time.Now()},
{float64(4), "attr", time.Now().Add(1 * time.Minute)},
},
ExpectAlerts: 1,
ExpectValue: 3,
},
{
Name: "No data in DB so no alerts fired",
Values: [][]any{},
ExpectAlerts: 0,
},
{
Name: "return first valid point in case of test notification skips NaN and Inf",
Values: [][]any{
{math.NaN(), "attr", time.Now()},
{math.Inf(1), "attr", time.Now().Add(1 * time.Minute)},
{float64(7), "attr", time.Now().Add(2 * time.Minute)},
},
ExpectAlerts: 1,
ExpectValue: 7,
},
{
Name: "If found matching alert with given target value, return the alerting value rather than first valid point",
Values: [][]any{
{float64(1), "attr", time.Now()},
{float64(2), "attr", time.Now().Add(1 * time.Minute)},
{float64(3), "attr", time.Now().Add(2 * time.Minute)},
{float64(12), "attr", time.Now().Add(3 * time.Minute)},
},
ExpectAlerts: 1,
ExpectValue: 12,
},
}
// TcTestNotificationSendUnmatchedPromRule contains test cases for PromQL rule test notifications
TcTestNotificationSendUnmatchedPromRule = []PromRuleTestCase{
{
Name: "return first valid point in case of test notification",
Values: []PromRuleTestValue{
{Offset: -4 * time.Minute, Value: 3},
{Offset: -3 * time.Minute, Value: 4},
},
ExpectAlerts: 1,
ExpectValue: 3,
},
{
Name: "No data in DB so no alerts fired",
Values: []PromRuleTestValue{},
ExpectAlerts: 0,
},
{
Name: "return first valid point in case of test notification skips NaN and Inf",
Values: []PromRuleTestValue{
{Offset: -4 * time.Minute, Value: math.NaN()},
{Offset: -3 * time.Minute, Value: math.Inf(1)},
{Offset: -2 * time.Minute, Value: 7},
},
ExpectAlerts: 1,
ExpectValue: 7,
},
{
Name: "If found matching alert with given target value, return the alerting value rather than first valid point",
Values: []PromRuleTestValue{
{Offset: -4 * time.Minute, Value: 1},
{Offset: -3 * time.Minute, Value: 2},
{Offset: -2 * time.Minute, Value: 3},
{Offset: -1 * time.Minute, Value: 12},
},
ExpectAlerts: 1,
ExpectValue: 12,
},
}
)