Merge branch 'master' into to-merge-release-2.19

pull/7379/head
Ganesh Vernekar 2020-06-10 20:01:25 +05:30 committed by GitHub
commit 9593b64ce6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1212 additions and 636 deletions

View File

@ -10,6 +10,9 @@
* [BUGFIX] React UI: Don't null out data when clicking on the current tab. #7243 * [BUGFIX] React UI: Don't null out data when clicking on the current tab. #7243
* [BUGFIX] PromQL: Correctly track number of samples for a query. #7307 * [BUGFIX] PromQL: Correctly track number of samples for a query. #7307
* [BUGFIX] PromQL: Return NaN when histogram buckets have 0 observations. #7318 * [BUGFIX] PromQL: Return NaN when histogram buckets have 0 observations. #7318
## 2.18.2 / 2020-06-09
* [BUGFIX] TSDB: Fix incorrect query results when using Prometheus with remote reads configured #7361 * [BUGFIX] TSDB: Fix incorrect query results when using Prometheus with remote reads configured #7361
## 2.18.1 / 2020-05-07 ## 2.18.1 / 2020-05-07

View File

@ -531,3 +531,84 @@ func TestLabels_Copy(t *testing.T) {
func TestLabels_Map(t *testing.T) { func TestLabels_Map(t *testing.T) {
testutil.Equals(t, map[string]string{"aaa": "111", "bbb": "222"}, Labels{{"aaa", "111"}, {"bbb", "222"}}.Map()) testutil.Equals(t, map[string]string{"aaa": "111", "bbb": "222"}, Labels{{"aaa", "111"}, {"bbb", "222"}}.Map())
} }
func TestLabels_WithLabels(t *testing.T) {
testutil.Equals(t, Labels{{"aaa", "111"}, {"bbb", "222"}}, Labels{{"aaa", "111"}, {"bbb", "222"}, {"ccc", "333"}}.WithLabels("aaa", "bbb"))
}
func TestLabels_WithoutLabels(t *testing.T) {
testutil.Equals(t, Labels{{"aaa", "111"}}, Labels{{"aaa", "111"}, {"bbb", "222"}, {"ccc", "333"}}.WithoutLabels("bbb", "ccc"))
testutil.Equals(t, Labels{{"aaa", "111"}}, Labels{{"aaa", "111"}, {"bbb", "222"}, {MetricName, "333"}}.WithoutLabels("bbb"))
}
func TestLabels_FromStrings(t *testing.T) {
testutil.Equals(t, Labels{{"aaa", "111"}, {"bbb", "222"}}, FromStrings("aaa", "111", "bbb", "222"))
}
func TestBulider_NewBulider(t *testing.T) {
testutil.Equals(
t,
&Builder{
base: Labels{{"aaa", "111"}},
del: []string{},
add: []Label{},
},
NewBuilder(Labels{{"aaa", "111"}}),
)
}
func TestBuilder_Del(t *testing.T) {
testutil.Equals(
t,
&Builder{
del: []string{"bbb"},
add: []Label{{"aaa", "111"}, {"ccc", "333"}},
},
(&Builder{
del: []string{},
add: []Label{{"aaa", "111"}, {"bbb", "222"}, {"ccc", "333"}},
}).Del("bbb"),
)
}
func TestBuilder_Set(t *testing.T) {
testutil.Equals(
t,
&Builder{
base: Labels{{"aaa", "111"}},
del: []string{},
add: []Label{{"bbb", "222"}},
},
(&Builder{
base: Labels{{"aaa", "111"}},
del: []string{},
add: []Label{},
}).Set("bbb", "222"),
)
testutil.Equals(
t,
&Builder{
base: Labels{{"aaa", "111"}},
del: []string{},
add: []Label{{"bbb", "333"}},
},
(&Builder{
base: Labels{{"aaa", "111"}},
del: []string{},
add: []Label{{"bbb", "222"}},
}).Set("bbb", "333"),
)
}
func TestBuilder_Labels(t *testing.T) {
testutil.Equals(
t,
Labels{{"aaa", "111"}, {"ccc", "333"}, {"ddd", "444"}},
(&Builder{
base: Labels{{"aaa", "111"}, {"bbb", "222"}, {"ccc", "333"}},
del: []string{"bbb"},
add: []Label{{"ddd", "444"}},
}).Labels(),
)
}

View File

@ -29,7 +29,7 @@ import (
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level" "github.com/go-kit/kit/log/level"
opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -418,7 +418,7 @@ func (ng *Engine) newTestQuery(f func(context.Context) error) Query {
// //
// At this point per query only one EvalStmt is evaluated. Alert and record // At this point per query only one EvalStmt is evaluated. Alert and record
// statements are not handled by the Engine. // statements are not handled by the Engine.
func (ng *Engine) exec(ctx context.Context, q *query) (v parser.Value, w storage.Warnings, err error) { func (ng *Engine) exec(ctx context.Context, q *query) (v parser.Value, ws storage.Warnings, err error) {
ng.metrics.currentQueries.Inc() ng.metrics.currentQueries.Inc()
defer ng.metrics.currentQueries.Dec() defer ng.metrics.currentQueries.Dec()
@ -517,13 +517,9 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval
} }
defer querier.Close() defer querier.Close()
warnings, err := ng.populateSeries(ctxPrepare, querier, s) ng.populateSeries(querier, s)
prepareSpanTimer.Finish() prepareSpanTimer.Finish()
if err != nil {
return nil, warnings, err
}
evalSpanTimer, ctxInnerEval := query.stats.GetSpanTimer(ctx, stats.InnerEvalTime, ng.metrics.queryInnerEval) evalSpanTimer, ctxInnerEval := query.stats.GetSpanTimer(ctx, stats.InnerEvalTime, ng.metrics.queryInnerEval)
// Instant evaluation. This is executed as a range evaluation with one step. // Instant evaluation. This is executed as a range evaluation with one step.
if s.Start == s.End && s.Interval == 0 { if s.Start == s.End && s.Interval == 0 {
@ -539,7 +535,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval
lookbackDelta: ng.lookbackDelta, lookbackDelta: ng.lookbackDelta,
} }
val, err := evaluator.Eval(s.Expr) val, warnings, err := evaluator.Eval(s.Expr)
if err != nil { if err != nil {
return nil, warnings, err return nil, warnings, err
} }
@ -588,7 +584,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval
logger: ng.logger, logger: ng.logger,
lookbackDelta: ng.lookbackDelta, lookbackDelta: ng.lookbackDelta,
} }
val, err := evaluator.Eval(s.Expr) val, warnings, err := evaluator.Eval(s.Expr)
if err != nil { if err != nil {
return nil, warnings, err return nil, warnings, err
} }
@ -649,35 +645,29 @@ func (ng *Engine) findMinTime(s *parser.EvalStmt) time.Time {
return s.Start.Add(-maxOffset) return s.Start.Add(-maxOffset)
} }
func (ng *Engine) populateSeries(ctx context.Context, querier storage.Querier, s *parser.EvalStmt) (storage.Warnings, error) { func (ng *Engine) populateSeries(querier storage.Querier, s *parser.EvalStmt) {
var ( // Whenever a MatrixSelector is evaluated, evalRange is set to the corresponding range.
// Whenever a MatrixSelector is evaluated, evalRange is set to the corresponding range. // The evaluation of the VectorSelector inside then evaluates the given range and unsets
// The evaluation of the VectorSelector inside then evaluates the given range and unsets // the variable.
// the variable. var evalRange time.Duration
evalRange time.Duration
warnings storage.Warnings
err error
)
parser.Inspect(s.Expr, func(node parser.Node, path []parser.Node) error { parser.Inspect(s.Expr, func(node parser.Node, path []parser.Node) error {
var set storage.SeriesSet
var wrn storage.Warnings
hints := &storage.SelectHints{
Start: timestamp.FromTime(s.Start),
End: timestamp.FromTime(s.End),
Step: durationToInt64Millis(s.Interval),
}
// We need to make sure we select the timerange selected by the subquery.
// TODO(gouthamve): cumulativeSubqueryOffset gives the sum of range and the offset
// we can optimise it by separating out the range and offsets, and subtracting the offsets
// from end also.
subqOffset := ng.cumulativeSubqueryOffset(path)
offsetMilliseconds := durationMilliseconds(subqOffset)
hints.Start = hints.Start - offsetMilliseconds
switch n := node.(type) { switch n := node.(type) {
case *parser.VectorSelector: case *parser.VectorSelector:
hints := &storage.SelectHints{
Start: timestamp.FromTime(s.Start),
End: timestamp.FromTime(s.End),
Step: durationToInt64Millis(s.Interval),
}
// We need to make sure we select the timerange selected by the subquery.
// TODO(gouthamve): cumulativeSubqueryOffset gives the sum of range and the offset
// we can optimise it by separating out the range and offsets, and subtracting the offsets
// from end also.
subqOffset := ng.cumulativeSubqueryOffset(path)
offsetMilliseconds := durationMilliseconds(subqOffset)
hints.Start = hints.Start - offsetMilliseconds
if evalRange == 0 { if evalRange == 0 {
hints.Start = hints.Start - durationMilliseconds(ng.lookbackDelta) hints.Start = hints.Start - durationMilliseconds(ng.lookbackDelta)
} else { } else {
@ -696,20 +686,12 @@ func (ng *Engine) populateSeries(ctx context.Context, querier storage.Querier, s
hints.End = hints.End - offsetMilliseconds hints.End = hints.End - offsetMilliseconds
} }
set, wrn, err = querier.Select(false, hints, n.LabelMatchers...) n.UnexpandedSeriesSet = querier.Select(false, hints, n.LabelMatchers...)
warnings = append(warnings, wrn...)
if err != nil {
level.Error(ng.logger).Log("msg", "error selecting series set", "err", err)
return err
}
n.UnexpandedSeriesSet = set
case *parser.MatrixSelector: case *parser.MatrixSelector:
evalRange = n.Range evalRange = n.Range
} }
return nil return nil
}) })
return warnings, err
} }
// extractFuncFromPath walks up the path and searches for the first instance of // extractFuncFromPath walks up the path and searches for the first instance of
@ -743,34 +725,40 @@ func extractGroupsFromPath(p []parser.Node) (bool, []string) {
return false, nil return false, nil
} }
func checkForSeriesSetExpansion(ctx context.Context, expr parser.Expr) { func checkAndExpandSeriesSet(ctx context.Context, expr parser.Expr) (storage.Warnings, error) {
switch e := expr.(type) { switch e := expr.(type) {
case *parser.MatrixSelector: case *parser.MatrixSelector:
checkForSeriesSetExpansion(ctx, e.VectorSelector) return checkAndExpandSeriesSet(ctx, e.VectorSelector)
case *parser.VectorSelector: case *parser.VectorSelector:
if e.Series == nil { if e.Series != nil {
series, err := expandSeriesSet(ctx, e.UnexpandedSeriesSet) return nil, nil
if err != nil {
panic(err)
} else {
e.Series = series
}
} }
series, ws, err := expandSeriesSet(ctx, e.UnexpandedSeriesSet)
e.Series = series
return ws, err
} }
return nil, nil
} }
func expandSeriesSet(ctx context.Context, it storage.SeriesSet) (res []storage.Series, err error) { func expandSeriesSet(ctx context.Context, it storage.SeriesSet) (res []storage.Series, ws storage.Warnings, err error) {
for it.Next() { for it.Next() {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, nil, ctx.Err()
default: default:
} }
res = append(res, it.At()) res = append(res, it.At())
} }
return res, it.Err() return res, it.Warnings(), it.Err()
} }
type errWithWarnings struct {
err error
warnings storage.Warnings
}
func (e errWithWarnings) Error() string { return e.err.Error() }
// An evaluator evaluates given expressions over given fixed timestamps. It // An evaluator evaluates given expressions over given fixed timestamps. It
// is attached to an engine through which it connects to a querier and reports // is attached to an engine through which it connects to a querier and reports
// errors. On timeout or cancellation of its context it terminates. // errors. On timeout or cancellation of its context it terminates.
@ -799,26 +787,33 @@ func (ev *evaluator) error(err error) {
} }
// recover is the handler that turns panics into returns from the top level of evaluation. // recover is the handler that turns panics into returns from the top level of evaluation.
func (ev *evaluator) recover(errp *error) { func (ev *evaluator) recover(ws *storage.Warnings, errp *error) {
e := recover() e := recover()
if e == nil { if e == nil {
return return
} }
if err, ok := e.(runtime.Error); ok {
switch err := e.(type) {
case runtime.Error:
// Print the stack trace but do not inhibit the running application. // Print the stack trace but do not inhibit the running application.
buf := make([]byte, 64<<10) buf := make([]byte, 64<<10)
buf = buf[:runtime.Stack(buf, false)] buf = buf[:runtime.Stack(buf, false)]
level.Error(ev.logger).Log("msg", "runtime panic in parser", "err", e, "stacktrace", string(buf)) level.Error(ev.logger).Log("msg", "runtime panic in parser", "err", e, "stacktrace", string(buf))
*errp = errors.Wrap(err, "unexpected error") *errp = errors.Wrap(err, "unexpected error")
} else { case errWithWarnings:
*errp = err.err
*ws = append(*ws, err.warnings...)
default:
*errp = e.(error) *errp = e.(error)
} }
} }
func (ev *evaluator) Eval(expr parser.Expr) (v parser.Value, err error) { func (ev *evaluator) Eval(expr parser.Expr) (v parser.Value, ws storage.Warnings, err error) {
defer ev.recover(&err) defer ev.recover(&ws, &err)
return ev.eval(expr), nil
v, ws = ev.eval(expr)
return v, ws, nil
} }
// EvalNodeHelper stores extra information and caches for evaluating a single node across steps. // EvalNodeHelper stores extra information and caches for evaluating a single node across steps.
@ -884,17 +879,20 @@ func (enh *EvalNodeHelper) signatureFunc(on bool, names ...string) func(labels.L
// the given function with the values computed for each expression at that // the given function with the values computed for each expression at that
// step. The return value is the combination into time series of all the // step. The return value is the combination into time series of all the
// function call results. // function call results.
func (ev *evaluator) rangeEval(f func([]parser.Value, *EvalNodeHelper) Vector, exprs ...parser.Expr) Matrix { func (ev *evaluator) rangeEval(f func([]parser.Value, *EvalNodeHelper) (Vector, storage.Warnings), exprs ...parser.Expr) (Matrix, storage.Warnings) {
numSteps := int((ev.endTimestamp-ev.startTimestamp)/ev.interval) + 1 numSteps := int((ev.endTimestamp-ev.startTimestamp)/ev.interval) + 1
matrixes := make([]Matrix, len(exprs)) matrixes := make([]Matrix, len(exprs))
origMatrixes := make([]Matrix, len(exprs)) origMatrixes := make([]Matrix, len(exprs))
originalNumSamples := ev.currentSamples originalNumSamples := ev.currentSamples
var warnings storage.Warnings
for i, e := range exprs { for i, e := range exprs {
// Functions will take string arguments from the expressions, not the values. // Functions will take string arguments from the expressions, not the values.
if e != nil && e.Type() != parser.ValueTypeString { if e != nil && e.Type() != parser.ValueTypeString {
// ev.currentSamples will be updated to the correct value within the ev.eval call. // ev.currentSamples will be updated to the correct value within the ev.eval call.
matrixes[i] = ev.eval(e).(Matrix) val, ws := ev.eval(e)
warnings = append(warnings, ws...)
matrixes[i] = val.(Matrix)
// Keep a copy of the original point slices so that they // Keep a copy of the original point slices so that they
// can be returned to the pool. // can be returned to the pool.
@ -946,11 +944,12 @@ func (ev *evaluator) rangeEval(f func([]parser.Value, *EvalNodeHelper) Vector, e
} }
// Make the function call. // Make the function call.
enh.ts = ts enh.ts = ts
result := f(args, enh) result, ws := f(args, enh)
if result.ContainsSameLabelset() { if result.ContainsSameLabelset() {
ev.errorf("vector cannot contain metrics with the same labelset") ev.errorf("vector cannot contain metrics with the same labelset")
} }
enh.out = result[:0] // Reuse result vector. enh.out = result[:0] // Reuse result vector.
warnings = append(warnings, ws...)
ev.currentSamples += len(result) ev.currentSamples += len(result)
// When we reset currentSamples to tempNumSamples during the next iteration of the loop it also // When we reset currentSamples to tempNumSamples during the next iteration of the loop it also
@ -969,7 +968,7 @@ func (ev *evaluator) rangeEval(f func([]parser.Value, *EvalNodeHelper) Vector, e
mat[i] = Series{Metric: s.Metric, Points: []Point{s.Point}} mat[i] = Series{Metric: s.Metric, Points: []Point{s.Point}}
} }
ev.currentSamples = originalNumSamples + mat.TotalSamples() ev.currentSamples = originalNumSamples + mat.TotalSamples()
return mat return mat, warnings
} }
// Add samples in output vector to output series. // Add samples in output vector to output series.
@ -1001,29 +1000,30 @@ func (ev *evaluator) rangeEval(f func([]parser.Value, *EvalNodeHelper) Vector, e
mat = append(mat, ss) mat = append(mat, ss)
} }
ev.currentSamples = originalNumSamples + mat.TotalSamples() ev.currentSamples = originalNumSamples + mat.TotalSamples()
return mat return mat, warnings
} }
// evalSubquery evaluates given SubqueryExpr and returns an equivalent // evalSubquery evaluates given SubqueryExpr and returns an equivalent
// evaluated MatrixSelector in its place. Note that the Name and LabelMatchers are not set. // evaluated MatrixSelector in its place. Note that the Name and LabelMatchers are not set.
func (ev *evaluator) evalSubquery(subq *parser.SubqueryExpr) *parser.MatrixSelector { func (ev *evaluator) evalSubquery(subq *parser.SubqueryExpr) (*parser.MatrixSelector, storage.Warnings) {
val := ev.eval(subq).(Matrix) val, ws := ev.eval(subq)
mat := val.(Matrix)
vs := &parser.VectorSelector{ vs := &parser.VectorSelector{
Offset: subq.Offset, Offset: subq.Offset,
Series: make([]storage.Series, 0, len(val)), Series: make([]storage.Series, 0, len(mat)),
} }
ms := &parser.MatrixSelector{ ms := &parser.MatrixSelector{
Range: subq.Range, Range: subq.Range,
VectorSelector: vs, VectorSelector: vs,
} }
for _, s := range val { for _, s := range mat {
vs.Series = append(vs.Series, NewStorageSeries(s)) vs.Series = append(vs.Series, NewStorageSeries(s))
} }
return ms return ms, ws
} }
// eval evaluates the given expression as the given AST expression node requires. // eval evaluates the given expression as the given AST expression node requires.
func (ev *evaluator) eval(expr parser.Expr) parser.Value { func (ev *evaluator) eval(expr parser.Expr) (parser.Value, storage.Warnings) {
// This is the top-level evaluation method. // This is the top-level evaluation method.
// Thus, we check for timeout/cancellation here. // Thus, we check for timeout/cancellation here.
if err := contextDone(ev.ctx, "expression evaluation"); err != nil { if err := contextDone(ev.ctx, "expression evaluation"); err != nil {
@ -1035,16 +1035,16 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
case *parser.AggregateExpr: case *parser.AggregateExpr:
unwrapParenExpr(&e.Param) unwrapParenExpr(&e.Param)
if s, ok := e.Param.(*parser.StringLiteral); ok { if s, ok := e.Param.(*parser.StringLiteral); ok {
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.aggregation(e.Op, e.Grouping, e.Without, s.Val, v[0].(Vector), enh) return ev.aggregation(e.Op, e.Grouping, e.Without, s.Val, v[0].(Vector), enh), nil
}, e.Expr) }, e.Expr)
} }
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
var param float64 var param float64
if e.Param != nil { if e.Param != nil {
param = v[0].(Vector)[0].V param = v[0].(Vector)[0].V
} }
return ev.aggregation(e.Op, e.Grouping, e.Without, param, v[1].(Vector), enh) return ev.aggregation(e.Op, e.Grouping, e.Without, param, v[1].(Vector), enh), nil
}, e.Param, e.Expr) }, e.Param, e.Expr)
case *parser.Call: case *parser.Call:
@ -1056,15 +1056,19 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
// a vector selector. // a vector selector.
vs, ok := e.Args[0].(*parser.VectorSelector) vs, ok := e.Args[0].(*parser.VectorSelector)
if ok { if ok {
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return call([]parser.Value{ev.vectorSelector(vs, enh.ts)}, e.Args, enh) val, ws := ev.vectorSelector(vs, enh.ts)
return call([]parser.Value{val}, e.Args, enh), ws
}) })
} }
} }
// Check if the function has a matrix argument. // Check if the function has a matrix argument.
var matrixArgIndex int var (
var matrixArg bool matrixArgIndex int
matrixArg bool
warnings storage.Warnings
)
for i := range e.Args { for i := range e.Args {
unwrapParenExpr(&e.Args[i]) unwrapParenExpr(&e.Args[i])
a := e.Args[i] a := e.Args[i]
@ -1078,14 +1082,16 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
matrixArgIndex = i matrixArgIndex = i
matrixArg = true matrixArg = true
// Replacing parser.SubqueryExpr with parser.MatrixSelector. // Replacing parser.SubqueryExpr with parser.MatrixSelector.
e.Args[i] = ev.evalSubquery(subq) val, ws := ev.evalSubquery(subq)
e.Args[i] = val
warnings = append(warnings, ws...)
break break
} }
} }
if !matrixArg { if !matrixArg {
// Does not have a matrix argument. // Does not have a matrix argument.
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return call(v, e.Args, enh) return call(v, e.Args, enh), warnings
}, e.Args...) }, e.Args...)
} }
@ -1095,16 +1101,22 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
otherInArgs := make([]Vector, len(e.Args)) otherInArgs := make([]Vector, len(e.Args))
for i, e := range e.Args { for i, e := range e.Args {
if i != matrixArgIndex { if i != matrixArgIndex {
otherArgs[i] = ev.eval(e).(Matrix) val, ws := ev.eval(e)
otherArgs[i] = val.(Matrix)
otherInArgs[i] = Vector{Sample{}} otherInArgs[i] = Vector{Sample{}}
inArgs[i] = otherInArgs[i] inArgs[i] = otherInArgs[i]
warnings = append(warnings, ws...)
} }
} }
sel := e.Args[matrixArgIndex].(*parser.MatrixSelector) sel := e.Args[matrixArgIndex].(*parser.MatrixSelector)
selVS := sel.VectorSelector.(*parser.VectorSelector) selVS := sel.VectorSelector.(*parser.VectorSelector)
checkForSeriesSetExpansion(ev.ctx, sel) ws, err := checkAndExpandSeriesSet(ev.ctx, sel)
warnings = append(warnings, ws...)
if err != nil {
ev.error(errWithWarnings{errors.Wrap(err, "expanding series"), warnings})
}
mat := make(Matrix, 0, len(selVS.Series)) // Output matrix. mat := make(Matrix, 0, len(selVS.Series)) // Output matrix.
offset := durationMilliseconds(selVS.Offset) offset := durationMilliseconds(selVS.Offset)
selRange := durationMilliseconds(sel.Range) selRange := durationMilliseconds(sel.Range)
@ -1182,7 +1194,7 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
// Iterate once to look for a complete series. // Iterate once to look for a complete series.
for _, s := range mat { for _, s := range mat {
if len(s.Points) == steps { if len(s.Points) == steps {
return Matrix{} return Matrix{}, warnings
} }
} }
@ -1193,7 +1205,7 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
found[p.T] = struct{}{} found[p.T] = struct{}{}
} }
if i > 0 && len(found) == steps { if i > 0 && len(found) == steps {
return Matrix{} return Matrix{}, warnings
} }
} }
@ -1209,20 +1221,21 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
Metric: createLabelsForAbsentFunction(e.Args[0]), Metric: createLabelsForAbsentFunction(e.Args[0]),
Points: newp, Points: newp,
}, },
} }, warnings
} }
if mat.ContainsSameLabelset() { if mat.ContainsSameLabelset() {
ev.errorf("vector cannot contain metrics with the same labelset") ev.errorf("vector cannot contain metrics with the same labelset")
} }
return mat return mat, warnings
case *parser.ParenExpr: case *parser.ParenExpr:
return ev.eval(e.Expr) return ev.eval(e.Expr)
case *parser.UnaryExpr: case *parser.UnaryExpr:
mat := ev.eval(e.Expr).(Matrix) val, ws := ev.eval(e.Expr)
mat := val.(Matrix)
if e.Op == parser.SUB { if e.Op == parser.SUB {
for i := range mat { for i := range mat {
mat[i].Metric = dropMetricName(mat[i].Metric) mat[i].Metric = dropMetricName(mat[i].Metric)
@ -1234,53 +1247,56 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
ev.errorf("vector cannot contain metrics with the same labelset") ev.errorf("vector cannot contain metrics with the same labelset")
} }
} }
return mat return mat, ws
case *parser.BinaryExpr: case *parser.BinaryExpr:
switch lt, rt := e.LHS.Type(), e.RHS.Type(); { switch lt, rt := e.LHS.Type(), e.RHS.Type(); {
case lt == parser.ValueTypeScalar && rt == parser.ValueTypeScalar: case lt == parser.ValueTypeScalar && rt == parser.ValueTypeScalar:
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
val := scalarBinop(e.Op, v[0].(Vector)[0].Point.V, v[1].(Vector)[0].Point.V) val := scalarBinop(e.Op, v[0].(Vector)[0].Point.V, v[1].(Vector)[0].Point.V)
return append(enh.out, Sample{Point: Point{V: val}}) return append(enh.out, Sample{Point: Point{V: val}}), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
case lt == parser.ValueTypeVector && rt == parser.ValueTypeVector: case lt == parser.ValueTypeVector && rt == parser.ValueTypeVector:
switch e.Op { switch e.Op {
case parser.LAND: case parser.LAND:
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.VectorAnd(v[0].(Vector), v[1].(Vector), e.VectorMatching, enh) return ev.VectorAnd(v[0].(Vector), v[1].(Vector), e.VectorMatching, enh), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
case parser.LOR: case parser.LOR:
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.VectorOr(v[0].(Vector), v[1].(Vector), e.VectorMatching, enh) return ev.VectorOr(v[0].(Vector), v[1].(Vector), e.VectorMatching, enh), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
case parser.LUNLESS: case parser.LUNLESS:
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.VectorUnless(v[0].(Vector), v[1].(Vector), e.VectorMatching, enh) return ev.VectorUnless(v[0].(Vector), v[1].(Vector), e.VectorMatching, enh), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
default: default:
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.VectorBinop(e.Op, v[0].(Vector), v[1].(Vector), e.VectorMatching, e.ReturnBool, enh) return ev.VectorBinop(e.Op, v[0].(Vector), v[1].(Vector), e.VectorMatching, e.ReturnBool, enh), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
} }
case lt == parser.ValueTypeVector && rt == parser.ValueTypeScalar: case lt == parser.ValueTypeVector && rt == parser.ValueTypeScalar:
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.VectorscalarBinop(e.Op, v[0].(Vector), Scalar{V: v[1].(Vector)[0].Point.V}, false, e.ReturnBool, enh) return ev.VectorscalarBinop(e.Op, v[0].(Vector), Scalar{V: v[1].(Vector)[0].Point.V}, false, e.ReturnBool, enh), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
case lt == parser.ValueTypeScalar && rt == parser.ValueTypeVector: case lt == parser.ValueTypeScalar && rt == parser.ValueTypeVector:
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.VectorscalarBinop(e.Op, v[1].(Vector), Scalar{V: v[0].(Vector)[0].Point.V}, true, e.ReturnBool, enh) return ev.VectorscalarBinop(e.Op, v[1].(Vector), Scalar{V: v[0].(Vector)[0].Point.V}, true, e.ReturnBool, enh), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
} }
case *parser.NumberLiteral: case *parser.NumberLiteral:
return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) Vector { return ev.rangeEval(func(v []parser.Value, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return append(enh.out, Sample{Point: Point{V: e.Val}}) return append(enh.out, Sample{Point: Point{V: e.Val}}), nil
}) })
case *parser.VectorSelector: case *parser.VectorSelector:
checkForSeriesSetExpansion(ev.ctx, e) ws, err := checkAndExpandSeriesSet(ev.ctx, e)
if err != nil {
ev.error(errWithWarnings{errors.Wrap(err, "expanding series"), ws})
}
mat := make(Matrix, 0, len(e.Series)) mat := make(Matrix, 0, len(e.Series))
it := storage.NewBuffer(durationMilliseconds(ev.lookbackDelta)) it := storage.NewBuffer(durationMilliseconds(ev.lookbackDelta))
for i, s := range e.Series { for i, s := range e.Series {
@ -1307,9 +1323,8 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
} else { } else {
putPointSlice(ss.Points) putPointSlice(ss.Points)
} }
} }
return mat return mat, ws
case *parser.MatrixSelector: case *parser.MatrixSelector:
if ev.startTimestamp != ev.endTimestamp { if ev.startTimestamp != ev.endTimestamp {
@ -1342,11 +1357,11 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
newEv.startTimestamp += newEv.interval newEv.startTimestamp += newEv.interval
} }
res := newEv.eval(e.Expr) res, ws := newEv.eval(e.Expr)
ev.currentSamples = newEv.currentSamples ev.currentSamples = newEv.currentSamples
return res return res, ws
case *parser.StringLiteral: case *parser.StringLiteral:
return String{V: e.Val, T: ev.startTimestamp} return String{V: e.Val, T: ev.startTimestamp}, nil
} }
panic(errors.Errorf("unhandled expression of type: %T", expr)) panic(errors.Errorf("unhandled expression of type: %T", expr))
@ -1357,13 +1372,12 @@ func durationToInt64Millis(d time.Duration) int64 {
} }
// vectorSelector evaluates a *parser.VectorSelector expression. // vectorSelector evaluates a *parser.VectorSelector expression.
func (ev *evaluator) vectorSelector(node *parser.VectorSelector, ts int64) Vector { func (ev *evaluator) vectorSelector(node *parser.VectorSelector, ts int64) (Vector, storage.Warnings) {
checkForSeriesSetExpansion(ev.ctx, node) ws, err := checkAndExpandSeriesSet(ev.ctx, node)
if err != nil {
var ( ev.error(errWithWarnings{errors.Wrap(err, "expanding series"), ws})
vec = make(Vector, 0, len(node.Series)) }
) vec := make(Vector, 0, len(node.Series))
it := storage.NewBuffer(durationMilliseconds(ev.lookbackDelta)) it := storage.NewBuffer(durationMilliseconds(ev.lookbackDelta))
for i, s := range node.Series { for i, s := range node.Series {
it.Reset(s.Iterator()) it.Reset(s.Iterator())
@ -1381,7 +1395,7 @@ func (ev *evaluator) vectorSelector(node *parser.VectorSelector, ts int64) Vecto
ev.error(ErrTooManySamples(env)) ev.error(ErrTooManySamples(env))
} }
} }
return vec return vec, ws
} }
// vectorSelectorSingle evaluates a instant vector for the iterator of one time series. // vectorSelectorSingle evaluates a instant vector for the iterator of one time series.
@ -1429,21 +1443,23 @@ func putPointSlice(p []Point) {
} }
// matrixSelector evaluates a *parser.MatrixSelector expression. // matrixSelector evaluates a *parser.MatrixSelector expression.
func (ev *evaluator) matrixSelector(node *parser.MatrixSelector) Matrix { func (ev *evaluator) matrixSelector(node *parser.MatrixSelector) (Matrix, storage.Warnings) {
checkForSeriesSetExpansion(ev.ctx, node)
vs := node.VectorSelector.(*parser.VectorSelector)
var ( var (
vs = node.VectorSelector.(*parser.VectorSelector)
offset = durationMilliseconds(vs.Offset) offset = durationMilliseconds(vs.Offset)
maxt = ev.startTimestamp - offset maxt = ev.startTimestamp - offset
mint = maxt - durationMilliseconds(node.Range) mint = maxt - durationMilliseconds(node.Range)
matrix = make(Matrix, 0, len(vs.Series)) matrix = make(Matrix, 0, len(vs.Series))
it = storage.NewBuffer(durationMilliseconds(node.Range))
) )
ws, err := checkAndExpandSeriesSet(ev.ctx, node)
if err != nil {
ev.error(errWithWarnings{errors.Wrap(err, "expanding series"), ws})
}
it := storage.NewBuffer(durationMilliseconds(node.Range))
series := vs.Series series := vs.Series
for i, s := range series { for i, s := range series {
if err := contextDone(ev.ctx, "expression evaluation"); err != nil { if err := contextDone(ev.ctx, "expression evaluation"); err != nil {
ev.error(err) ev.error(err)
@ -1461,7 +1477,7 @@ func (ev *evaluator) matrixSelector(node *parser.MatrixSelector) Matrix {
putPointSlice(ss.Points) putPointSlice(ss.Points)
} }
} }
return matrix return matrix, ws
} }
// matrixIterSlice populates a matrix vector covering the requested range for a // matrixIterSlice populates a matrix vector covering the requested range for a

View File

@ -19,7 +19,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"sort" "sort"
"strings"
"testing" "testing"
"time" "time"
@ -117,9 +116,7 @@ func TestQueryTimeout(t *testing.T) {
testutil.NotOk(t, res.Err, "expected timeout error but got none") testutil.NotOk(t, res.Err, "expected timeout error but got none")
var e ErrQueryTimeout var e ErrQueryTimeout
// TODO: when circleci-windows moves to go 1.13: testutil.Assert(t, errors.As(res.Err, &e), "expected timeout error but got: %s", res.Err)
// testutil.Assert(t, errors.As(res.Err, &e), "expected timeout error but got: %s", res.Err)
testutil.Assert(t, strings.HasPrefix(res.Err.Error(), e.Error()), "expected timeout error but got: %s", res.Err)
} }
const errQueryCanceled = ErrQueryCanceled("test statement execution") const errQueryCanceled = ErrQueryCanceled("test statement execution")
@ -175,8 +172,8 @@ type errQuerier struct {
err error err error
} }
func (q *errQuerier) Select(bool, *storage.SelectHints, ...*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (q *errQuerier) Select(bool, *storage.SelectHints, ...*labels.Matcher) storage.SeriesSet {
return errSeriesSet{err: q.err}, nil, q.err return errSeriesSet{err: q.err}
} }
func (*errQuerier) LabelValues(string) ([]string, storage.Warnings, error) { return nil, nil, nil } func (*errQuerier) LabelValues(string) ([]string, storage.Warnings, error) { return nil, nil, nil }
func (*errQuerier) LabelNames() ([]string, storage.Warnings, error) { return nil, nil, nil } func (*errQuerier) LabelNames() ([]string, storage.Warnings, error) { return nil, nil, nil }
@ -187,9 +184,10 @@ type errSeriesSet struct {
err error err error
} }
func (errSeriesSet) Next() bool { return false } func (errSeriesSet) Next() bool { return false }
func (errSeriesSet) At() storage.Series { return nil } func (errSeriesSet) At() storage.Series { return nil }
func (e errSeriesSet) Err() error { return e.err } func (e errSeriesSet) Err() error { return e.err }
func (e errSeriesSet) Warnings() storage.Warnings { return nil }
func TestQueryError(t *testing.T) { func TestQueryError(t *testing.T) {
opts := EngineOpts{ opts := EngineOpts{
@ -211,14 +209,14 @@ func TestQueryError(t *testing.T) {
res := vectorQuery.Exec(ctx) res := vectorQuery.Exec(ctx)
testutil.NotOk(t, res.Err, "expected error on failed select but got none") testutil.NotOk(t, res.Err, "expected error on failed select but got none")
testutil.Equals(t, errStorage, res.Err) testutil.Assert(t, errors.Is(res.Err, errStorage), "expected error doesn't match")
matrixQuery, err := engine.NewInstantQuery(queryable, "foo[1m]", time.Unix(1, 0)) matrixQuery, err := engine.NewInstantQuery(queryable, "foo[1m]", time.Unix(1, 0))
testutil.Ok(t, err) testutil.Ok(t, err)
res = matrixQuery.Exec(ctx) res = matrixQuery.Exec(ctx)
testutil.NotOk(t, res.Err, "expected error on failed select but got none") testutil.NotOk(t, res.Err, "expected error on failed select but got none")
testutil.Equals(t, errStorage, res.Err) testutil.Assert(t, errors.Is(res.Err, errStorage), "expected error doesn't match")
} }
// hintCheckerQuerier implements storage.Querier which checks the start and end times // hintCheckerQuerier implements storage.Querier which checks the start and end times
@ -234,7 +232,7 @@ type hintCheckerQuerier struct {
t *testing.T t *testing.T
} }
func (q *hintCheckerQuerier) Select(_ bool, sp *storage.SelectHints, _ ...*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (q *hintCheckerQuerier) Select(_ bool, sp *storage.SelectHints, _ ...*labels.Matcher) storage.SeriesSet {
testutil.Equals(q.t, q.start, sp.Start) testutil.Equals(q.t, q.start, sp.Start)
testutil.Equals(q.t, q.end, sp.End) testutil.Equals(q.t, q.end, sp.End)
testutil.Equals(q.t, q.grouping, sp.Grouping) testutil.Equals(q.t, q.grouping, sp.Grouping)
@ -242,7 +240,7 @@ func (q *hintCheckerQuerier) Select(_ bool, sp *storage.SelectHints, _ ...*label
testutil.Equals(q.t, q.selRange, sp.Range) testutil.Equals(q.t, q.selRange, sp.Range)
testutil.Equals(q.t, q.function, sp.Func) testutil.Equals(q.t, q.function, sp.Func)
return errSeriesSet{err: nil}, nil, nil return errSeriesSet{err: nil}
} }
func (*hintCheckerQuerier) LabelValues(string) ([]string, storage.Warnings, error) { func (*hintCheckerQuerier) LabelValues(string) ([]string, storage.Warnings, error) {
return nil, nil, nil return nil, nil, nil
@ -499,9 +497,7 @@ func TestEngineShutdown(t *testing.T) {
testutil.NotOk(t, res2.Err, "expected error on querying with canceled context but got none") testutil.NotOk(t, res2.Err, "expected error on querying with canceled context but got none")
var e ErrQueryCanceled var e ErrQueryCanceled
// TODO: when circleci-windows moves to go 1.13: testutil.Assert(t, errors.As(res2.Err, &e), "expected cancellation error but got: %s", res2.Err)
// testutil.Assert(t, errors.As(res2.Err, &e), "expected cancellation error but got: %s", res2.Err)
testutil.Assert(t, strings.HasPrefix(res2.Err.Error(), e.Error()), "expected cancellation error but got: %s", res2.Err)
} }
func TestEngineEvalStmtTimestamps(t *testing.T) { func TestEngineEvalStmtTimestamps(t *testing.T) {
@ -599,9 +595,8 @@ load 10s
} }
testutil.Ok(t, res.Err) testutil.Ok(t, res.Err)
testutil.Equals(t, c.Result, res.Value) testutil.Equals(t, c.Result, res.Value, "query %q failed", c.Query)
} }
} }
func TestMaxQuerySamples(t *testing.T) { func TestMaxQuerySamples(t *testing.T) {
@ -831,7 +826,7 @@ load 10s
res := qry.Exec(test.Context()) res := qry.Exec(test.Context())
testutil.Equals(t, c.Result.Err, res.Err) testutil.Equals(t, c.Result.Err, res.Err)
testutil.Equals(t, c.Result.Value, res.Value) testutil.Equals(t, c.Result.Value, res.Value, "query %q failed", c.Query)
} }
} }
@ -839,7 +834,7 @@ func TestRecoverEvaluatorRuntime(t *testing.T) {
ev := &evaluator{logger: log.NewNopLogger()} ev := &evaluator{logger: log.NewNopLogger()}
var err error var err error
defer ev.recover(&err) defer ev.recover(nil, &err)
// Cause a runtime panic. // Cause a runtime panic.
var a []int var a []int
@ -862,7 +857,31 @@ func TestRecoverEvaluatorError(t *testing.T) {
t.Fatalf("wrong error message: %q, expected %q", err, e) t.Fatalf("wrong error message: %q, expected %q", err, e)
} }
}() }()
defer ev.recover(&err) defer ev.recover(nil, &err)
panic(e)
}
func TestRecoverEvaluatorErrorWithWarnings(t *testing.T) {
ev := &evaluator{logger: log.NewNopLogger()}
var err error
var ws storage.Warnings
warnings := storage.Warnings{errors.New("custom warning")}
e := errWithWarnings{
err: errors.New("custom error"),
warnings: warnings,
}
defer func() {
if err.Error() != e.Error() {
t.Fatalf("wrong error message: %q, expected %q", err, e)
}
if len(ws) != len(warnings) && ws[0] != warnings[0] {
t.Fatalf("wrong warning message: %q, expected %q", ws[0], warnings[0])
}
}()
defer ev.recover(&ws, &err)
panic(e) panic(e)
} }

View File

@ -133,8 +133,7 @@ func TestLazyLoader_WithSamplesTill(t *testing.T) {
} }
// Get the series for the matcher. // Get the series for the matcher.
ss, _, err := querier.Select(false, nil, matchers...) ss := querier.Select(false, nil, matchers...)
testutil.Ok(t, err)
testutil.Assert(t, ss.Next(), "") testutil.Assert(t, ss.Next(), "")
storageSeries := ss.At() storageSeries := ss.At()
testutil.Assert(t, !ss.Next(), "Expecting only 1 series") testutil.Assert(t, !ss.Next(), "Expecting only 1 series")

View File

@ -707,12 +707,7 @@ func (g *Group) RestoreForState(ts time.Time) {
matchers = append(matchers, mt) matchers = append(matchers, mt)
} }
sset, err, _ := q.Select(false, nil, matchers...) sset := q.Select(false, nil, matchers...)
if err != nil {
level.Error(g.logger).Log("msg", "Failed to restore 'for' state",
labels.AlertName, alertRule.Name(), "stage", "Select", "err", err)
return
}
seriesFound := false seriesFound := false
var s storage.Series var s storage.Series
@ -727,6 +722,17 @@ func (g *Group) RestoreForState(ts time.Time) {
} }
} }
if err := sset.Err(); err != nil {
// Querier Warnings are ignored. We do not care unless we have an error.
level.Error(g.logger).Log(
"msg", "Failed to restore 'for' state",
labels.AlertName, alertRule.Name(),
"stage", "Select",
"err", err,
)
return
}
if !seriesFound { if !seriesFound {
return return
} }

View File

@ -563,9 +563,7 @@ func TestStaleness(t *testing.T) {
matcher, err := labels.NewMatcher(labels.MatchEqual, model.MetricNameLabel, "a_plus_one") matcher, err := labels.NewMatcher(labels.MatchEqual, model.MetricNameLabel, "a_plus_one")
testutil.Ok(t, err) testutil.Ok(t, err)
set, _, err := querier.Select(false, nil, matcher) set := querier.Select(false, nil, matcher)
testutil.Ok(t, err)
samples, err := readSeriesSet(set) samples, err := readSeriesSet(set)
testutil.Ok(t, err) testutil.Ok(t, err)
@ -686,9 +684,7 @@ func TestDeletedRuleMarkedStale(t *testing.T) {
matcher, err := labels.NewMatcher(labels.MatchEqual, "l1", "v1") matcher, err := labels.NewMatcher(labels.MatchEqual, "l1", "v1")
testutil.Ok(t, err) testutil.Ok(t, err)
set, _, err := querier.Select(false, nil, matcher) set := querier.Select(false, nil, matcher)
testutil.Ok(t, err)
samples, err := readSeriesSet(set) samples, err := readSeriesSet(set)
testutil.Ok(t, err) testutil.Ok(t, err)
@ -1107,9 +1103,7 @@ func countStaleNaN(t *testing.T, st storage.Storage) int {
matcher, err := labels.NewMatcher(labels.MatchEqual, model.MetricNameLabel, "test_2") matcher, err := labels.NewMatcher(labels.MatchEqual, model.MetricNameLabel, "test_2")
testutil.Ok(t, err) testutil.Ok(t, err)
set, _, err := querier.Select(false, nil, matcher) set := querier.Select(false, nil, matcher)
testutil.Ok(t, err)
samples, err := readSeriesSet(set) samples, err := readSeriesSet(set)
testutil.Ok(t, err) testutil.Ok(t, err)

View File

@ -1140,7 +1140,7 @@ loop:
if ok { if ok {
err = app.AddFast(ce.ref, t, v) err = app.AddFast(ce.ref, t, v)
sampleAdded, err = sl.checkAddError(ce, met, tp, err, &sampleLimitErr, &appErrs) _, err = sl.checkAddError(ce, met, tp, err, &sampleLimitErr, &appErrs)
// In theory this should never happen. // In theory this should never happen.
if err == storage.ErrNotFound { if err == storage.ErrNotFound {
ok = false ok = false
@ -1187,10 +1187,10 @@ loop:
} }
} }
// Increment added even if there's a sampleLimitErr so we correctly report the number of samples scraped. // Increment added even if there's an error so we correctly report the
if sampleAdded || sampleLimitErr != nil { // number of samples remaining after relabelling.
added++ added++
}
} }
if sampleLimitErr != nil { if sampleLimitErr != nil {
if err == nil { if err == nil {
@ -1275,7 +1275,7 @@ const (
scrapeSeriesAddedMetricName = "scrape_series_added" + "\xff" scrapeSeriesAddedMetricName = "scrape_series_added" + "\xff"
) )
func (sl *scrapeLoop) report(start time.Time, duration time.Duration, scraped, appended, seriesAdded int, scrapeErr error) (err error) { func (sl *scrapeLoop) report(start time.Time, duration time.Duration, scraped, added, seriesAdded int, scrapeErr error) (err error) {
sl.scraper.Report(start, duration, scrapeErr) sl.scraper.Report(start, duration, scrapeErr)
ts := timestamp.FromTime(start) ts := timestamp.FromTime(start)
@ -1302,7 +1302,7 @@ func (sl *scrapeLoop) report(start time.Time, duration time.Duration, scraped, a
if err = sl.addReportSample(app, scrapeSamplesMetricName, ts, float64(scraped)); err != nil { if err = sl.addReportSample(app, scrapeSamplesMetricName, ts, float64(scraped)); err != nil {
return return
} }
if err = sl.addReportSample(app, samplesPostRelabelMetricName, ts, float64(appended)); err != nil { if err = sl.addReportSample(app, samplesPostRelabelMetricName, ts, float64(added)); err != nil {
return return
} }
if err = sl.addReportSample(app, scrapeSeriesAddedMetricName, ts, float64(seriesAdded)); err != nil { if err = sl.addReportSample(app, scrapeSeriesAddedMetricName, ts, float64(seriesAdded)); err != nil {

View File

@ -1340,7 +1340,7 @@ func TestScrapeLoopAppendGracefullyIfAmendOrOutOfOrderOrOutOfBounds(t *testing.T
} }
testutil.Equals(t, want, app.result, "Appended samples not as expected") testutil.Equals(t, want, app.result, "Appended samples not as expected")
testutil.Equals(t, 4, total) testutil.Equals(t, 4, total)
testutil.Equals(t, 1, added) testutil.Equals(t, 4, added)
testutil.Equals(t, 1, seriesAdded) testutil.Equals(t, 1, seriesAdded)
} }
@ -1365,7 +1365,7 @@ func TestScrapeLoopOutOfBoundsTimeError(t *testing.T) {
now := time.Now().Add(20 * time.Minute) now := time.Now().Add(20 * time.Minute)
total, added, seriesAdded, err := sl.append([]byte("normal 1\n"), "", now) total, added, seriesAdded, err := sl.append([]byte("normal 1\n"), "", now)
testutil.Equals(t, 1, total) testutil.Equals(t, 1, total)
testutil.Equals(t, 0, added) testutil.Equals(t, 1, added)
testutil.Equals(t, 0, seriesAdded) testutil.Equals(t, 0, seriesAdded)
testutil.Ok(t, err) testutil.Ok(t, err)
@ -1617,9 +1617,9 @@ func TestScrapeLoopDiscardDuplicateLabels(t *testing.T) {
q, err := s.Querier(ctx, time.Time{}.UnixNano(), 0) q, err := s.Querier(ctx, time.Time{}.UnixNano(), 0)
testutil.Ok(t, err) testutil.Ok(t, err)
series, _, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "__name__", ".*")) series := q.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "__name__", ".*"))
testutil.Ok(t, err)
testutil.Equals(t, false, series.Next(), "series found in tsdb") testutil.Equals(t, false, series.Next(), "series found in tsdb")
testutil.Ok(t, series.Err())
// We add a good metric to check that it is recorded. // We add a good metric to check that it is recorded.
_, _, _, err = sl.append([]byte("test_metric{le=\"500\"} 1\n"), "", time.Time{}) _, _, _, err = sl.append([]byte("test_metric{le=\"500\"} 1\n"), "", time.Time{})
@ -1627,9 +1627,9 @@ func TestScrapeLoopDiscardDuplicateLabels(t *testing.T) {
q, err = s.Querier(ctx, time.Time{}.UnixNano(), 0) q, err = s.Querier(ctx, time.Time{}.UnixNano(), 0)
testutil.Ok(t, err) testutil.Ok(t, err)
series, _, err = q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "le", "500")) series = q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "le", "500"))
testutil.Ok(t, err)
testutil.Equals(t, true, series.Next(), "series not found in tsdb") testutil.Equals(t, true, series.Next(), "series not found in tsdb")
testutil.Ok(t, series.Err())
testutil.Equals(t, false, series.Next(), "more than one series found in tsdb") testutil.Equals(t, false, series.Next(), "more than one series found in tsdb")
} }
@ -1663,9 +1663,9 @@ func TestScrapeLoopDiscardUnnamedMetrics(t *testing.T) {
q, err := s.Querier(ctx, time.Time{}.UnixNano(), 0) q, err := s.Querier(ctx, time.Time{}.UnixNano(), 0)
testutil.Ok(t, err) testutil.Ok(t, err)
series, _, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "__name__", ".*")) series := q.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "__name__", ".*"))
testutil.Ok(t, err)
testutil.Equals(t, false, series.Next(), "series found in tsdb") testutil.Equals(t, false, series.Next(), "series found in tsdb")
testutil.Ok(t, series.Err())
} }
func TestReusableConfig(t *testing.T) { func TestReusableConfig(t *testing.T) {

View File

@ -16,14 +16,15 @@ package storage
import ( import (
"container/heap" "container/heap"
"context" "context"
"reflect"
"sort" "sort"
"strings" "strings"
"sync"
"github.com/go-kit/kit/log" "github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level" "github.com/go-kit/kit/log/level"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/tsdb/chunkenc" "github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/chunks" "github.com/prometheus/prometheus/tsdb/chunks"
@ -37,8 +38,15 @@ type fanout struct {
secondaries []Storage secondaries []Storage
} }
// NewFanout returns a new fan-out Storage, which proxies reads and writes // NewFanout returns a new fanout Storage, which proxies reads and writes
// through to multiple underlying storages. // through to multiple underlying storages.
//
// The difference between primary and secondary Storage is only for read (Querier) path and it goes as follows:
// * If the primary querier returns an error, then any of the Querier operations will fail.
// * If any secondary querier returns an error the result from that queries is discarded. The overall operation will succeed,
// and the error from the secondary querier will be returned as a warning.
//
// NOTE: In the case of Prometheus, it treats all remote storages as secondary / best effort.
func NewFanout(logger log.Logger, primary Storage, secondaries ...Storage) Storage { func NewFanout(logger log.Logger, primary Storage, secondaries ...Storage) Storage {
return &fanout{ return &fanout{
logger: logger, logger: logger,
@ -56,8 +64,8 @@ func (f *fanout) StartTime() (int64, error) {
return int64(model.Latest), err return int64(model.Latest), err
} }
for _, storage := range f.secondaries { for _, s := range f.secondaries {
t, err := storage.StartTime() t, err := s.StartTime()
if err != nil { if err != nil {
return int64(model.Latest), err return int64(model.Latest), err
} }
@ -69,29 +77,27 @@ func (f *fanout) StartTime() (int64, error) {
} }
func (f *fanout) Querier(ctx context.Context, mint, maxt int64) (Querier, error) { func (f *fanout) Querier(ctx context.Context, mint, maxt int64) (Querier, error) {
queriers := make([]Querier, 0, 1+len(f.secondaries)) primary, err := f.primary.Querier(ctx, mint, maxt)
// Add primary querier.
primaryQuerier, err := f.primary.Querier(ctx, mint, maxt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
queriers = append(queriers, primaryQuerier)
// Add secondary queriers. secondaries := make([]Querier, 0, len(f.secondaries))
for _, storage := range f.secondaries { for _, storage := range f.secondaries {
querier, err := storage.Querier(ctx, mint, maxt) querier, err := storage.Querier(ctx, mint, maxt)
if err != nil { if err != nil {
for _, q := range queriers { // Close already open Queriers, append potential errors to returned error.
// TODO(bwplotka): Log error. errs := tsdb_errors.MultiError{err}
_ = q.Close() errs.Add(primary.Close())
for _, q := range secondaries {
errs.Add(q.Close())
} }
return nil, err return nil, errs.Err()
} }
queriers = append(queriers, querier)
}
return NewMergeQuerier(primaryQuerier, queriers, ChainedSeriesMerge), nil secondaries = append(secondaries, querier)
}
return NewMergeQuerier(primary, secondaries, ChainedSeriesMerge), nil
} }
func (f *fanout) Appender() Appender { func (f *fanout) Appender() Appender {
@ -109,18 +115,12 @@ func (f *fanout) Appender() Appender {
// Close closes the storage and all its underlying resources. // Close closes the storage and all its underlying resources.
func (f *fanout) Close() error { func (f *fanout) Close() error {
if err := f.primary.Close(); err != nil { errs := tsdb_errors.MultiError{}
return err errs.Add(f.primary.Close())
for _, s := range f.secondaries {
errs.Add(s.Close())
} }
return errs.Err()
// TODO return multiple errors?
var lastErr error
for _, storage := range f.secondaries {
if err := storage.Close(); err != nil {
lastErr = err
}
}
return lastErr
} }
// fanoutAppender implements Appender. // fanoutAppender implements Appender.
@ -188,153 +188,138 @@ func (f *fanoutAppender) Rollback() (err error) {
} }
type mergeGenericQuerier struct { type mergeGenericQuerier struct {
mergeFunc genericSeriesMergeFunc queriers []genericQuerier
primaryQuerier genericQuerier // mergeFn is used when we see series from different queriers Selects with the same labels.
queriers []genericQuerier mergeFn genericSeriesMergeFunc
failedQueriers map[genericQuerier]struct{}
setQuerierMap map[genericSeriesSet]genericQuerier
} }
// NewMergeQuerier returns a new Querier that merges results of chkQuerierSeries queriers. // NewMergeQuerier returns a new Querier that merges results of given primary and slice of secondary queriers.
// NewMergeQuerier will return NoopQuerier if no queriers are passed to it // See NewFanout commentary to learn more about primary vs secondary differences.
// and will filter NoopQueriers from its arguments, in order to reduce overhead //
// when only one querier is passed. // In case of overlaps between the data given by primary + secondaries Selects, merge function will be used.
// The difference between primary and secondary is as follows: f the primaryQuerier returns an error, query fails. func NewMergeQuerier(primary Querier, secondaries []Querier, mergeFn VerticalSeriesMergeFunc) Querier {
// For secondaries it just return warnings. queriers := make([]genericQuerier, 0, len(secondaries)+1)
func NewMergeQuerier(primaryQuerier Querier, queriers []Querier, mergeFunc VerticalSeriesMergeFunc) Querier { if primary != nil {
filtered := make([]genericQuerier, 0, len(queriers)) queriers = append(queriers, newGenericQuerierFrom(primary))
for _, querier := range queriers { }
for _, querier := range secondaries {
if _, ok := querier.(noopQuerier); !ok && querier != nil { if _, ok := querier.(noopQuerier); !ok && querier != nil {
filtered = append(filtered, newGenericQuerierFrom(querier)) queriers = append(queriers, newSecondaryQuerierFrom(querier))
} }
} }
if len(filtered) == 0 {
return primaryQuerier
}
if primaryQuerier == nil && len(filtered) == 1 {
return &querierAdapter{filtered[0]}
}
return &querierAdapter{&mergeGenericQuerier{ return &querierAdapter{&mergeGenericQuerier{
mergeFunc: (&seriesMergerAdapter{VerticalSeriesMergeFunc: mergeFunc}).Merge, mergeFn: (&seriesMergerAdapter{VerticalSeriesMergeFunc: mergeFn}).Merge,
primaryQuerier: newGenericQuerierFrom(primaryQuerier), queriers: queriers,
queriers: filtered,
failedQueriers: make(map[genericQuerier]struct{}),
setQuerierMap: make(map[genericSeriesSet]genericQuerier),
}} }}
} }
// NewMergeChunkQuerier returns a new ChunkQuerier that merges results of chkQuerierSeries chunk queriers. // NewMergeChunkQuerier returns a new ChunkQuerier that merges results of given primary and slice of secondary chunk queriers.
// NewMergeChunkQuerier will return NoopChunkQuerier if no chunk queriers are passed to it, // See NewFanout commentary to learn more about primary vs secondary differences.
// and will filter NoopQuerieNoopChunkQuerierrs from its arguments, in order to reduce overhead //
// when only one chunk querier is passed. // In case of overlaps between the data given by primary + secondaries Selects, merge function will be used.
func NewMergeChunkQuerier(primaryQuerier ChunkQuerier, queriers []ChunkQuerier, merger VerticalChunkSeriesMergerFunc) ChunkQuerier { // TODO(bwplotka): Currently merge will compact overlapping chunks with bigger chunk, without limit. Split it: https://github.com/prometheus/tsdb/issues/670
filtered := make([]genericQuerier, 0, len(queriers)) func NewMergeChunkQuerier(primary ChunkQuerier, secondaries []ChunkQuerier, mergeFn VerticalChunkSeriesMergerFunc) ChunkQuerier {
for _, querier := range queriers { queriers := make([]genericQuerier, 0, len(secondaries)+1)
if primary != nil {
queriers = append(queriers, newGenericQuerierFromChunk(primary))
}
for _, querier := range secondaries {
if _, ok := querier.(noopChunkQuerier); !ok && querier != nil { if _, ok := querier.(noopChunkQuerier); !ok && querier != nil {
filtered = append(filtered, newGenericQuerierFromChunk(querier)) queriers = append(queriers, newSecondaryQuerierFromChunk(querier))
} }
} }
if len(filtered) == 0 {
return primaryQuerier
}
if primaryQuerier == nil && len(filtered) == 1 {
return &chunkQuerierAdapter{filtered[0]}
}
return &chunkQuerierAdapter{&mergeGenericQuerier{ return &chunkQuerierAdapter{&mergeGenericQuerier{
mergeFunc: (&chunkSeriesMergerAdapter{VerticalChunkSeriesMergerFunc: merger}).Merge, mergeFn: (&chunkSeriesMergerAdapter{VerticalChunkSeriesMergerFunc: mergeFn}).Merge,
primaryQuerier: newGenericQuerierFromChunk(primaryQuerier), queriers: queriers,
queriers: filtered,
failedQueriers: make(map[genericQuerier]struct{}),
setQuerierMap: make(map[genericSeriesSet]genericQuerier),
}} }}
} }
// Select returns a set of series that matches the given label matchers. // Select returns a set of series that matches the given label matchers.
func (q *mergeGenericQuerier) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) (genericSeriesSet, Warnings, error) { func (q *mergeGenericQuerier) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) genericSeriesSet {
if len(q.queriers) == 1 { if len(q.queriers) == 1 {
return q.queriers[0].Select(sortSeries, hints, matchers...) return q.queriers[0].Select(sortSeries, hints, matchers...)
} }
var ( var (
seriesSets = make([]genericSeriesSet, 0, len(q.queriers)) seriesSets = make([]genericSeriesSet, 0, len(q.queriers))
warnings Warnings wg sync.WaitGroup
priErr error seriesSetChan = make(chan genericSeriesSet)
) )
type queryResult struct {
qr genericQuerier
set genericSeriesSet
wrn Warnings
selectError error
}
queryResultChan := make(chan *queryResult)
// Schedule all Selects for all queriers we know about.
for _, querier := range q.queriers { for _, querier := range q.queriers {
wg.Add(1)
go func(qr genericQuerier) { go func(qr genericQuerier) {
defer wg.Done()
// We need to sort for NewMergeSeriesSet to work. // We need to sort for NewMergeSeriesSet to work.
set, wrn, err := qr.Select(true, hints, matchers...) seriesSetChan <- qr.Select(true, hints, matchers...)
queryResultChan <- &queryResult{qr: qr, set: set, wrn: wrn, selectError: err}
}(querier) }(querier)
} }
for i := 0; i < len(q.queriers); i++ { go func() {
qryResult := <-queryResultChan wg.Wait()
q.setQuerierMap[qryResult.set] = qryResult.qr close(seriesSetChan)
if qryResult.wrn != nil { }()
warnings = append(warnings, qryResult.wrn...)
for r := range seriesSetChan {
seriesSets = append(seriesSets, r)
}
return &lazySeriesSet{create: create(seriesSets, q.mergeFn)}
}
func create(seriesSets []genericSeriesSet, mergeFn genericSeriesMergeFunc) func() (genericSeriesSet, bool) {
// Returned function gets called with the first call to Next().
return func() (genericSeriesSet, bool) {
if len(seriesSets) == 1 {
return seriesSets[0], seriesSets[0].Next()
} }
if qryResult.selectError != nil { var h genericSeriesSetHeap
q.failedQueriers[qryResult.qr] = struct{}{} for _, set := range seriesSets {
// If the error source isn't the primary querier, return the error as a warning and continue. if set == nil {
if !reflect.DeepEqual(qryResult.qr, q.primaryQuerier) { continue
warnings = append(warnings, qryResult.selectError) }
} else { if set.Next() {
priErr = qryResult.selectError heap.Push(&h, set)
continue
}
// When primary fails ignore results from secondaries.
// Only the primary querier returns error.
if err := set.Err(); err != nil {
return errorOnlySeriesSet{err}, false
} }
continue
} }
seriesSets = append(seriesSets, qryResult.set) set := &genericMergeSeriesSet{
mergeFn: mergeFn,
sets: seriesSets,
heap: h,
}
return set, set.Next()
} }
if priErr != nil {
return nil, nil, priErr
}
return newGenericMergeSeriesSet(seriesSets, q, q.mergeFunc), warnings, nil
} }
// LabelValues returns all potential values for a label name. // LabelValues returns all potential values for a label name.
func (q *mergeGenericQuerier) LabelValues(name string) ([]string, Warnings, error) { func (q *mergeGenericQuerier) LabelValues(name string) ([]string, Warnings, error) {
var results [][]string var (
var warnings Warnings results [][]string
warnings Warnings
)
for _, querier := range q.queriers { for _, querier := range q.queriers {
values, wrn, err := querier.LabelValues(name) values, wrn, err := querier.LabelValues(name)
if wrn != nil { if wrn != nil {
// TODO(bwplotka): We could potentially wrap warnings.
warnings = append(warnings, wrn...) warnings = append(warnings, wrn...)
} }
if err != nil { if err != nil {
q.failedQueriers[querier] = struct{}{} return nil, nil, errors.Wrapf(err, "LabelValues() from Querier for label %s", name)
// If the error source isn't the primary querier, return the error as a warning and continue.
if querier != q.primaryQuerier {
warnings = append(warnings, err)
continue
} else {
return nil, nil, err
}
} }
results = append(results, values) results = append(results, values)
} }
return mergeStringSlices(results), warnings, nil return mergeStringSlices(results), warnings, nil
} }
func (q *mergeGenericQuerier) IsFailedSet(set genericSeriesSet) bool {
_, isFailedQuerier := q.failedQueriers[q.setQuerierMap[set]]
return isFailedQuerier
}
func mergeStringSlices(ss [][]string) []string { func mergeStringSlices(ss [][]string) []string {
switch len(ss) { switch len(ss) {
case 0: case 0:
@ -381,37 +366,31 @@ func (q *mergeGenericQuerier) LabelNames() ([]string, Warnings, error) {
for _, querier := range q.queriers { for _, querier := range q.queriers {
names, wrn, err := querier.LabelNames() names, wrn, err := querier.LabelNames()
if wrn != nil { if wrn != nil {
// TODO(bwplotka): We could potentially wrap warnings.
warnings = append(warnings, wrn...) warnings = append(warnings, wrn...)
} }
if err != nil { if err != nil {
q.failedQueriers[querier] = struct{}{} return nil, nil, errors.Wrap(err, "LabelNames() from Querier")
// If the error source isn't the primaryQuerier querier, return the error as a warning and continue.
if querier != q.primaryQuerier {
warnings = append(warnings, err)
continue
} else {
return nil, nil, errors.Wrap(err, "LabelNames() from Querier")
}
} }
for _, name := range names { for _, name := range names {
labelNamesMap[name] = struct{}{} labelNamesMap[name] = struct{}{}
} }
} }
if len(labelNamesMap) == 0 {
return nil, warnings, nil
}
labelNames := make([]string, 0, len(labelNamesMap)) labelNames := make([]string, 0, len(labelNamesMap))
for name := range labelNamesMap { for name := range labelNamesMap {
labelNames = append(labelNames, name) labelNames = append(labelNames, name)
} }
sort.Strings(labelNames) sort.Strings(labelNames)
return labelNames, warnings, nil return labelNames, warnings, nil
} }
// Close releases the resources of the Querier. // Close releases the resources of the Querier.
func (q *mergeGenericQuerier) Close() error { func (q *mergeGenericQuerier) Close() error {
var errs tsdb_errors.MultiError errs := tsdb_errors.MultiError{}
for _, querier := range q.queriers { for _, querier := range q.queriers {
if err := querier.Close(); err != nil { if err := querier.Close(); err != nil {
errs.Add(err) errs.Add(err)
@ -420,18 +399,6 @@ func (q *mergeGenericQuerier) Close() error {
return errs.Err() return errs.Err()
} }
// genericMergeSeriesSet implements genericSeriesSet
type genericMergeSeriesSet struct {
currentLabels labels.Labels
mergeFunc genericSeriesMergeFunc
heap genericSeriesSetHeap
sets []genericSeriesSet
currentSets []genericSeriesSet
querier *mergeGenericQuerier
}
// VerticalSeriesMergeFunc returns merged series implementation that merges series with same labels together. // VerticalSeriesMergeFunc returns merged series implementation that merges series with same labels together.
// It has to handle time-overlapped series as well. // It has to handle time-overlapped series as well.
type VerticalSeriesMergeFunc func(...Series) Series type VerticalSeriesMergeFunc func(...Series) Series
@ -447,7 +414,7 @@ func NewMergeSeriesSet(sets []SeriesSet, merger VerticalSeriesMergeFunc) SeriesS
genericSets = append(genericSets, &genericSeriesSetAdapter{s}) genericSets = append(genericSets, &genericSeriesSetAdapter{s})
} }
return &seriesSetAdapter{newGenericMergeSeriesSet(genericSets, nil, (&seriesMergerAdapter{VerticalSeriesMergeFunc: merger}).Merge)} return &seriesSetAdapter{newGenericMergeSeriesSet(genericSets, (&seriesMergerAdapter{VerticalSeriesMergeFunc: merger}).Merge)}
} }
// NewMergeChunkSeriesSet returns a new ChunkSeriesSet that merges results of chkQuerierSeries ChunkSeriesSets. // NewMergeChunkSeriesSet returns a new ChunkSeriesSet that merges results of chkQuerierSeries ChunkSeriesSets.
@ -457,21 +424,30 @@ func NewMergeChunkSeriesSet(sets []ChunkSeriesSet, merger VerticalChunkSeriesMer
genericSets = append(genericSets, &genericChunkSeriesSetAdapter{s}) genericSets = append(genericSets, &genericChunkSeriesSetAdapter{s})
} }
return &chunkSeriesSetAdapter{newGenericMergeSeriesSet(genericSets, nil, (&chunkSeriesMergerAdapter{VerticalChunkSeriesMergerFunc: merger}).Merge)} return &chunkSeriesSetAdapter{newGenericMergeSeriesSet(genericSets, (&chunkSeriesMergerAdapter{VerticalChunkSeriesMergerFunc: merger}).Merge)}
}
// genericMergeSeriesSet implements genericSeriesSet.
type genericMergeSeriesSet struct {
currentLabels labels.Labels
mergeFn genericSeriesMergeFunc
heap genericSeriesSetHeap
sets []genericSeriesSet
currentSets []genericSeriesSet
} }
// newGenericMergeSeriesSet returns a new genericSeriesSet that merges (and deduplicates) // newGenericMergeSeriesSet returns a new genericSeriesSet that merges (and deduplicates)
// series returned by the chkQuerierSeries series sets when iterating. // series returned by the series sets when iterating.
// Each chkQuerierSeries series set must return its series in labels order, otherwise // Each series set must return its series in labels order, otherwise
// merged series set will be incorrect. // merged series set will be incorrect.
// Argument 'querier' is optional and can be nil. Pass Querier if you want to retry query in case of failing series set. // Overlapping cases are merged using provided mergeFn.
// Overlapped situations are merged using provided mergeFunc. func newGenericMergeSeriesSet(sets []genericSeriesSet, mergeFn genericSeriesMergeFunc) genericSeriesSet {
func newGenericMergeSeriesSet(sets []genericSeriesSet, querier *mergeGenericQuerier, mergeFunc genericSeriesMergeFunc) genericSeriesSet {
if len(sets) == 1 { if len(sets) == 1 {
return sets[0] return sets[0]
} }
// Sets need to be pre-advanced, so we can introspect the label of the // We are pre-advancing sets, so we can introspect the label of the
// series under the cursor. // series under the cursor.
var h genericSeriesSetHeap var h genericSeriesSetHeap
for _, set := range sets { for _, set := range sets {
@ -483,26 +459,25 @@ func newGenericMergeSeriesSet(sets []genericSeriesSet, querier *mergeGenericQuer
} }
} }
return &genericMergeSeriesSet{ return &genericMergeSeriesSet{
mergeFunc: mergeFunc, mergeFn: mergeFn,
heap: h, sets: sets,
sets: sets, heap: h,
querier: querier,
} }
} }
func (c *genericMergeSeriesSet) Next() bool { func (c *genericMergeSeriesSet) Next() bool {
// Run in a loop because the "next" series sets may not be valid anymore. // Run in a loop because the "next" series sets may not be valid anymore.
// If a remote querier fails, we discard all series sets from that querier.
// If, for the current label set, all the next series sets come from // If, for the current label set, all the next series sets come from
// failed remote storage sources, we want to keep trying with the next label set. // failed remote storage sources, we want to keep trying with the next label set.
for { for {
// Firstly advance all the current series sets. If any of them have run out // Firstly advance all the current series sets. If any of them have run out
// we can drop them, otherwise they should be inserted back into the heap. // we can drop them, otherwise they should be inserted back into the heap.
for _, set := range c.currentSets { for _, set := range c.currentSets {
if set.Next() { if set.Next() {
heap.Push(&c.heap, set) heap.Push(&c.heap, set)
} }
} }
if len(c.heap) == 0 { if len(c.heap) == 0 {
return false return false
} }
@ -512,9 +487,6 @@ func (c *genericMergeSeriesSet) Next() bool {
c.currentLabels = c.heap[0].At().Labels() c.currentLabels = c.heap[0].At().Labels()
for len(c.heap) > 0 && labels.Equal(c.currentLabels, c.heap[0].At().Labels()) { for len(c.heap) > 0 && labels.Equal(c.currentLabels, c.heap[0].At().Labels()) {
set := heap.Pop(&c.heap).(genericSeriesSet) set := heap.Pop(&c.heap).(genericSeriesSet)
if c.querier != nil && c.querier.IsFailedSet(set) {
continue
}
c.currentSets = append(c.currentSets, set) c.currentSets = append(c.currentSets, set)
} }
@ -535,7 +507,7 @@ func (c *genericMergeSeriesSet) At() Labels {
for _, seriesSet := range c.currentSets { for _, seriesSet := range c.currentSets {
series = append(series, seriesSet.At()) series = append(series, seriesSet.At())
} }
return c.mergeFunc(series...) return c.mergeFn(series...)
} }
func (c *genericMergeSeriesSet) Err() error { func (c *genericMergeSeriesSet) Err() error {
@ -547,6 +519,14 @@ func (c *genericMergeSeriesSet) Err() error {
return nil return nil
} }
func (c *genericMergeSeriesSet) Warnings() Warnings {
var ws Warnings
for _, set := range c.sets {
ws = append(ws, set.Warnings()...)
}
return ws
}
type genericSeriesSetHeap []genericSeriesSet type genericSeriesSetHeap []genericSeriesSet
func (h genericSeriesSetHeap) Len() int { return len(h) } func (h genericSeriesSetHeap) Len() int { return len(h) }

View File

@ -16,14 +16,14 @@ package storage
import ( import (
"context" "context"
"errors" "errors"
"testing"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/util/teststorage" "github.com/prometheus/prometheus/util/teststorage"
"github.com/prometheus/prometheus/util/testutil" "github.com/prometheus/prometheus/util/testutil"
"testing"
) )
func TestSelectSorted(t *testing.T) { func TestSelectSorted(t *testing.T) {
@ -79,8 +79,7 @@ func TestSelectSorted(t *testing.T) {
matcher, err := labels.NewMatcher(labels.MatchEqual, model.MetricNameLabel, "a") matcher, err := labels.NewMatcher(labels.MatchEqual, model.MetricNameLabel, "a")
testutil.Ok(t, err) testutil.Ok(t, err)
seriesSet, _, err := querier.Select(true, nil, matcher) seriesSet := querier.Select(true, nil, matcher)
testutil.Ok(t, err)
result := make(map[int64]float64) result := make(map[int64]float64)
var labelsResult labels.Labels var labelsResult labels.Labels
@ -95,6 +94,7 @@ func TestSelectSorted(t *testing.T) {
} }
} }
testutil.Ok(t, seriesSet.Err())
testutil.Equals(t, labelsResult, outputLabel) testutil.Equals(t, labelsResult, outputLabel)
testutil.Equals(t, inputTotalSize, len(result)) testutil.Equals(t, inputTotalSize, len(result))
} }
@ -131,19 +131,12 @@ func TestFanoutErrors(t *testing.T) {
defer querier.Close() defer querier.Close()
matcher := labels.MustNewMatcher(labels.MatchEqual, "a", "b") matcher := labels.MustNewMatcher(labels.MatchEqual, "a", "b")
ss, warnings, err := querier.Select(true, nil, matcher) ss := querier.Select(true, nil, matcher)
testutil.Equals(t, tc.err, err)
testutil.Equals(t, tc.warnings, warnings)
// Only test series iteration if there are no errors.
if err != nil {
continue
}
for ss.Next() { for ss.Next() {
ss.At() ss.At()
} }
testutil.Ok(t, ss.Err()) testutil.Equals(t, tc.err, ss.Err())
testutil.Equals(t, tc.warnings, ss.Warnings())
} }
} }
@ -169,8 +162,8 @@ func (errStorage) Close() error {
type errQuerier struct{} type errQuerier struct{}
func (errQuerier) Select(bool, *storage.SelectHints, ...*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (errQuerier) Select(bool, *storage.SelectHints, ...*labels.Matcher) storage.SeriesSet {
return nil, nil, errSelect return storage.ErrSeriesSet(errSelect)
} }
func (errQuerier) LabelValues(name string) ([]string, storage.Warnings, error) { func (errQuerier) LabelValues(name string) ([]string, storage.Warnings, error) {

View File

@ -17,8 +17,11 @@ import (
"fmt" "fmt"
"math" "math"
"sort" "sort"
"sync"
"testing" "testing"
"github.com/pkg/errors"
"github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/tsdb/chunkenc" "github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/tsdbutil" "github.com/prometheus/prometheus/tsdb/tsdbutil"
@ -56,24 +59,37 @@ func TestMergeTwoStringSlices(t *testing.T) {
func TestMergeQuerierWithChainMerger(t *testing.T) { func TestMergeQuerierWithChainMerger(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
querierSeries [][]Series primaryQuerierSeries []Series
extraQueriers []Querier querierSeries [][]Series
extraQueriers []Querier
expected SeriesSet expected SeriesSet
}{ }{
{ {
name: "1 querier with no series", name: "one primary querier with no series",
primaryQuerierSeries: []Series{},
expected: NewMockSeriesSet(),
},
{
name: "one secondary querier with no series",
querierSeries: [][]Series{{}}, querierSeries: [][]Series{{}},
expected: NewMockSeriesSet(), expected: NewMockSeriesSet(),
}, },
{ {
name: "many queriers with no series", name: "many secondary queriers with no series",
querierSeries: [][]Series{{}, {}, {}, {}, {}, {}, {}}, querierSeries: [][]Series{{}, {}, {}, {}, {}, {}, {}},
expected: NewMockSeriesSet(), expected: NewMockSeriesSet(),
}, },
{ {
name: "1 querier, two series", name: "mix of queriers with no series",
primaryQuerierSeries: []Series{},
querierSeries: [][]Series{{}, {}, {}, {}, {}, {}, {}},
expected: NewMockSeriesSet(),
},
// Test rest of cases on secondary queriers as the different between primary vs secondary is just error handling.
{
name: "one querier, two series",
querierSeries: [][]Series{{ querierSeries: [][]Series{{
NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{1, 1}, sample{2, 2}, sample{3, 3}}), NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{1, 1}, sample{2, 2}, sample{3, 3}}),
NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, 0}, sample{1, 1}, sample{2, 2}}), NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, 0}, sample{1, 1}, sample{2, 2}}),
@ -84,7 +100,7 @@ func TestMergeQuerierWithChainMerger(t *testing.T) {
), ),
}, },
{ {
name: "2 queriers, 1 different series each", name: "two queriers, one different series each",
querierSeries: [][]Series{{ querierSeries: [][]Series{{
NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{1, 1}, sample{2, 2}, sample{3, 3}}), NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{1, 1}, sample{2, 2}, sample{3, 3}}),
}, { }, {
@ -96,7 +112,7 @@ func TestMergeQuerierWithChainMerger(t *testing.T) {
), ),
}, },
{ {
name: "2 time unsorted queriers, 2 series each", name: "two time unsorted queriers, two series each",
querierSeries: [][]Series{{ querierSeries: [][]Series{{
NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{5, 5}, sample{6, 6}}), NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{5, 5}, sample{6, 6}}),
NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, 0}, sample{1, 1}, sample{2, 2}}), NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, 0}, sample{1, 1}, sample{2, 2}}),
@ -116,7 +132,7 @@ func TestMergeQuerierWithChainMerger(t *testing.T) {
), ),
}, },
{ {
name: "5 queriers, only 2 queriers have 2 time unsorted series each", name: "five queriers, only two queriers have two time unsorted series each",
querierSeries: [][]Series{{}, {}, { querierSeries: [][]Series{{}, {}, {
NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{5, 5}, sample{6, 6}}), NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{5, 5}, sample{6, 6}}),
NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, 0}, sample{1, 1}, sample{2, 2}}), NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, 0}, sample{1, 1}, sample{2, 2}}),
@ -136,7 +152,7 @@ func TestMergeQuerierWithChainMerger(t *testing.T) {
), ),
}, },
{ {
name: "2 queriers, only 2 queriers have 2 time unsorted series each, with 3 noop and one nil querier together", name: "two queriers, only two queriers have two time unsorted series each, with 3 noop and one nil querier together",
querierSeries: [][]Series{{}, {}, { querierSeries: [][]Series{{}, {}, {
NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{5, 5}, sample{6, 6}}), NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{5, 5}, sample{6, 6}}),
NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, 0}, sample{1, 1}, sample{2, 2}}), NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, 0}, sample{1, 1}, sample{2, 2}}),
@ -157,7 +173,7 @@ func TestMergeQuerierWithChainMerger(t *testing.T) {
), ),
}, },
{ {
name: "2 queriers, with 2 series, one is overlapping", name: "two queriers, with two series, one is overlapping",
querierSeries: [][]Series{{}, {}, { querierSeries: [][]Series{{}, {}, {
NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{2, 21}, sample{3, 31}, sample{5, 5}, sample{6, 6}}), NewListSeries(labels.FromStrings("bar", "baz"), []tsdbutil.Sample{sample{2, 21}, sample{3, 31}, sample{5, 5}, sample{6, 6}}),
NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, 0}, sample{1, 1}, sample{2, 2}}), NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, 0}, sample{1, 1}, sample{2, 2}}),
@ -177,7 +193,7 @@ func TestMergeQuerierWithChainMerger(t *testing.T) {
), ),
}, },
{ {
name: "2 queries, one with NaN samples series", name: "two queries, one with NaN samples series",
querierSeries: [][]Series{{ querierSeries: [][]Series{{
NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, math.NaN()}}), NewListSeries(labels.FromStrings("foo", "bar"), []tsdbutil.Sample{sample{0, math.NaN()}}),
}, { }, {
@ -189,13 +205,17 @@ func TestMergeQuerierWithChainMerger(t *testing.T) {
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
var p Querier
if tc.primaryQuerierSeries != nil {
p = &mockQuerier{toReturn: tc.primaryQuerierSeries}
}
var qs []Querier var qs []Querier
for _, in := range tc.querierSeries { for _, in := range tc.querierSeries {
qs = append(qs, &mockQuerier{toReturn: in}) qs = append(qs, &mockQuerier{toReturn: in})
} }
qs = append(qs, tc.extraQueriers...) qs = append(qs, tc.extraQueriers...)
mergedQuerier, _, _ := NewMergeQuerier(qs[0], qs, ChainedSeriesMerge).Select(false, nil) mergedQuerier := NewMergeQuerier(p, qs, ChainedSeriesMerge).Select(false, nil)
// Get all merged series upfront to make sure there are no incorrectly retained shared // Get all merged series upfront to make sure there are no incorrectly retained shared
// buffers causing bugs. // buffers causing bugs.
@ -222,22 +242,35 @@ func TestMergeQuerierWithChainMerger(t *testing.T) {
func TestMergeChunkQuerierWithNoVerticalChunkSeriesMerger(t *testing.T) { func TestMergeChunkQuerierWithNoVerticalChunkSeriesMerger(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
chkQuerierSeries [][]ChunkSeries primaryChkQuerierSeries []ChunkSeries
extraQueriers []ChunkQuerier chkQuerierSeries [][]ChunkSeries
extraQueriers []ChunkQuerier
expected ChunkSeriesSet expected ChunkSeriesSet
}{ }{
{ {
name: "one querier with no series", name: "one primary querier with no series",
primaryChkQuerierSeries: []ChunkSeries{},
expected: NewMockChunkSeriesSet(),
},
{
name: "one secondary querier with no series",
chkQuerierSeries: [][]ChunkSeries{{}}, chkQuerierSeries: [][]ChunkSeries{{}},
expected: NewMockChunkSeriesSet(), expected: NewMockChunkSeriesSet(),
}, },
{ {
name: "many queriers with no series", name: "many secondary queriers with no series",
chkQuerierSeries: [][]ChunkSeries{{}, {}, {}, {}, {}, {}, {}}, chkQuerierSeries: [][]ChunkSeries{{}, {}, {}, {}, {}, {}, {}},
expected: NewMockChunkSeriesSet(), expected: NewMockChunkSeriesSet(),
}, },
{
name: "mix of queriers with no series",
primaryChkQuerierSeries: []ChunkSeries{},
chkQuerierSeries: [][]ChunkSeries{{}, {}, {}, {}, {}, {}, {}},
expected: NewMockChunkSeriesSet(),
},
// Test rest of cases on secondary queriers as the different between primary vs secondary is just error handling.
{ {
name: "one querier, two series", name: "one querier, two series",
chkQuerierSeries: [][]ChunkSeries{{ chkQuerierSeries: [][]ChunkSeries{{
@ -347,13 +380,19 @@ func TestMergeChunkQuerierWithNoVerticalChunkSeriesMerger(t *testing.T) {
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
var p ChunkQuerier
if tc.primaryChkQuerierSeries != nil {
p = &mockChunkQurier{toReturn: tc.primaryChkQuerierSeries}
}
var qs []ChunkQuerier var qs []ChunkQuerier
for _, in := range tc.chkQuerierSeries { for _, in := range tc.chkQuerierSeries {
qs = append(qs, &mockChunkQurier{toReturn: in}) qs = append(qs, &mockChunkQurier{toReturn: in})
} }
qs = append(qs, tc.extraQueriers...) qs = append(qs, tc.extraQueriers...)
merged, _, _ := NewMergeChunkQuerier(qs[0], qs, NewVerticalChunkSeriesMerger(nil)).Select(false, nil) // TODO(bwplotka): Add case of overlap to check if those are handled well.
merged := NewMergeChunkQuerier(p, qs, NewVerticalChunkSeriesMerger(nil)).Select(false, nil)
for merged.Next() { for merged.Next() {
testutil.Assert(t, tc.expected.Next(), "Expected Next() to be true") testutil.Assert(t, tc.expected.Next(), "Expected Next() to be true")
actualSeries := merged.At() actualSeries := merged.At()
@ -384,14 +423,14 @@ func (a seriesByLabel) Len() int { return len(a) }
func (a seriesByLabel) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a seriesByLabel) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a seriesByLabel) Less(i, j int) bool { return labels.Compare(a[i].Labels(), a[j].Labels()) < 0 } func (a seriesByLabel) Less(i, j int) bool { return labels.Compare(a[i].Labels(), a[j].Labels()) < 0 }
func (m *mockQuerier) Select(sortSeries bool, _ *SelectHints, _ ...*labels.Matcher) (SeriesSet, Warnings, error) { func (m *mockQuerier) Select(sortSeries bool, _ *SelectHints, _ ...*labels.Matcher) SeriesSet {
cpy := make([]Series, len(m.toReturn)) cpy := make([]Series, len(m.toReturn))
copy(cpy, m.toReturn) copy(cpy, m.toReturn)
if sortSeries { if sortSeries {
sort.Sort(seriesByLabel(cpy)) sort.Sort(seriesByLabel(cpy))
} }
return NewMockSeriesSet(cpy...), nil, nil return NewMockSeriesSet(cpy...)
} }
type mockChunkQurier struct { type mockChunkQurier struct {
@ -408,14 +447,14 @@ func (a chunkSeriesByLabel) Less(i, j int) bool {
return labels.Compare(a[i].Labels(), a[j].Labels()) < 0 return labels.Compare(a[i].Labels(), a[j].Labels()) < 0
} }
func (m *mockChunkQurier) Select(sortSeries bool, _ *SelectHints, _ ...*labels.Matcher) (ChunkSeriesSet, Warnings, error) { func (m *mockChunkQurier) Select(sortSeries bool, _ *SelectHints, _ ...*labels.Matcher) ChunkSeriesSet {
cpy := make([]ChunkSeries, len(m.toReturn)) cpy := make([]ChunkSeries, len(m.toReturn))
copy(cpy, m.toReturn) copy(cpy, m.toReturn)
if sortSeries { if sortSeries {
sort.Sort(chunkSeriesByLabel(cpy)) sort.Sort(chunkSeriesByLabel(cpy))
} }
return NewMockChunkSeriesSet(cpy...), nil, nil return NewMockChunkSeriesSet(cpy...)
} }
type mockSeriesSet struct { type mockSeriesSet struct {
@ -439,6 +478,8 @@ func (m *mockSeriesSet) At() Series { return m.series[m.idx] }
func (m *mockSeriesSet) Err() error { return nil } func (m *mockSeriesSet) Err() error { return nil }
func (m *mockSeriesSet) Warnings() Warnings { return nil }
type mockChunkSeriesSet struct { type mockChunkSeriesSet struct {
idx int idx int
series []ChunkSeries series []ChunkSeries
@ -460,6 +501,8 @@ func (m *mockChunkSeriesSet) At() ChunkSeries { return m.series[m.idx] }
func (m *mockChunkSeriesSet) Err() error { return nil } func (m *mockChunkSeriesSet) Err() error { return nil }
func (m *mockChunkSeriesSet) Warnings() Warnings { return nil }
func TestChainSampleIterator(t *testing.T) { func TestChainSampleIterator(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
input []chunkenc.Iterator input []chunkenc.Iterator
@ -571,7 +614,7 @@ func makeMergeSeriesSet(numSeriesSets, numSeries, numSamples int) SeriesSet {
for i := 0; i < numSeriesSets; i++ { for i := 0; i < numSeriesSets; i++ {
seriesSets = append(seriesSets, &genericSeriesSetAdapter{makeSeriesSet(numSeries, numSamples)}) seriesSets = append(seriesSets, &genericSeriesSetAdapter{makeSeriesSet(numSeries, numSamples)})
} }
return &seriesSetAdapter{newGenericMergeSeriesSet(seriesSets, nil, (&seriesMergerAdapter{VerticalSeriesMergeFunc: ChainedSeriesMerge}).Merge)} return &seriesSetAdapter{newGenericMergeSeriesSet(seriesSets, (&seriesMergerAdapter{VerticalSeriesMergeFunc: ChainedSeriesMerge}).Merge)}
} }
func benchmarkDrain(seriesSet SeriesSet, b *testing.B) { func benchmarkDrain(seriesSet SeriesSet, b *testing.B) {
@ -603,3 +646,260 @@ func BenchmarkMergeSeriesSet(b *testing.B) {
}) })
} }
} }
type mockGenericQuerier struct {
mtx sync.Mutex
closed bool
labelNamesCalls int
labelNamesRequested []string
sortedSeriesRequested []bool
resp []string
warnings Warnings
err error
}
func (m *mockGenericQuerier) Select(b bool, _ *SelectHints, _ ...*labels.Matcher) genericSeriesSet {
m.mtx.Lock()
m.sortedSeriesRequested = append(m.sortedSeriesRequested, b)
m.mtx.Unlock()
return &mockGenericSeriesSet{resp: m.resp, warnings: m.warnings, err: m.err}
}
func (m *mockGenericQuerier) LabelValues(name string) ([]string, Warnings, error) {
m.mtx.Lock()
m.labelNamesRequested = append(m.labelNamesRequested, name)
m.mtx.Unlock()
return m.resp, m.warnings, m.err
}
func (m *mockGenericQuerier) LabelNames() ([]string, Warnings, error) {
m.mtx.Lock()
m.labelNamesCalls++
m.mtx.Unlock()
return m.resp, m.warnings, m.err
}
func (m *mockGenericQuerier) Close() error {
m.closed = true
return nil
}
type mockGenericSeriesSet struct {
resp []string
warnings Warnings
err error
curr int
}
func (m *mockGenericSeriesSet) Next() bool {
if m.err != nil {
return false
}
if m.curr >= len(m.resp) {
return false
}
m.curr++
return true
}
func (m *mockGenericSeriesSet) Err() error { return m.err }
func (m *mockGenericSeriesSet) Warnings() Warnings { return m.warnings }
func (m *mockGenericSeriesSet) At() Labels {
return mockLabels(m.resp[m.curr-1])
}
type mockLabels string
func (l mockLabels) Labels() labels.Labels {
return labels.FromStrings("test", string(l))
}
func unwrapMockGenericQuerier(t *testing.T, qr genericQuerier) *mockGenericQuerier {
m, ok := qr.(*mockGenericQuerier)
if !ok {
s, ok := qr.(*secondaryQuerier)
testutil.Assert(t, ok, "expected secondaryQuerier got something else")
m, ok = s.genericQuerier.(*mockGenericQuerier)
testutil.Assert(t, ok, "expected mockGenericQuerier got something else")
}
return m
}
func TestMergeGenericQuerierWithSecondaries_ErrorHandling(t *testing.T) {
var (
errStorage = errors.New("storage error")
warnStorage = errors.New("storage warning")
)
for _, tcase := range []struct {
name string
queriers []genericQuerier
expectedSelectsSeries []labels.Labels
expectedLabels []string
expectedWarnings [3]Warnings
expectedErrs [3]error
}{
{},
{
name: "one successful primary querier",
queriers: []genericQuerier{&mockGenericQuerier{resp: []string{"a", "b"}, warnings: nil, err: nil}},
expectedSelectsSeries: []labels.Labels{
labels.FromStrings("test", "a"),
labels.FromStrings("test", "b"),
},
expectedLabels: []string{"a", "b"},
},
{
name: "multiple successful primary queriers",
queriers: []genericQuerier{
&mockGenericQuerier{resp: []string{"a", "b"}, warnings: nil, err: nil},
&mockGenericQuerier{resp: []string{"b", "c"}, warnings: nil, err: nil},
},
expectedSelectsSeries: []labels.Labels{
labels.FromStrings("test", "a"),
labels.FromStrings("test", "b"),
labels.FromStrings("test", "c"),
},
expectedLabels: []string{"a", "b", "c"},
},
{
name: "one failed primary querier",
queriers: []genericQuerier{&mockGenericQuerier{warnings: nil, err: errStorage}},
expectedErrs: [3]error{errStorage, errStorage, errStorage},
},
{
name: "one successful primary querier with successful secondaries",
queriers: []genericQuerier{
&mockGenericQuerier{resp: []string{"a", "b"}, warnings: nil, err: nil},
&secondaryQuerier{genericQuerier: &mockGenericQuerier{resp: []string{"b"}, warnings: nil, err: nil}},
&secondaryQuerier{genericQuerier: &mockGenericQuerier{resp: []string{"c"}, warnings: nil, err: nil}},
},
expectedSelectsSeries: []labels.Labels{
labels.FromStrings("test", "a"),
labels.FromStrings("test", "b"),
labels.FromStrings("test", "c"),
},
expectedLabels: []string{"a", "b", "c"},
},
{
name: "one successful primary querier with empty response and successful secondaries",
queriers: []genericQuerier{
&mockGenericQuerier{resp: []string{}, warnings: nil, err: nil},
&secondaryQuerier{genericQuerier: &mockGenericQuerier{resp: []string{"b"}, warnings: nil, err: nil}},
&secondaryQuerier{genericQuerier: &mockGenericQuerier{resp: []string{"c"}, warnings: nil, err: nil}},
},
expectedSelectsSeries: []labels.Labels{
labels.FromStrings("test", "b"),
labels.FromStrings("test", "c"),
},
expectedLabels: []string{"b", "c"},
},
{
name: "one failed primary querier with successful secondaries",
queriers: []genericQuerier{
&mockGenericQuerier{warnings: nil, err: errStorage},
&secondaryQuerier{genericQuerier: &mockGenericQuerier{resp: []string{"b"}, warnings: nil, err: nil}},
&secondaryQuerier{genericQuerier: &mockGenericQuerier{resp: []string{"c"}, warnings: nil, err: nil}},
},
expectedErrs: [3]error{errStorage, errStorage, errStorage},
},
{
name: "one successful primary querier with failed secondaries",
queriers: []genericQuerier{
&mockGenericQuerier{resp: []string{"a"}, warnings: nil, err: nil},
&secondaryQuerier{genericQuerier: &mockGenericQuerier{resp: []string{"b"}, warnings: nil, err: errStorage}},
&secondaryQuerier{genericQuerier: &mockGenericQuerier{resp: []string{"c"}, warnings: nil, err: errStorage}},
},
expectedSelectsSeries: []labels.Labels{
labels.FromStrings("test", "a"),
},
expectedLabels: []string{"a"},
expectedWarnings: [3]Warnings{
[]error{errStorage, errStorage},
[]error{errStorage, errStorage},
[]error{errStorage, errStorage},
},
},
{
name: "successful queriers with warnings",
queriers: []genericQuerier{
&mockGenericQuerier{resp: []string{"a"}, warnings: []error{warnStorage}, err: nil},
&secondaryQuerier{genericQuerier: &mockGenericQuerier{resp: []string{"b"}, warnings: []error{warnStorage}, err: nil}},
},
expectedSelectsSeries: []labels.Labels{
labels.FromStrings("test", "a"),
labels.FromStrings("test", "b"),
},
expectedLabels: []string{"a", "b"},
expectedWarnings: [3]Warnings{
[]error{warnStorage, warnStorage},
[]error{warnStorage, warnStorage},
[]error{warnStorage, warnStorage},
},
},
} {
t.Run(tcase.name, func(t *testing.T) {
q := &mergeGenericQuerier{
queriers: tcase.queriers,
mergeFn: func(l ...Labels) Labels { return l[0] },
}
t.Run("Select", func(t *testing.T) {
res := q.Select(false, nil)
var lbls []labels.Labels
for res.Next() {
lbls = append(lbls, res.At().Labels())
}
testutil.Equals(t, tcase.expectedWarnings[0], res.Warnings())
testutil.Equals(t, tcase.expectedErrs[0], res.Err())
testutil.Assert(t, errors.Is(res.Err(), tcase.expectedErrs[0]), "expected error doesn't match")
testutil.Equals(t, tcase.expectedSelectsSeries, lbls)
for _, qr := range q.queriers {
m := unwrapMockGenericQuerier(t, qr)
exp := []bool{true}
if len(q.queriers) == 1 {
exp[0] = false
}
testutil.Equals(t, exp, m.sortedSeriesRequested)
}
})
t.Run("LabelNames", func(t *testing.T) {
res, w, err := q.LabelNames()
testutil.Equals(t, tcase.expectedWarnings[1], w)
testutil.Assert(t, errors.Is(err, tcase.expectedErrs[1]), "expected error doesn't match")
testutil.Equals(t, tcase.expectedLabels, res)
if err != nil {
return
}
for _, qr := range q.queriers {
m := unwrapMockGenericQuerier(t, qr)
testutil.Equals(t, 1, m.labelNamesCalls)
}
})
t.Run("LabelValues", func(t *testing.T) {
res, w, err := q.LabelValues("test")
testutil.Equals(t, tcase.expectedWarnings[2], w)
testutil.Assert(t, errors.Is(err, tcase.expectedErrs[2]), "expected error doesn't match")
testutil.Equals(t, tcase.expectedLabels, res)
if err != nil {
return
}
for _, qr := range q.queriers {
m := unwrapMockGenericQuerier(t, qr)
testutil.Equals(t, []string{"test"}, m.labelNamesRequested)
}
})
})
}
}

View File

@ -20,13 +20,14 @@ import "github.com/prometheus/prometheus/pkg/labels"
type genericQuerier interface { type genericQuerier interface {
baseQuerier baseQuerier
Select(bool, *SelectHints, ...*labels.Matcher) (genericSeriesSet, Warnings, error) Select(bool, *SelectHints, ...*labels.Matcher) genericSeriesSet
} }
type genericSeriesSet interface { type genericSeriesSet interface {
Next() bool Next() bool
At() Labels At() Labels
Err() error Err() error
Warnings() Warnings
} }
type genericSeriesMergeFunc func(...Labels) Labels type genericSeriesMergeFunc func(...Labels) Labels
@ -55,13 +56,11 @@ type genericQuerierAdapter struct {
cq ChunkQuerier cq ChunkQuerier
} }
func (q *genericQuerierAdapter) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) (genericSeriesSet, Warnings, error) { func (q *genericQuerierAdapter) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) genericSeriesSet {
if q.q != nil { if q.q != nil {
s, w, err := q.q.Select(sortSeries, hints, matchers...) return &genericSeriesSetAdapter{q.q.Select(sortSeries, hints, matchers...)}
return &genericSeriesSetAdapter{s}, w, err
} }
s, w, err := q.cq.Select(sortSeries, hints, matchers...) return &genericChunkSeriesSetAdapter{q.cq.Select(sortSeries, hints, matchers...)}
return &genericChunkSeriesSetAdapter{s}, w, err
} }
func newGenericQuerierFrom(q Querier) genericQuerier { func newGenericQuerierFrom(q Querier) genericQuerier {
@ -84,9 +83,8 @@ func (a *seriesSetAdapter) At() Series {
return a.genericSeriesSet.At().(Series) return a.genericSeriesSet.At().(Series)
} }
func (q *querierAdapter) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) (SeriesSet, Warnings, error) { func (q *querierAdapter) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) SeriesSet {
s, w, err := q.genericQuerier.Select(sortSeries, hints, matchers...) return &seriesSetAdapter{q.genericQuerier.Select(sortSeries, hints, matchers...)}
return &seriesSetAdapter{s}, w, err
} }
type chunkQuerierAdapter struct { type chunkQuerierAdapter struct {
@ -101,9 +99,8 @@ func (a *chunkSeriesSetAdapter) At() ChunkSeries {
return a.genericSeriesSet.At().(ChunkSeries) return a.genericSeriesSet.At().(ChunkSeries)
} }
func (q *chunkQuerierAdapter) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) (ChunkSeriesSet, Warnings, error) { func (q *chunkQuerierAdapter) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) ChunkSeriesSet {
s, w, err := q.genericQuerier.Select(sortSeries, hints, matchers...) return &chunkSeriesSetAdapter{q.genericQuerier.Select(sortSeries, hints, matchers...)}
return &chunkSeriesSetAdapter{s}, w, err
} }
type seriesMergerAdapter struct { type seriesMergerAdapter struct {
@ -129,3 +126,13 @@ func (a *chunkSeriesMergerAdapter) Merge(s ...Labels) Labels {
} }
return a.VerticalChunkSeriesMergerFunc(buf...) return a.VerticalChunkSeriesMergerFunc(buf...)
} }
type noopGenericSeriesSet struct{}
func (noopGenericSeriesSet) Next() bool { return false }
func (noopGenericSeriesSet) At() Labels { return nil }
func (noopGenericSeriesSet) Err() error { return nil }
func (noopGenericSeriesSet) Warnings() Warnings { return nil }

View File

@ -65,7 +65,7 @@ type Querier interface {
// Select returns a set of series that matches the given label matchers. // Select returns a set of series that matches the given label matchers.
// Caller can specify if it requires returned series to be sorted. Prefer not requiring sorting for better performance. // Caller can specify if it requires returned series to be sorted. Prefer not requiring sorting for better performance.
// It allows passing hints that can help in optimising select, but it's up to implementation how this is used if used at all. // It allows passing hints that can help in optimising select, but it's up to implementation how this is used if used at all.
Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) (SeriesSet, Warnings, error) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) SeriesSet
} }
// A ChunkQueryable handles queries against a storage. // A ChunkQueryable handles queries against a storage.
@ -82,7 +82,7 @@ type ChunkQuerier interface {
// Select returns a set of series that matches the given label matchers. // Select returns a set of series that matches the given label matchers.
// Caller can specify if it requires returned series to be sorted. Prefer not requiring sorting for better performance. // Caller can specify if it requires returned series to be sorted. Prefer not requiring sorting for better performance.
// It allows passing hints that can help in optimising select, but it's up to implementation how this is used if used at all. // It allows passing hints that can help in optimising select, but it's up to implementation how this is used if used at all.
Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) (ChunkSeriesSet, Warnings, error) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) ChunkSeriesSet
} }
type baseQuerier interface { type baseQuerier interface {
@ -153,7 +153,12 @@ type Appender interface {
type SeriesSet interface { type SeriesSet interface {
Next() bool Next() bool
At() Series At() Series
// The error that iteration as failed with.
// When an error occurs, set cannot continue to iterate.
Err() error Err() error
// A collection of warnings for the whole set.
// Warnings could be return even iteration has not failed with error.
Warnings() Warnings
} }
var emptySeriesSet = errSeriesSet{} var emptySeriesSet = errSeriesSet{}
@ -164,12 +169,19 @@ func EmptySeriesSet() SeriesSet {
} }
type errSeriesSet struct { type errSeriesSet struct {
ws Warnings
err error err error
} }
func (s errSeriesSet) Next() bool { return false } func (s errSeriesSet) Next() bool { return false }
func (s errSeriesSet) At() Series { return nil } func (s errSeriesSet) At() Series { return nil }
func (s errSeriesSet) Err() error { return s.err } func (s errSeriesSet) Err() error { return s.err }
func (s errSeriesSet) Warnings() Warnings { return s.ws }
// ErrSeriesSet returns a series set that wraps an error.
func ErrSeriesSet(err error) SeriesSet {
return errSeriesSet{err: err}
}
// Series exposes a single time series and allows iterating over samples. // Series exposes a single time series and allows iterating over samples.
type Series interface { type Series interface {
@ -181,7 +193,12 @@ type Series interface {
type ChunkSeriesSet interface { type ChunkSeriesSet interface {
Next() bool Next() bool
At() ChunkSeries At() ChunkSeries
// The error that iteration has failed with.
// When an error occurs, set cannot continue to iterate.
Err() error Err() error
// A collection of warnings for the whole set.
// Warnings could be return even iteration has not failed with error.
Warnings() Warnings
} }
// ChunkSeries exposes a single time series and allows iterating over chunks. // ChunkSeries exposes a single time series and allows iterating over chunks.

67
storage/lazy.go Normal file
View File

@ -0,0 +1,67 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
type lazySeriesSet struct {
create func() (s genericSeriesSet, ok bool)
set genericSeriesSet
}
func (c *lazySeriesSet) Next() bool {
if c.set != nil {
return c.set.Next()
}
var ok bool
c.set, ok = c.create()
return ok
}
func (c *lazySeriesSet) Err() error {
if c.set != nil {
return c.set.Err()
}
return nil
}
func (c *lazySeriesSet) At() Labels {
if c.set != nil {
return c.set.At()
}
return nil
}
func (c *lazySeriesSet) Warnings() Warnings {
if c.set != nil {
return c.set.Warnings()
}
return nil
}
type warningsOnlySeriesSet Warnings
func (warningsOnlySeriesSet) Next() bool { return false }
func (warningsOnlySeriesSet) Err() error { return nil }
func (warningsOnlySeriesSet) At() Labels { return nil }
func (c warningsOnlySeriesSet) Warnings() Warnings { return Warnings(c) }
type errorOnlySeriesSet struct {
err error
}
func (errorOnlySeriesSet) Next() bool { return false }
func (errorOnlySeriesSet) At() Labels { return nil }
func (s errorOnlySeriesSet) Err() error { return s.err }
func (errorOnlySeriesSet) Warnings() Warnings { return nil }

View File

@ -24,8 +24,8 @@ func NoopQuerier() Querier {
return noopQuerier{} return noopQuerier{}
} }
func (noopQuerier) Select(bool, *SelectHints, ...*labels.Matcher) (SeriesSet, Warnings, error) { func (noopQuerier) Select(bool, *SelectHints, ...*labels.Matcher) SeriesSet {
return NoopSeriesSet(), nil, nil return NoopSeriesSet()
} }
func (noopQuerier) LabelValues(string) ([]string, Warnings, error) { func (noopQuerier) LabelValues(string) ([]string, Warnings, error) {
@ -47,8 +47,8 @@ func NoopChunkedQuerier() ChunkQuerier {
return noopChunkQuerier{} return noopChunkQuerier{}
} }
func (noopChunkQuerier) Select(bool, *SelectHints, ...*labels.Matcher) (ChunkSeriesSet, Warnings, error) { func (noopChunkQuerier) Select(bool, *SelectHints, ...*labels.Matcher) ChunkSeriesSet {
return NoopChunkedSeriesSet(), nil, nil return NoopChunkedSeriesSet()
} }
func (noopChunkQuerier) LabelValues(string) ([]string, Warnings, error) { func (noopChunkQuerier) LabelValues(string) ([]string, Warnings, error) {
@ -76,6 +76,8 @@ func (noopSeriesSet) At() Series { return nil }
func (noopSeriesSet) Err() error { return nil } func (noopSeriesSet) Err() error { return nil }
func (noopSeriesSet) Warnings() Warnings { return nil }
type noopChunkedSeriesSet struct{} type noopChunkedSeriesSet struct{}
// NoopChunkedSeriesSet is a ChunkSeriesSet that does nothing. // NoopChunkedSeriesSet is a ChunkSeriesSet that does nothing.
@ -88,3 +90,5 @@ func (noopChunkedSeriesSet) Next() bool { return false }
func (noopChunkedSeriesSet) At() ChunkSeries { return nil } func (noopChunkedSeriesSet) At() ChunkSeries { return nil }
func (noopChunkedSeriesSet) Err() error { return nil } func (noopChunkedSeriesSet) Err() error { return nil }
func (noopChunkedSeriesSet) Warnings() Warnings { return nil }

View File

@ -107,7 +107,7 @@ func ToQuery(from, to int64, matchers []*labels.Matcher, hints *storage.SelectHi
} }
// ToQueryResult builds a QueryResult proto. // ToQueryResult builds a QueryResult proto.
func ToQueryResult(ss storage.SeriesSet, sampleLimit int) (*prompb.QueryResult, error) { func ToQueryResult(ss storage.SeriesSet, sampleLimit int) (*prompb.QueryResult, storage.Warnings, error) {
numSamples := 0 numSamples := 0
resp := &prompb.QueryResult{} resp := &prompb.QueryResult{}
for ss.Next() { for ss.Next() {
@ -118,7 +118,7 @@ func ToQueryResult(ss storage.SeriesSet, sampleLimit int) (*prompb.QueryResult,
for iter.Next() { for iter.Next() {
numSamples++ numSamples++
if sampleLimit > 0 && numSamples > sampleLimit { if sampleLimit > 0 && numSamples > sampleLimit {
return nil, HTTPError{ return nil, ss.Warnings(), HTTPError{
msg: fmt.Sprintf("exceeded sample limit (%d)", sampleLimit), msg: fmt.Sprintf("exceeded sample limit (%d)", sampleLimit),
status: http.StatusBadRequest, status: http.StatusBadRequest,
} }
@ -130,7 +130,7 @@ func ToQueryResult(ss storage.SeriesSet, sampleLimit int) (*prompb.QueryResult,
}) })
} }
if err := iter.Err(); err != nil { if err := iter.Err(); err != nil {
return nil, err return nil, ss.Warnings(), err
} }
resp.Timeseries = append(resp.Timeseries, &prompb.TimeSeries{ resp.Timeseries = append(resp.Timeseries, &prompb.TimeSeries{
@ -139,9 +139,9 @@ func ToQueryResult(ss storage.SeriesSet, sampleLimit int) (*prompb.QueryResult,
}) })
} }
if err := ss.Err(); err != nil { if err := ss.Err(); err != nil {
return nil, err return nil, ss.Warnings(), err
} }
return resp, nil return resp, ss.Warnings(), nil
} }
// FromQueryResult unpacks and sorts a QueryResult proto. // FromQueryResult unpacks and sorts a QueryResult proto.
@ -195,7 +195,7 @@ func StreamChunkedReadResponses(
ss storage.SeriesSet, ss storage.SeriesSet,
sortedExternalLabels []prompb.Label, sortedExternalLabels []prompb.Label,
maxBytesInFrame int, maxBytesInFrame int,
) error { ) (storage.Warnings, error) {
var ( var (
chks []prompb.Chunk chks []prompb.Chunk
lbls []prompb.Label lbls []prompb.Label
@ -218,7 +218,7 @@ func StreamChunkedReadResponses(
// TODO(bwplotka): Use ChunkIterator once available in TSDB instead of re-encoding: https://github.com/prometheus/prometheus/pull/5882 // TODO(bwplotka): Use ChunkIterator once available in TSDB instead of re-encoding: https://github.com/prometheus/prometheus/pull/5882
chks, err = encodeChunks(iter, chks, maxBytesInFrame-lblsSize) chks, err = encodeChunks(iter, chks, maxBytesInFrame-lblsSize)
if err != nil { if err != nil {
return err return ss.Warnings(), err
} }
if len(chks) == 0 { if len(chks) == 0 {
@ -234,25 +234,25 @@ func StreamChunkedReadResponses(
QueryIndex: queryIndex, QueryIndex: queryIndex,
}) })
if err != nil { if err != nil {
return errors.Wrap(err, "marshal ChunkedReadResponse") return ss.Warnings(), errors.Wrap(err, "marshal ChunkedReadResponse")
} }
if _, err := stream.Write(b); err != nil { if _, err := stream.Write(b); err != nil {
return errors.Wrap(err, "write to stream") return ss.Warnings(), errors.Wrap(err, "write to stream")
} }
chks = chks[:0] chks = chks[:0]
} }
if err := iter.Err(); err != nil { if err := iter.Err(); err != nil {
return err return ss.Warnings(), err
} }
} }
if err := ss.Err(); err != nil { if err := ss.Err(); err != nil {
return err return ss.Warnings(), err
} }
return nil return ss.Warnings(), nil
} }
// encodeChunks expects iterator to be ready to use (aka iter.Next() called before invoking). // encodeChunks expects iterator to be ready to use (aka iter.Next() called before invoking).
@ -365,6 +365,8 @@ func (e errSeriesSet) Err() error {
return e.err return e.err
} }
func (e errSeriesSet) Warnings() storage.Warnings { return nil }
// concreteSeriesSet implements storage.SeriesSet. // concreteSeriesSet implements storage.SeriesSet.
type concreteSeriesSet struct { type concreteSeriesSet struct {
cur int cur int
@ -384,6 +386,8 @@ func (c *concreteSeriesSet) Err() error {
return nil return nil
} }
func (c *concreteSeriesSet) Warnings() storage.Warnings { return nil }
// concreteSeries implements storage.Series. // concreteSeries implements storage.Series.
type concreteSeries struct { type concreteSeries struct {
labels labels.Labels labels labels.Labels

View File

@ -115,7 +115,7 @@ func newQueueManagerMetrics(r prometheus.Registerer, rn, e string) *queueManager
Subsystem: subsystem, Subsystem: subsystem,
Name: "sent_batch_duration_seconds", Name: "sent_batch_duration_seconds",
Help: "Duration of sample batch send calls to the remote storage.", Help: "Duration of sample batch send calls to the remote storage.",
Buckets: prometheus.DefBuckets, Buckets: append(prometheus.DefBuckets, 25, 60, 120, 300),
ConstLabels: constLabels, ConstLabels: constLabels,
}) })
m.highestSentTimestamp = &maxGauge{ m.highestSentTimestamp = &maxGauge{

View File

@ -71,10 +71,10 @@ type querier struct {
} }
// Select implements storage.Querier and uses the given matchers to read series sets from the Client. // Select implements storage.Querier and uses the given matchers to read series sets from the Client.
func (q *querier) Select(sortSeries bool, hints *storage.SelectHints, matchers ...*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (q *querier) Select(sortSeries bool, hints *storage.SelectHints, matchers ...*labels.Matcher) storage.SeriesSet {
query, err := ToQuery(q.mint, q.maxt, matchers, hints) query, err := ToQuery(q.mint, q.maxt, matchers, hints)
if err != nil { if err != nil {
return nil, nil, err return storage.ErrSeriesSet(err)
} }
remoteReadGauge := remoteReadQueries.WithLabelValues(q.client.remoteName, q.client.url.String()) remoteReadGauge := remoteReadQueries.WithLabelValues(q.client.remoteName, q.client.url.String())
@ -86,10 +86,10 @@ func (q *querier) Select(sortSeries bool, hints *storage.SelectHints, matchers .
res, err := q.client.Read(q.ctx, query) res, err := q.client.Read(q.ctx, query)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("remote_read: %v", err) return storage.ErrSeriesSet(fmt.Errorf("remote_read: %v", err))
} }
return FromQueryResult(sortSeries, res), nil, nil return FromQueryResult(sortSeries, res)
} }
// LabelValues implements storage.Querier and is a noop. // LabelValues implements storage.Querier and is a noop.
@ -132,13 +132,9 @@ type externalLabelsQuerier struct {
// Select adds equality matchers for all external labels to the list of matchers // Select adds equality matchers for all external labels to the list of matchers
// before calling the wrapped storage.Queryable. The added external labels are // before calling the wrapped storage.Queryable. The added external labels are
// removed from the returned series sets. // removed from the returned series sets.
func (q externalLabelsQuerier) Select(sortSeries bool, hints *storage.SelectHints, matchers ...*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (q externalLabelsQuerier) Select(sortSeries bool, hints *storage.SelectHints, matchers ...*labels.Matcher) storage.SeriesSet {
m, added := q.addExternalLabels(matchers) m, added := q.addExternalLabels(matchers)
s, warnings, err := q.Querier.Select(sortSeries, hints, m...) return newSeriesSetFilter(q.Querier.Select(sortSeries, hints, m...), added)
if err != nil {
return nil, warnings, err
}
return newSeriesSetFilter(s, added), warnings, nil
} }
// PreferLocalStorageFilter returns a QueryableFunc which creates a NoopQuerier // PreferLocalStorageFilter returns a QueryableFunc which creates a NoopQuerier
@ -185,7 +181,7 @@ type requiredMatchersQuerier struct {
// Select returns a NoopSeriesSet if the given matchers don't match the label // Select returns a NoopSeriesSet if the given matchers don't match the label
// set of the requiredMatchersQuerier. Otherwise it'll call the wrapped querier. // set of the requiredMatchersQuerier. Otherwise it'll call the wrapped querier.
func (q requiredMatchersQuerier) Select(sortSeries bool, hints *storage.SelectHints, matchers ...*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (q requiredMatchersQuerier) Select(sortSeries bool, hints *storage.SelectHints, matchers ...*labels.Matcher) storage.SeriesSet {
ms := q.requiredMatchers ms := q.requiredMatchers
for _, m := range matchers { for _, m := range matchers {
for i, r := range ms { for i, r := range ms {
@ -199,7 +195,7 @@ func (q requiredMatchersQuerier) Select(sortSeries bool, hints *storage.SelectHi
} }
} }
if len(ms) > 0 { if len(ms) > 0 {
return storage.NoopSeriesSet(), nil, nil return storage.NoopSeriesSet()
} }
return q.Querier.Select(sortSeries, hints, matchers...) return q.Querier.Select(sortSeries, hints, matchers...)
} }

View File

@ -117,10 +117,8 @@ func TestExternalLabelsQuerierSelect(t *testing.T) {
}, },
} }
want := newSeriesSetFilter(mockSeriesSet{}, q.externalLabels) want := newSeriesSetFilter(mockSeriesSet{}, q.externalLabels)
have, _, err := q.Select(false, nil, matchers...) have := q.Select(false, nil, matchers...)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(want, have) { if !reflect.DeepEqual(want, have) {
t.Errorf("expected series set %+v, got %+v", want, have) t.Errorf("expected series set %+v, got %+v", want, have)
} }
@ -218,16 +216,12 @@ func TestSeriesSetFilter(t *testing.T) {
}, },
} }
for i, tc := range tests { for _, tc := range tests {
filtered := newSeriesSetFilter(FromQueryResult(true, tc.in), tc.toRemove) filtered := newSeriesSetFilter(FromQueryResult(true, tc.in), tc.toRemove)
have, err := ToQueryResult(filtered, 1e6) act, ws, err := ToQueryResult(filtered, 1e6)
if err != nil { testutil.Ok(t, err)
t.Fatal(err) testutil.Equals(t, 0, len(ws))
} testutil.Equals(t, tc.expected, act)
if !reflect.DeepEqual(have, tc.expected) {
t.Fatalf("%d. unexpected labels; want %v, got %v", i, tc.expected, have)
}
} }
} }
@ -242,8 +236,8 @@ type mockSeriesSet struct {
storage.SeriesSet storage.SeriesSet
} }
func (mockQuerier) Select(bool, *storage.SelectHints, ...*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (mockQuerier) Select(bool, *storage.SelectHints, ...*labels.Matcher) storage.SeriesSet {
return mockSeriesSet{}, nil, nil return mockSeriesSet{}
} }
func TestPreferLocalStorageFilter(t *testing.T) { func TestPreferLocalStorageFilter(t *testing.T) {
@ -398,10 +392,8 @@ func TestRequiredLabelsQuerierSelect(t *testing.T) {
requiredMatchers: test.requiredMatchers, requiredMatchers: test.requiredMatchers,
} }
have, _, err := q.Select(false, nil, test.matchers...) have := q.Select(false, nil, test.matchers...)
if err != nil {
t.Error(err)
}
if want := test.seriesSet; want != have { if want := test.seriesSet; want != have {
t.Errorf("%d. expected series set %+v, got %+v", i, want, have) t.Errorf("%d. expected series set %+v, got %+v", i, want, have)
} }

View File

@ -132,6 +132,9 @@ func (s *Storage) StartTime() (int64, error) {
// Querier returns a storage.MergeQuerier combining the remote client queriers // Querier returns a storage.MergeQuerier combining the remote client queriers
// of each configured remote read endpoint. // of each configured remote read endpoint.
// Returned querier will never return error as all queryables are assumed best effort.
// Additionally all returned queriers ensure that its Select's SeriesSets have ready data after first `Next` invoke.
// This is because Prometheus (fanout and secondary queries) can't handle the stream failing half way through by design.
func (s *Storage) Querier(ctx context.Context, mint, maxt int64) (storage.Querier, error) { func (s *Storage) Querier(ctx context.Context, mint, maxt int64) (storage.Querier, error) {
s.mtx.Lock() s.mtx.Lock()
queryables := s.queryables queryables := s.queryables

112
storage/secondary.go Normal file
View File

@ -0,0 +1,112 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"sync"
"github.com/prometheus/prometheus/pkg/labels"
)
// secondaryQuerier is a wrapper that allows a querier to be treated in a best effort manner.
// This means that an error on any method returned by Querier except Close will be returned as a warning,
// and the result will be empty.
//
// Additionally, Querier ensures that if ANY SeriesSet returned by this querier's Select failed on an initial Next,
// All other SeriesSet will be return no response as well. This ensures consistent partial response strategy, where you
// have either full results or none from each secondary Querier.
// NOTE: This works well only for implementations that only fail during first Next() (e.g fetch from network). If implementation fails
// during further iterations, set will panic. If Select is invoked after first Next of any returned SeriesSet, querier will panic.
//
// Not go-routine safe.
// NOTE: Prometheus treats all remote storages as secondary / best effort.
type secondaryQuerier struct {
genericQuerier
once sync.Once
done bool
asyncSets []genericSeriesSet
}
func newSecondaryQuerierFrom(q Querier) genericQuerier {
return &secondaryQuerier{genericQuerier: newGenericQuerierFrom(q)}
}
func newSecondaryQuerierFromChunk(cq ChunkQuerier) genericQuerier {
return &secondaryQuerier{genericQuerier: newGenericQuerierFromChunk(cq)}
}
func (s *secondaryQuerier) LabelValues(name string) ([]string, Warnings, error) {
vals, w, err := s.genericQuerier.LabelValues(name)
if err != nil {
return nil, append([]error{err}, w...), nil
}
return vals, w, nil
}
func (s *secondaryQuerier) LabelNames() ([]string, Warnings, error) {
names, w, err := s.genericQuerier.LabelNames()
if err != nil {
return nil, append([]error{err}, w...), nil
}
return names, w, nil
}
func (s *secondaryQuerier) createFn(asyncSet genericSeriesSet) func() (genericSeriesSet, bool) {
s.asyncSets = append(s.asyncSets, asyncSet)
curr := len(s.asyncSets) - 1
return func() (genericSeriesSet, bool) {
s.once.Do(func() {
// At first create invocation we iterate over all sets and ensure its Next() returns some value without
// errors. This is to ensure we support consistent partial failures.
for i, set := range s.asyncSets {
if set.Next() {
continue
}
ws := set.Warnings()
// Failed set.
if err := set.Err(); err != nil {
ws = append([]error{err}, ws...)
// Promote the warnings to the current one.
s.asyncSets[curr] = warningsOnlySeriesSet(ws)
// One of the sets failed, ensure rest of the sets returns nothing. (All or nothing logic).
for i := range s.asyncSets {
if curr != i {
s.asyncSets[i] = noopGenericSeriesSet{}
}
}
break
}
// Exhausted set.
s.asyncSets[i] = warningsOnlySeriesSet(ws)
}
s.done = true
})
switch s.asyncSets[curr].(type) {
case warningsOnlySeriesSet, noopGenericSeriesSet:
return s.asyncSets[curr], false
default:
return s.asyncSets[curr], true
}
}
}
func (s *secondaryQuerier) Select(sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) genericSeriesSet {
if s.done {
panic("secondaryQuerier: Select invoked after first Next of any returned SeriesSet was done")
}
return &lazySeriesSet{create: s.createFn(s.genericQuerier.Select(sortSeries, hints, matchers...))}
}

View File

@ -203,9 +203,7 @@ func TestCorruptedChunk(t *testing.T) {
querier, err := NewBlockQuerier(b, 0, 1) querier, err := NewBlockQuerier(b, 0, 1)
testutil.Ok(t, err) testutil.Ok(t, err)
defer func() { testutil.Ok(t, querier.Close()) }() defer func() { testutil.Ok(t, querier.Close()) }()
set, ws, err := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b")) set := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
// Check query err. // Check query err.
testutil.Equals(t, false, set.Next()) testutil.Equals(t, false, set.Next())

View File

@ -617,18 +617,7 @@ func dumpSamples(db *tsdb.DBReadOnly, mint, maxt int64) (err error) {
err = merr.Err() err = merr.Err()
}() }()
ss, ws, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "", ".*")) ss := q.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "", ".*"))
if err != nil {
return err
}
if len(ws) > 0 {
var merr tsdb_errors.MultiError
for _, w := range ws {
merr.Add(w)
}
return merr.Err()
}
for ss.Next() { for ss.Next() {
series := ss.At() series := ss.At()
@ -643,6 +632,14 @@ func dumpSamples(db *tsdb.DBReadOnly, mint, maxt int64) (err error) {
} }
} }
if ws := ss.Warnings(); len(ws) > 0 {
var merr tsdb_errors.MultiError
for _, w := range ws {
merr.Add(w)
}
return merr.Err()
}
if ss.Err() != nil { if ss.Err() != nil {
return ss.Err() return ss.Err()
} }

View File

@ -67,15 +67,12 @@ func openTestDB(t testing.TB, opts *Options, rngs []int64) (db *DB, close func()
// query runs a matcher query against the querier and fully expands its data. // query runs a matcher query against the querier and fully expands its data.
func query(t testing.TB, q storage.Querier, matchers ...*labels.Matcher) map[string][]tsdbutil.Sample { func query(t testing.TB, q storage.Querier, matchers ...*labels.Matcher) map[string][]tsdbutil.Sample {
ss, ws, err := q.Select(false, nil, matchers...) ss := q.Select(false, nil, matchers...)
defer func() { defer func() {
testutil.Ok(t, q.Close()) testutil.Ok(t, q.Close())
}() }()
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
result := map[string][]tsdbutil.Sample{} result := map[string][]tsdbutil.Sample{}
for ss.Next() { for ss.Next() {
series := ss.At() series := ss.At()
@ -95,6 +92,7 @@ func query(t testing.TB, q storage.Querier, matchers ...*labels.Matcher) map[str
result[name] = samples result[name] = samples
} }
testutil.Ok(t, ss.Err()) testutil.Ok(t, ss.Err())
testutil.Equals(t, 0, len(ss.Warnings()))
return result return result
} }
@ -315,9 +313,7 @@ Outer:
q, err := db.Querier(context.TODO(), 0, numSamples) q, err := db.Querier(context.TODO(), 0, numSamples)
testutil.Ok(t, err) testutil.Ok(t, err)
res, ws, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b")) res := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
expSamples := make([]tsdbutil.Sample, 0, len(c.remaint)) expSamples := make([]tsdbutil.Sample, 0, len(c.remaint))
for _, ts := range c.remaint { for _, ts := range c.remaint {
@ -333,6 +329,7 @@ Outer:
testutil.Equals(t, eok, rok) testutil.Equals(t, eok, rok)
if !eok { if !eok {
testutil.Equals(t, 0, len(res.Warnings()))
continue Outer continue Outer
} }
sexp := expss.At() sexp := expss.At()
@ -491,10 +488,7 @@ func TestDB_Snapshot(t *testing.T) {
defer func() { testutil.Ok(t, querier.Close()) }() defer func() { testutil.Ok(t, querier.Close()) }()
// sum values // sum values
seriesSet, ws, err := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")) seriesSet := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
sum := 0.0 sum := 0.0
for seriesSet.Next() { for seriesSet.Next() {
series := seriesSet.At().Iterator() series := seriesSet.At().Iterator()
@ -505,6 +499,7 @@ func TestDB_Snapshot(t *testing.T) {
testutil.Ok(t, series.Err()) testutil.Ok(t, series.Err())
} }
testutil.Ok(t, seriesSet.Err()) testutil.Ok(t, seriesSet.Err())
testutil.Equals(t, 0, len(seriesSet.Warnings()))
testutil.Equals(t, 1000.0, sum) testutil.Equals(t, 1000.0, sum)
} }
@ -546,10 +541,7 @@ func TestDB_Snapshot_ChunksOutsideOfCompactedRange(t *testing.T) {
defer func() { testutil.Ok(t, querier.Close()) }() defer func() { testutil.Ok(t, querier.Close()) }()
// Sum values. // Sum values.
seriesSet, ws, err := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")) seriesSet := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
sum := 0.0 sum := 0.0
for seriesSet.Next() { for seriesSet.Next() {
series := seriesSet.At().Iterator() series := seriesSet.At().Iterator()
@ -560,6 +552,7 @@ func TestDB_Snapshot_ChunksOutsideOfCompactedRange(t *testing.T) {
testutil.Ok(t, series.Err()) testutil.Ok(t, series.Err())
} }
testutil.Ok(t, seriesSet.Err()) testutil.Ok(t, seriesSet.Err())
testutil.Equals(t, 0, len(seriesSet.Warnings()))
// Since we snapshotted with MaxTime - 10, so expect 10 less samples. // Since we snapshotted with MaxTime - 10, so expect 10 less samples.
testutil.Equals(t, 1000.0-10, sum) testutil.Equals(t, 1000.0-10, sum)
@ -618,9 +611,7 @@ Outer:
testutil.Ok(t, err) testutil.Ok(t, err)
defer func() { testutil.Ok(t, q.Close()) }() defer func() { testutil.Ok(t, q.Close()) }()
res, ws, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b")) res := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
expSamples := make([]tsdbutil.Sample, 0, len(c.remaint)) expSamples := make([]tsdbutil.Sample, 0, len(c.remaint))
for _, ts := range c.remaint { for _, ts := range c.remaint {
@ -641,6 +632,7 @@ Outer:
testutil.Equals(t, eok, rok) testutil.Equals(t, eok, rok)
if !eok { if !eok {
testutil.Equals(t, 0, len(res.Warnings()))
continue Outer continue Outer
} }
sexp := expss.At() sexp := expss.At()
@ -792,10 +784,7 @@ func TestDB_e2e(t *testing.T) {
q, err := db.Querier(context.TODO(), mint, maxt) q, err := db.Querier(context.TODO(), mint, maxt)
testutil.Ok(t, err) testutil.Ok(t, err)
ss, ws, err := q.Select(false, nil, qry.ms...) ss := q.Select(false, nil, qry.ms...)
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
result := map[string][]tsdbutil.Sample{} result := map[string][]tsdbutil.Sample{}
for ss.Next() { for ss.Next() {
@ -810,6 +799,7 @@ func TestDB_e2e(t *testing.T) {
} }
testutil.Ok(t, ss.Err()) testutil.Ok(t, ss.Err())
testutil.Equals(t, 0, len(ss.Warnings()))
testutil.Equals(t, expected, result) testutil.Equals(t, expected, result)
q.Close() q.Close()
@ -968,9 +958,7 @@ func TestTombstoneClean(t *testing.T) {
testutil.Ok(t, err) testutil.Ok(t, err)
defer q.Close() defer q.Close()
res, ws, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b")) res := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
expSamples := make([]tsdbutil.Sample, 0, len(c.remaint)) expSamples := make([]tsdbutil.Sample, 0, len(c.remaint))
for _, ts := range c.remaint { for _, ts := range c.remaint {
@ -1004,6 +992,7 @@ func TestTombstoneClean(t *testing.T) {
testutil.Equals(t, errExp, errRes) testutil.Equals(t, errExp, errRes)
testutil.Equals(t, smplExp, smplRes) testutil.Equals(t, smplExp, smplRes)
} }
testutil.Equals(t, 0, len(res.Warnings()))
for _, b := range db.Blocks() { for _, b := range db.Blocks() {
testutil.Equals(t, tombstones.NewMemTombstones(), b.tombstones) testutil.Equals(t, tombstones.NewMemTombstones(), b.tombstones)
@ -1306,19 +1295,17 @@ func TestNotMatcherSelectsLabelsUnsetSeries(t *testing.T) {
defer func() { testutil.Ok(t, q.Close()) }() defer func() { testutil.Ok(t, q.Close()) }()
for _, c := range cases { for _, c := range cases {
ss, ws, err := q.Select(false, nil, c.selector...) ss := q.Select(false, nil, c.selector...)
lres, _, ws, err := expandSeriesSet(ss)
testutil.Ok(t, err) testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws)) testutil.Equals(t, 0, len(ws))
lres, _, err := expandSeriesSet(ss)
testutil.Ok(t, err)
testutil.Equals(t, c.series, lres) testutil.Equals(t, c.series, lres)
} }
} }
// expandSeriesSet returns the raw labels in the order they are retrieved from // expandSeriesSet returns the raw labels in the order they are retrieved from
// the series set and the samples keyed by Labels().String(). // the series set and the samples keyed by Labels().String().
func expandSeriesSet(ss storage.SeriesSet) ([]labels.Labels, map[string][]sample, error) { func expandSeriesSet(ss storage.SeriesSet) ([]labels.Labels, map[string][]sample, storage.Warnings, error) {
resultLabels := []labels.Labels{} resultLabels := []labels.Labels{}
resultSamples := map[string][]sample{} resultSamples := map[string][]sample{}
for ss.Next() { for ss.Next() {
@ -1332,7 +1319,7 @@ func expandSeriesSet(ss storage.SeriesSet) ([]labels.Labels, map[string][]sample
resultLabels = append(resultLabels, series.Labels()) resultLabels = append(resultLabels, series.Labels())
resultSamples[series.Labels().String()] = samples resultSamples[series.Labels().String()] = samples
} }
return resultLabels, resultSamples, ss.Err() return resultLabels, resultSamples, ss.Warnings(), ss.Err()
} }
func TestOverlappingBlocksDetectsAllOverlaps(t *testing.T) { func TestOverlappingBlocksDetectsAllOverlaps(t *testing.T) {
@ -2503,9 +2490,7 @@ func TestDBReadOnly_FlushWAL(t *testing.T) {
defer func() { testutil.Ok(t, querier.Close()) }() defer func() { testutil.Ok(t, querier.Close()) }()
// Sum the values. // Sum the values.
seriesSet, ws, err := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, defaultLabelName, "flush")) seriesSet := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, defaultLabelName, "flush"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
sum := 0.0 sum := 0.0
for seriesSet.Next() { for seriesSet.Next() {
@ -2517,6 +2502,7 @@ func TestDBReadOnly_FlushWAL(t *testing.T) {
testutil.Ok(t, series.Err()) testutil.Ok(t, series.Err())
} }
testutil.Ok(t, seriesSet.Err()) testutil.Ok(t, seriesSet.Err())
testutil.Equals(t, 0, len(seriesSet.Warnings()))
testutil.Equals(t, 1000.0, sum) testutil.Equals(t, 1000.0, sum)
} }
@ -2568,11 +2554,11 @@ func TestDBCannotSeePartialCommits(t *testing.T) {
testutil.Ok(t, err) testutil.Ok(t, err)
defer querier.Close() defer querier.Close()
ss, _, err := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")) ss := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"))
_, seriesSet, ws, err := expandSeriesSet(ss)
testutil.Ok(t, err) testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
_, seriesSet, err := expandSeriesSet(ss)
testutil.Ok(t, err)
values := map[float64]struct{}{} values := map[float64]struct{}{}
for _, series := range seriesSet { for _, series := range seriesSet {
values[series[len(series)-1].v] = struct{}{} values[series[len(series)-1].v] = struct{}{}
@ -2608,16 +2594,16 @@ func TestDBQueryDoesntSeeAppendsAfterCreation(t *testing.T) {
defer querierAfterAddButBeforeCommit.Close() defer querierAfterAddButBeforeCommit.Close()
// None of the queriers should return anything after the Add but before the commit. // None of the queriers should return anything after the Add but before the commit.
ss, _, err := querierBeforeAdd.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")) ss := querierBeforeAdd.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"))
testutil.Ok(t, err) _, seriesSet, ws, err := expandSeriesSet(ss)
_, seriesSet, err := expandSeriesSet(ss)
testutil.Ok(t, err) testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
testutil.Equals(t, map[string][]sample{}, seriesSet) testutil.Equals(t, map[string][]sample{}, seriesSet)
ss, _, err = querierAfterAddButBeforeCommit.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")) ss = querierAfterAddButBeforeCommit.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"))
testutil.Ok(t, err) _, seriesSet, ws, err = expandSeriesSet(ss)
_, seriesSet, err = expandSeriesSet(ss)
testutil.Ok(t, err) testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
testutil.Equals(t, map[string][]sample{}, seriesSet) testutil.Equals(t, map[string][]sample{}, seriesSet)
// This commit is after the queriers are created, so should not be returned. // This commit is after the queriers are created, so should not be returned.
@ -2625,17 +2611,17 @@ func TestDBQueryDoesntSeeAppendsAfterCreation(t *testing.T) {
testutil.Ok(t, err) testutil.Ok(t, err)
// Nothing returned for querier created before the Add. // Nothing returned for querier created before the Add.
ss, _, err = querierBeforeAdd.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")) ss = querierBeforeAdd.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"))
testutil.Ok(t, err) _, seriesSet, ws, err = expandSeriesSet(ss)
_, seriesSet, err = expandSeriesSet(ss)
testutil.Ok(t, err) testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
testutil.Equals(t, map[string][]sample{}, seriesSet) testutil.Equals(t, map[string][]sample{}, seriesSet)
// Series exists but has no samples for querier created after Add. // Series exists but has no samples for querier created after Add.
ss, _, err = querierAfterAddButBeforeCommit.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")) ss = querierAfterAddButBeforeCommit.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"))
testutil.Ok(t, err) _, seriesSet, ws, err = expandSeriesSet(ss)
_, seriesSet, err = expandSeriesSet(ss)
testutil.Ok(t, err) testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
testutil.Equals(t, map[string][]sample{`{foo="bar"}`: {}}, seriesSet) testutil.Equals(t, map[string][]sample{`{foo="bar"}`: {}}, seriesSet)
querierAfterCommit, err := db.Querier(context.Background(), 0, 1000000) querierAfterCommit, err := db.Querier(context.Background(), 0, 1000000)
@ -2643,10 +2629,10 @@ func TestDBQueryDoesntSeeAppendsAfterCreation(t *testing.T) {
defer querierAfterCommit.Close() defer querierAfterCommit.Close()
// Samples are returned for querier created after Commit. // Samples are returned for querier created after Commit.
ss, _, err = querierAfterCommit.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")) ss = querierAfterCommit.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"))
testutil.Ok(t, err) _, seriesSet, ws, err = expandSeriesSet(ss)
_, seriesSet, err = expandSeriesSet(ss)
testutil.Ok(t, err) testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
testutil.Equals(t, map[string][]sample{`{foo="bar"}`: {{t: 0, v: 0}}}, seriesSet) testutil.Equals(t, map[string][]sample{`{foo="bar"}`: {{t: 0, v: 0}}}, seriesSet)
} }

View File

@ -562,9 +562,7 @@ func TestHeadDeleteSimple(t *testing.T) {
for _, h := range []*Head{head, reloadedHead} { for _, h := range []*Head{head, reloadedHead} {
q, err := NewBlockQuerier(h, h.MinTime(), h.MaxTime()) q, err := NewBlockQuerier(h, h.MinTime(), h.MaxTime())
testutil.Ok(t, err) testutil.Ok(t, err)
actSeriesSet, ws, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, lblDefault.Name, lblDefault.Value)) actSeriesSet := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, lblDefault.Name, lblDefault.Value))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
testutil.Ok(t, q.Close()) testutil.Ok(t, q.Close())
expSeriesSet := newMockSeriesSet([]storage.Series{ expSeriesSet := newMockSeriesSet([]storage.Series{
newSeries(map[string]string{lblDefault.Name: lblDefault.Value}, func() []tsdbutil.Sample { newSeries(map[string]string{lblDefault.Name: lblDefault.Value}, func() []tsdbutil.Sample {
@ -583,6 +581,8 @@ func TestHeadDeleteSimple(t *testing.T) {
if !eok { if !eok {
testutil.Ok(t, h.Close()) testutil.Ok(t, h.Close())
testutil.Ok(t, actSeriesSet.Err())
testutil.Equals(t, 0, len(actSeriesSet.Warnings()))
continue Outer continue Outer
} }
expSeries := expSeriesSet.At() expSeries := expSeriesSet.At()
@ -623,13 +623,15 @@ func TestDeleteUntilCurMax(t *testing.T) {
// Test the series returns no samples. The series is cleared only after compaction. // Test the series returns no samples. The series is cleared only after compaction.
q, err := NewBlockQuerier(hb, 0, 100000) q, err := NewBlockQuerier(hb, 0, 100000)
testutil.Ok(t, err) testutil.Ok(t, err)
res, ws, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b")) res := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
testutil.Assert(t, res.Next(), "series is not present") testutil.Assert(t, res.Next(), "series is not present")
s := res.At() s := res.At()
it := s.Iterator() it := s.Iterator()
testutil.Assert(t, !it.Next(), "expected no samples") testutil.Assert(t, !it.Next(), "expected no samples")
for res.Next() {
}
testutil.Ok(t, res.Err())
testutil.Equals(t, 0, len(res.Warnings()))
// Add again and test for presence. // Add again and test for presence.
app = hb.Appender() app = hb.Appender()
@ -638,15 +640,17 @@ func TestDeleteUntilCurMax(t *testing.T) {
testutil.Ok(t, app.Commit()) testutil.Ok(t, app.Commit())
q, err = NewBlockQuerier(hb, 0, 100000) q, err = NewBlockQuerier(hb, 0, 100000)
testutil.Ok(t, err) testutil.Ok(t, err)
res, ws, err = q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b")) res = q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "b"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
testutil.Assert(t, res.Next(), "series don't exist") testutil.Assert(t, res.Next(), "series don't exist")
exps := res.At() exps := res.At()
it = exps.Iterator() it = exps.Iterator()
resSamples, err := expandSeriesIterator(it) resSamples, err := expandSeriesIterator(it)
testutil.Ok(t, err) testutil.Ok(t, err)
testutil.Equals(t, []tsdbutil.Sample{sample{11, 1}}, resSamples) testutil.Equals(t, []tsdbutil.Sample{sample{11, 1}}, resSamples)
for res.Next() {
}
testutil.Ok(t, res.Err())
testutil.Equals(t, 0, len(res.Warnings()))
} }
func TestDeletedSamplesAndSeriesStillInWALAfterCheckpoint(t *testing.T) { func TestDeletedSamplesAndSeriesStillInWALAfterCheckpoint(t *testing.T) {
@ -807,9 +811,7 @@ func TestDelete_e2e(t *testing.T) {
q, err := NewBlockQuerier(hb, 0, 100000) q, err := NewBlockQuerier(hb, 0, 100000)
testutil.Ok(t, err) testutil.Ok(t, err)
defer q.Close() defer q.Close()
ss, ws, err := q.Select(true, nil, del.ms...) ss := q.Select(true, nil, del.ms...)
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
// Build the mockSeriesSet. // Build the mockSeriesSet.
matchedSeries := make([]storage.Series, 0, len(matched)) matchedSeries := make([]storage.Series, 0, len(matched))
for _, m := range matched { for _, m := range matched {
@ -850,6 +852,8 @@ func TestDelete_e2e(t *testing.T) {
testutil.Equals(t, errExp, errRes) testutil.Equals(t, errExp, errRes)
testutil.Equals(t, smplExp, smplRes) testutil.Equals(t, smplExp, smplRes)
} }
testutil.Ok(t, ss.Err())
testutil.Equals(t, 0, len(ss.Warnings()))
} }
} }
} }
@ -1118,11 +1122,12 @@ func TestUncommittedSamplesNotLostOnTruncate(t *testing.T) {
testutil.Ok(t, err) testutil.Ok(t, err)
defer q.Close() defer q.Close()
ss, ws, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "1")) ss := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "1"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
testutil.Equals(t, true, ss.Next()) testutil.Equals(t, true, ss.Next())
for ss.Next() {
}
testutil.Ok(t, ss.Err())
testutil.Equals(t, 0, len(ss.Warnings()))
} }
func TestRemoveSeriesAfterRollbackAndTruncate(t *testing.T) { func TestRemoveSeriesAfterRollbackAndTruncate(t *testing.T) {
@ -1148,11 +1153,9 @@ func TestRemoveSeriesAfterRollbackAndTruncate(t *testing.T) {
testutil.Ok(t, err) testutil.Ok(t, err)
defer q.Close() defer q.Close()
ss, ws, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "1")) ss := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "a", "1"))
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
testutil.Equals(t, false, ss.Next()) testutil.Equals(t, false, ss.Next())
testutil.Equals(t, 0, len(ss.Warnings()))
// Truncate again, this time the series should be deleted // Truncate again, this time the series should be deleted
testutil.Ok(t, h.Truncate(2050)) testutil.Ok(t, h.Truncate(2050))
@ -1434,11 +1437,11 @@ func TestMemSeriesIsolation(t *testing.T) {
testutil.Ok(t, err) testutil.Ok(t, err)
defer querier.Close() defer querier.Close()
ss, _, err := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")) ss := querier.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"))
_, seriesSet, ws, err := expandSeriesSet(ss)
testutil.Ok(t, err) testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
_, seriesSet, err := expandSeriesSet(ss)
testutil.Ok(t, err)
for _, series := range seriesSet { for _, series := range seriesSet {
return int(series[len(series)-1].v) return int(series[len(series)-1].v)
} }
@ -1790,8 +1793,9 @@ func testHeadSeriesChunkRace(t *testing.T) {
h.gc() h.gc()
wg.Done() wg.Done()
}() }()
ss, _, err := q.Select(false, nil, matcher) ss := q.Select(false, nil, matcher)
testutil.Ok(t, err) for ss.Next() {
}
testutil.Ok(t, ss.Err()) testutil.Ok(t, ss.Err())
wg.Wait() wg.Wait()
} }

View File

@ -85,9 +85,9 @@ func (q *querier) lvals(qs []storage.Querier, n string) ([]string, storage.Warni
return mergeStrings(s1, s2), ws, nil return mergeStrings(s1, s2), ws, nil
} }
func (q *querier) Select(sortSeries bool, hints *storage.SelectHints, ms ...*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (q *querier) Select(sortSeries bool, hints *storage.SelectHints, ms ...*labels.Matcher) storage.SeriesSet {
if len(q.blocks) == 0 { if len(q.blocks) == 0 {
return storage.EmptySeriesSet(), nil, nil return storage.EmptySeriesSet()
} }
if len(q.blocks) == 1 { if len(q.blocks) == 1 {
// Sorting Head series is slow, and unneeded when only the // Sorting Head series is slow, and unneeded when only the
@ -96,18 +96,12 @@ func (q *querier) Select(sortSeries bool, hints *storage.SelectHints, ms ...*lab
} }
ss := make([]storage.SeriesSet, len(q.blocks)) ss := make([]storage.SeriesSet, len(q.blocks))
var ws storage.Warnings
for i, b := range q.blocks { for i, b := range q.blocks {
// We have to sort if blocks > 1 as MergedSeriesSet requires it. // We have to sort if blocks > 1 as MergedSeriesSet requires it.
s, w, err := b.Select(true, hints, ms...) ss[i] = b.Select(true, hints, ms...)
ws = append(ws, w...)
if err != nil {
return nil, ws, err
}
ss[i] = s
} }
return NewMergedSeriesSet(ss), ws, nil return NewMergedSeriesSet(ss)
} }
func (q *querier) Close() error { func (q *querier) Close() error {
@ -125,31 +119,23 @@ type verticalQuerier struct {
querier querier
} }
func (q *verticalQuerier) Select(sortSeries bool, hints *storage.SelectHints, ms ...*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (q *verticalQuerier) Select(sortSeries bool, hints *storage.SelectHints, ms ...*labels.Matcher) storage.SeriesSet {
return q.sel(sortSeries, hints, q.blocks, ms) return q.sel(sortSeries, hints, q.blocks, ms)
} }
func (q *verticalQuerier) sel(sortSeries bool, hints *storage.SelectHints, qs []storage.Querier, ms []*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (q *verticalQuerier) sel(sortSeries bool, hints *storage.SelectHints, qs []storage.Querier, ms []*labels.Matcher) storage.SeriesSet {
if len(qs) == 0 { if len(qs) == 0 {
return storage.EmptySeriesSet(), nil, nil return storage.EmptySeriesSet()
} }
if len(qs) == 1 { if len(qs) == 1 {
return qs[0].Select(sortSeries, hints, ms...) return qs[0].Select(sortSeries, hints, ms...)
} }
l := len(qs) / 2 l := len(qs) / 2
var ws storage.Warnings return newMergedVerticalSeriesSet(
a, w, err := q.sel(sortSeries, hints, qs[:l], ms) q.sel(sortSeries, hints, qs[:l], ms),
ws = append(ws, w...) q.sel(sortSeries, hints, qs[l:], ms),
if err != nil { )
return nil, ws, err
}
b, w, err := q.sel(sortSeries, hints, qs[l:], ms)
ws = append(ws, w...)
if err != nil {
return nil, ws, err
}
return newMergedVerticalSeriesSet(a, b), ws, nil
} }
// NewBlockQuerier returns a querier against the reader. // NewBlockQuerier returns a querier against the reader.
@ -189,7 +175,7 @@ type blockQuerier struct {
mint, maxt int64 mint, maxt int64
} }
func (q *blockQuerier) Select(sortSeries bool, hints *storage.SelectHints, ms ...*labels.Matcher) (storage.SeriesSet, storage.Warnings, error) { func (q *blockQuerier) Select(sortSeries bool, hints *storage.SelectHints, ms ...*labels.Matcher) storage.SeriesSet {
var base storage.DeprecatedChunkSeriesSet var base storage.DeprecatedChunkSeriesSet
var err error var err error
@ -199,7 +185,7 @@ func (q *blockQuerier) Select(sortSeries bool, hints *storage.SelectHints, ms ..
base, err = LookupChunkSeries(q.index, q.tombstones, ms...) base, err = LookupChunkSeries(q.index, q.tombstones, ms...)
} }
if err != nil { if err != nil {
return nil, nil, err return storage.ErrSeriesSet(err)
} }
mint := q.mint mint := q.mint
@ -218,7 +204,7 @@ func (q *blockQuerier) Select(sortSeries bool, hints *storage.SelectHints, ms ..
mint: mint, mint: mint,
maxt: maxt, maxt: maxt,
}, nil, nil }
} }
func (q *blockQuerier) LabelValues(name string) ([]string, storage.Warnings, error) { func (q *blockQuerier) LabelValues(name string) ([]string, storage.Warnings, error) {
@ -501,6 +487,14 @@ func (s *mergedSeriesSet) Err() error {
return s.err return s.err
} }
func (s *mergedSeriesSet) Warnings() storage.Warnings {
var ws storage.Warnings
for _, ss := range s.all {
ws = append(ws, ss.Warnings()...)
}
return ws
}
// nextAll is to call Next() for all SeriesSet. // nextAll is to call Next() for all SeriesSet.
// Because the order of the SeriesSet slice will affect the results, // Because the order of the SeriesSet slice will affect the results,
// we need to use an buffer slice to hold the order. // we need to use an buffer slice to hold the order.
@ -509,7 +503,10 @@ func (s *mergedSeriesSet) nextAll() {
for _, ss := range s.all { for _, ss := range s.all {
if ss.Next() { if ss.Next() {
s.buf = append(s.buf, ss) s.buf = append(s.buf, ss)
} else if ss.Err() != nil { continue
}
if ss.Err() != nil {
s.done = true s.done = true
s.err = ss.Err() s.err = ss.Err()
break break
@ -623,6 +620,13 @@ func (s *mergedVerticalSeriesSet) Err() error {
return s.b.Err() return s.b.Err()
} }
func (s *mergedVerticalSeriesSet) Warnings() storage.Warnings {
var ws storage.Warnings
ws = append(ws, s.a.Warnings()...)
ws = append(ws, s.b.Warnings()...)
return ws
}
func (s *mergedVerticalSeriesSet) compare() int { func (s *mergedVerticalSeriesSet) compare() int {
if s.adone { if s.adone {
return 1 return 1
@ -848,8 +852,9 @@ func (s *blockSeriesSet) Next() bool {
return false return false
} }
func (s *blockSeriesSet) At() storage.Series { return s.cur } func (s *blockSeriesSet) At() storage.Series { return s.cur }
func (s *blockSeriesSet) Err() error { return s.err } func (s *blockSeriesSet) Err() error { return s.err }
func (s *blockSeriesSet) Warnings() storage.Warnings { return nil }
// chunkSeries is a series that is backed by a sequence of chunks holding // chunkSeries is a series that is backed by a sequence of chunks holding
// time series data. // time series data.

View File

@ -155,8 +155,7 @@ func BenchmarkQuerierSelect(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
ss, _, err := q.Select(sorted, nil, matcher) ss := q.Select(sorted, nil, matcher)
testutil.Ok(b, err)
for ss.Next() { for ss.Next() {
} }
testutil.Ok(b, ss.Err()) testutil.Ok(b, ss.Err())

View File

@ -39,12 +39,14 @@ import (
type mockSeriesSet struct { type mockSeriesSet struct {
next func() bool next func() bool
series func() storage.Series series func() storage.Series
ws func() storage.Warnings
err func() error err func() error
} }
func (m *mockSeriesSet) Next() bool { return m.next() } func (m *mockSeriesSet) Next() bool { return m.next() }
func (m *mockSeriesSet) At() storage.Series { return m.series() } func (m *mockSeriesSet) At() storage.Series { return m.series() }
func (m *mockSeriesSet) Err() error { return m.err() } func (m *mockSeriesSet) Err() error { return m.err() }
func (m *mockSeriesSet) Warnings() storage.Warnings { return m.ws() }
func newMockSeriesSet(list []storage.Series) *mockSeriesSet { func newMockSeriesSet(list []storage.Series) *mockSeriesSet {
i := -1 i := -1
@ -57,11 +59,11 @@ func newMockSeriesSet(list []storage.Series) *mockSeriesSet {
return list[i] return list[i]
}, },
err: func() error { return nil }, err: func() error { return nil },
ws: func() storage.Warnings { return nil },
} }
} }
func TestMergedSeriesSet(t *testing.T) { func TestMergedSeriesSet(t *testing.T) {
cases := []struct { cases := []struct {
// The input sets in order (samples in series in b are strictly // The input sets in order (samples in series in b are strictly
// after those in a). // after those in a).
@ -373,15 +375,14 @@ Outer:
maxt: c.maxt, maxt: c.maxt,
} }
res, ws, err := querier.Select(false, nil, c.ms...) res := querier.Select(false, nil, c.ms...)
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
for { for {
eok, rok := c.exp.Next(), res.Next() eok, rok := c.exp.Next(), res.Next()
testutil.Equals(t, eok, rok) testutil.Equals(t, eok, rok)
if !eok { if !eok {
testutil.Equals(t, 0, len(res.Warnings()))
continue Outer continue Outer
} }
sexp := c.exp.At() sexp := c.exp.At()
@ -536,15 +537,14 @@ Outer:
maxt: c.maxt, maxt: c.maxt,
} }
res, ws, err := querier.Select(false, nil, c.ms...) res := querier.Select(false, nil, c.ms...)
testutil.Ok(t, err)
testutil.Equals(t, 0, len(ws))
for { for {
eok, rok := c.exp.Next(), res.Next() eok, rok := c.exp.Next(), res.Next()
testutil.Equals(t, eok, rok) testutil.Equals(t, eok, rok)
if !eok { if !eok {
testutil.Equals(t, 0, len(res.Warnings()))
continue Outer continue Outer
} }
sexp := c.exp.At() sexp := c.exp.At()
@ -1654,7 +1654,7 @@ func BenchmarkQuerySeek(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
ss, ws, err := sq.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "__name__", ".*")) ss := sq.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "__name__", ".*"))
for ss.Next() { for ss.Next() {
it := ss.At().Iterator() it := ss.At().Iterator()
for t := mint; t <= maxt; t++ { for t := mint; t <= maxt; t++ {
@ -1664,7 +1664,7 @@ func BenchmarkQuerySeek(b *testing.B) {
} }
testutil.Ok(b, ss.Err()) testutil.Ok(b, ss.Err())
testutil.Ok(b, err) testutil.Ok(b, err)
testutil.Equals(b, 0, len(ws)) testutil.Equals(b, 0, len(ss.Warnings()))
}) })
} }
} }
@ -1792,9 +1792,11 @@ func BenchmarkSetMatcher(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
_, ws, err := que.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "test", c.pattern)) ss := que.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "test", c.pattern))
testutil.Ok(b, err) for ss.Next() {
testutil.Equals(b, 0, len(ws)) }
testutil.Ok(b, ss.Err())
testutil.Equals(b, 0, len(ss.Warnings()))
} }
}) })
} }
@ -2252,9 +2254,7 @@ func benchQuery(b *testing.B, expExpansions int, q storage.Querier, selectors la
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
ss, ws, err := q.Select(false, nil, selectors...) ss := q.Select(false, nil, selectors...)
testutil.Ok(b, err)
testutil.Equals(b, 0, len(ws))
var actualExpansions int var actualExpansions int
for ss.Next() { for ss.Next() {
s := ss.At() s := ss.At()
@ -2264,6 +2264,8 @@ func benchQuery(b *testing.B, expExpansions int, q storage.Querier, selectors la
} }
actualExpansions++ actualExpansions++
} }
testutil.Ok(b, ss.Err())
testutil.Equals(b, 0, len(ss.Warnings()))
testutil.Equals(b, expExpansions, actualExpansions) testutil.Equals(b, expExpansions, actualExpansions)
testutil.Ok(b, ss.Err()) testutil.Ok(b, ss.Err())
} }

View File

@ -596,13 +596,8 @@ func (api *API) series(r *http.Request) (result apiFuncResult) {
} }
var sets []storage.SeriesSet var sets []storage.SeriesSet
var warnings storage.Warnings
for _, mset := range matcherSets { for _, mset := range matcherSets {
s, wrn, err := q.Select(false, nil, mset...) s := q.Select(false, nil, mset...)
warnings = append(warnings, wrn...)
if err != nil {
return apiFuncResult{nil, &apiError{errorExec, err}, warnings, closer}
}
sets = append(sets, s) sets = append(sets, s)
} }
@ -611,6 +606,8 @@ func (api *API) series(r *http.Request) (result apiFuncResult) {
for set.Next() { for set.Next() {
metrics = append(metrics, set.At().Labels()) metrics = append(metrics, set.At().Labels())
} }
warnings := set.Warnings()
if set.Err() != nil { if set.Err() != nil {
return apiFuncResult{nil, &apiError{errorExec, set.Err()}, warnings, closer} return apiFuncResult{nil, &apiError{errorExec, set.Err()}, warnings, closer}
} }
@ -1226,12 +1223,9 @@ func (api *API) remoteRead(w http.ResponseWriter, r *http.Request) {
return return
} }
for i, query := range req.Queries { for i, query := range req.Queries {
err := api.remoteReadQuery(ctx, query, externalLabels, func(querier storage.Querier, hints *storage.SelectHints, filteredMatchers []*labels.Matcher) error { ws, err := api.remoteReadQuery(ctx, query, externalLabels, func(querier storage.Querier, hints *storage.SelectHints, filteredMatchers []*labels.Matcher) (storage.Warnings, error) {
// The streaming API has to provide the series sorted. // The streaming API has to provide the series sorted.
set, _, err := querier.Select(true, hints, filteredMatchers...) set := querier.Select(true, hints, filteredMatchers...)
if err != nil {
return err
}
return remote.StreamChunkedReadResponses( return remote.StreamChunkedReadResponses(
remote.NewChunkedWriter(w, f), remote.NewChunkedWriter(w, f),
@ -1241,6 +1235,9 @@ func (api *API) remoteRead(w http.ResponseWriter, r *http.Request) {
api.remoteReadMaxBytesInFrame, api.remoteReadMaxBytesInFrame,
) )
}) })
for _, w := range ws {
level.Warn(api.logger).Log("msg", "warnings on remote read query", "err", w.Error())
}
if err != nil { if err != nil {
if httpErr, ok := err.(remote.HTTPError); ok { if httpErr, ok := err.(remote.HTTPError); ok {
http.Error(w, httpErr.Error(), httpErr.Status()) http.Error(w, httpErr.Error(), httpErr.Status())
@ -1259,22 +1256,26 @@ func (api *API) remoteRead(w http.ResponseWriter, r *http.Request) {
Results: make([]*prompb.QueryResult, len(req.Queries)), Results: make([]*prompb.QueryResult, len(req.Queries)),
} }
for i, query := range req.Queries { for i, query := range req.Queries {
err := api.remoteReadQuery(ctx, query, externalLabels, func(querier storage.Querier, hints *storage.SelectHints, filteredMatchers []*labels.Matcher) error { ws, err := api.remoteReadQuery(ctx, query, externalLabels, func(querier storage.Querier, hints *storage.SelectHints, filteredMatchers []*labels.Matcher) (storage.Warnings, error) {
set, _, err := querier.Select(false, hints, filteredMatchers...) set := querier.Select(false, hints, filteredMatchers...)
if err != nil {
return err
}
resp.Results[i], err = remote.ToQueryResult(set, api.remoteReadSampleLimit) var (
ws storage.Warnings
err error
)
resp.Results[i], ws, err = remote.ToQueryResult(set, api.remoteReadSampleLimit)
if err != nil { if err != nil {
return err return ws, err
} }
for _, ts := range resp.Results[i].Timeseries { for _, ts := range resp.Results[i].Timeseries {
ts.Labels = remote.MergeLabels(ts.Labels, sortedExternalLabels) ts.Labels = remote.MergeLabels(ts.Labels, sortedExternalLabels)
} }
return nil return ws, nil
}) })
for _, w := range ws {
level.Warn(api.logger).Log("msg", "warnings on remote read query", "err", w.Error())
}
if err != nil { if err != nil {
if httpErr, ok := err.(remote.HTTPError); ok { if httpErr, ok := err.(remote.HTTPError); ok {
http.Error(w, httpErr.Error(), httpErr.Status()) http.Error(w, httpErr.Error(), httpErr.Status())
@ -1318,15 +1319,15 @@ func filterExtLabelsFromMatchers(pbMatchers []*prompb.LabelMatcher, externalLabe
return filteredMatchers, nil return filteredMatchers, nil
} }
func (api *API) remoteReadQuery(ctx context.Context, query *prompb.Query, externalLabels map[string]string, seriesHandleFn func(querier storage.Querier, hints *storage.SelectHints, filteredMatchers []*labels.Matcher) error) error { func (api *API) remoteReadQuery(ctx context.Context, query *prompb.Query, externalLabels map[string]string, seriesHandleFn func(querier storage.Querier, hints *storage.SelectHints, filteredMatchers []*labels.Matcher) (storage.Warnings, error)) (storage.Warnings, error) {
filteredMatchers, err := filterExtLabelsFromMatchers(query.Matchers, externalLabels) filteredMatchers, err := filterExtLabelsFromMatchers(query.Matchers, externalLabels)
if err != nil { if err != nil {
return err return nil, err
} }
querier, err := api.Queryable.Querier(ctx, query.StartTimestampMs, query.EndTimestampMs) querier, err := api.Queryable.Querier(ctx, query.StartTimestampMs, query.EndTimestampMs)
if err != nil { if err != nil {
return err return nil, err
} }
defer func() { defer func() {
if err := querier.Close(); err != nil { if err := querier.Close(); err != nil {

View File

@ -517,12 +517,8 @@ func setupRemote(s storage.Storage) *httptest.Server {
} }
defer querier.Close() defer querier.Close()
set, _, err := querier.Select(false, hints, matchers...) set := querier.Select(false, hints, matchers...)
if err != nil { resp.Results[i], _, err = remote.ToQueryResult(set, 1e6)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp.Results[i], err = remote.ToQueryResult(set, 1e6)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -95,16 +95,7 @@ func (h *Handler) federation(w http.ResponseWriter, req *http.Request) {
var sets []storage.SeriesSet var sets []storage.SeriesSet
for _, mset := range matcherSets { for _, mset := range matcherSets {
s, wrns, err := q.Select(false, hints, mset...) s := q.Select(false, hints, mset...)
if wrns != nil {
level.Debug(h.logger).Log("msg", "Federation select returned warnings", "warnings", wrns)
federationWarnings.Add(float64(len(wrns)))
}
if err != nil {
federationErrors.Inc()
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sets = append(sets, s) sets = append(sets, s)
} }
@ -142,6 +133,10 @@ func (h *Handler) federation(w http.ResponseWriter, req *http.Request) {
Point: promql.Point{T: t, V: v}, Point: promql.Point{T: t, V: v},
}) })
} }
if ws := set.Warnings(); len(ws) > 0 {
level.Debug(h.logger).Log("msg", "Federation select returned warnings", "warnings", ws)
federationWarnings.Add(float64(len(ws)))
}
if set.Err() != nil { if set.Err() != nil {
federationErrors.Inc() federationErrors.Inc()
http.Error(w, set.Err().Error(), http.StatusInternalServerError) http.Error(w, set.Err().Error(), http.StatusInternalServerError)

View File

@ -11222,9 +11222,9 @@ websocket-driver@>=0.5.1:
websocket-extensions ">=0.1.1" websocket-extensions ">=0.1.1"
websocket-extensions@>=0.1.1: websocket-extensions@>=0.1.1:
version "0.1.3" version "0.1.4"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
version "1.0.5" version "1.0.5"