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  }