github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/ruler/compat_test.go (about) 1 package ruler 2 3 import ( 4 "context" 5 "errors" 6 "math" 7 "net/http" 8 "testing" 9 "time" 10 11 "github.com/go-kit/log" 12 "github.com/prometheus/client_golang/prometheus" 13 "github.com/prometheus/client_golang/prometheus/testutil" 14 "github.com/prometheus/common/model" 15 "github.com/prometheus/prometheus/pkg/value" 16 "github.com/prometheus/prometheus/promql" 17 "github.com/prometheus/prometheus/promql/parser" 18 "github.com/stretchr/testify/require" 19 "github.com/weaveworks/common/httpgrpc" 20 21 "github.com/cortexproject/cortex/pkg/cortexpb" 22 ) 23 24 type fakePusher struct { 25 request *cortexpb.WriteRequest 26 response *cortexpb.WriteResponse 27 err error 28 } 29 30 func (p *fakePusher) Push(ctx context.Context, r *cortexpb.WriteRequest) (*cortexpb.WriteResponse, error) { 31 p.request = r 32 return p.response, p.err 33 } 34 35 func TestPusherAppendable(t *testing.T) { 36 pusher := &fakePusher{} 37 pa := NewPusherAppendable(pusher, "user-1", nil, prometheus.NewCounter(prometheus.CounterOpts{}), prometheus.NewCounter(prometheus.CounterOpts{})) 38 39 for _, tc := range []struct { 40 name string 41 series string 42 evalDelay time.Duration 43 value float64 44 expectedTS int64 45 }{ 46 { 47 name: "tenant without delay, normal value", 48 series: "foo_bar", 49 value: 1.234, 50 expectedTS: 120_000, 51 }, 52 { 53 name: "tenant without delay, stale nan value", 54 series: "foo_bar", 55 value: math.Float64frombits(value.StaleNaN), 56 expectedTS: 120_000, 57 }, 58 { 59 name: "tenant with delay, normal value", 60 series: "foo_bar", 61 value: 1.234, 62 expectedTS: 120_000, 63 evalDelay: time.Minute, 64 }, 65 { 66 name: "tenant with delay, stale nan value", 67 value: math.Float64frombits(value.StaleNaN), 68 expectedTS: 60_000, 69 evalDelay: time.Minute, 70 }, 71 { 72 name: "ALERTS without delay, normal value", 73 series: `ALERTS{alertname="boop"}`, 74 value: 1.234, 75 expectedTS: 120_000, 76 }, 77 { 78 name: "ALERTS without delay, stale nan value", 79 series: `ALERTS{alertname="boop"}`, 80 value: math.Float64frombits(value.StaleNaN), 81 expectedTS: 120_000, 82 }, 83 { 84 name: "ALERTS with delay, normal value", 85 series: `ALERTS{alertname="boop"}`, 86 value: 1.234, 87 expectedTS: 60_000, 88 evalDelay: time.Minute, 89 }, 90 { 91 name: "ALERTS with delay, stale nan value", 92 series: `ALERTS_FOR_STATE{alertname="boop"}`, 93 value: math.Float64frombits(value.StaleNaN), 94 expectedTS: 60_000, 95 evalDelay: time.Minute, 96 }, 97 } { 98 t.Run(tc.name, func(t *testing.T) { 99 ctx := context.Background() 100 pa.rulesLimits = &ruleLimits{ 101 evalDelay: tc.evalDelay, 102 } 103 104 lbls, err := parser.ParseMetric(tc.series) 105 require.NoError(t, err) 106 107 pusher.response = &cortexpb.WriteResponse{} 108 a := pa.Appender(ctx) 109 _, err = a.Append(0, lbls, 120_000, tc.value) 110 require.NoError(t, err) 111 112 require.NoError(t, a.Commit()) 113 114 require.Equal(t, tc.expectedTS, pusher.request.Timeseries[0].Samples[0].TimestampMs) 115 116 }) 117 } 118 } 119 120 func TestPusherErrors(t *testing.T) { 121 for name, tc := range map[string]struct { 122 returnedError error 123 expectedWrites int 124 expectedFailures int 125 }{ 126 "no error": { 127 expectedWrites: 1, 128 expectedFailures: 0, 129 }, 130 131 "400 error": { 132 returnedError: httpgrpc.Errorf(http.StatusBadRequest, "test error"), 133 expectedWrites: 1, 134 expectedFailures: 0, // 400 errors not reported as failures. 135 }, 136 137 "500 error": { 138 returnedError: httpgrpc.Errorf(http.StatusInternalServerError, "test error"), 139 expectedWrites: 1, 140 expectedFailures: 1, // 500 errors are failures 141 }, 142 143 "unknown error": { 144 returnedError: errors.New("test error"), 145 expectedWrites: 1, 146 expectedFailures: 1, // unknown errors are not 400, so they are reported. 147 }, 148 } { 149 t.Run(name, func(t *testing.T) { 150 ctx := context.Background() 151 152 pusher := &fakePusher{err: tc.returnedError, response: &cortexpb.WriteResponse{}} 153 154 writes := prometheus.NewCounter(prometheus.CounterOpts{}) 155 failures := prometheus.NewCounter(prometheus.CounterOpts{}) 156 157 pa := NewPusherAppendable(pusher, "user-1", ruleLimits{evalDelay: 10 * time.Second}, writes, failures) 158 159 lbls, err := parser.ParseMetric("foo_bar") 160 require.NoError(t, err) 161 162 a := pa.Appender(ctx) 163 _, err = a.Append(0, lbls, int64(model.Now()), 123456) 164 require.NoError(t, err) 165 166 require.Equal(t, tc.returnedError, a.Commit()) 167 168 require.Equal(t, tc.expectedWrites, int(testutil.ToFloat64(writes))) 169 require.Equal(t, tc.expectedFailures, int(testutil.ToFloat64(failures))) 170 }) 171 } 172 } 173 174 func TestMetricsQueryFuncErrors(t *testing.T) { 175 for name, tc := range map[string]struct { 176 returnedError error 177 expectedQueries int 178 expectedFailedQueries int 179 }{ 180 "no error": { 181 expectedQueries: 1, 182 expectedFailedQueries: 0, 183 }, 184 185 "400 error": { 186 returnedError: httpgrpc.Errorf(http.StatusBadRequest, "test error"), 187 expectedQueries: 1, 188 expectedFailedQueries: 0, // 400 errors not reported as failures. 189 }, 190 191 "500 error": { 192 returnedError: httpgrpc.Errorf(http.StatusInternalServerError, "test error"), 193 expectedQueries: 1, 194 expectedFailedQueries: 1, // 500 errors are failures 195 }, 196 197 "promql.ErrStorage": { 198 returnedError: promql.ErrStorage{Err: errors.New("test error")}, 199 expectedQueries: 1, 200 expectedFailedQueries: 1, 201 }, 202 203 "promql.ErrQueryCanceled": { 204 returnedError: promql.ErrQueryCanceled("test error"), 205 expectedQueries: 1, 206 expectedFailedQueries: 0, // Not interesting. 207 }, 208 209 "promql.ErrQueryTimeout": { 210 returnedError: promql.ErrQueryTimeout("test error"), 211 expectedQueries: 1, 212 expectedFailedQueries: 0, // Not interesting. 213 }, 214 215 "promql.ErrTooManySamples": { 216 returnedError: promql.ErrTooManySamples("test error"), 217 expectedQueries: 1, 218 expectedFailedQueries: 0, // Not interesting. 219 }, 220 221 "unknown error": { 222 returnedError: errors.New("test error"), 223 expectedQueries: 1, 224 expectedFailedQueries: 1, // unknown errors are not 400, so they are reported. 225 }, 226 } { 227 t.Run(name, func(t *testing.T) { 228 queries := prometheus.NewCounter(prometheus.CounterOpts{}) 229 failures := prometheus.NewCounter(prometheus.CounterOpts{}) 230 231 mockFunc := func(ctx context.Context, q string, t time.Time) (promql.Vector, error) { 232 return promql.Vector{}, WrapQueryableErrors(tc.returnedError) 233 } 234 qf := MetricsQueryFunc(mockFunc, queries, failures) 235 236 _, err := qf(context.Background(), "test", time.Now()) 237 require.Equal(t, tc.returnedError, err) 238 239 require.Equal(t, tc.expectedQueries, int(testutil.ToFloat64(queries))) 240 require.Equal(t, tc.expectedFailedQueries, int(testutil.ToFloat64(failures))) 241 }) 242 } 243 } 244 245 func TestRecordAndReportRuleQueryMetrics(t *testing.T) { 246 queryTime := prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"user"}) 247 248 mockFunc := func(ctx context.Context, q string, t time.Time) (promql.Vector, error) { 249 time.Sleep(1 * time.Second) 250 return promql.Vector{}, nil 251 } 252 qf := RecordAndReportRuleQueryMetrics(mockFunc, queryTime.WithLabelValues("userID"), log.NewNopLogger()) 253 _, _ = qf(context.Background(), "test", time.Now()) 254 255 require.GreaterOrEqual(t, testutil.ToFloat64(queryTime.WithLabelValues("userID")), float64(1)) 256 }