Approximate `quantile_over_time` (#10417)

**What this PR does / why we need it**:
This change shards `quantile_over_time` queries using t-digest or
DDSketch approximations. It can be enabled with `querier.shard_aggregations=quantile_over_time`.

Outstanding
- [x] Replace generic return type of `StepEvaluator` with interface
`StepResult`.
- [x] Send mapped query with quantile sketch expression from frontend to
querier over the wire.
- [x] Serialize sketches. See
https://github.com/influxdata/tdigest/issues/34
- [x] Add feature flag.

**Checklist**
- [x] Reviewed the
[`CONTRIBUTING.md`](https://github.com/grafana/loki/blob/main/CONTRIBUTING.md)
guide (**required**)
- [x] Documentation added
- [x] Tests updated
- [x] `CHANGELOG.md` updated
- [x] If the change is worth mentioning in the release notes, add
`add-to-release-notes` label
- [x] Changes that require user attention or interaction to upgrade are
documented in `docs/sources/setup/upgrade/_index.md`
- [ ] For Helm chart changes bump the Helm chart version in
`production/helm/loki/Chart.yaml` and update
`production/helm/loki/CHANGELOG.md` and
`production/helm/loki/README.md`. [Example
PR](d10549e3ec)

---------

Signed-off-by: Callum Styan <callumstyan@gmail.com>
Co-authored-by: Callum Styan <callumstyan@gmail.com>
pull/11424/head
Karsten Jeschkies 2 years ago committed by GitHub
parent 5e3496739c
commit f67fff3eb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 5
      docs/sources/configure/_index.md
  3. 42
      pkg/ingester/ingester.go
  4. 23
      pkg/ingester/ingester_test.go
  5. 17
      pkg/ingester/instance_test.go
  6. 5
      pkg/ingester/stream_test.go
  7. 8
      pkg/ingester/tailer.go
  8. 17
      pkg/ingester/tailer_test.go
  9. 12
      pkg/loghttp/tail.go
  10. 5
      pkg/loghttp/tail_test.go
  11. 934
      pkg/logproto/logproto.pb.go
  12. 13
      pkg/logproto/logproto.proto
  13. 86
      pkg/logql/downstream.go
  14. 100
      pkg/logql/downstream_test.go
  15. 47
      pkg/logql/engine.go
  16. 36
      pkg/logql/engine_test.go
  17. 59
      pkg/logql/evaluator.go
  18. 6
      pkg/logql/explain.go
  19. 2
      pkg/logql/explain_test.go
  20. 6
      pkg/logql/optimize.go
  21. 413
      pkg/logql/quantile_over_time_sketch.go
  22. 109
      pkg/logql/quantile_over_time_sketch_test.go
  23. 70
      pkg/logql/range_vector_test.go
  24. 65
      pkg/logql/shardmapper.go
  25. 8
      pkg/logql/shardmapper_test.go
  26. 97
      pkg/logql/sketch/quantile.go
  27. 81
      pkg/logql/sketch/quantile_test.go
  28. 5
      pkg/logql/step_evaluator.go
  29. 30
      pkg/logql/syntax/ast.go
  30. 298
      pkg/logql/syntax/clone.go
  31. 114
      pkg/logql/syntax/clone_test.go
  32. 4
      pkg/logqlmodel/logqlmodel.go
  33. 10
      pkg/loki/config_compat.go
  34. 73
      pkg/lokifrontend/frontend/v2/frontend_test.go
  35. 11
      pkg/querier/http.go
  36. 9
      pkg/querier/http_test.go
  37. 10
      pkg/querier/multi_tenant_querier.go
  38. 10
      pkg/querier/multi_tenant_querier_test.go
  39. 24
      pkg/querier/querier.go
  40. 45
      pkg/querier/querier_test.go
  41. 2
      pkg/querier/queryrange/limits.go
  42. 11
      pkg/querier/queryrange/limits_test.go
  43. 4
      pkg/querier/queryrange/marshal.go
  44. 14
      pkg/querier/queryrange/marshal_test.go
  45. 504
      pkg/querier/queryrange/queryrange.pb.go
  46. 10
      pkg/querier/queryrange/queryrange.proto
  47. 22
      pkg/querier/queryrange/queryrangebase/roundtrip.go
  48. 39
      pkg/querier/queryrange/querysharding.go
  49. 9
      pkg/querier/queryrange/querysharding_test.go
  50. 21
      pkg/querier/queryrange/roundtrip.go
  51. 25
      pkg/querier/queryrange/roundtrip_test.go
  52. 20
      pkg/querier/queryrange/split_by_interval.go
  53. 13
      pkg/querier/queryrange/split_by_interval_test.go
  54. 22
      pkg/storage/store_test.go
  55. 7
      pkg/storage/util_test.go
  56. 2
      tools/dev/loki-boltdb-storage-s3/compose-up.sh
  57. 1
      tools/dev/loki-boltdb-storage-s3/config/loki.yaml

@ -38,6 +38,7 @@
* [10727](https://github.com/grafana/loki/pull/10727) **sandeepsukhani** Native otlp ingestion support
* [11051](https://github.com/grafana/loki/pull/11051) Refactor to not use global logger in modules
* [10956](https://github.com/grafana/loki/pull/10956) **jeschkies** do not wrap requests but send pure Protobuf from frontend v2 via scheduler to querier when `-frontend.encoding=protobuf`.
* [10417](https://github.com/grafana/loki/pull/10417) **jeschkies** shard `quantile_over_time` range queries using probabilistic data structures.
* [11284](https://github.com/grafana/loki/pull/11284) **ashwanthgoli** Config: Adds `frontend.max-query-capacity` to tune per-tenant query capacity.
##### Fixes

@ -842,6 +842,11 @@ results_cache:
# CLI flag: -querier.parallelise-shardable-queries
[parallelise_shardable_queries: <boolean> | default = true]
# A comma-separated list of LogQL vector and range aggregations that should be
# sharded
# CLI flag: -querier.shard-aggregation
[shard_aggregations: <string> | default = ""]
# Cache index stats query results.
# CLI flag: -querier.cache-index-stats-results
[cache_index_stats_results: <boolean> | default = false]

@ -37,6 +37,7 @@ import (
"github.com/grafana/loki/pkg/logql"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/logqlmodel/stats"
"github.com/grafana/loki/pkg/querier/plan"
"github.com/grafana/loki/pkg/runtime"
"github.com/grafana/loki/pkg/storage"
"github.com/grafana/loki/pkg/storage/chunk"
@ -851,6 +852,16 @@ func (i *Ingester) Query(req *logproto.QueryRequest, queryServer logproto.Querie
// initialize stats collection for ingester queries.
_, ctx := stats.NewContext(queryServer.Context())
if req.Plan == nil {
parsed, err := syntax.ParseLogSelector(req.Selector, true)
if err != nil {
return err
}
req.Plan = &plan.QueryPlan{
AST: parsed,
}
}
instanceID, err := tenant.TenantID(ctx)
if err != nil {
return err
@ -874,6 +885,7 @@ func (i *Ingester) Query(req *logproto.QueryRequest, queryServer logproto.Querie
Limit: req.Limit,
Shards: req.Shards,
Deletes: req.Deletes,
Plan: req.Plan,
}}
storeItr, err := i.store.SelectLogs(ctx, storeReq)
if err != nil {
@ -900,6 +912,17 @@ func (i *Ingester) QuerySample(req *logproto.SampleQueryRequest, queryServer log
_, ctx := stats.NewContext(queryServer.Context())
sp := opentracing.SpanFromContext(ctx)
// If the plan is empty we want all series to be returned.
if req.Plan == nil {
parsed, err := syntax.ParseSampleExpr(req.Selector)
if err != nil {
return err
}
req.Plan = &plan.QueryPlan{
AST: parsed,
}
}
instanceID, err := tenant.TenantID(ctx)
if err != nil {
return err
@ -925,6 +948,7 @@ func (i *Ingester) QuerySample(req *logproto.SampleQueryRequest, queryServer log
Selector: req.Selector,
Shards: req.Shards,
Deletes: req.Deletes,
Plan: req.Plan,
}}
storeItr, err := i.store.SelectSamples(ctx, storeReq)
if err != nil {
@ -1234,6 +1258,16 @@ func (i *Ingester) Tail(req *logproto.TailRequest, queryServer logproto.Querier_
default:
}
if req.Plan == nil {
parsed, err := syntax.ParseLogSelector(req.Query, true)
if err != nil {
return err
}
req.Plan = &plan.QueryPlan{
AST: parsed,
}
}
instanceID, err := tenant.TenantID(queryServer.Context())
if err != nil {
return err
@ -1243,7 +1277,13 @@ func (i *Ingester) Tail(req *logproto.TailRequest, queryServer logproto.Querier_
if err != nil {
return err
}
tailer, err := newTailer(instanceID, req.Query, queryServer, i.cfg.MaxDroppedStreams)
expr, ok := req.Plan.AST.(syntax.LogSelectorExpr)
if !ok {
return fmt.Errorf("unsupported query expression: want (LogSelectorExpr), got (%T)", req.Plan.AST)
}
tailer, err := newTailer(instanceID, expr, queryServer, i.cfg.MaxDroppedStreams)
if err != nil {
return err
}

@ -35,6 +35,8 @@ import (
"github.com/grafana/loki/pkg/iter"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/querier/plan"
"github.com/grafana/loki/pkg/runtime"
"github.com/grafana/loki/pkg/storage/chunk"
"github.com/grafana/loki/pkg/storage/chunk/fetcher"
@ -812,6 +814,9 @@ func Test_DedupeIngester(t *testing.T) {
End: time.Unix(0, requests+1),
Limit: uint32(requests * streamCount),
Direction: logproto.BACKWARD,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{foo="bar"} | label_format bar=""`),
},
})
require.NoError(t, err)
iterators = append(iterators, iter.NewQueryClientIterator(stream, logproto.BACKWARD))
@ -870,6 +875,9 @@ func Test_DedupeIngester(t *testing.T) {
Selector: `sum(rate({foo="bar"}[1m])) by (bar)`,
Start: time.Unix(0, 0),
End: time.Unix(0, requests+1),
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`sum(rate({foo="bar"}[1m])) by (bar)`),
},
})
require.NoError(t, err)
iterators = append(iterators, iter.NewSampleQueryClientIterator(stream))
@ -905,6 +913,9 @@ func Test_DedupeIngester(t *testing.T) {
Selector: `sum(rate({foo="bar"}[1m]))`,
Start: time.Unix(0, 0),
End: time.Unix(0, requests+1),
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`sum(rate({foo="bar"}[1m]))`),
},
})
require.NoError(t, err)
iterators = append(iterators, iter.NewSampleQueryClientIterator(stream))
@ -965,6 +976,9 @@ func Test_DedupeIngesterParser(t *testing.T) {
End: time.Unix(0, int64(requests+1)),
Limit: uint32(requests * streamCount * 2),
Direction: logproto.BACKWARD,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{foo="bar"} | json`),
},
})
require.NoError(t, err)
iterators = append(iterators, iter.NewQueryClientIterator(stream, logproto.BACKWARD))
@ -992,6 +1006,9 @@ func Test_DedupeIngesterParser(t *testing.T) {
End: time.Unix(0, int64(requests+1)),
Limit: uint32(requests * streamCount * 2),
Direction: logproto.FORWARD,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{foo="bar"} | json`),
},
})
require.NoError(t, err)
iterators = append(iterators, iter.NewQueryClientIterator(stream, logproto.FORWARD))
@ -1016,6 +1033,9 @@ func Test_DedupeIngesterParser(t *testing.T) {
Selector: `rate({foo="bar"} | json [1m])`,
Start: time.Unix(0, 0),
End: time.Unix(0, int64(requests+1)),
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`rate({foo="bar"} | json [1m])`),
},
})
require.NoError(t, err)
iterators = append(iterators, iter.NewSampleQueryClientIterator(stream))
@ -1041,6 +1061,9 @@ func Test_DedupeIngesterParser(t *testing.T) {
Selector: `sum by (c,d,e,foo) (rate({foo="bar"} | json [1m]))`,
Start: time.Unix(0, 0),
End: time.Unix(0, int64(requests+1)),
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`sum by (c,d,e,foo) (rate({foo="bar"} | json [1m]))`),
},
})
require.NoError(t, err)
iterators = append(iterators, iter.NewSampleQueryClientIterator(stream))

@ -21,6 +21,7 @@ import (
"github.com/grafana/loki/pkg/logql"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/querier/astmapper"
"github.com/grafana/loki/pkg/querier/plan"
loki_runtime "github.com/grafana/loki/pkg/runtime"
"github.com/grafana/loki/pkg/storage/chunk"
"github.com/grafana/loki/pkg/storage/config"
@ -537,7 +538,9 @@ func Benchmark_instance_addNewTailer(b *testing.B) {
ctx := context.Background()
inst, _ := newInstance(&Config{}, defaultPeriodConfigs, "test", limiter, loki_runtime.DefaultTenantConfigs(), noopWAL{}, NilMetrics, &OnceSwitch{}, nil, NewStreamRateCalculator(), nil)
t, err := newTailer("foo", `{namespace="foo",pod="bar",instance=~"10.*"}`, nil, 10)
expr, err := syntax.ParseLogSelector(`{namespace="foo",pod="bar",instance=~"10.*"}`, true)
require.NoError(b, err)
t, err := newTailer("foo", expr, nil, 10)
require.NoError(b, err)
for i := 0; i < 10000; i++ {
require.NoError(b, inst.Push(ctx, &logproto.PushRequest{
@ -596,6 +599,9 @@ func Test_Iterator(t *testing.T) {
Start: time.Unix(0, 0),
End: time.Unix(0, 100000000),
Direction: logproto.BACKWARD,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{job="3"} | logfmt`),
},
},
},
)
@ -648,6 +654,9 @@ func Test_ChunkFilter(t *testing.T) {
Start: time.Unix(0, 0),
End: time.Unix(0, 100000000),
Direction: logproto.BACKWARD,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{job="3"}`),
},
},
},
)
@ -690,6 +699,9 @@ func Test_QueryWithDelete(t *testing.T) {
End: 10 * 1e6,
},
},
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{job="3"}`),
},
},
},
)
@ -730,6 +742,9 @@ func Test_QuerySampleWithDelete(t *testing.T) {
End: 10 * 1e6,
},
},
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`count_over_time({job="3"}[5m])`),
},
},
},
)

@ -18,6 +18,7 @@ import (
"github.com/grafana/loki/pkg/iter"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql/log"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/util/flagext"
"github.com/grafana/loki/pkg/validation"
)
@ -524,7 +525,9 @@ func Benchmark_PushStream(b *testing.B) {
chunkfmt, headfmt := defaultChunkFormat(b)
s := newStream(chunkfmt, headfmt, &Config{MaxChunkAge: 24 * time.Hour}, limiter, "fake", model.Fingerprint(0), ls, true, NewStreamRateCalculator(), NilMetrics, nil)
t, err := newTailer("foo", `{namespace="loki-dev"}`, &fakeTailServer{}, 10)
expr, err := syntax.ParseLogSelector(`{namespace="loki-dev"}`, true)
require.NoError(b, err)
t, err := newTailer("foo", expr, &fakeTailServer{}, 10)
require.NoError(b, err)
go t.loop()

@ -46,11 +46,7 @@ type tailer struct {
conn TailServer
}
func newTailer(orgID, query string, conn TailServer, maxDroppedStreams int) (*tailer, error) {
expr, err := syntax.ParseLogSelector(query, true)
if err != nil {
return nil, err
}
func newTailer(orgID string, expr syntax.LogSelectorExpr, conn TailServer, maxDroppedStreams int) (*tailer, error) {
// Make sure we can build a pipeline. The stream processing code doesn't have a place to handle
// this error so make sure we handle it here.
pipeline, err := expr.Pipeline()
@ -66,7 +62,7 @@ func newTailer(orgID, query string, conn TailServer, maxDroppedStreams int) (*ta
conn: conn,
droppedStreams: make([]*logproto.DroppedStream, 0, maxDroppedStreams),
maxDroppedStreams: maxDroppedStreams,
id: generateUniqueID(orgID, query),
id: generateUniqueID(orgID, expr.String()),
closeChan: make(chan struct{}),
pipeline: pipeline,
}, nil

@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql/syntax"
)
func TestTailer_sendRaceConditionOnSendWhileClosing(t *testing.T) {
@ -26,7 +27,9 @@ func TestTailer_sendRaceConditionOnSendWhileClosing(t *testing.T) {
}
for run := 0; run < runs; run++ {
tailer, err := newTailer("org-id", stream.Labels, nil, 10)
expr, err := syntax.ParseLogSelector(stream.Labels, true)
require.NoError(t, err)
tailer, err := newTailer("org-id", expr, nil, 10)
require.NoError(t, err)
require.NotNil(t, tailer)
@ -78,7 +81,9 @@ func Test_dropstream(t *testing.T) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
tail, err := newTailer("foo", `{app="foo"} |= "foo"`, &fakeTailServer{}, maxDroppedStreams)
expr, err := syntax.ParseLogSelector(`{app="foo"} |= "foo"`, true)
require.NoError(t, err)
tail, err := newTailer("foo", expr, &fakeTailServer{}, maxDroppedStreams)
require.NoError(t, err)
for i := 0; i < c.drop; i++ {
@ -114,7 +119,9 @@ func (f *fakeTailServer) Reset() {
}
func Test_TailerSendRace(t *testing.T) {
tail, err := newTailer("foo", `{app="foo"} |= "foo"`, &fakeTailServer{}, 10)
expr, err := syntax.ParseLogSelector(`{app="foo"} |= "foo"`, true)
require.NoError(t, err)
tail, err := newTailer("foo", expr, &fakeTailServer{}, 10)
require.NoError(t, err)
var wg sync.WaitGroup
@ -250,7 +257,9 @@ func Test_StructuredMetadata(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
var server fakeTailServer
tail, err := newTailer("foo", tc.query, &server, 10)
expr, err := syntax.ParseLogSelector(tc.query, true)
require.NoError(t, err)
tail, err := newTailer("foo", expr, &server, 10)
require.NoError(t, err)
var wg sync.WaitGroup

@ -11,6 +11,8 @@ import (
"github.com/grafana/dskit/httpgrpc"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/querier/plan"
)
const (
@ -67,8 +69,16 @@ func (s *DroppedStream) UnmarshalJSON(data []byte) error {
// ParseTailQuery parses a TailRequest request from an http request.
func ParseTailQuery(r *http.Request) (*logproto.TailRequest, error) {
var err error
qs := query(r)
parsed, err := syntax.ParseExpr(qs)
if err != nil {
return nil, err
}
req := logproto.TailRequest{
Query: query(r),
Query: qs,
Plan: &plan.QueryPlan{
AST: parsed,
},
}
req.Query, err = parseRegexQuery(r)

@ -9,6 +9,8 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/querier/plan"
)
func TestParseTailQuery(t *testing.T) {
@ -38,6 +40,9 @@ func TestParseTailQuery(t *testing.T) {
DelayFor: 5,
Start: time.Date(2017, 06, 10, 21, 42, 24, 760738998, time.UTC),
Limit: 1000,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{foo="bar"}`),
},
}, false},
}
for _, tt := range tests {

File diff suppressed because it is too large Load Diff

@ -51,7 +51,7 @@ message StreamRate {
}
message QueryRequest {
string selector = 1;
string selector = 1 [deprecated = true];
uint32 limit = 2;
google.protobuf.Timestamp start = 3 [
(gogoproto.stdtime) = true,
@ -65,10 +65,11 @@ message QueryRequest {
reserved 6;
repeated string shards = 7 [(gogoproto.jsontag) = "shards,omitempty"];
repeated Delete deletes = 8;
Plan plan = 9 [(gogoproto.customtype) = "github.com/grafana/loki/pkg/querier/plan.QueryPlan"];
}
message SampleQueryRequest {
string selector = 1;
string selector = 1 [deprecated = true]; // mark as reserved once we've fully migrated to plan.
google.protobuf.Timestamp start = 2 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
@ -79,6 +80,11 @@ message SampleQueryRequest {
];
repeated string shards = 4 [(gogoproto.jsontag) = "shards,omitempty"];
repeated Delete deletes = 5;
Plan plan = 6 [(gogoproto.customtype) = "github.com/grafana/loki/pkg/querier/plan.QueryPlan"];
}
message Plan {
bytes raw = 1;
}
message Delete {
@ -148,7 +154,7 @@ message Series {
}
message TailRequest {
string query = 1;
string query = 1 [deprecated = true];
reserved 2;
uint32 delayFor = 3;
uint32 limit = 4;
@ -156,6 +162,7 @@ message TailRequest {
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
bytes plan = 6 [(gogoproto.customtype) = "github.com/grafana/loki/pkg/querier/plan.QueryPlan"];
}
message TailResponse {

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
@ -155,6 +156,50 @@ func (c ConcatLogSelectorExpr) string(maxDepth int) string {
return fmt.Sprintf("%s ++ %s", c.DownstreamLogSelectorExpr.String(), c.next.string(maxDepth-1))
}
// QuantileSketchEvalExpr evaluates a quantile sketch to the actual quantile.
type QuantileSketchEvalExpr struct {
syntax.SampleExpr
quantileMergeExpr *QuantileSketchMergeExpr
quantile *float64
}
func (e QuantileSketchEvalExpr) String() string {
return fmt.Sprintf("quantileSketchEval<%s>", e.quantileMergeExpr.String())
}
func (e *QuantileSketchEvalExpr) Walk(f syntax.WalkFn) {
f(e)
e.quantileMergeExpr.Walk(f)
}
type QuantileSketchMergeExpr struct {
syntax.SampleExpr
downstreams []DownstreamSampleExpr
}
func (e QuantileSketchMergeExpr) String() string {
var sb strings.Builder
for i, d := range e.downstreams {
if i >= defaultMaxDepth {
break
}
if i > 0 {
sb.WriteString(" ++ ")
}
sb.WriteString(d.String())
}
return fmt.Sprintf("quantileSketchMerge<%s>", sb.String())
}
func (e *QuantileSketchMergeExpr) Walk(f syntax.WalkFn) {
f(e)
for _, d := range e.downstreams {
d.Walk(f)
}
}
type Shards []astmapper.ShardAnnotation
func (xs Shards) Encode() (encoded []string) {
@ -308,6 +353,47 @@ func (ev *DownstreamEvaluator) NewStepEvaluator(
}
return NewConcatStepEvaluator(xs), nil
case *QuantileSketchEvalExpr:
var queries []DownstreamQuery
if e.quantileMergeExpr != nil {
for _, d := range e.quantileMergeExpr.downstreams {
qry := DownstreamQuery{
Params: ParamsWithExpressionOverride{
Params: params,
ExpressionOverride: d.SampleExpr,
},
}
if shard := d.shard; shard != nil {
qry.Params = ParamsWithShardsOverride{
Params: qry.Params,
ShardsOverride: Shards{*shard}.Encode(),
}
}
queries = append(queries, qry)
}
}
results, err := ev.Downstream(ctx, queries)
if err != nil {
return nil, fmt.Errorf("error running quantile sketch downstream query: %w", err)
}
xs := make([]StepEvaluator, 0, len(queries))
for _, res := range results {
if res.Data.Type() != QuantileSketchMatrixType {
return nil, fmt.Errorf("unexpected matrix data type: got (%s), want (%s)", res.Data.Type(), QuantileSketchMatrixType)
}
data, ok := res.Data.(ProbabilisticQuantileMatrix)
if !ok {
return nil, fmt.Errorf("unexpected matrix type: got (%T), want (ProbabilisticQuantileMatrix)", res.Data)
}
stepper := NewQuantileSketchMatrixStepEvaluator(data, params)
xs = append(xs, stepper)
}
inner := NewQuantileSketchMergeStepEvaluator(xs)
return NewQuantileSketchVectorStepEvaluator(inner, *e.quantile), nil
default:
return ev.defaultEvaluator.NewStepEvaluator(ctx, nextEvFactory, e, params)

@ -54,6 +54,7 @@ func TestMappingEquivalence(t *testing.T) {
{`sum(rate({a=~".+"} |= "foo" != "foo"[1s]) or vector(1))`, false},
{`avg_over_time({a=~".+"} | logfmt | unwrap value [1s])`, false},
{`avg_over_time({a=~".+"} | logfmt | unwrap value [1s]) by (a)`, true},
{`quantile_over_time(0.99, {a=~".+"} | logfmt | unwrap value [1s])`, true},
// topk prefers already-seen values in tiebreakers. Since the test data generates
// the same log lines for each series & the resulting promql.Vectors aren't deterministically
// sorted by labels, we don't expect this to pass.
@ -85,17 +86,17 @@ func TestMappingEquivalence(t *testing.T) {
qry := regular.Query(params)
ctx := user.InjectOrgID(context.Background(), "fake")
mapper := NewShardMapper(ConstantShards(shards), nilShardMetrics)
mapper := NewShardMapper(ConstantShards(shards), nilShardMetrics, []string{})
_, _, mapped, err := mapper.Parse(params.GetExpression())
require.Nil(t, err)
require.NoError(t, err)
shardedQry := sharded.Query(ctx, ParamsWithExpressionOverride{Params: params, ExpressionOverride: mapped})
res, err := qry.Exec(ctx)
require.Nil(t, err)
require.NoError(t, err)
shardedRes, err := shardedQry.Exec(ctx)
require.Nil(t, err)
require.NoError(t, err)
if tc.approximate {
approximatelyEquals(t, res.Data.(promql.Matrix), shardedRes.Data.(promql.Matrix))
@ -106,6 +107,70 @@ func TestMappingEquivalence(t *testing.T) {
}
}
func TestMappingEquivalenceSketches(t *testing.T) {
var (
shards = 3
nStreams = 10_000
rounds = 20
streams = randomStreams(nStreams, rounds+1, shards, []string{"a", "b", "c", "d"}, true)
start = time.Unix(0, 0)
end = time.Unix(0, int64(time.Second*time.Duration(rounds)))
step = time.Second
interval = time.Duration(0)
limit = 100
)
for _, tc := range []struct {
query string
realtiveError float64
}{
{`quantile_over_time(0.70, {a=~".+"} | logfmt | unwrap value [1s]) by (a)`, 0.03},
{`quantile_over_time(0.99, {a=~".+"} | logfmt | unwrap value [1s]) by (a)`, 0.02},
} {
q := NewMockQuerier(
shards,
streams,
)
opts := EngineOpts{}
regular := NewEngine(opts, q, NoLimits, log.NewNopLogger())
sharded := NewDownstreamEngine(opts, MockDownstreamer{regular}, NoLimits, log.NewNopLogger())
t.Run(tc.query, func(t *testing.T) {
params, err := NewLiteralParams(
tc.query,
start,
end,
step,
interval,
logproto.FORWARD,
uint32(limit),
nil,
)
require.NoError(t, err)
qry := regular.Query(params)
ctx := user.InjectOrgID(context.Background(), "fake")
mapper := NewShardMapper(ConstantShards(shards), nilShardMetrics, []string{ShardQuantileOverTime})
_, _, mapped, err := mapper.Parse(params.GetExpression())
require.NoError(t, err)
shardedQry := sharded.Query(ctx, ParamsWithExpressionOverride{
Params: params,
ExpressionOverride: mapped,
})
res, err := qry.Exec(ctx)
require.NoError(t, err)
shardedRes, err := shardedQry.Exec(ctx)
require.NoError(t, err)
relativeError(t, res.Data.(promql.Matrix), shardedRes.Data.(promql.Matrix), tc.realtiveError)
})
}
}
func TestShardCounter(t *testing.T) {
var (
shards = 3
@ -151,7 +216,7 @@ func TestShardCounter(t *testing.T) {
require.NoError(t, err)
ctx := user.InjectOrgID(context.Background(), "fake")
mapper := NewShardMapper(ConstantShards(shards), nilShardMetrics)
mapper := NewShardMapper(ConstantShards(shards), nilShardMetrics, []string{ShardQuantileOverTime})
noop, _, mapped, err := mapper.Parse(params.GetExpression())
require.NoError(t, err)
@ -412,13 +477,13 @@ func TestRangeMappingEquivalence(t *testing.T) {
// Regular engine
qry := regularEngine.Query(params)
res, err := qry.Exec(ctx)
require.Nil(t, err)
require.NoError(t, err)
// Downstream engine - split by range
rangeMapper, err := NewRangeMapper(tc.splitByInterval, nilRangeMetrics, NewMapperStats())
require.Nil(t, err)
require.NoError(t, err)
noop, rangeExpr, err := rangeMapper.Parse(syntax.MustParseExpr(tc.query))
require.Nil(t, err)
require.NoError(t, err)
require.False(t, noop, "downstream engine cannot execute noop")
@ -451,3 +516,22 @@ func approximatelyEquals(t *testing.T, as, bs promql.Matrix) {
require.Equalf(t, a, b, "metric %s differs from %s at %d", a.Metric, b.Metric, i)
}
}
func relativeError(t *testing.T, expected, actual promql.Matrix, alpha float64) {
require.Len(t, actual, len(expected))
for i := 0; i < len(expected); i++ {
expectedSeries := expected[i]
actualSeries := actual[i]
require.Equal(t, expectedSeries.Metric, actualSeries.Metric)
require.Lenf(t, actualSeries.Floats, len(expectedSeries.Floats), "for series %s", expectedSeries.Metric)
e := make([]float64, len(expectedSeries.Floats))
a := make([]float64, len(expectedSeries.Floats))
for j := 0; j < len(expectedSeries.Floats); j++ {
e[j] = expectedSeries.Floats[j].F
a[j] = actualSeries.Floats[j].F
}
require.InEpsilonSlice(t, e, a, alpha)
}
}

@ -2,6 +2,7 @@ package logql
import (
"context"
"errors"
"flag"
"fmt"
"math"
@ -83,7 +84,14 @@ func (s SelectLogParams) String() string {
// LogSelector returns the LogSelectorExpr from the SelectParams.
// The `LogSelectorExpr` can then returns all matchers and filters to use for that request.
func (s SelectLogParams) LogSelector() (syntax.LogSelectorExpr, error) {
return syntax.ParseLogSelector(s.Selector, true)
if s.QueryRequest.Plan == nil {
return nil, errors.New("query plan is empty")
}
expr, ok := s.QueryRequest.Plan.AST.(syntax.LogSelectorExpr)
if !ok {
return nil, errors.New("only log selector is supported")
}
return expr, nil
}
type SelectSampleParams struct {
@ -93,13 +101,20 @@ type SelectSampleParams struct {
// Expr returns the SampleExpr from the SelectSampleParams.
// The `LogSelectorExpr` can then returns all matchers and filters to use for that request.
func (s SelectSampleParams) Expr() (syntax.SampleExpr, error) {
return syntax.ParseSampleExpr(s.Selector)
if s.SampleQueryRequest.Plan == nil {
return nil, errors.New("query plan is empty")
}
expr, ok := s.SampleQueryRequest.Plan.AST.(syntax.SampleExpr)
if !ok {
return nil, errors.New("only sample expression supported")
}
return expr, nil
}
// LogSelector returns the LogSelectorExpr from the SelectParams.
// The `LogSelectorExpr` can then returns all matchers and filters to use for that request.
func (s SelectSampleParams) LogSelector() (syntax.LogSelectorExpr, error) {
expr, err := syntax.ParseSampleExpr(s.Selector)
expr, err := s.Expr()
if err != nil {
return nil, err
}
@ -327,21 +342,37 @@ func (q *query) evalSample(ctx context.Context, expr syntax.SampleExpr) (promql_
if err != nil {
return nil, err
}
stepEvaluator, err := q.evaluator.NewStepEvaluator(ctx, q.evaluator, expr, q.params)
if err != nil {
return nil, err
}
defer util.LogErrorWithContext(ctx, "closing SampleExpr", stepEvaluator.Close)
maxSeriesCapture := func(id string) int { return q.limits.MaxQuerySeries(ctx, id) }
maxSeries := validation.SmallestPositiveIntPerTenant(tenantIDs, maxSeriesCapture)
seriesIndex := map[uint64]*promql.Series{}
next, ts, r := stepEvaluator.Next()
if stepEvaluator.Error() != nil {
return nil, stepEvaluator.Error()
}
if next && r != nil {
switch vec := r.(type) {
case SampleVector:
maxSeriesCapture := func(id string) int { return q.limits.MaxQuerySeries(ctx, id) }
maxSeries := validation.SmallestPositiveIntPerTenant(tenantIDs, maxSeriesCapture)
return q.JoinSampleVector(next, ts, vec, stepEvaluator, maxSeries)
case ProbabilisticQuantileVector:
return JoinQuantileSketchVector(next, vec, stepEvaluator)
default:
return nil, fmt.Errorf("unsupported result type: %T", r)
}
}
return nil, nil
}
func (q *query) JoinSampleVector(next bool, ts int64, r StepResult, stepEvaluator StepEvaluator, maxSeries int) (promql_parser.Value, error) {
seriesIndex := map[uint64]*promql.Series{}
vec := promql.Vector{}
if next {
vec = r.SampleVector()

@ -12,6 +12,7 @@ import (
"time"
"github.com/grafana/loki/pkg/logqlmodel/metadata"
"github.com/grafana/loki/pkg/querier/plan"
"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase/definitions"
"github.com/go-kit/log"
@ -64,8 +65,15 @@ func TestEngine_LogsRateUnwrap(t *testing.T) {
{newSeries(testSize, offset(46, constantValue(1)), `{app="foo"}`)},
},
[]SelectSampleParams{
{&logproto.SampleQueryRequest{Start: time.Unix(30, 0), End: time.Unix(60, 0), Selector: `rate({app="foo"} | unwrap foo[30s])`}},
},
{&logproto.SampleQueryRequest{
Start: time.Unix(30, 0),
End: time.Unix(60, 0),
Selector: `rate({app="foo"} | unwrap foo[30s])`,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`rate({app="foo"} | unwrap foo[30s])`),
},
},
}},
// there are 15 samples (from 47 to 61) matched from the generated series
// SUM(n=47, 61, 1) = 15
// 15 / 30 = 0.5
@ -82,7 +90,14 @@ func TestEngine_LogsRateUnwrap(t *testing.T) {
{newSeries(testSize, offset(46, incValue(1)), `{app="foo"}`)},
},
[]SelectSampleParams{
{&logproto.SampleQueryRequest{Start: time.Unix(30, 0), End: time.Unix(60, 0), Selector: `rate({app="foo"} | unwrap foo[30s])`}},
{&logproto.SampleQueryRequest{
Start: time.Unix(30, 0),
End: time.Unix(60, 0),
Selector: `rate({app="foo"} | unwrap foo[30s])`,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`rate({app="foo"} | unwrap foo[30s])`),
},
}},
},
// there are 15 samples (from 47 to 61) matched from the generated series
// SUM(n=47, 61, n) = (47+48+...+61) = 810
@ -100,7 +115,14 @@ func TestEngine_LogsRateUnwrap(t *testing.T) {
{newSeries(testSize, offset(46, constantValue(1)), `{app="foo"}`)},
},
[]SelectSampleParams{
{&logproto.SampleQueryRequest{Start: time.Unix(30, 0), End: time.Unix(60, 0), Selector: `rate_counter({app="foo"} | unwrap foo[30s])`}},
{&logproto.SampleQueryRequest{
Start: time.Unix(30, 0),
End: time.Unix(60, 0),
Selector: `rate_counter({app="foo"} | unwrap foo[30s])`,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`rate_counter({app="foo"} | unwrap foo[30s])`),
},
}},
},
// there are 15 samples (from 47 to 61) matched from the generated series
// (1 - 1) / 30 = 0
@ -2669,6 +2691,9 @@ func newQuerierRecorder(t *testing.T, data interface{}, params interface{}) *que
if streamsIn, ok := data.([][]logproto.Stream); ok {
if paramsIn, ok2 := params.([]SelectLogParams); ok2 {
for i, p := range paramsIn {
p.Plan = &plan.QueryPlan{
AST: syntax.MustParseExpr(p.Selector),
}
streams[paramsID(p)] = streamsIn[i]
}
}
@ -2678,6 +2703,9 @@ func newQuerierRecorder(t *testing.T, data interface{}, params interface{}) *que
if seriesIn, ok := data.([][]logproto.Series); ok {
if paramsIn, ok2 := params.([]SelectSampleParams); ok2 {
for i, p := range paramsIn {
p.Plan = &plan.QueryPlan{
AST: syntax.MustParseExpr(p.Selector),
}
series[paramsID(p)] = seriesIn[i]
}
}

@ -17,6 +17,7 @@ import (
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/logqlmodel"
"github.com/grafana/loki/pkg/querier/plan"
"github.com/grafana/loki/pkg/util"
)
@ -210,6 +211,9 @@ func (ev *DefaultEvaluator) NewIterator(ctx context.Context, expr syntax.LogSele
Direction: q.Direction(),
Selector: expr.String(),
Shards: q.Shards(),
Plan: &plan.QueryPlan{
AST: expr,
},
},
}
@ -238,6 +242,9 @@ func (ev *DefaultEvaluator) NewStepEvaluator(
End: q.End().Add(-rangExpr.Left.Offset),
Selector: e.String(), // intentionally send the vector for reducing labels.
Shards: q.Shards(),
Plan: &plan.QueryPlan{
AST: expr,
},
},
})
if err != nil {
@ -254,6 +261,9 @@ func (ev *DefaultEvaluator) NewStepEvaluator(
End: q.End().Add(-e.Left.Offset),
Selector: expr.String(),
Shards: q.Shards(),
Plan: &plan.QueryPlan{
AST: expr,
},
},
})
if err != nil {
@ -515,17 +525,18 @@ func newRangeAggEvaluator(
q Params,
o time.Duration,
) (StepEvaluator, error) {
switch expr.Operation {
case syntax.OpRangeTypeAbsent:
iter, err := newRangeVectorIterator(
it, expr,
expr.Left.Interval.Nanoseconds(),
q.Step().Nanoseconds(),
q.Start().UnixNano(), q.End().UnixNano(), o.Nanoseconds(),
)
if err != nil {
return nil, err
}
iter, err := newRangeVectorIterator(
it, expr,
expr.Left.Interval.Nanoseconds(),
q.Step().Nanoseconds(),
q.Start().UnixNano(), q.End().UnixNano(), o.Nanoseconds(),
)
if err != nil {
return nil, err
}
if expr.Operation == syntax.OpRangeTypeAbsent {
absentLabels, err := absentLabels(expr)
if err != nil {
return nil, err
@ -534,10 +545,32 @@ func newRangeAggEvaluator(
iter: iter,
lbs: absentLabels,
}, nil
case syntax.OpRangeTypeQuantileSketch:
iter := newQuantileSketchIterator(
it,
expr.Left.Interval.Nanoseconds(),
q.Step().Nanoseconds(),
q.Start().UnixNano(), q.End().UnixNano(), o.Nanoseconds(),
)
return &QuantileSketchStepEvaluator{
iter: iter,
}, nil
default:
iter, err := newRangeVectorIterator(
it, expr,
expr.Left.Interval.Nanoseconds(),
q.Step().Nanoseconds(),
q.Start().UnixNano(), q.End().UnixNano(), o.Nanoseconds(),
)
if err != nil {
return nil, err
}
return &RangeVectorEvaluator{
iter: iter,
}, nil
}
return &RangeVectorEvaluator{
iter: iter,
}, nil
}
type RangeVectorEvaluator struct {

@ -1,5 +1,9 @@
package logql
// MaxChildrenDisplay defines the maximum number of children that should be
// shown by explain.
const MaxChildrenDisplay = 3
func (e *LiteralStepEvaluator) Explain(parent Node) {
b := parent.Child("Literal")
e.nextEv.Explain(b)
@ -25,7 +29,7 @@ func (e *VectorStepEvaluator) Explain(parent Node) {
func (e *ConcatStepEvaluator) Explain(parent Node) {
b := parent.Child("Concat")
if len(e.evaluators) < 3 {
if len(e.evaluators) < MaxChildrenDisplay {
for _, child := range e.evaluators {
child.Explain(b)
}

@ -28,7 +28,7 @@ func TestExplain(t *testing.T) {
defaultEv := NewDefaultEvaluator(querier, 30*time.Second)
downEv := &DownstreamEvaluator{Downstreamer: MockDownstreamer{regular}, defaultEvaluator: defaultEv}
mapper := NewShardMapper(ConstantShards(4), nilShardMetrics)
mapper := NewShardMapper(ConstantShards(4), nilShardMetrics, []string{ShardQuantileOverTime})
_, _, expr, err := mapper.Parse(syntax.MustParseExpr(query))
require.NoError(t, err)

@ -8,7 +8,7 @@ func optimizeSampleExpr(expr syntax.SampleExpr) (syntax.SampleExpr, error) {
// we skip sharding AST for now, it's not easy to clone them since they are not part of the language.
expr.Walk(func(e syntax.Expr) {
switch e.(type) {
case *ConcatSampleExpr, *DownstreamSampleExpr:
case *ConcatSampleExpr, *DownstreamSampleExpr, *QuantileSketchEvalExpr, *QuantileSketchMergeExpr:
skip = true
return
}
@ -16,9 +16,7 @@ func optimizeSampleExpr(expr syntax.SampleExpr) (syntax.SampleExpr, error) {
if skip {
return expr, nil
}
// clone the expr.
q := expr.String()
expr, err := syntax.ParseSampleExpr(q)
expr, err := syntax.Clone[syntax.SampleExpr](expr)
if err != nil {
return nil, err
}

@ -0,0 +1,413 @@
package logql
import (
"fmt"
"time"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql"
promql_parser "github.com/prometheus/prometheus/promql/parser"
"github.com/grafana/loki/pkg/iter"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql/sketch"
"github.com/grafana/loki/pkg/logqlmodel"
)
const (
QuantileSketchMatrixType = "QuantileSketchMatrix"
)
type ProbabilisticQuantileVector []ProbabilisticQuantileSample
type ProbabilisticQuantileMatrix []ProbabilisticQuantileVector
func (q ProbabilisticQuantileVector) Merge(right ProbabilisticQuantileVector) (ProbabilisticQuantileVector, error) {
// labels hash to vector index map
groups := make(map[uint64]int)
for i, sample := range q {
groups[sample.Metric.Hash()] = i
}
for _, sample := range right {
i, ok := groups[sample.Metric.Hash()]
if !ok {
q = append(q, sample)
continue
}
_, err := q[i].F.Merge(sample.F)
if err != nil {
return q, err
}
}
return q, nil
}
func (ProbabilisticQuantileVector) SampleVector() promql.Vector {
return promql.Vector{}
}
func (q ProbabilisticQuantileVector) QuantileSketchVec() ProbabilisticQuantileVector {
return q
}
func (q ProbabilisticQuantileVector) ToProto() *logproto.QuantileSketchVector {
samples := make([]*logproto.QuantileSketchSample, len(q))
for i, sample := range q {
samples[i] = sample.ToProto()
}
return &logproto.QuantileSketchVector{Samples: samples}
}
func ProbabilisticQuantileVectorFromProto(proto *logproto.QuantileSketchVector) (ProbabilisticQuantileVector, error) {
out := make([]ProbabilisticQuantileSample, len(proto.Samples))
var s ProbabilisticQuantileSample
var err error
for i, sample := range proto.Samples {
s, err = probabilisticQuantileSampleFromProto(sample)
if err != nil {
return ProbabilisticQuantileVector{}, err
}
out[i] = s
}
return out, nil
}
func (ProbabilisticQuantileMatrix) String() string {
return "QuantileSketchMatrix()"
}
func (ProbabilisticQuantileMatrix) Type() promql_parser.ValueType { return QuantileSketchMatrixType }
func (m ProbabilisticQuantileMatrix) ToProto() *logproto.QuantileSketchMatrix {
values := make([]*logproto.QuantileSketchVector, len(m))
for i, vec := range m {
values[i] = vec.ToProto()
}
return &logproto.QuantileSketchMatrix{Values: values}
}
func ProbabilisticQuantileMatrixFromProto(proto *logproto.QuantileSketchMatrix) (ProbabilisticQuantileMatrix, error) {
out := make([]ProbabilisticQuantileVector, len(proto.Values))
var s ProbabilisticQuantileVector
var err error
for i, v := range proto.Values {
s, err = ProbabilisticQuantileVectorFromProto(v)
if err != nil {
return ProbabilisticQuantileMatrix{}, err
}
out[i] = s
}
return out, nil
}
type QuantileSketchStepEvaluator struct {
iter RangeVectorIterator
err error
}
func (e *QuantileSketchStepEvaluator) Next() (bool, int64, StepResult) {
next := e.iter.Next()
if !next {
return false, 0, ProbabilisticQuantileVector{}
}
ts, r := e.iter.At()
vec := r.QuantileSketchVec()
for _, s := range vec {
// Errors are not allowed in metrics unless they've been specifically requested.
if s.Metric.Has(logqlmodel.ErrorLabel) && s.Metric.Get(logqlmodel.PreserveErrorLabel) != "true" {
e.err = logqlmodel.NewPipelineErr(s.Metric)
return false, 0, ProbabilisticQuantileVector{}
}
}
return true, ts, vec
}
func (e *QuantileSketchStepEvaluator) Close() error { return e.iter.Close() }
func (e *QuantileSketchStepEvaluator) Error() error {
if e.err != nil {
return e.err
}
return e.iter.Error()
}
func (e *QuantileSketchStepEvaluator) Explain(parent Node) {
parent.Child("QuantileSketch")
}
func newQuantileSketchIterator(
it iter.PeekingSampleIterator,
selRange, step, start, end, offset int64) RangeVectorIterator {
inner := &batchRangeVectorIterator{
iter: it,
step: step,
end: end,
selRange: selRange,
metrics: map[string]labels.Labels{},
window: map[string]*promql.Series{},
agg: nil,
current: start - step, // first loop iteration will set it to start
offset: offset,
}
return &quantileSketchBatchRangeVectorIterator{
batchRangeVectorIterator: inner,
}
}
//batch
type ProbabilisticQuantileSample struct {
T int64
F sketch.QuantileSketch
Metric labels.Labels
}
func (q ProbabilisticQuantileSample) ToProto() *logproto.QuantileSketchSample {
metric := make([]*logproto.LabelPair, len(q.Metric))
for i, m := range q.Metric {
metric[i] = &logproto.LabelPair{Name: m.Name, Value: m.Value}
}
sketch := q.F.ToProto()
return &logproto.QuantileSketchSample{
F: sketch,
TimestampMs: q.T,
Metric: metric,
}
}
func probabilisticQuantileSampleFromProto(proto *logproto.QuantileSketchSample) (ProbabilisticQuantileSample, error) {
s, err := sketch.QuantileSketchFromProto(proto.F)
if err != nil {
return ProbabilisticQuantileSample{}, err
}
out := ProbabilisticQuantileSample{
T: proto.TimestampMs,
F: s,
Metric: make(labels.Labels, len(proto.Metric)),
}
for i, p := range proto.Metric {
out.Metric[i] = labels.Label{Name: p.Name, Value: p.Value}
}
return out, nil
}
type quantileSketchBatchRangeVectorIterator struct {
*batchRangeVectorIterator
at []ProbabilisticQuantileSample
}
func (r *quantileSketchBatchRangeVectorIterator) At() (int64, StepResult) {
if r.at == nil {
r.at = make([]ProbabilisticQuantileSample, 0, len(r.window))
}
r.at = r.at[:0]
// convert ts from nano to milli seconds as the iterator work with nanoseconds
ts := r.current/1e+6 + r.offset/1e+6
for _, series := range r.window {
r.at = append(r.at, ProbabilisticQuantileSample{
F: r.agg(series.Floats),
T: ts,
Metric: series.Metric,
})
}
return ts, ProbabilisticQuantileVector(r.at)
}
func (r *quantileSketchBatchRangeVectorIterator) agg(samples []promql.FPoint) sketch.QuantileSketch {
s := sketch.NewDDSketch()
for _, v := range samples {
// The sketch from the underlying sketch package we are using
// cannot return an error when calling Add.
s.Add(v.F) //nolint:errcheck
}
return s
}
// JoinQuantileSketchVector joins the results from stepEvaluator into a ProbabilisticQuantileMatrix.
func JoinQuantileSketchVector(next bool, r StepResult, stepEvaluator StepEvaluator) (promql_parser.Value, error) {
vec := r.QuantileSketchVec()
if stepEvaluator.Error() != nil {
return nil, stepEvaluator.Error()
}
result := make([]ProbabilisticQuantileVector, 0)
for next {
result = append(result, vec)
next, _, r = stepEvaluator.Next()
vec = r.QuantileSketchVec()
if stepEvaluator.Error() != nil {
return nil, stepEvaluator.Error()
}
}
return ProbabilisticQuantileMatrix(result), stepEvaluator.Error()
}
// QuantileSketchMatrixStepEvaluator steps through a matrix of quantile sketch
// vectors, ie t-digest or DDSketch structures per time step.
type QuantileSketchMatrixStepEvaluator struct {
start, end, ts time.Time
step time.Duration
m ProbabilisticQuantileMatrix
}
func NewQuantileSketchMatrixStepEvaluator(m ProbabilisticQuantileMatrix, params Params) *QuantileSketchMatrixStepEvaluator {
var (
start = params.Start()
end = params.End()
step = params.Step()
)
return &QuantileSketchMatrixStepEvaluator{
start: start,
end: end,
ts: start.Add(-step), // will be corrected on first Next() call
step: step,
m: m,
}
}
func (m *QuantileSketchMatrixStepEvaluator) Next() (bool, int64, StepResult) {
m.ts = m.ts.Add(m.step)
if m.ts.After(m.end) {
return false, 0, nil
}
ts := m.ts.UnixNano() / int64(time.Millisecond)
if len(m.m) == 0 {
return false, 0, nil
}
vec := m.m[0]
// Reset for next step
m.m = m.m[1:]
return true, ts, vec
}
func (*QuantileSketchMatrixStepEvaluator) Close() error { return nil }
func (*QuantileSketchMatrixStepEvaluator) Error() error { return nil }
func (*QuantileSketchMatrixStepEvaluator) Explain(parent Node) {
parent.Child("QuantileSketchMatrix")
}
// QuantileSketchMergeStepEvaluator merges multiple quantile sketches into one for each
// step.
type QuantileSketchMergeStepEvaluator struct {
evaluators []StepEvaluator
err error
}
func NewQuantileSketchMergeStepEvaluator(evaluators []StepEvaluator) *QuantileSketchMergeStepEvaluator {
return &QuantileSketchMergeStepEvaluator{
evaluators: evaluators,
err: nil,
}
}
func (e *QuantileSketchMergeStepEvaluator) Next() (bool, int64, StepResult) {
ok, ts, r := e.evaluators[0].Next()
var cur ProbabilisticQuantileVector
if ok {
cur = r.QuantileSketchVec()
}
if len(e.evaluators) == 1 {
return ok, ts, cur
}
for _, eval := range e.evaluators[1:] {
ok, nextTs, vec := eval.Next()
if ok {
if cur == nil {
cur = vec.QuantileSketchVec()
} else {
if ts != nextTs {
e.err = fmt.Errorf("timestamps of sketches differ: %d!=%d", ts, nextTs)
return false, 0, nil
}
_, e.err = cur.Merge(vec.QuantileSketchVec())
if e.err != nil {
return false, 0, nil
}
}
}
}
return ok, ts, cur
}
func (*QuantileSketchMergeStepEvaluator) Close() error { return nil }
func (e *QuantileSketchMergeStepEvaluator) Error() error { return e.err }
func (e *QuantileSketchMergeStepEvaluator) Explain(parent Node) {
b := parent.Child("QuantileSketchMerge")
if len(e.evaluators) < MaxChildrenDisplay {
for _, child := range e.evaluators {
child.Explain(b)
}
} else {
e.evaluators[0].Explain(b)
b.Child("...")
e.evaluators[len(e.evaluators)-1].Explain(b)
}
}
// QuantileSketchVectorStepEvaluator evaluates a quantile sketch into a
// promql.Vector.
type QuantileSketchVectorStepEvaluator struct {
inner StepEvaluator
quantile float64
}
var _ StepEvaluator = NewQuantileSketchVectorStepEvaluator(nil, 0)
func NewQuantileSketchVectorStepEvaluator(inner StepEvaluator, quantile float64) *QuantileSketchVectorStepEvaluator {
return &QuantileSketchVectorStepEvaluator{
inner: inner,
quantile: quantile,
}
}
func (e *QuantileSketchVectorStepEvaluator) Next() (bool, int64, StepResult) {
ok, ts, r := e.inner.Next()
quantileSketchVec := r.QuantileSketchVec()
vec := make(promql.Vector, len(quantileSketchVec))
for i, quantileSketch := range quantileSketchVec {
f, _ := quantileSketch.F.Quantile(e.quantile)
vec[i] = promql.Sample{
T: quantileSketch.T,
F: f,
Metric: quantileSketch.Metric,
}
}
return ok, ts, SampleVector(vec)
}
func (*QuantileSketchVectorStepEvaluator) Close() error { return nil }
func (*QuantileSketchVectorStepEvaluator) Error() error { return nil }
func (e *QuantileSketchVectorStepEvaluator) Explain(parent Node) {
b := parent.Child("QuantileSketchVector")
e.inner.Explain(b)
}

@ -0,0 +1,109 @@
package logql
import (
"errors"
"testing"
"github.com/prometheus/prometheus/model/labels"
"github.com/stretchr/testify/require"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql/sketch"
"github.com/grafana/loki/pkg/logqlmodel"
)
func TestProbabilisticMQuantileMatrixSerialization(t *testing.T) {
emptySketch := sketch.NewDDSketch()
ddsketchBytes := make([]byte, 0)
emptySketch.Encode(&ddsketchBytes, false)
matrix := ProbabilisticQuantileMatrix([]ProbabilisticQuantileVector{
[]ProbabilisticQuantileSample{
{T: 0, F: emptySketch, Metric: []labels.Label{{Name: "foo", Value: "bar"}}},
},
})
proto := &logproto.QuantileSketchMatrix{
Values: []*logproto.QuantileSketchVector{
{
Samples: []*logproto.QuantileSketchSample{
{
TimestampMs: 0,
F: &logproto.QuantileSketch{Sketch: &logproto.QuantileSketch_Ddsketch{Ddsketch: ddsketchBytes}},
Metric: []*logproto.LabelPair{{Name: "foo", Value: "bar"}},
},
},
},
},
}
actual := matrix.ToProto()
require.Equal(t, proto, actual)
_, err := ProbabilisticQuantileMatrixFromProto(actual)
require.NoError(t, err)
}
func TestQuantileSketchStepEvaluatorError(t *testing.T) {
iter := errorRangeVectorIterator{
result: ProbabilisticQuantileVector([]ProbabilisticQuantileSample{
{T: 43, F: nil, Metric: labels.Labels{{Name: logqlmodel.ErrorLabel, Value: "my error"}}},
}),
}
ev := QuantileSketchStepEvaluator{
iter: iter,
}
ok, _, _ := ev.Next()
require.False(t, ok)
err := ev.Error()
require.ErrorContains(t, err, "my error")
}
func TestJoinQuantileSketchVectorError(t *testing.T) {
result := ProbabilisticQuantileVector{}
ev := errorStepEvaluator{
err: errors.New("could not evaluate"),
}
_, err := JoinQuantileSketchVector(true, result, ev)
require.ErrorContains(t, err, "could not evaluate")
}
type errorRangeVectorIterator struct {
err error
result StepResult
}
func (e errorRangeVectorIterator) Next() bool {
return e.result != nil
}
func (e errorRangeVectorIterator) At() (int64, StepResult) {
return 0, e.result
}
func (errorRangeVectorIterator) Close() error {
return nil
}
func (e errorRangeVectorIterator) Error() error {
return e.err
}
type errorStepEvaluator struct {
err error
}
func (errorStepEvaluator) Next() (ok bool, ts int64, r StepResult) {
return false, 0, nil
}
func (errorStepEvaluator) Close() error {
return nil
}
func (e errorStepEvaluator) Error() error {
return e.err
}
func (e errorStepEvaluator) Explain(Node) {}

@ -3,6 +3,8 @@ package logql
import (
"context"
"fmt"
"math/rand"
"sort"
"testing"
"time"
@ -13,7 +15,9 @@ import (
"github.com/grafana/loki/pkg/iter"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql/sketch"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/logql/vector"
)
var samples = []logproto.Sample{
@ -442,3 +446,69 @@ func value(value float64, negative bool) float64 {
}
return value
}
func TestQuantiles(t *testing.T) {
// v controls the distribution of values along the curve, a greater v
// value means there's a large distance between generated values
vs := []float64{1.0, 5.0, 10.0}
// s controls the exponential curve of the distribution
// the higher the s values the faster the drop off from max value to lesser values
// s must be > 1.0
ss := []float64{1.01, 2.0, 3.0, 4.0}
// T-Digest is too big for 1_000 samples. However, we did not optimize
// the format for size.
nSamples := []int{5_000, 10_000, 100_000, 1_000_000}
factories := []struct {
newSketch sketch.QuantileSketchFactory
name string
relativeError float64
}{
{newSketch: func() sketch.QuantileSketch { return sketch.NewDDSketch() }, name: "DDSketch", relativeError: 0.02},
{newSketch: sketch.NewTDigestSketch, name: "T-Digest", relativeError: 0.05},
}
for _, tc := range factories {
for _, samplesCount := range nSamples {
for _, s := range ss {
for _, v := range vs {
t.Run(fmt.Sprintf("sketch=%s, s=%.2f, v=%.2f, events=%d", tc.name, s, v, samplesCount), func(t *testing.T) {
sk := tc.newSketch()
r := rand.New(rand.NewSource(42))
z := rand.NewZipf(r, s, v, 1_000)
values := make(vector.HeapByMaxValue, 0)
for i := 0; i < samplesCount; i++ {
value := float64(z.Uint64())
values = append(values, promql.Sample{F: value})
err := sk.Add(value)
require.NoError(t, err)
}
sort.Sort(values)
// Size
var buf []byte
var err error
switch s := sk.(type) {
case *sketch.DDSketchQuantile:
buf, err = proto.Marshal(s.DDSketch.ToProto())
require.NoError(t, err)
case *sketch.TDigestQuantile:
buf, err = proto.Marshal(s.ToProto())
require.NoError(t, err)
}
require.Less(t, len(buf), samplesCount*8)
// Accuracy
expected := Quantile(0.99, values)
actual, err := sk.Quantile(0.99)
require.NoError(t, err)
require.InEpsilonf(t, expected, actual, tc.relativeError, "expected quantile %f, actual quantile %f", expected, actual)
})
}
}
}
}
}

@ -25,15 +25,27 @@ type ConstantShards int
func (s ConstantShards) Shards(_ syntax.Expr) (int, uint64, error) { return int(s), 0, nil }
func (s ConstantShards) GetStats(_ syntax.Expr) (stats.Stats, error) { return stats.Stats{}, nil }
const (
ShardQuantileOverTime = "quantile_over_time"
)
type ShardMapper struct {
shards ShardResolver
metrics *MapperMetrics
shards ShardResolver
metrics *MapperMetrics
quantileOverTimeSharding bool
}
func NewShardMapper(resolver ShardResolver, metrics *MapperMetrics) ShardMapper {
func NewShardMapper(resolver ShardResolver, metrics *MapperMetrics, shardAggregation []string) ShardMapper {
quantileOverTimeSharding := false
for _, a := range shardAggregation {
if a == ShardQuantileOverTime {
quantileOverTimeSharding = true
}
}
return ShardMapper{
shards: resolver,
metrics: metrics,
shards: resolver,
metrics: metrics,
quantileOverTimeSharding: quantileOverTimeSharding,
}
}
@ -158,11 +170,11 @@ func (m ShardMapper) mapSampleExpr(expr syntax.SampleExpr, r *downstreamRecorder
},
}, bytesPerShard, nil
}
for i := shards - 1; i >= 0; i-- {
for shard := shards - 1; shard >= 0; shard-- {
head = &ConcatSampleExpr{
DownstreamSampleExpr: DownstreamSampleExpr{
shard: &astmapper.ShardAnnotation{
Shard: i,
Shard: shard,
Of: shards,
},
SampleExpr: expr,
@ -374,7 +386,7 @@ func (m ShardMapper) mapRangeAggregationExpr(expr *syntax.RangeAggregationExpr,
return m.mapSampleExpr(expr, r)
}
// avg_overtime() by (foo) -> sum by (foo) (sum_over_time()) / sum by (foo) (count_over_time())
// avg_over_time() by (foo) -> sum by (foo) (sum_over_time()) / sum by (foo) (count_over_time())
lhs, lhsBytesPerShard, err := m.mapVectorAggregationExpr(&syntax.VectorAggregationExpr{
Left: &syntax.RangeAggregationExpr{
Left: expr.Left,
@ -414,6 +426,43 @@ func (m ShardMapper) mapRangeAggregationExpr(expr *syntax.RangeAggregationExpr,
Op: syntax.OpTypeDiv,
}, bytesPerShard, nil
case syntax.OpRangeTypeQuantile:
potentialConflict := syntax.ReducesLabels(expr)
if !potentialConflict && (expr.Grouping == nil || expr.Grouping.Noop()) {
return m.mapSampleExpr(expr, r)
}
shards, bytesPerShard, err := m.shards.Shards(expr)
if err != nil {
return nil, 0, err
}
if shards == 0 || !m.quantileOverTimeSharding {
return m.mapSampleExpr(expr, r)
}
// quantile_over_time() by (foo) ->
// quantile_sketch_eval(quantile_merge by (foo)
// (__quantile_sketch_over_time__() by (foo)))
downstreams := make([]DownstreamSampleExpr, 0, shards)
expr.Operation = syntax.OpRangeTypeQuantileSketch
for shard := shards - 1; shard >= 0; shard-- {
downstreams = append(downstreams, DownstreamSampleExpr{
shard: &astmapper.ShardAnnotation{
Shard: shard,
Of: shards,
},
SampleExpr: expr,
})
}
return &QuantileSketchEvalExpr{
quantileMergeExpr: &QuantileSketchMergeExpr{
downstreams: downstreams,
},
quantile: expr.Params,
}, bytesPerShard, nil
default:
// don't shard if there's not an appropriate optimization
exprStats, err := m.shards.GetStats(expr)

@ -51,7 +51,7 @@ func TestShardedStringer(t *testing.T) {
}
func TestMapSampleExpr(t *testing.T) {
m := NewShardMapper(ConstantShards(2), nilShardMetrics)
m := NewShardMapper(ConstantShards(2), nilShardMetrics, []string{ShardQuantileOverTime})
for _, tc := range []struct {
in syntax.SampleExpr
@ -113,7 +113,7 @@ func TestMapSampleExpr(t *testing.T) {
}
func TestMappingStrings(t *testing.T) {
m := NewShardMapper(ConstantShards(2), nilShardMetrics)
m := NewShardMapper(ConstantShards(2), nilShardMetrics, []string{ShardQuantileOverTime})
for _, tc := range []struct {
in string
out string
@ -418,7 +418,7 @@ func TestMappingStrings(t *testing.T) {
}
func TestMapping(t *testing.T) {
m := NewShardMapper(ConstantShards(2), nilShardMetrics)
m := NewShardMapper(ConstantShards(2), nilShardMetrics, []string{ShardQuantileOverTime})
for _, tc := range []struct {
in string
@ -1409,7 +1409,7 @@ func TestStringTrimming(t *testing.T) {
},
} {
t.Run(tc.expr.String(), func(t *testing.T) {
m := NewShardMapper(ConstantShards(tc.shards), nilShardMetrics)
m := NewShardMapper(ConstantShards(tc.shards), nilShardMetrics, []string{ShardQuantileOverTime})
_, _, mappedExpr, err := m.Parse(tc.expr)
require.Nil(t, err)
require.Equal(t, removeWhiteSpace(tc.expected), removeWhiteSpace(mappedExpr.String()))

@ -6,107 +6,10 @@ import (
"github.com/DataDog/sketches-go/ddsketch"
"github.com/influxdata/tdigest"
"github.com/prometheus/prometheus/model/labels"
promql_parser "github.com/prometheus/prometheus/promql/parser"
"github.com/grafana/loki/pkg/logproto"
)
// QuantileSketchVector represents multiple qunatile sketches at the same point in
// time.
type QuantileSketchVector []quantileSketchSample
// QuantileSketchMatrix contains multiples QuantileSketchVectors across many
// points in time.
type QuantileSketchMatrix []QuantileSketchVector
// ToProto converts a quantile sketch vector to its protobuf definition.
func (q QuantileSketchVector) ToProto() *logproto.QuantileSketchVector {
samples := make([]*logproto.QuantileSketchSample, len(q))
for i, sample := range q {
samples[i] = sample.ToProto()
}
return &logproto.QuantileSketchVector{Samples: samples}
}
func QuantileSketchVectorFromProto(proto *logproto.QuantileSketchVector) (QuantileSketchVector, error) {
out := make([]quantileSketchSample, len(proto.Samples))
var err error
for i, s := range proto.Samples {
out[i], err = quantileSketchSampleFromProto(s)
if err != nil {
return nil, err
}
}
return out, nil
}
func (QuantileSketchMatrix) String() string {
return "QuantileSketchMatrix()"
}
func (QuantileSketchMatrix) Type() promql_parser.ValueType { return "QuantileSketchMatrix" }
func (m QuantileSketchMatrix) ToProto() *logproto.QuantileSketchMatrix {
values := make([]*logproto.QuantileSketchVector, len(m))
for i, vec := range m {
values[i] = vec.ToProto()
}
return &logproto.QuantileSketchMatrix{Values: values}
}
func QuantileSketchMatrixFromProto(proto *logproto.QuantileSketchMatrix) (QuantileSketchMatrix, error) {
out := make([]QuantileSketchVector, len(proto.Values))
var err error
for i, v := range proto.Values {
out[i], err = QuantileSketchVectorFromProto(v)
if err != nil {
return nil, err
}
}
return out, nil
}
type quantileSketchSample struct {
T int64
F QuantileSketch
Metric labels.Labels
}
func (q quantileSketchSample) ToProto() *logproto.QuantileSketchSample {
metric := make([]*logproto.LabelPair, len(q.Metric))
for i, m := range q.Metric {
metric[i] = &logproto.LabelPair{Name: m.Name, Value: m.Value}
}
sketch := q.F.ToProto()
return &logproto.QuantileSketchSample{
F: sketch,
TimestampMs: q.T,
Metric: metric,
}
}
func quantileSketchSampleFromProto(proto *logproto.QuantileSketchSample) (quantileSketchSample, error) {
sketch, err := QuantileSketchFromProto(proto.F)
if err != nil {
return quantileSketchSample{}, err
}
out := quantileSketchSample{
T: proto.TimestampMs,
F: sketch,
Metric: make(labels.Labels, len(proto.Metric)),
}
for i, p := range proto.Metric {
out.Metric[i] = labels.Label{Name: p.Name, Value: p.Value}
}
return out, nil
}
// QuantileSketch estimates quantiles over time.
type QuantileSketch interface {
Add(float64) error

@ -1,81 +0,0 @@
package sketch
import (
"fmt"
"math/rand"
"sort"
"testing"
"github.com/gogo/protobuf/proto"
"github.com/prometheus/prometheus/promql"
"github.com/stretchr/testify/require"
"github.com/grafana/loki/pkg/logql"
"github.com/grafana/loki/pkg/logql/vector"
)
func TestQuantiles(t *testing.T) {
// v controls the distribution of values along the curve, a greater v
// value means there's a large distance between generated values
vs := []float64{1.0, 5.0, 10.0}
// s controls the exponential curve of the distribution
// the higher the s values the faster the drop off from max value to lesser values
// s must be > 1.0
ss := []float64{1.01, 2.0, 3.0, 4.0}
// T-Digest is too big for 1_000 samples. However, we did not optimize
// the format for size.
nSamples := []int{5_000, 10_000, 100_000, 1_000_000}
factories := []struct {
newSketch QuantileSketchFactory
name string
relativeError float64
}{
{newSketch: func() QuantileSketch { return NewDDSketch() }, name: "DDSketch", relativeError: 0.02},
{newSketch: NewTDigestSketch, name: "T-Digest", relativeError: 0.05},
}
for _, tc := range factories {
for _, samplesCount := range nSamples {
for _, s := range ss {
for _, v := range vs {
t.Run(fmt.Sprintf("sketch=%s, s=%.2f, v=%.2f, events=%d", tc.name, s, v, samplesCount), func(t *testing.T) {
sketch := tc.newSketch()
r := rand.New(rand.NewSource(42))
z := rand.NewZipf(r, s, v, 1_000)
values := make(vector.HeapByMaxValue, 0)
for i := 0; i < samplesCount; i++ {
value := float64(z.Uint64())
values = append(values, promql.Sample{F: value})
err := sketch.Add(value)
require.NoError(t, err)
}
sort.Sort(values)
// Size
var buf []byte
var err error
switch s := sketch.(type) {
case *DDSketchQuantile:
buf, err = proto.Marshal(s.DDSketch.ToProto())
require.NoError(t, err)
case *TDigestQuantile:
buf, err = proto.Marshal(s.ToProto())
require.NoError(t, err)
}
require.Less(t, len(buf), samplesCount*8)
// Accuracy
expected := logql.Quantile(0.99, values)
actual, err := sketch.Quantile(0.99)
require.NoError(t, err)
require.InEpsilonf(t, expected, actual, tc.relativeError, "expected quantile %f, actual quantile %f", expected, actual)
})
}
}
}
}
}

@ -6,6 +6,7 @@ import (
type StepResult interface {
SampleVector() promql.Vector
QuantileSketchVec() ProbabilisticQuantileVector
}
type SampleVector promql.Vector
@ -16,6 +17,10 @@ func (p SampleVector) SampleVector() promql.Vector {
return promql.Vector(p)
}
func (p SampleVector) QuantileSketchVec() ProbabilisticQuantileVector {
return ProbabilisticQuantileVector{}
}
// StepEvaluator evaluate a single step of a query.
type StepEvaluator interface {
// while Next returns a promql.Value, the only acceptable types are Scalar and Vector.

@ -37,17 +37,23 @@ type Expr interface {
func Clone[T Expr](e T) (T, error) {
var empty T
copied, err := ParseExpr(e.String())
if err != nil {
return empty, err
}
cast, ok := copied.(T)
v := &cloneVisitor{}
e.Accept(v)
cast, ok := v.cloned.(T)
if !ok {
return empty, fmt.Errorf("unpexpected type of cloned expression: want %T, got %T", empty, copied)
return empty, fmt.Errorf("unexpected type of cloned expression: want %T, got %T", empty, v.cloned)
}
return cast, nil
}
func MustClone[T Expr](e T) T {
copied, err := Clone[T](e)
if err != nil {
panic(err)
}
return copied
}
// implicit holds default implementations
type implicit struct{}
@ -1156,6 +1162,11 @@ const (
// parser flags
OpStrict = "--strict"
OpKeepEmpty = "--keep-empty"
// internal expressions not represented in LogQL. These are used to
// evaluate expressions differently resulting in intermediate formats
// that are not consumable by LogQL clients but are used for sharding.
OpRangeTypeQuantileSketch = "__quantile_sketch_over_time__"
)
func IsComparisonOperator(op string) bool {
@ -1204,7 +1215,7 @@ type RangeAggregationExpr struct {
func newRangeAggregationExpr(left *LogRange, operation string, gr *Grouping, stringParams *string) SampleExpr {
var params *float64
if stringParams != nil {
if operation != OpRangeTypeQuantile {
if operation != OpRangeTypeQuantile && operation != OpRangeTypeQuantileSketch {
return &RangeAggregationExpr{err: logqlmodel.NewParseError(fmt.Sprintf("parameter %s not supported for operation %s", *stringParams, operation), 0, 0)}
}
var err error
@ -1259,7 +1270,7 @@ func (e *RangeAggregationExpr) MatcherGroups() ([]MatcherRange, error) {
func (e RangeAggregationExpr) validate() error {
if e.Grouping != nil {
switch e.Operation {
case OpRangeTypeAvg, OpRangeTypeStddev, OpRangeTypeStdvar, OpRangeTypeQuantile, OpRangeTypeMax, OpRangeTypeMin, OpRangeTypeFirst, OpRangeTypeLast:
case OpRangeTypeAvg, OpRangeTypeStddev, OpRangeTypeStdvar, OpRangeTypeQuantile, OpRangeTypeQuantileSketch, OpRangeTypeMax, OpRangeTypeMin, OpRangeTypeFirst, OpRangeTypeLast:
default:
return fmt.Errorf("grouping not allowed for %s aggregation", e.Operation)
}
@ -1268,7 +1279,7 @@ func (e RangeAggregationExpr) validate() error {
switch e.Operation {
case OpRangeTypeAvg, OpRangeTypeSum, OpRangeTypeMax, OpRangeTypeMin, OpRangeTypeStddev,
OpRangeTypeStdvar, OpRangeTypeQuantile, OpRangeTypeRate, OpRangeTypeRateCounter,
OpRangeTypeAbsent, OpRangeTypeFirst, OpRangeTypeLast:
OpRangeTypeAbsent, OpRangeTypeFirst, OpRangeTypeLast, OpRangeTypeQuantileSketch:
return nil
default:
return fmt.Errorf("invalid aggregation %s with unwrap", e.Operation)
@ -2128,6 +2139,7 @@ var shardableOps = map[string]bool{
OpRangeTypeSum: true,
OpRangeTypeMax: true,
OpRangeTypeMin: true,
OpRangeTypeQuantile: true,
// binops - arith
OpTypeAdd: true,

@ -0,0 +1,298 @@
package syntax
import (
"github.com/prometheus/prometheus/model/labels"
"github.com/grafana/loki/pkg/logql/log"
)
type cloneVisitor struct {
cloned Expr
}
var _ RootVisitor = &cloneVisitor{}
func cloneGrouping(g *Grouping) *Grouping {
copied := &Grouping{
Without: g.Without,
}
if g.Groups != nil {
copied.Groups = make([]string, len(g.Groups))
copy(copied.Groups, g.Groups)
}
return copied
}
func cloneVectorMatching(v *VectorMatching) *VectorMatching {
copied := *v
copy(copied.Include, v.Include)
copy(copied.MatchingLabels, v.MatchingLabels)
return &copied
}
func (v *cloneVisitor) VisitBinOp(e *BinOpExpr) {
lhs := MustClone[SampleExpr](e.SampleExpr)
rhs := MustClone[SampleExpr](e.RHS)
copied := &BinOpExpr{
SampleExpr: lhs,
RHS: rhs,
Op: e.Op,
}
if e.Opts != nil {
copied.Opts = &BinOpOptions{
ReturnBool: e.Opts.ReturnBool,
VectorMatching: cloneVectorMatching(e.Opts.VectorMatching),
}
}
v.cloned = copied
}
func (v *cloneVisitor) VisitVectorAggregation(e *VectorAggregationExpr) {
copied := &VectorAggregationExpr{
Left: MustClone[SampleExpr](e.Left),
Params: e.Params,
Operation: e.Operation,
}
if e.Grouping != nil {
copied.Grouping = cloneGrouping(e.Grouping)
}
v.cloned = copied
}
func (v *cloneVisitor) VisitRangeAggregation(e *RangeAggregationExpr) {
copied := &RangeAggregationExpr{
Left: MustClone[*LogRange](e.Left),
Operation: e.Operation,
}
if e.Grouping != nil {
copied.Grouping = cloneGrouping(e.Grouping)
}
if e.Params != nil {
tmp := *e.Params
copied.Params = &tmp
}
v.cloned = copied
}
func (v *cloneVisitor) VisitLabelReplace(e *LabelReplaceExpr) {
left := MustClone[SampleExpr](e.Left)
v.cloned = mustNewLabelReplaceExpr(left, e.Dst, e.Replacement, e.Src, e.Regex)
}
func (v *cloneVisitor) VisitLiteral(e *LiteralExpr) {
v.cloned = &LiteralExpr{Val: e.Val}
}
func (v *cloneVisitor) VisitVector(e *VectorExpr) {
v.cloned = &VectorExpr{Val: e.Val}
}
func (v *cloneVisitor) VisitLogRange(e *LogRange) {
copied := &LogRange{
Left: MustClone[LogSelectorExpr](e.Left),
Interval: e.Interval,
Offset: e.Offset,
}
if e.Unwrap != nil {
copied.Unwrap = &UnwrapExpr{
Identifier: e.Unwrap.Identifier,
Operation: e.Unwrap.Operation,
}
if e.Unwrap.PostFilters != nil {
copied.Unwrap.PostFilters = make([]log.LabelFilterer, len(e.Unwrap.PostFilters))
for i, f := range e.Unwrap.PostFilters {
copied.Unwrap.PostFilters[i] = cloneLabelFilterer(f)
}
}
}
v.cloned = copied
}
func (v *cloneVisitor) VisitMatchers(e *MatchersExpr) {
copied := &MatchersExpr{
Mts: make([]*labels.Matcher, len(e.Mts)),
}
for i, m := range e.Mts {
copied.Mts[i] = labels.MustNewMatcher(m.Type, m.Name, m.Value)
}
v.cloned = copied
}
func (v *cloneVisitor) VisitPipeline(e *PipelineExpr) {
copied := &PipelineExpr{
Left: MustClone[*MatchersExpr](e.Left),
MultiStages: make(MultiStageExpr, len(e.MultiStages)),
}
for i, s := range e.MultiStages {
copied.MultiStages[i] = MustClone[StageExpr](s)
}
v.cloned = copied
}
func (v *cloneVisitor) VisitDecolorize(*DecolorizeExpr) {
v.cloned = &DecolorizeExpr{}
}
func (v *cloneVisitor) VisitDropLabels(e *DropLabelsExpr) {
copied := &DropLabelsExpr{
dropLabels: make([]log.DropLabel, len(e.dropLabels)),
}
for i, l := range e.dropLabels {
var matcher *labels.Matcher
if l.Matcher != nil {
matcher = labels.MustNewMatcher(l.Matcher.Type, l.Matcher.Name, l.Matcher.Value)
}
copied.dropLabels[i] = log.NewDropLabel(matcher, l.Name)
}
v.cloned = copied
}
func (v *cloneVisitor) VisitJSONExpressionParser(e *JSONExpressionParser) {
copied := &JSONExpressionParser{
Expressions: make([]log.LabelExtractionExpr, len(e.Expressions)),
}
copy(copied.Expressions, e.Expressions)
v.cloned = copied
}
func (v *cloneVisitor) VisitKeepLabel(e *KeepLabelsExpr) {
copied := &KeepLabelsExpr{
keepLabels: make([]log.KeepLabel, len(e.keepLabels)),
}
for i, k := range e.keepLabels {
copied.keepLabels[i] = log.KeepLabel{
Name: k.Name,
}
if k.Matcher != nil {
copied.keepLabels[i].Matcher = labels.MustNewMatcher(k.Matcher.Type, k.Matcher.Name, k.Matcher.Value)
}
}
v.cloned = copied
}
func (v *cloneVisitor) VisitLabelFilter(e *LabelFilterExpr) {
v.cloned = &LabelFilterExpr{
LabelFilterer: cloneLabelFilterer(e.LabelFilterer),
}
}
func cloneLabelFilterer(filter log.LabelFilterer) log.LabelFilterer {
switch concrete := filter.(type) {
case *log.BinaryLabelFilter:
return &log.BinaryLabelFilter{
Left: cloneLabelFilterer(concrete.Left),
Right: cloneLabelFilterer(concrete.Right),
And: concrete.And,
}
case *log.NoopLabelFilter:
copied := &log.NoopLabelFilter{}
if concrete.Matcher != nil {
copied.Matcher = mustNewMatcher(concrete.Type, concrete.Name, concrete.Value)
}
return copied
case *log.BytesLabelFilter:
return &log.BytesLabelFilter{
Name: concrete.Name,
Value: concrete.Value,
Type: concrete.Type,
}
case *log.DurationLabelFilter:
return &log.DurationLabelFilter{
Name: concrete.Name,
Value: concrete.Value,
Type: concrete.Type,
}
case *log.NumericLabelFilter:
return &log.NumericLabelFilter{
Name: concrete.Name,
Value: concrete.Value,
Type: concrete.Type,
}
case *log.StringLabelFilter:
copied := &log.StringLabelFilter{}
if concrete.Matcher != nil {
copied.Matcher = mustNewMatcher(concrete.Type, concrete.Name, concrete.Value)
}
return copied
case *log.LineFilterLabelFilter:
copied := &log.LineFilterLabelFilter{}
if concrete.Matcher != nil {
copied.Matcher = mustNewMatcher(concrete.Type, concrete.Name, concrete.Value)
}
return copied
case *log.IPLabelFilter:
return log.NewIPLabelFilter(concrete.Pattern, concrete.Label, concrete.Ty)
}
return nil
}
func (v *cloneVisitor) VisitLabelFmt(e *LabelFmtExpr) {
copied := &LabelFmtExpr{
Formats: make([]log.LabelFmt, len(e.Formats)),
}
copy(copied.Formats, e.Formats)
v.cloned = copied
}
func (v *cloneVisitor) VisitLabelParser(e *LabelParserExpr) {
v.cloned = &LabelParserExpr{
Op: e.Op,
Param: e.Param,
}
}
func (v *cloneVisitor) VisitLineFilter(e *LineFilterExpr) {
copied := &LineFilterExpr{
Ty: e.Ty,
Match: e.Match,
Op: e.Op,
IsOrChild: e.IsOrChild,
}
if e.Left != nil {
copied.Left = MustClone[*LineFilterExpr](e.Left)
}
if e.Or != nil {
copied.Or = MustClone[*LineFilterExpr](e.Or)
}
v.cloned = copied
}
func (v *cloneVisitor) VisitLineFmt(e *LineFmtExpr) {
v.cloned = &LineFmtExpr{Value: e.Value}
}
func (v *cloneVisitor) VisitLogfmtExpressionParser(e *LogfmtExpressionParser) {
copied := &LogfmtExpressionParser{
Expressions: make([]log.LabelExtractionExpr, len(e.Expressions)),
Strict: e.Strict,
KeepEmpty: e.KeepEmpty,
}
copy(copied.Expressions, e.Expressions)
v.cloned = copied
}
func (v *cloneVisitor) VisitLogfmtParser(e *LogfmtParserExpr) {
v.cloned = &LogfmtParserExpr{
Strict: e.Strict,
KeepEmpty: e.KeepEmpty,
}
}

@ -0,0 +1,114 @@
package syntax
import (
"strings"
"testing"
"github.com/prometheus/prometheus/model/labels"
"github.com/stretchr/testify/require"
"github.com/grafana/loki/pkg/logql/log"
)
func TestClone(t *testing.T) {
tests := map[string]struct {
query string
}{
"simple matchers": {
query: `{env="prod", app=~"loki.*"}`,
},
"simple aggregation": {
query: `count_over_time({env="prod", app=~"loki.*"}[5m])`,
},
"simple aggregation with unwrap": {
query: `sum_over_time({env="prod", app=~"loki.*"} | unwrap bytes[5m])`,
},
"bin op": {
query: `(count_over_time({env="prod", app=~"loki.*"}[5m]) >= 0)`,
},
"label filter": {
query: `{app="foo"} |= "bar" | json | ( latency>=250ms or ( status_code<500 , status_code>200 ) )`,
},
"line filter": {
query: `{app="foo"} |= "bar" | json |= "500" or "200"`,
},
"drop label": {
query: `{app="foo"} |= "bar" | json | drop latency, status_code="200"`,
},
"keep label": {
query: `{app="foo"} |= "bar" | json | keep latency, status_code="200"`,
},
"regexp": {
query: `{env="prod", app=~"loki.*"} |~ ".*foo.*"`,
},
"vector matching": {
query: `(sum by (cluster)(rate({foo="bar"}[5m])) / ignoring (cluster) count(rate({foo="bar"}[5m])))`,
},
"sum over or vector": {
query: `(sum(count_over_time({foo="bar"}[5m])) or vector(1.000000))`,
},
"label replace": {
query: `label_replace(vector(0.000000),"foo","bar","","")`,
},
"filters with bytes": {
query: `{app="foo"} |= "bar" | json | ( status_code <500 or ( status_code>200 , size>=2.5KiB ) )`,
},
"post filter": {
query: `quantile_over_time(0.99998,{app="foo"} |= "bar" | json | latency >= 250ms or ( status_code < 500 and status_code > 200)
| line_format "blip{{ .foo }}blop {{.status_code}}" | label_format foo=bar,status_code="buzz{{.bar}}" | unwrap foo
| __error__ !~".+"[5m]) by (namespace,instance)`,
},
"multiple post filters": {
query: `rate({app="foo"} | json | unwrap foo | latency >= 250ms or bytes > 42B or ( status_code < 500 and status_code > 200) or source = ip("") and user = "me" [1m])`,
},
"true filter": {
query: `{ foo = "bar" } | foo =~".*"`,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
expr, err := ParseExpr(test.query)
require.NoError(t, err)
actual, err := Clone[Expr](expr)
require.NoError(t, err)
require.Equal(t, expr.Pretty(0), actual.Pretty(0))
})
}
}
func TestCloneStringLabelFilter(t *testing.T) {
expr := newPipelineExpr(
newMatcherExpr([]*labels.Matcher{mustNewMatcher(labels.MatchEqual, "foo", "bar")}),
MultiStageExpr{
newLogfmtParserExpr(nil),
newLabelFilterExpr(&log.StringLabelFilter{Matcher: labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}),
},
)
actual, err := Clone[Expr](expr)
require.NoError(t, err)
require.Equal(t, expr.Pretty(0), actual.Pretty(0))
}
func TestCloneParseTestCases(t *testing.T) {
for _, tc := range ParseTestCases {
if tc.err == nil {
t.Run(tc.in, func(t *testing.T) {
ast, err := ParseExpr(tc.in)
require.NoError(t, err)
if strings.Contains(tc.in, "KiB") {
t.Skipf("Byte roundtrip conversion is broken. '%s' vs '%s'", tc.in, ast.String())
}
actual, err := Clone[Expr](ast)
require.NoError(t, err)
require.Equal(t, ast.Pretty(0), actual.Pretty(0))
})
}
}
}

@ -5,8 +5,8 @@ import (
"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase/definitions"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logqlmodel/stats"
"github.com/grafana/loki/pkg/push"
)
// ValueTypeStreams promql.ValueType for log streams
@ -23,7 +23,7 @@ type Result struct {
}
// Streams is promql.Value
type Streams []logproto.Stream
type Streams []push.Stream
func (streams Streams) Len() int { return len(streams) }
func (streams Streams) Swap(i, j int) { streams[i], streams[j] = streams[j], streams[i] }

@ -1,15 +1,18 @@
package loki
import (
"errors"
"fmt"
"github.com/grafana/loki/pkg/ingester/index"
frontend "github.com/grafana/loki/pkg/lokifrontend/frontend/v2"
"github.com/grafana/loki/pkg/storage/config"
)
func ValidateConfigCompatibility(c Config) error {
for _, fn := range []func(Config) error{
ensureInvertedIndexShardingCompatibility,
ensureProtobufEncodingForAggregationSharding,
} {
if err := fn(c); err != nil {
return err
@ -40,3 +43,10 @@ func ensureInvertedIndexShardingCompatibility(c Config) error {
}
return nil
}
func ensureProtobufEncodingForAggregationSharding(c Config) error {
if len(c.QueryRange.ShardAggregations) > 0 && c.Frontend.FrontendV2.Encoding != frontend.EncodingProtobuf {
return errors.New("shard_aggregation requires frontend.encoding=protobuf")
}
return nil
}

@ -19,7 +19,9 @@ import (
"go.uber.org/atomic"
"google.golang.org/grpc"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/lokifrontend/frontend/v2/frontendv2pb"
"github.com/grafana/loki/pkg/querier/plan"
"github.com/grafana/loki/pkg/querier/queryrange"
"github.com/grafana/loki/pkg/querier/stats"
"github.com/grafana/loki/pkg/scheduler/schedulerpb"
@ -29,7 +31,7 @@ import (
const testFrontendWorkerConcurrency = 5
func setupFrontend(t *testing.T, schedulerReplyFunc func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend) (*Frontend, *mockScheduler) {
func setupFrontend(t *testing.T, cfg Config, schedulerReplyFunc func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend) (*Frontend, *mockScheduler) {
l, err := net.Listen("tcp", "")
require.NoError(t, err)
@ -41,8 +43,6 @@ func setupFrontend(t *testing.T, schedulerReplyFunc func(f *Frontend, msg *sched
grpcPort, err := strconv.Atoi(p)
require.NoError(t, err)
cfg := Config{}
flagext.DefaultValues(&cfg)
cfg.SchedulerAddress = l.Addr().String()
cfg.WorkerConcurrency = testFrontendWorkerConcurrency
cfg.Addr = h
@ -102,7 +102,9 @@ func TestFrontendBasicWorkflow(t *testing.T) {
userID = "test"
)
f, _ := setupFrontend(t, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
cfg := Config{}
flagext.DefaultValues(&cfg)
f, _ := setupFrontend(t, cfg, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
// We cannot call QueryResult directly, as Frontend is not yet waiting for the response.
// It first needs to be told that enqueuing has succeeded.
go sendResponseWithDelay(f, 100*time.Millisecond, userID, msg.QueryID, &httpgrpc.HTTPResponse{
@ -119,6 +121,41 @@ func TestFrontendBasicWorkflow(t *testing.T) {
require.Equal(t, []byte(body), resp.Body)
}
func TestFrontendBasicWorkflowProto(t *testing.T) {
const (
userID = "test"
)
ctx := user.InjectOrgID(context.Background(), userID)
req := &queryrange.LokiRequest{
Query: `{foo="bar"} | json`,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{foo="bar"} | json`),
},
}
resp, err := queryrange.NewEmptyResponse(req)
require.NoError(t, err)
httpReq := &httpgrpc.HTTPRequest{Url: "/loki/api/v1/query_range"}
httpResp, err := queryrange.DefaultCodec.EncodeHTTPGrpcResponse(ctx, httpReq, resp)
require.NoError(t, err)
cfg := Config{}
flagext.DefaultValues(&cfg)
cfg.Encoding = EncodingProtobuf
f, _ := setupFrontend(t, cfg, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
// We cannot call QueryResult directly, as Frontend is not yet waiting for the response.
// It first needs to be told that enqueuing has succeeded.
go sendResponseWithDelay(f, 100*time.Millisecond, userID, msg.QueryID, httpResp)
return &schedulerpb.SchedulerToFrontend{Status: schedulerpb.OK}
})
actualResp, err := f.Do(ctx, req)
require.NoError(t, err)
require.Equal(t, resp.(*queryrange.LokiResponse).Data, actualResp.(*queryrange.LokiResponse).Data)
}
func TestFrontendRetryEnqueue(t *testing.T) {
// Frontend uses worker concurrency to compute number of retries. We use one less failure.
failures := atomic.NewInt64(testFrontendWorkerConcurrency - 1)
@ -127,7 +164,9 @@ func TestFrontendRetryEnqueue(t *testing.T) {
userID = "test"
)
f, _ := setupFrontend(t, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
cfg := Config{}
flagext.DefaultValues(&cfg)
f, _ := setupFrontend(t, cfg, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
fail := failures.Dec()
if fail >= 0 {
return &schedulerpb.SchedulerToFrontend{Status: schedulerpb.SHUTTING_DOWN}
@ -145,7 +184,9 @@ func TestFrontendRetryEnqueue(t *testing.T) {
}
func TestFrontendEnqueueFailure(t *testing.T) {
f, _ := setupFrontend(t, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
cfg := Config{}
flagext.DefaultValues(&cfg)
f, _ := setupFrontend(t, cfg, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
return &schedulerpb.SchedulerToFrontend{Status: schedulerpb.SHUTTING_DOWN}
})
@ -155,7 +196,9 @@ func TestFrontendEnqueueFailure(t *testing.T) {
}
func TestFrontendCancellation(t *testing.T) {
f, ms := setupFrontend(t, nil)
cfg := Config{}
flagext.DefaultValues(&cfg)
f, ms := setupFrontend(t, cfg, nil)
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
@ -184,7 +227,9 @@ func TestFrontendCancellation(t *testing.T) {
// all the frontend workers thus not reaching the scheduler as well.
// Issue: https://github.com/grafana/loki/issues/5132
func TestFrontendWorkerCancellation(t *testing.T) {
f, ms := setupFrontend(t, nil)
cfg := Config{}
flagext.DefaultValues(&cfg)
f, ms := setupFrontend(t, cfg, nil)
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
@ -219,7 +264,9 @@ func TestFrontendWorkerCancellation(t *testing.T) {
}
func TestFrontendFailedCancellation(t *testing.T) {
f, ms := setupFrontend(t, nil)
cfg := Config{}
flagext.DefaultValues(&cfg)
f, ms := setupFrontend(t, cfg, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -258,7 +305,9 @@ func TestFrontendFailedCancellation(t *testing.T) {
func TestFrontendStoppingWaitsForEmptyInflightRequests(t *testing.T) {
delayResponse := 10 * time.Millisecond
f, _ := setupFrontend(t, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
cfg := Config{}
flagext.DefaultValues(&cfg)
f, _ := setupFrontend(t, cfg, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
// We cannot call QueryResult directly, as Frontend is not yet waiting for the response.
// It first needs to be told that enqueuing has succeeded.
go sendResponseWithDelay(f, 2*delayResponse, "test", msg.QueryID, &httpgrpc.HTTPResponse{
@ -296,7 +345,9 @@ func TestFrontendStoppingWaitsForEmptyInflightRequests(t *testing.T) {
func TestFrontendShuttingDownLetsSubRequestsPass(t *testing.T) {
delayResponse := 100 * time.Millisecond
f, _ := setupFrontend(t, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
cfg := Config{}
flagext.DefaultValues(&cfg)
f, _ := setupFrontend(t, cfg, func(f *Frontend, msg *schedulerpb.FrontendToScheduler) *schedulerpb.SchedulerToFrontend {
// We cannot call QueryResult directly, as Frontend is not yet waiting for the response.
// It first needs to be told that enqueuing has succeeded.
go sendResponseWithDelay(f, delayResponse, "test", msg.QueryID, &httpgrpc.HTTPResponse{

@ -69,7 +69,7 @@ func NewQuerierAPI(cfg Config, querier Querier, limits Limits, logger log.Logger
// RangeQueryHandler is a http.HandlerFunc for range queries and legacy log queries
func (q *QuerierAPI) RangeQueryHandler(ctx context.Context, req *queryrange.LokiRequest) (logqlmodel.Result, error) {
if err := q.validateMaxEntriesLimits(ctx, req.Query, req.Limit); err != nil {
if err := q.validateMaxEntriesLimits(ctx, req.Plan.AST, req.Limit); err != nil {
return logqlmodel.Result{}, err
}
@ -84,7 +84,7 @@ func (q *QuerierAPI) RangeQueryHandler(ctx context.Context, req *queryrange.Loki
// InstantQueryHandler is a http.HandlerFunc for instant queries.
func (q *QuerierAPI) InstantQueryHandler(ctx context.Context, req *queryrange.LokiInstantRequest) (logqlmodel.Result, error) {
if err := q.validateMaxEntriesLimits(ctx, req.Query, req.Limit); err != nil {
if err := q.validateMaxEntriesLimits(ctx, req.Plan.AST, req.Limit); err != nil {
return logqlmodel.Result{}, err
}
@ -343,17 +343,12 @@ func (q *QuerierAPI) VolumeHandler(ctx context.Context, req *logproto.VolumeRequ
return resp, nil
}
func (q *QuerierAPI) validateMaxEntriesLimits(ctx context.Context, query string, limit uint32) error {
func (q *QuerierAPI) validateMaxEntriesLimits(ctx context.Context, expr syntax.Expr, limit uint32) error {
tenantIDs, err := tenant.TenantIDs(ctx)
if err != nil {
return httpgrpc.Errorf(http.StatusBadRequest, err.Error())
}
expr, err := syntax.ParseExpr(query)
if err != nil {
return err
}
// entry limit does not apply to metric queries.
if _, ok := expr.(syntax.SampleExpr); ok {
return nil

@ -31,7 +31,14 @@ func TestTailHandler(t *testing.T) {
api := NewQuerierAPI(mockQuerierConfig(), nil, limits, log.NewNopLogger())
req, err := http.NewRequest("GET", "/", nil)
req, err := http.NewRequest("GET", `/`, nil)
require.NoError(t, err)
q := req.URL.Query()
q.Add("query", `{app="loki"}`)
req.URL.RawQuery = q.Encode()
err = req.ParseForm()
require.NoError(t, err)
ctx := user.InjectOrgID(req.Context(), "1|2")
req = req.WithContext(ctx)
require.NoError(t, err)

@ -2,7 +2,9 @@ package querier
import (
"context"
"fmt"
"github.com/grafana/loki/pkg/querier/plan"
"github.com/grafana/loki/pkg/storage/stores/index/seriesvolume"
"github.com/go-kit/log"
@ -53,6 +55,14 @@ func (q *MultiTenantQuerier) SelectLogs(ctx context.Context, params logql.Select
matchedTenants, filteredMatchers := filterValuesByMatchers(defaultTenantLabel, tenantIDs, selector.Matchers()...)
params.Selector = replaceMatchers(selector, filteredMatchers).String()
parsed, err := syntax.ParseLogSelector(params.Selector, true)
if err != nil {
return nil, fmt.Errorf("log selector is invalid after matcher update: %w", err)
}
params.Plan = &plan.QueryPlan{
AST: parsed,
}
iters := make([]iter.EntryIterator, len(matchedTenants))
i := 0
for id := range matchedTenants {

@ -21,6 +21,7 @@ import (
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/querier/plan"
)
func TestMultiTenantQuerier_SelectLogs(t *testing.T) {
@ -90,6 +91,9 @@ func TestMultiTenantQuerier_SelectLogs(t *testing.T) {
Shards: nil,
Start: time.Unix(0, 1),
End: time.Unix(0, time.Now().UnixNano()),
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(tc.selector),
},
}}
iter, err := multiTenantQuerier.SelectLogs(ctx, params)
require.NoError(t, err)
@ -161,6 +165,9 @@ func TestMultiTenantQuerier_SelectSamples(t *testing.T) {
ctx := user.InjectOrgID(context.Background(), tc.orgID)
params := logql.SelectSampleParams{SampleQueryRequest: &logproto.SampleQueryRequest{
Selector: tc.selector,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(tc.selector),
},
}}
iter, err := multiTenantQuerier.SelectSamples(ctx, params)
require.NoError(t, err)
@ -191,6 +198,9 @@ func TestMultiTenantQuerier_TenantFilter(t *testing.T) {
t.Run(tc.selector, func(t *testing.T) {
params := logql.SelectSampleParams{SampleQueryRequest: &logproto.SampleQueryRequest{
Selector: tc.selector,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(tc.selector),
},
}}
_, updatedSelector, err := removeTenantSelector(params, []string{})
require.NoError(t, err)

@ -29,6 +29,7 @@ import (
"github.com/grafana/loki/pkg/logql"
"github.com/grafana/loki/pkg/logql/syntax"
querier_limits "github.com/grafana/loki/pkg/querier/limits"
"github.com/grafana/loki/pkg/querier/plan"
"github.com/grafana/loki/pkg/storage"
"github.com/grafana/loki/pkg/storage/stores/index/stats"
listutil "github.com/grafana/loki/pkg/util"
@ -443,6 +444,16 @@ func (q *SingleTenantQuerier) Tail(ctx context.Context, req *logproto.TailReques
return nil, err
}
if req.Plan == nil {
parsed, err := syntax.ParseExpr(req.Query)
if err != nil {
return nil, err
}
req.Plan = &plan.QueryPlan{
AST: parsed,
}
}
deletes, err := q.deletesForUser(ctx, req.Start, time.Now())
if err != nil {
level.Error(spanlogger.FromContext(ctx)).Log("msg", "failed loading deletes for user", "err", err)
@ -456,6 +467,7 @@ func (q *SingleTenantQuerier) Tail(ctx context.Context, req *logproto.TailReques
Limit: req.Limit,
Direction: logproto.BACKWARD,
Deletes: deletes,
Plan: req.Plan,
},
}
@ -629,6 +641,15 @@ func (q *SingleTenantQuerier) seriesForMatchers(
// seriesForMatcher fetches series from the store for a given matcher
func (q *SingleTenantQuerier) seriesForMatcher(ctx context.Context, from, through time.Time, matcher string, shards []string) ([]logproto.SeriesIdentifier, error) {
var parsed syntax.Expr
var err error
if matcher != "" {
parsed, err = syntax.ParseExpr(matcher)
if err != nil {
return nil, err
}
}
ids, err := q.store.SelectSeries(ctx, logql.SelectLogParams{
QueryRequest: &logproto.QueryRequest{
Selector: matcher,
@ -637,6 +658,9 @@ func (q *SingleTenantQuerier) seriesForMatcher(ctx context.Context, from, throug
End: through,
Direction: logproto.FORWARD,
Shards: shards,
Plan: &plan.QueryPlan{
AST: parsed,
},
},
})
if err != nil {

@ -23,6 +23,8 @@ import (
"github.com/grafana/loki/pkg/ingester/client"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/querier/plan"
"github.com/grafana/loki/pkg/storage"
"github.com/grafana/loki/pkg/util/constants"
"github.com/grafana/loki/pkg/validation"
@ -84,10 +86,13 @@ func TestQuerier_Label_QueryTimeoutConfigFlag(t *testing.T) {
func TestQuerier_Tail_QueryTimeoutConfigFlag(t *testing.T) {
request := logproto.TailRequest{
Query: "{type=\"test\"}",
Query: `{type="test"}`,
DelayFor: 0,
Limit: 10,
Start: time.Now(),
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{type="test"}`),
},
}
store := newStoreMock()
@ -168,11 +173,14 @@ func defaultLimitsTestConfig() validation.Limits {
func TestQuerier_validateQueryRequest(t *testing.T) {
request := logproto.QueryRequest{
Selector: "{type=\"test\", fail=\"yes\"} |= \"foo\"",
Selector: `{type="test", fail="yes"} |= "foo"`,
Limit: 10,
Start: time.Now().Add(-1 * time.Minute),
End: time.Now(),
Direction: logproto.FORWARD,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{type="test", fail="yes"} |= "foo"`),
},
}
store := newStoreMock()
@ -205,7 +213,10 @@ func TestQuerier_validateQueryRequest(t *testing.T) {
_, err = q.SelectLogs(ctx, logql.SelectLogParams{QueryRequest: &request})
require.Equal(t, httpgrpc.Errorf(http.StatusBadRequest, "max streams matchers per query exceeded, matchers-count > limit (2 > 1)"), err)
request.Selector = "{type=\"test\"}"
request.Selector = `{type="test"}`
request.Plan = &plan.QueryPlan{
AST: syntax.MustParseExpr(`{type="test"}`),
}
_, err = q.SelectLogs(ctx, logql.SelectLogParams{QueryRequest: &request})
require.NoError(t, err)
@ -395,6 +406,9 @@ func TestQuerier_IngesterMaxQueryLookback(t *testing.T) {
Start: tc.end.Add(-6 * time.Hour),
End: tc.end,
Direction: logproto.FORWARD,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{app="foo"}`),
},
}
queryClient := newQueryClientMock()
@ -442,6 +456,9 @@ func TestQuerier_concurrentTailLimits(t *testing.T) {
DelayFor: 0,
Limit: 10,
Start: time.Now(),
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr("{type=\"test\"}"),
},
}
t.Parallel()
@ -879,11 +896,14 @@ func TestQuerier_RequestingIngesters(t *testing.T) {
do: func(querier *SingleTenantQuerier, start, end time.Time) error {
_, err := querier.SelectLogs(ctx, logql.SelectLogParams{
QueryRequest: &logproto.QueryRequest{
Selector: "{type=\"test\", fail=\"yes\"} |= \"foo\"",
Selector: `{type="test", fail="yes"} |= "foo"`,
Limit: 10,
Start: start,
End: end,
Direction: logproto.FORWARD,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{type="test", fail="yes"} |= "foo"`),
},
},
})
@ -895,9 +915,12 @@ func TestQuerier_RequestingIngesters(t *testing.T) {
do: func(querier *SingleTenantQuerier, start, end time.Time) error {
_, err := querier.SelectSamples(ctx, logql.SelectSampleParams{
SampleQueryRequest: &logproto.SampleQueryRequest{
Selector: "count_over_time({foo=\"bar\"}[5m])",
Selector: `count_over_time({foo="bar"}[5m])`,
Start: start,
End: end,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`count_over_time({foo="bar"}[5m])`),
},
},
})
return err
@ -1204,6 +1227,9 @@ func TestQuerier_SelectLogWithDeletes(t *testing.T) {
Start: time.Unix(0, 300000000),
End: time.Unix(0, 600000000),
Direction: logproto.FORWARD,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{type="test"} |= "foo"`),
},
}
_, err = q.SelectLogs(ctx, logql.SelectLogParams{QueryRequest: &request})
@ -1220,6 +1246,9 @@ func TestQuerier_SelectLogWithDeletes(t *testing.T) {
{Selector: "2", Start: 400000000, End: 500000000},
{Selector: "3", Start: 500000000, End: 700000000},
},
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(request.Selector),
},
}
require.Contains(t, store.Calls[0].Arguments, logql.SelectLogParams{QueryRequest: expectedRequest})
@ -1264,6 +1293,9 @@ func TestQuerier_SelectSamplesWithDeletes(t *testing.T) {
Selector: `count_over_time({foo="bar"}[5m])`,
Start: time.Unix(0, 300000000),
End: time.Unix(0, 600000000),
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`count_over_time({foo="bar"}[5m])`),
},
}
_, err = q.SelectSamples(ctx, logql.SelectSampleParams{SampleQueryRequest: &request})
@ -1279,6 +1311,9 @@ func TestQuerier_SelectSamplesWithDeletes(t *testing.T) {
{Selector: "2", Start: 400000000, End: 500000000},
{Selector: "3", Start: 500000000, End: 700000000},
},
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(request.Selector),
},
},
}

@ -304,7 +304,7 @@ func (q *querySizeLimiter) getBytesReadForRequest(ctx context.Context, r queryra
}
func (q *querySizeLimiter) getSchemaCfg(r queryrangebase.Request) (config.PeriodConfig, error) {
maxRVDuration, maxOffset, err := maxRangeVectorAndOffsetDuration(r.GetQuery())
maxRVDuration, maxOffset, err := maxRangeVectorAndOffsetDurationFromQueryString(r.GetQuery())
if err != nil {
return config.PeriodConfig{}, errors.New("failed to get range-vector and offset duration: " + err.Error())
}

@ -17,7 +17,9 @@ import (
"gopkg.in/yaml.v2"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/logqlmodel"
"github.com/grafana/loki/pkg/querier/plan"
base "github.com/grafana/loki/pkg/querier/queryrange/queryrangebase"
"github.com/grafana/loki/pkg/storage/config"
"github.com/grafana/loki/pkg/util/constants"
@ -72,6 +74,9 @@ func Test_seriesLimiter(t *testing.T) {
EndTs: testTime,
Direction: logproto.FORWARD,
Path: "/query_range",
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`rate({app="foo"} |= "foo"[1m])`),
},
}
ctx := user.InjectOrgID(context.Background(), "1")
@ -241,6 +246,9 @@ func Test_MaxQueryLookBack(t *testing.T) {
EndTs: testTime,
Direction: logproto.FORWARD,
Path: "/loki/api/v1/query_range",
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{app="foo"} |= "foo"`),
},
}
ctx := user.InjectOrgID(context.Background(), "1")
@ -589,6 +597,9 @@ func Test_MaxQuerySize(t *testing.T) {
EndTs: tc.queryEnd,
Direction: logproto.FORWARD,
Path: "/query_range",
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(tc.query),
},
}
ctx := user.InjectOrgID(context.Background(), "foo")

@ -119,7 +119,7 @@ func ResultToResponse(result logqlmodel.Result, params logql.Params) (queryrange
case sketch.TopKMatrix:
sk, err := data.ToProto()
return &TopKSketchesResponse{Response: sk}, err
case sketch.QuantileSketchMatrix:
case logql.ProbabilisticQuantileMatrix:
return &QuantileSketchResponse{Response: data.ToProto()}, nil
}
@ -172,7 +172,7 @@ func ResponseToResult(resp queryrangebase.Response) (logqlmodel.Result, error) {
Headers: resp.GetHeaders(),
}, nil
case *QuantileSketchResponse:
matrix, err := sketch.QuantileSketchMatrixFromProto(r.Response)
matrix, err := logql.ProbabilisticQuantileMatrixFromProto(r.Response)
if err != nil {
return logqlmodel.Result{}, fmt.Errorf("cannot decode quantile sketch: %w", err)
}

@ -7,6 +7,8 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/loki/pkg/loghttp"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql"
"github.com/grafana/loki/pkg/logqlmodel"
"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase"
)
@ -32,6 +34,18 @@ func TestResultToResponse(t *testing.T) {
},
},
},
{
name: "empty probabilistic quantile matrix",
result: logqlmodel.Result{
Data: logql.ProbabilisticQuantileMatrix([]logql.ProbabilisticQuantileVector{}),
},
response: &QuantileSketchResponse{
Response: &logproto.QuantileSketchMatrix{
Values: []*logproto.QuantileSketchVector{},
},
Headers: []queryrangebase.PrometheusResponseHeader(nil),
},
},
}
for _, tt := range tests {

@ -4,6 +4,7 @@
package queryrange
import (
bytes "bytes"
fmt "fmt"
rpc "github.com/gogo/googleapis/google/rpc"
_ "github.com/gogo/protobuf/gogoproto"
@ -232,6 +233,49 @@ func (m *LokiInstantRequest) GetShards() []string {
return nil
}
type Plan struct {
Raw []byte `protobuf:"bytes,1,opt,name=raw,proto3" json:"raw,omitempty"`
}
func (m *Plan) Reset() { *m = Plan{} }
func (*Plan) ProtoMessage() {}
func (*Plan) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{2}
}
func (m *Plan) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Plan) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Plan.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Plan) XXX_Merge(src proto.Message) {
xxx_messageInfo_Plan.Merge(m, src)
}
func (m *Plan) XXX_Size() int {
return m.Size()
}
func (m *Plan) XXX_DiscardUnknown() {
xxx_messageInfo_Plan.DiscardUnknown(m)
}
var xxx_messageInfo_Plan proto.InternalMessageInfo
func (m *Plan) GetRaw() []byte {
if m != nil {
return m.Raw
}
return nil
}
type LokiResponse struct {
Status string `protobuf:"bytes,1,opt,name=Status,proto3" json:"status"`
Data LokiData `protobuf:"bytes,2,opt,name=Data,proto3" json:"data,omitempty"`
@ -247,7 +291,7 @@ type LokiResponse struct {
func (m *LokiResponse) Reset() { *m = LokiResponse{} }
func (*LokiResponse) ProtoMessage() {}
func (*LokiResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{2}
return fileDescriptor_51b9d53b40d11902, []int{3}
}
func (m *LokiResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -343,7 +387,7 @@ type LokiSeriesRequest struct {
func (m *LokiSeriesRequest) Reset() { *m = LokiSeriesRequest{} }
func (*LokiSeriesRequest) ProtoMessage() {}
func (*LokiSeriesRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{3}
return fileDescriptor_51b9d53b40d11902, []int{4}
}
func (m *LokiSeriesRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -418,7 +462,7 @@ type LokiSeriesResponse struct {
func (m *LokiSeriesResponse) Reset() { *m = LokiSeriesResponse{} }
func (*LokiSeriesResponse) ProtoMessage() {}
func (*LokiSeriesResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{4}
return fileDescriptor_51b9d53b40d11902, []int{5}
}
func (m *LokiSeriesResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -486,7 +530,7 @@ type LokiLabelNamesResponse struct {
func (m *LokiLabelNamesResponse) Reset() { *m = LokiLabelNamesResponse{} }
func (*LokiLabelNamesResponse) ProtoMessage() {}
func (*LokiLabelNamesResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{5}
return fileDescriptor_51b9d53b40d11902, []int{6}
}
func (m *LokiLabelNamesResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -551,7 +595,7 @@ type LokiData struct {
func (m *LokiData) Reset() { *m = LokiData{} }
func (*LokiData) ProtoMessage() {}
func (*LokiData) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{6}
return fileDescriptor_51b9d53b40d11902, []int{7}
}
func (m *LokiData) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -596,7 +640,7 @@ type LokiPromResponse struct {
func (m *LokiPromResponse) Reset() { *m = LokiPromResponse{} }
func (*LokiPromResponse) ProtoMessage() {}
func (*LokiPromResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{7}
return fileDescriptor_51b9d53b40d11902, []int{8}
}
func (m *LokiPromResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -647,7 +691,7 @@ type IndexStatsResponse struct {
func (m *IndexStatsResponse) Reset() { *m = IndexStatsResponse{} }
func (*IndexStatsResponse) ProtoMessage() {}
func (*IndexStatsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{8}
return fileDescriptor_51b9d53b40d11902, []int{9}
}
func (m *IndexStatsResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -684,7 +728,7 @@ type VolumeResponse struct {
func (m *VolumeResponse) Reset() { *m = VolumeResponse{} }
func (*VolumeResponse) ProtoMessage() {}
func (*VolumeResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{9}
return fileDescriptor_51b9d53b40d11902, []int{10}
}
func (m *VolumeResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -721,7 +765,7 @@ type TopKSketchesResponse struct {
func (m *TopKSketchesResponse) Reset() { *m = TopKSketchesResponse{} }
func (*TopKSketchesResponse) ProtoMessage() {}
func (*TopKSketchesResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{10}
return fileDescriptor_51b9d53b40d11902, []int{11}
}
func (m *TopKSketchesResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -758,7 +802,7 @@ type QuantileSketchResponse struct {
func (m *QuantileSketchResponse) Reset() { *m = QuantileSketchResponse{} }
func (*QuantileSketchResponse) ProtoMessage() {}
func (*QuantileSketchResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{11}
return fileDescriptor_51b9d53b40d11902, []int{12}
}
func (m *QuantileSketchResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -804,7 +848,7 @@ type QueryResponse struct {
func (m *QueryResponse) Reset() { *m = QueryResponse{} }
func (*QueryResponse) ProtoMessage() {}
func (*QueryResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{12}
return fileDescriptor_51b9d53b40d11902, []int{13}
}
func (m *QueryResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -973,7 +1017,7 @@ type QueryRequest struct {
func (m *QueryRequest) Reset() { *m = QueryRequest{} }
func (*QueryRequest) ProtoMessage() {}
func (*QueryRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_51b9d53b40d11902, []int{13}
return fileDescriptor_51b9d53b40d11902, []int{14}
}
func (m *QueryRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1106,6 +1150,7 @@ func (*QueryRequest) XXX_OneofWrappers() []interface{} {
func init() {
proto.RegisterType((*LokiRequest)(nil), "queryrange.LokiRequest")
proto.RegisterType((*LokiInstantRequest)(nil), "queryrange.LokiInstantRequest")
proto.RegisterType((*Plan)(nil), "queryrange.Plan")
proto.RegisterType((*LokiResponse)(nil), "queryrange.LokiResponse")
proto.RegisterType((*LokiSeriesRequest)(nil), "queryrange.LokiSeriesRequest")
proto.RegisterType((*LokiSeriesResponse)(nil), "queryrange.LokiSeriesResponse")
@ -1126,101 +1171,102 @@ func init() {
}
var fileDescriptor_51b9d53b40d11902 = []byte{
// 1498 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x58, 0x4b, 0x6f, 0xdb, 0xc6,
0x1a, 0x15, 0xf5, 0xb4, 0xc6, 0x8f, 0x9b, 0x3b, 0x36, 0x1c, 0x5e, 0x27, 0x97, 0x14, 0x04, 0xdc,
0x44, 0xb7, 0x68, 0xa9, 0xc6, 0x4e, 0xf3, 0x6c, 0x8b, 0x86, 0x4d, 0x02, 0xa7, 0x4d, 0x8a, 0x84,
0x36, 0xba, 0xe8, 0x6e, 0x2c, 0x8d, 0x25, 0x56, 0x7c, 0x99, 0x33, 0x32, 0xe2, 0x5d, 0x7f, 0x40,
0x0b, 0xe4, 0x07, 0x74, 0x5d, 0x14, 0x68, 0x50, 0xa0, 0x8b, 0x6e, 0xba, 0xec, 0x2a, 0xcb, 0x2c,
0x03, 0x01, 0x65, 0x1b, 0xa5, 0x8b, 0xc2, 0xab, 0xfc, 0x84, 0x62, 0x1e, 0xa4, 0x48, 0x49, 0x49,
0xe4, 0xb4, 0x8b, 0x04, 0xe8, 0x46, 0x9a, 0x19, 0x7e, 0x87, 0x1c, 0x9e, 0x73, 0xbe, 0x6f, 0x66,
0x08, 0x4e, 0x07, 0xbd, 0x4e, 0x73, 0xaf, 0x8f, 0x43, 0x1b, 0x87, 0xfc, 0xff, 0x20, 0x44, 0x5e,
0x07, 0xa7, 0x9a, 0x46, 0x10, 0xfa, 0xd4, 0x87, 0x60, 0x34, 0xb2, 0xb6, 0xde, 0xb1, 0x69, 0xb7,
0xbf, 0x63, 0xb4, 0x7c, 0xb7, 0xd9, 0xf1, 0x3b, 0x7e, 0xb3, 0xe3, 0xfb, 0x1d, 0x07, 0xa3, 0xc0,
0x26, 0xb2, 0xd9, 0x0c, 0x83, 0x56, 0x93, 0x50, 0x44, 0xfb, 0x44, 0xe0, 0xd7, 0x56, 0x58, 0x20,
0x6f, 0x72, 0x88, 0x1c, 0xd5, 0x65, 0x38, 0xef, 0xed, 0xf4, 0x77, 0x9b, 0xd4, 0x76, 0x31, 0xa1,
0xc8, 0x0d, 0x64, 0xc0, 0x09, 0x36, 0x3f, 0xc7, 0xef, 0x08, 0x64, 0xdc, 0x90, 0x17, 0xff, 0x93,
0xb9, 0x48, 0x7a, 0x98, 0xb6, 0xba, 0xf2, 0x52, 0x4d, 0x5e, 0xda, 0x73, 0x5c, 0xbf, 0x8d, 0x1d,
0x3e, 0x17, 0x22, 0x7e, 0x65, 0xc4, 0x32, 0x8b, 0x08, 0xfa, 0xa4, 0xcb, 0x7f, 0xe4, 0xe0, 0x87,
0x2f, 0xa4, 0x63, 0x07, 0x11, 0xdc, 0x6c, 0xe3, 0x5d, 0xdb, 0xb3, 0xa9, 0xed, 0x7b, 0x24, 0xdd,
0x96, 0x37, 0x39, 0x37, 0xdb, 0x4d, 0xc6, 0x29, 0xae, 0x7f, 0x5d, 0x00, 0xf3, 0x37, 0xfd, 0x9e,
0x6d, 0xe1, 0xbd, 0x3e, 0x26, 0x14, 0xae, 0x80, 0x12, 0x8f, 0x51, 0x95, 0x9a, 0xd2, 0xa8, 0x5a,
0xa2, 0xc3, 0x46, 0x1d, 0xdb, 0xb5, 0xa9, 0x9a, 0xaf, 0x29, 0x8d, 0x45, 0x4b, 0x74, 0x20, 0x04,
0x45, 0x42, 0x71, 0xa0, 0x16, 0x6a, 0x4a, 0xa3, 0x60, 0xf1, 0x36, 0x5c, 0x03, 0x73, 0xb6, 0x47,
0x71, 0xb8, 0x8f, 0x1c, 0xb5, 0xca, 0xc7, 0x93, 0x3e, 0x7c, 0x1f, 0x54, 0x08, 0x45, 0x21, 0xdd,
0x26, 0x6a, 0xb1, 0xa6, 0x34, 0xe6, 0xd7, 0xd7, 0x0c, 0x21, 0x85, 0x11, 0x4b, 0x61, 0x6c, 0xc7,
0x52, 0x98, 0x73, 0x0f, 0x22, 0x3d, 0x77, 0xef, 0x57, 0x5d, 0xb1, 0x62, 0x10, 0xbc, 0x04, 0x4a,
0xd8, 0x6b, 0x6f, 0x13, 0xb5, 0x74, 0x04, 0xb4, 0x80, 0xc0, 0x33, 0xa0, 0xda, 0xb6, 0x43, 0xdc,
0x62, 0x9c, 0xa9, 0xe5, 0x9a, 0xd2, 0x58, 0x5a, 0x5f, 0x36, 0x12, 0x69, 0xaf, 0xc6, 0x97, 0xac,
0x51, 0x14, 0x7b, 0xbd, 0x00, 0xd1, 0xae, 0x5a, 0xe1, 0x4c, 0xf0, 0x36, 0xac, 0x83, 0x32, 0xe9,
0xa2, 0xb0, 0x4d, 0xd4, 0xb9, 0x5a, 0xa1, 0x51, 0x35, 0xc1, 0x61, 0xa4, 0xcb, 0x11, 0x4b, 0xfe,
0xc3, 0x8f, 0x40, 0x31, 0x70, 0x90, 0xa7, 0x82, 0x9a, 0xd2, 0x58, 0x30, 0xcf, 0x0d, 0x22, 0x3d,
0xe3, 0xdd, 0x10, 0xed, 0x22, 0x0f, 0x35, 0x1d, 0xbf, 0x67, 0x37, 0xd3, 0xa2, 0x31, 0x8c, 0x71,
0x87, 0xd1, 0x7d, 0xdb, 0x41, 0x9e, 0xc5, 0xef, 0x51, 0xff, 0x31, 0x0f, 0x20, 0x93, 0xe7, 0x86,
0x47, 0x28, 0xf2, 0xe8, 0xcb, 0xa8, 0xf4, 0x2e, 0x28, 0x33, 0x83, 0x6f, 0x13, 0xae, 0xd3, 0xac,
0xb4, 0x49, 0x4c, 0x96, 0xb7, 0xe2, 0x91, 0x78, 0x2b, 0x4d, 0xe5, 0xad, 0xfc, 0x42, 0xde, 0x2a,
0x7f, 0x03, 0x6f, 0xdf, 0x15, 0xc1, 0x82, 0xb0, 0x35, 0x09, 0x7c, 0x8f, 0x60, 0x36, 0x81, 0x2d,
0x5e, 0x1a, 0x04, 0x65, 0x72, 0x02, 0x7c, 0xc4, 0x92, 0x57, 0xe0, 0x07, 0xa0, 0x78, 0x15, 0x51,
0xc4, 0xe9, 0x9b, 0x5f, 0x5f, 0x31, 0x52, 0xc9, 0xc2, 0xee, 0xc5, 0xae, 0x99, 0xab, 0x8c, 0xa1,
0xc3, 0x48, 0x5f, 0x6a, 0x23, 0x8a, 0xde, 0xf4, 0x5d, 0x9b, 0x62, 0x37, 0xa0, 0x07, 0x16, 0x47,
0xc2, 0x77, 0x40, 0xf5, 0x5a, 0x18, 0xfa, 0xe1, 0xf6, 0x41, 0x80, 0x39, 0xdd, 0x55, 0xf3, 0xf8,
0x61, 0xa4, 0x2f, 0xe3, 0x78, 0x30, 0x85, 0x18, 0x45, 0xc2, 0xff, 0x83, 0x12, 0xef, 0x70, 0x82,
0xab, 0xe6, 0xf2, 0x61, 0xa4, 0xff, 0x8b, 0x43, 0x52, 0xe1, 0x22, 0x22, 0xab, 0x47, 0x69, 0x26,
0x3d, 0x12, 0x5b, 0x94, 0xd3, 0xb6, 0x50, 0x41, 0x65, 0x1f, 0x87, 0x84, 0xdd, 0xa6, 0xc2, 0xc7,
0xe3, 0x2e, 0xbc, 0x02, 0x00, 0x23, 0xc6, 0x26, 0xd4, 0x6e, 0x31, 0x9f, 0x33, 0x32, 0x16, 0x0d,
0x51, 0xc6, 0x2c, 0x4c, 0xfa, 0x0e, 0x35, 0xa1, 0x64, 0x21, 0x15, 0x68, 0xa5, 0xda, 0xf0, 0xbe,
0x02, 0x2a, 0x9b, 0x18, 0xb5, 0x71, 0x48, 0xd4, 0x6a, 0xad, 0xd0, 0x98, 0x5f, 0xff, 0x9f, 0x91,
0xae, 0x59, 0xb7, 0x43, 0xdf, 0xc5, 0xb4, 0x8b, 0xfb, 0x24, 0x16, 0x48, 0x44, 0x9b, 0xbd, 0x41,
0xa4, 0xef, 0xcc, 0xa2, 0xfa, 0x4c, 0x75, 0xf2, 0x99, 0xcf, 0x39, 0x8c, 0x74, 0xe5, 0x2d, 0x2b,
0x9e, 0x62, 0xfd, 0x17, 0x05, 0xfc, 0x9b, 0x29, 0xbc, 0xc5, 0xee, 0x4d, 0x52, 0x49, 0xe6, 0x22,
0xda, 0xea, 0xaa, 0x0a, 0xb3, 0xac, 0x25, 0x3a, 0xe9, 0x22, 0x96, 0xff, 0x4b, 0x45, 0xac, 0x70,
0xf4, 0x22, 0x16, 0x67, 0x56, 0x71, 0x6a, 0x66, 0x95, 0x9e, 0x95, 0x59, 0xf5, 0x2f, 0x0b, 0xa2,
0x8a, 0xc4, 0xef, 0x77, 0x84, 0x9c, 0xb8, 0x9e, 0xe4, 0x44, 0x81, 0xcf, 0x36, 0xb1, 0x9a, 0xb8,
0xd7, 0x8d, 0x36, 0xf6, 0xa8, 0xbd, 0x6b, 0xe3, 0xf0, 0x05, 0x99, 0x91, 0xb2, 0x5b, 0x21, 0x6b,
0xb7, 0xb4, 0x57, 0x8a, 0xaf, 0xbc, 0x57, 0xc6, 0xb2, 0xa3, 0xf4, 0x12, 0xd9, 0x51, 0x7f, 0x9a,
0x07, 0xab, 0x4c, 0x8e, 0x9b, 0x68, 0x07, 0x3b, 0x9f, 0x20, 0xf7, 0x88, 0x92, 0x9c, 0x4a, 0x49,
0x52, 0x35, 0xe1, 0x3f, 0x94, 0xcf, 0x40, 0xf9, 0x37, 0x0a, 0x98, 0x8b, 0x6b, 0x38, 0x34, 0x00,
0x10, 0x30, 0x5e, 0xa6, 0x05, 0xd1, 0x4b, 0x0c, 0x1c, 0x26, 0xa3, 0x56, 0x2a, 0x02, 0x7e, 0x0e,
0xca, 0xa2, 0x27, 0xb3, 0xe0, 0x78, 0x2a, 0x0b, 0x68, 0x88, 0x91, 0x7b, 0xa5, 0x8d, 0x02, 0x8a,
0x43, 0xf3, 0x22, 0x9b, 0xc5, 0x20, 0xd2, 0x4f, 0x3f, 0x8f, 0x22, 0xbe, 0xf3, 0x13, 0x38, 0x26,
0xae, 0x78, 0xa6, 0x25, 0x9f, 0x50, 0xff, 0x4a, 0x01, 0xc7, 0xd8, 0x44, 0x19, 0x35, 0x89, 0x2b,
0xae, 0x82, 0xb9, 0x50, 0xb6, 0xf9, 0x74, 0xe7, 0xd7, 0xeb, 0x46, 0x96, 0xd6, 0x29, 0x54, 0x9a,
0xc5, 0x07, 0x91, 0xae, 0x58, 0x09, 0x12, 0x6e, 0x64, 0x68, 0xcc, 0x4f, 0xa3, 0x91, 0x41, 0x72,
0x19, 0xe2, 0x7e, 0xca, 0x03, 0x78, 0xc3, 0x6b, 0xe3, 0xbb, 0xcc, 0x7c, 0x23, 0x9f, 0xf6, 0x27,
0x66, 0x74, 0x72, 0x44, 0xca, 0x64, 0xbc, 0x79, 0x79, 0x10, 0xe9, 0xe7, 0x9f, 0xc7, 0xca, 0x73,
0xc0, 0xa9, 0x57, 0x48, 0x1b, 0x37, 0xff, 0xea, 0xaf, 0x2b, 0xdf, 0xe7, 0xc1, 0xd2, 0xa7, 0xbe,
0xd3, 0x77, 0x71, 0x42, 0x9c, 0x3b, 0x41, 0x9c, 0x3a, 0x22, 0x2e, 0x1b, 0x6b, 0x9e, 0x1f, 0x44,
0xfa, 0xc6, 0x4c, 0xa4, 0x65, 0x81, 0xaf, 0x2f, 0x61, 0xf7, 0xf3, 0x60, 0x65, 0xdb, 0x0f, 0x3e,
0xde, 0xe2, 0xc7, 0xaa, 0x54, 0x5d, 0xc4, 0x13, 0xb4, 0xad, 0x8c, 0x68, 0x63, 0x88, 0x5b, 0x88,
0x86, 0xf6, 0x5d, 0x73, 0x63, 0x10, 0xe9, 0xcd, 0x99, 0x28, 0x1b, 0x81, 0x5e, 0x5f, 0xba, 0x7e,
0xce, 0x83, 0xd5, 0x3b, 0x7d, 0xe4, 0x51, 0xdb, 0xc1, 0x82, 0xb2, 0x84, 0xb0, 0x83, 0x09, 0xc2,
0xb4, 0x11, 0x61, 0x59, 0x8c, 0xa4, 0xee, 0xbd, 0x41, 0xa4, 0x5f, 0x9c, 0x89, 0xba, 0x69, 0xf0,
0xd7, 0x97, 0xc4, 0x1f, 0x8a, 0x60, 0x91, 0x1f, 0x1f, 0x12, 0xee, 0xde, 0x00, 0x72, 0xc9, 0x95,
0xcc, 0xc1, 0x78, 0x8f, 0x16, 0x06, 0x2d, 0x63, 0x4b, 0x2e, 0xc6, 0x22, 0x02, 0x5e, 0x00, 0x65,
0xc2, 0x77, 0x42, 0xb2, 0xa0, 0x6a, 0xe3, 0xa7, 0x86, 0xec, 0x9e, 0x6b, 0x33, 0x67, 0xc9, 0x78,
0x76, 0x2e, 0x73, 0xd8, 0x06, 0x20, 0xde, 0x09, 0xd6, 0xc7, 0x91, 0x93, 0xdb, 0x03, 0x86, 0x16,
0x18, 0x78, 0x0e, 0x94, 0x78, 0xe5, 0x96, 0x27, 0xe9, 0xcc, 0x63, 0x27, 0x4b, 0xe8, 0x66, 0xce,
0x12, 0xe1, 0x70, 0x1d, 0x14, 0x83, 0xd0, 0x77, 0xe5, 0x2a, 0x7a, 0x72, 0xfc, 0x99, 0xe9, 0x65,
0x67, 0x33, 0x67, 0xf1, 0x58, 0x78, 0x96, 0x6d, 0x79, 0xd9, 0x7a, 0x45, 0xf8, 0x11, 0x82, 0x95,
0xac, 0x31, 0x58, 0x0a, 0x12, 0x87, 0xc2, 0xb3, 0xa0, 0xbc, 0xcf, 0xcb, 0x12, 0x3f, 0x5f, 0xb0,
0xbd, 0x63, 0x0a, 0x94, 0x2d, 0x58, 0xec, 0xbd, 0x44, 0x2c, 0xbc, 0x0e, 0x16, 0xa8, 0x1f, 0xf4,
0xe2, 0x02, 0x20, 0x8f, 0x1f, 0xb5, 0x34, 0x76, 0x5a, 0x81, 0xd8, 0xcc, 0x59, 0x19, 0x1c, 0xbc,
0x0d, 0x8e, 0xed, 0x65, 0x6c, 0x8a, 0x09, 0xff, 0x1e, 0x31, 0xc6, 0xf3, 0xf4, 0xec, 0xd9, 0xcc,
0x59, 0x13, 0x68, 0x13, 0x8c, 0x32, 0xaa, 0xfe, 0x7b, 0x01, 0x2c, 0x48, 0xcf, 0x88, 0xb3, 0xc2,
0xf9, 0xc4, 0x06, 0xc2, 0x32, 0xff, 0x7d, 0x96, 0x0d, 0x78, 0x78, 0xca, 0x05, 0x6f, 0x27, 0x2e,
0x10, 0xfe, 0x59, 0x1d, 0x65, 0x29, 0xd7, 0x3f, 0x85, 0x90, 0xca, 0x6f, 0xc4, 0xca, 0x0b, 0xdb,
0x9c, 0x98, 0xbe, 0xee, 0xc6, 0x28, 0x29, 0xfb, 0x25, 0x50, 0xb1, 0xc5, 0x27, 0x84, 0x69, 0x86,
0x99, 0xfc, 0xc2, 0xc0, 0x84, 0x94, 0x00, 0xb8, 0x31, 0x92, 0x5f, 0xb8, 0xe6, 0xf8, 0xa4, 0xfc,
0x09, 0x28, 0x56, 0xff, 0x4c, 0xa2, 0x7e, 0x59, 0x62, 0x26, 0x16, 0xab, 0xe4, 0xc5, 0xa4, 0xf4,
0x9b, 0x60, 0xce, 0xc5, 0x14, 0xb1, 0xbd, 0xac, 0x5a, 0xe1, 0x75, 0xe3, 0x54, 0x56, 0xaa, 0x11,
0xdf, 0xc6, 0x2d, 0x19, 0x78, 0xcd, 0xa3, 0xe1, 0x81, 0xdc, 0xb6, 0x24, 0xe8, 0xb5, 0xcb, 0x60,
0x31, 0x13, 0x00, 0x8f, 0x81, 0x42, 0x0f, 0xc7, 0x5f, 0x4b, 0x58, 0x93, 0x1d, 0xee, 0xf6, 0x91,
0xd3, 0xc7, 0x9c, 0xf6, 0xaa, 0x25, 0x3a, 0x97, 0xf2, 0x17, 0x14, 0xb3, 0x0a, 0x2a, 0xa1, 0x78,
0x8a, 0xd9, 0x7e, 0xf8, 0x58, 0xcb, 0x3d, 0x7a, 0xac, 0xe5, 0x9e, 0x3e, 0xd6, 0x94, 0x2f, 0x86,
0x9a, 0xf2, 0xed, 0x50, 0x53, 0x1e, 0x0c, 0x35, 0xe5, 0xe1, 0x50, 0x53, 0x7e, 0x1b, 0x6a, 0xca,
0x1f, 0x43, 0x2d, 0xf7, 0x74, 0xa8, 0x29, 0xf7, 0x9e, 0x68, 0xb9, 0x87, 0x4f, 0xb4, 0xdc, 0xa3,
0x27, 0x5a, 0xee, 0x33, 0xe3, 0x68, 0x25, 0x6c, 0xa7, 0xcc, 0x69, 0xd9, 0xf8, 0x33, 0x00, 0x00,
0xff, 0xff, 0x33, 0xb4, 0xee, 0x07, 0x17, 0x15, 0x00, 0x00,
// 1514 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x58, 0x5b, 0x6f, 0x1b, 0x45,
0x1b, 0xf6, 0xfa, 0x18, 0x4f, 0x0e, 0x5f, 0xbe, 0x49, 0x94, 0xee, 0x97, 0xf6, 0xdb, 0xb5, 0x2c,
0xd1, 0x06, 0x04, 0x6b, 0x9a, 0x94, 0x1e, 0x01, 0xd1, 0xa5, 0xad, 0x52, 0xd1, 0xa2, 0x76, 0x13,
0x71, 0x81, 0xb8, 0x99, 0xd8, 0x13, 0x7b, 0xf1, 0x9e, 0xb2, 0x33, 0x0e, 0xcd, 0x1d, 0x3f, 0x00,
0xa4, 0xfe, 0x0a, 0x84, 0x44, 0x55, 0x89, 0x5b, 0x2e, 0xb9, 0xa1, 0x97, 0xbd, 0xac, 0x2c, 0xb1,
0x50, 0x97, 0x0b, 0x94, 0xab, 0xfe, 0x04, 0x34, 0x87, 0x5d, 0xef, 0xda, 0x6e, 0xeb, 0x14, 0x21,
0xb5, 0x12, 0x37, 0xf6, 0x1c, 0xde, 0x67, 0xf6, 0xdd, 0xe7, 0x79, 0xdf, 0x77, 0x66, 0x16, 0x9c,
0x0a, 0xba, 0xed, 0xc6, 0x5e, 0x0f, 0x87, 0x36, 0x0e, 0xf9, 0xff, 0x41, 0x88, 0xbc, 0x36, 0x4e,
0x35, 0x8d, 0x20, 0xf4, 0xa9, 0x0f, 0xc1, 0x70, 0x64, 0x75, 0xbd, 0x6d, 0xd3, 0x4e, 0x6f, 0xc7,
0x68, 0xfa, 0x6e, 0xa3, 0xed, 0xb7, 0xfd, 0x46, 0xdb, 0xf7, 0xdb, 0x0e, 0x46, 0x81, 0x4d, 0x64,
0xb3, 0x11, 0x06, 0xcd, 0x06, 0xa1, 0x88, 0xf6, 0x88, 0xc0, 0xaf, 0x2e, 0x33, 0x43, 0xde, 0xe4,
0x10, 0x39, 0xaa, 0x4b, 0x73, 0xde, 0xdb, 0xe9, 0xed, 0x36, 0xa8, 0xed, 0x62, 0x42, 0x91, 0x1b,
0x48, 0x83, 0xe3, 0xcc, 0x3f, 0xc7, 0x6f, 0x0b, 0x64, 0xdc, 0x90, 0x93, 0xff, 0xcb, 0x4c, 0x92,
0x2e, 0xa6, 0xcd, 0x8e, 0x9c, 0xaa, 0xc9, 0xa9, 0x3d, 0xc7, 0xf5, 0x5b, 0xd8, 0xe1, 0xbe, 0x10,
0xf1, 0x2b, 0x2d, 0x96, 0x98, 0x45, 0xd0, 0x23, 0x1d, 0xfe, 0x23, 0x07, 0x3f, 0x7e, 0x21, 0x1d,
0x3b, 0x88, 0xe0, 0x46, 0x0b, 0xef, 0xda, 0x9e, 0x4d, 0x6d, 0xdf, 0x23, 0xe9, 0xb6, 0x5c, 0xe4,
0xec, 0x74, 0x8b, 0x8c, 0x52, 0x5c, 0xbf, 0x5f, 0x00, 0xb3, 0x37, 0xfc, 0xae, 0x6d, 0xe1, 0xbd,
0x1e, 0x26, 0x14, 0x2e, 0x83, 0x12, 0xb7, 0x51, 0x95, 0x9a, 0xb2, 0x56, 0xb5, 0x44, 0x87, 0x8d,
0x3a, 0xb6, 0x6b, 0x53, 0x35, 0x5f, 0x53, 0xd6, 0xe6, 0x2d, 0xd1, 0x81, 0x10, 0x14, 0x09, 0xc5,
0x81, 0x5a, 0xa8, 0x29, 0x6b, 0x05, 0x8b, 0xb7, 0xe1, 0x2a, 0x98, 0xb1, 0x3d, 0x8a, 0xc3, 0x7d,
0xe4, 0xa8, 0x55, 0x3e, 0x9e, 0xf4, 0xe1, 0x87, 0xa0, 0x42, 0x28, 0x0a, 0xe9, 0x36, 0x51, 0x8b,
0x35, 0x65, 0x6d, 0x76, 0x7d, 0xd5, 0x10, 0x52, 0x18, 0xb1, 0x14, 0xc6, 0x76, 0x2c, 0x85, 0x39,
0xf3, 0x20, 0xd2, 0x73, 0x77, 0x7f, 0xd3, 0x15, 0x2b, 0x06, 0xc1, 0x8b, 0xa0, 0x84, 0xbd, 0xd6,
0x36, 0x51, 0x4b, 0x47, 0x40, 0x0b, 0x08, 0x3c, 0x0d, 0xaa, 0x2d, 0x3b, 0xc4, 0x4d, 0xc6, 0x99,
0x5a, 0xae, 0x29, 0x6b, 0x0b, 0xeb, 0x4b, 0x46, 0x22, 0xed, 0x95, 0x78, 0xca, 0x1a, 0x5a, 0xb1,
0xd7, 0x0b, 0x10, 0xed, 0xa8, 0x15, 0xce, 0x04, 0x6f, 0xc3, 0x3a, 0x28, 0x93, 0x0e, 0x0a, 0x5b,
0x44, 0x9d, 0xa9, 0x15, 0xd6, 0xaa, 0x26, 0x38, 0x8c, 0x74, 0x39, 0x62, 0xc9, 0x7f, 0xf8, 0x05,
0x28, 0x06, 0x0e, 0xf2, 0x54, 0xc0, 0xbd, 0x5c, 0x34, 0x52, 0x9c, 0xdf, 0x72, 0x90, 0x67, 0x9e,
0xed, 0x47, 0x7a, 0x26, 0x9a, 0x43, 0xb4, 0x8b, 0x3c, 0xd4, 0x70, 0xfc, 0xae, 0xdd, 0x48, 0xcb,
0xc8, 0x56, 0x31, 0x6e, 0x33, 0x34, 0xc3, 0x59, 0x7c, 0xd5, 0xfa, 0x2f, 0x79, 0x00, 0x99, 0x60,
0xd7, 0x3d, 0x42, 0x91, 0x47, 0x5f, 0x46, 0xb7, 0xf7, 0x41, 0x99, 0x85, 0xfc, 0x36, 0xe1, 0xca,
0x4d, 0x4b, 0xa4, 0xc4, 0x64, 0x99, 0x2c, 0x1e, 0x89, 0xc9, 0xd2, 0x44, 0x26, 0xcb, 0x2f, 0x64,
0xb2, 0xf2, 0x8f, 0x30, 0xa9, 0x82, 0x22, 0xeb, 0xc1, 0x45, 0x50, 0x08, 0xd1, 0x57, 0x9c, 0xb8,
0x39, 0x8b, 0x35, 0xeb, 0x3f, 0x14, 0xc1, 0x9c, 0x48, 0x0a, 0x12, 0xf8, 0x1e, 0xc1, 0xcc, 0xd9,
0x2d, 0x5e, 0x58, 0x04, 0xbd, 0xd2, 0x59, 0x3e, 0x62, 0xc9, 0x19, 0xf8, 0x11, 0x28, 0x5e, 0x41,
0x14, 0x71, 0xaa, 0x67, 0xd7, 0x97, 0xd3, 0xce, 0xb2, 0xb5, 0xd8, 0x9c, 0xb9, 0xc2, 0xd8, 0x3c,
0x8c, 0xf4, 0x85, 0x16, 0xa2, 0xe8, 0x6d, 0xdf, 0xb5, 0x29, 0x76, 0x03, 0x7a, 0x60, 0x71, 0x24,
0x7c, 0x0f, 0x54, 0xaf, 0x86, 0xa1, 0x1f, 0x6e, 0x1f, 0x04, 0x98, 0x4b, 0x53, 0x35, 0x8f, 0x1d,
0x46, 0xfa, 0x12, 0x8e, 0x07, 0x53, 0x88, 0xa1, 0x25, 0x7c, 0x13, 0x94, 0x78, 0x87, 0x8b, 0x51,
0x35, 0x97, 0x0e, 0x23, 0xfd, 0x3f, 0x1c, 0x92, 0x32, 0x17, 0x16, 0x59, 0xed, 0x4a, 0x53, 0x69,
0x97, 0x84, 0x50, 0x39, 0x1d, 0x42, 0x2a, 0xa8, 0xec, 0xe3, 0x90, 0xb0, 0x65, 0x2a, 0x7c, 0x3c,
0xee, 0xc2, 0xcb, 0x00, 0x30, 0x62, 0x6c, 0x42, 0xed, 0x26, 0xcb, 0x12, 0x46, 0xc6, 0xbc, 0x21,
0x8a, 0xa0, 0x85, 0x49, 0xcf, 0xa1, 0x26, 0x94, 0x2c, 0xa4, 0x0c, 0xad, 0x54, 0x1b, 0xde, 0x53,
0x40, 0x65, 0x13, 0xa3, 0x16, 0x0e, 0x89, 0x5a, 0xad, 0x15, 0xd6, 0x66, 0xd7, 0xdf, 0x30, 0xd2,
0x15, 0xef, 0x56, 0xe8, 0xbb, 0x98, 0x76, 0x70, 0x8f, 0xc4, 0x02, 0x09, 0x6b, 0xb3, 0xdb, 0x8f,
0xf4, 0x9d, 0x69, 0xe2, 0x61, 0xaa, 0x2a, 0xfb, 0xcc, 0xe7, 0x1c, 0x46, 0xba, 0xf2, 0x8e, 0x15,
0xbb, 0x58, 0xff, 0x55, 0x01, 0xff, 0x65, 0x0a, 0x6f, 0xb1, 0xb5, 0x49, 0x2a, 0x21, 0x5d, 0x44,
0x9b, 0x1d, 0x55, 0x61, 0xe1, 0x6d, 0x89, 0x4e, 0xba, 0x04, 0xe6, 0xff, 0x56, 0x09, 0x2c, 0x1c,
0xbd, 0x04, 0xc6, 0x59, 0x58, 0x9c, 0x98, 0x85, 0xa5, 0x67, 0x65, 0x61, 0xfd, 0x9b, 0x82, 0xa8,
0x38, 0xf1, 0xfb, 0x1d, 0x21, 0x27, 0xae, 0x25, 0x39, 0x51, 0xe0, 0xde, 0x26, 0xa1, 0x26, 0xd6,
0xba, 0xde, 0xc2, 0x1e, 0xb5, 0x77, 0x6d, 0x1c, 0xbe, 0x20, 0x33, 0x52, 0xe1, 0x56, 0xc8, 0x86,
0x5b, 0x3a, 0x56, 0x8a, 0xaf, 0x7c, 0xac, 0x8c, 0x64, 0x47, 0xe9, 0x25, 0xb2, 0xa3, 0xfe, 0x34,
0x0f, 0x56, 0x98, 0x1c, 0x37, 0xd0, 0x0e, 0x76, 0x3e, 0x45, 0xee, 0x11, 0x25, 0x39, 0x99, 0x92,
0xa4, 0x6a, 0xc2, 0x7f, 0x29, 0x9f, 0x82, 0xf2, 0xef, 0x14, 0x30, 0x13, 0xd7, 0x70, 0x68, 0x00,
0x20, 0x60, 0xbc, 0x4c, 0x0b, 0xa2, 0x17, 0x18, 0x38, 0x4c, 0x46, 0xad, 0x94, 0x05, 0xfc, 0x12,
0x94, 0x45, 0x4f, 0x66, 0xc1, 0xb1, 0x54, 0x16, 0xd0, 0x10, 0x23, 0xf7, 0x72, 0x0b, 0x05, 0x14,
0x87, 0xe6, 0x05, 0xe6, 0x45, 0x3f, 0xd2, 0x4f, 0x3d, 0x8f, 0x22, 0x7e, 0x6e, 0x14, 0x38, 0x26,
0xae, 0x78, 0xa6, 0x25, 0x9f, 0x50, 0xff, 0x56, 0x01, 0x8b, 0xcc, 0x51, 0x46, 0x4d, 0x12, 0x15,
0x57, 0xc0, 0x4c, 0x28, 0xdb, 0xdc, 0xdd, 0xd9, 0xf5, 0xba, 0x91, 0xa5, 0x75, 0x02, 0x95, 0x66,
0xf1, 0x41, 0xa4, 0x2b, 0x56, 0x82, 0x84, 0x1b, 0x19, 0x1a, 0xf3, 0x93, 0x68, 0x64, 0x90, 0x5c,
0x86, 0xb8, 0x9f, 0xf2, 0x00, 0x5e, 0xf7, 0x5a, 0xf8, 0x0e, 0x0b, 0xbe, 0x61, 0x9c, 0xf6, 0xc6,
0x3c, 0x3a, 0x31, 0x24, 0x65, 0xdc, 0xde, 0xbc, 0xd4, 0x8f, 0xf4, 0x73, 0xcf, 0x63, 0xe5, 0x39,
0xe0, 0xd4, 0x2b, 0xa4, 0x03, 0x37, 0xff, 0xea, 0xef, 0x2b, 0xf7, 0xf3, 0x60, 0xe1, 0x33, 0xdf,
0xe9, 0xb9, 0x38, 0x21, 0xce, 0x1d, 0x23, 0x4e, 0x1d, 0x12, 0x97, 0xb5, 0x35, 0xcf, 0xf5, 0x23,
0x7d, 0x63, 0x2a, 0xd2, 0xb2, 0xc0, 0xd7, 0x97, 0xb0, 0x7b, 0x79, 0xb0, 0xbc, 0xed, 0x07, 0x9f,
0x6c, 0xf1, 0x4b, 0x59, 0xaa, 0x2e, 0xe2, 0x31, 0xda, 0x96, 0x87, 0xb4, 0x31, 0xc4, 0x4d, 0x44,
0x43, 0xfb, 0x8e, 0xb9, 0xd1, 0x8f, 0xf4, 0xc6, 0x54, 0x94, 0x0d, 0x41, 0xaf, 0x2f, 0x5d, 0x3f,
0xe7, 0xc1, 0xca, 0xed, 0x1e, 0xf2, 0xa8, 0xed, 0x60, 0x41, 0x59, 0x42, 0xd8, 0xc1, 0x18, 0x61,
0xda, 0x90, 0xb0, 0x2c, 0x46, 0x52, 0xf7, 0x41, 0x3f, 0xd2, 0x2f, 0x4c, 0x45, 0xdd, 0x24, 0xf8,
0xeb, 0x4b, 0xe2, 0x8f, 0x45, 0x30, 0xcf, 0x2f, 0x16, 0x09, 0x77, 0x6f, 0x01, 0xb9, 0xe5, 0x4a,
0xe6, 0x60, 0x7c, 0x46, 0x0b, 0x83, 0xa6, 0xb1, 0x25, 0x37, 0x63, 0x61, 0x01, 0xcf, 0x83, 0x32,
0xe1, 0x27, 0x21, 0x59, 0x50, 0xb5, 0xd1, 0x5b, 0x43, 0xf6, 0xcc, 0xb5, 0x99, 0xb3, 0xa4, 0x3d,
0xbb, 0xc3, 0x39, 0xec, 0x00, 0x10, 0x9f, 0x04, 0xeb, 0xa3, 0xc8, 0xf1, 0xe3, 0x01, 0x43, 0x0b,
0x0c, 0x3c, 0x0b, 0x4a, 0xbc, 0x72, 0xcb, 0x7b, 0x78, 0xe6, 0xb1, 0xe3, 0x25, 0x74, 0x33, 0x67,
0x09, 0x73, 0xb8, 0x0e, 0x8a, 0x41, 0xe8, 0xbb, 0x72, 0x17, 0x3d, 0x31, 0xfa, 0xcc, 0xf4, 0xb6,
0xb3, 0x99, 0xb3, 0xb8, 0x2d, 0x3c, 0xc3, 0x8e, 0xbc, 0x6c, 0xbf, 0x22, 0xfc, 0x0a, 0xc1, 0x4a,
0xd6, 0x08, 0x2c, 0x05, 0x89, 0x4d, 0xe1, 0x19, 0x50, 0xde, 0xe7, 0x65, 0x49, 0x5e, 0xfe, 0x56,
0xd3, 0xa0, 0x6c, 0xc1, 0x62, 0xef, 0x25, 0x6c, 0xe1, 0x35, 0x30, 0x47, 0xfd, 0xa0, 0x1b, 0x17,
0x00, 0x79, 0xfd, 0xa8, 0xa5, 0xb1, 0x93, 0x0a, 0xc4, 0x66, 0xce, 0xca, 0xe0, 0xe0, 0x2d, 0xb0,
0xb8, 0x97, 0x09, 0x53, 0x4c, 0xf8, 0xd7, 0x8c, 0x11, 0x9e, 0x27, 0x67, 0xcf, 0x66, 0xce, 0x1a,
0x43, 0x9b, 0x60, 0x98, 0x51, 0xf5, 0x3f, 0x0a, 0x60, 0x4e, 0xc6, 0x8c, 0xb8, 0x2b, 0x9c, 0x4b,
0xc2, 0x40, 0x84, 0xcc, 0xff, 0x9f, 0x15, 0x06, 0xdc, 0x3c, 0x15, 0x05, 0xef, 0x26, 0x51, 0x20,
0xe2, 0x67, 0x65, 0x98, 0xa5, 0x5c, 0xff, 0x14, 0x42, 0x2a, 0xbf, 0x11, 0x2b, 0x2f, 0xc2, 0xe6,
0xf8, 0xe4, 0x7d, 0x37, 0x46, 0x49, 0xd9, 0x2f, 0x82, 0x8a, 0x2d, 0x3e, 0x37, 0x4c, 0x0a, 0x98,
0xf1, 0xaf, 0x11, 0x4c, 0x48, 0x09, 0x80, 0x1b, 0x43, 0xf9, 0x45, 0xd4, 0x1c, 0x1b, 0x97, 0x3f,
0x01, 0xc5, 0xea, 0x9f, 0x4e, 0xd4, 0x2f, 0x4b, 0xcc, 0xd8, 0x66, 0x95, 0xbc, 0x98, 0x94, 0x7e,
0x13, 0xcc, 0xb8, 0x98, 0x22, 0x76, 0x96, 0x55, 0x2b, 0xbc, 0x6e, 0x9c, 0xcc, 0x4a, 0x35, 0xe4,
0xdb, 0xb8, 0x29, 0x0d, 0xaf, 0x7a, 0x34, 0x3c, 0x90, 0xc7, 0x96, 0x04, 0xbd, 0x7a, 0x09, 0xcc,
0x67, 0x0c, 0xe0, 0x22, 0x28, 0x74, 0x71, 0xfc, 0x65, 0x85, 0x35, 0xd9, 0xe5, 0x6e, 0x1f, 0x39,
0x3d, 0xcc, 0x69, 0xaf, 0x5a, 0xa2, 0x73, 0x31, 0x7f, 0x5e, 0x31, 0xab, 0xa0, 0x12, 0x8a, 0xa7,
0x98, 0xad, 0x87, 0x8f, 0xb5, 0xdc, 0xa3, 0xc7, 0x5a, 0xee, 0xe9, 0x63, 0x4d, 0xf9, 0x7a, 0xa0,
0x29, 0xdf, 0x0f, 0x34, 0xe5, 0xc1, 0x40, 0x53, 0x1e, 0x0e, 0x34, 0xe5, 0xf7, 0x81, 0xa6, 0xfc,
0x39, 0xd0, 0x72, 0x4f, 0x07, 0x9a, 0x72, 0xf7, 0x89, 0x96, 0x7b, 0xf8, 0x44, 0xcb, 0x3d, 0x7a,
0xa2, 0xe5, 0x3e, 0x37, 0x8e, 0x56, 0xc2, 0x76, 0xca, 0x9c, 0x96, 0x8d, 0xbf, 0x02, 0x00, 0x00,
0xff, 0xff, 0xe6, 0x4a, 0x9a, 0x06, 0x55, 0x15, 0x00, 0x00,
}
func (this *LokiRequest) Equal(that interface{}) bool {
@ -1334,6 +1380,30 @@ func (this *LokiInstantRequest) Equal(that interface{}) bool {
}
return true
}
func (this *Plan) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
that1, ok := that.(*Plan)
if !ok {
that2, ok := that.(Plan)
if ok {
that1 = &that2
} else {
return false
}
}
if that1 == nil {
return this == nil
} else if this == nil {
return false
}
if !bytes.Equal(this.Raw, that1.Raw) {
return false
}
return true
}
func (this *LokiResponse) Equal(that interface{}) bool {
if that == nil {
return this == nil
@ -2170,6 +2240,16 @@ func (this *LokiInstantRequest) GoString() string {
s = append(s, "}")
return strings.Join(s, "")
}
func (this *Plan) GoString() string {
if this == nil {
return "nil"
}
s := make([]string, 0, 5)
s = append(s, "&queryrange.Plan{")
s = append(s, "Raw: "+fmt.Sprintf("%#v", this.Raw)+",\n")
s = append(s, "}")
return strings.Join(s, "")
}
func (this *LokiResponse) GoString() string {
if this == nil {
return "nil"
@ -2522,21 +2602,21 @@ func (m *LokiRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0x30
}
n1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTs, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTs):])
if err1 != nil {
return 0, err1
}
i -= n1
i = encodeVarintQueryrange(dAtA, i, uint64(n1))
i--
dAtA[i] = 0x2a
n2, err2 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTs, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTs):])
n2, err2 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTs, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTs):])
if err2 != nil {
return 0, err2
}
i -= n2
i = encodeVarintQueryrange(dAtA, i, uint64(n2))
i--
dAtA[i] = 0x2a
n3, err3 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTs, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTs):])
if err3 != nil {
return 0, err3
}
i -= n3
i = encodeVarintQueryrange(dAtA, i, uint64(n3))
i--
dAtA[i] = 0x22
if m.Step != 0 {
i = encodeVarintQueryrange(dAtA, i, uint64(m.Step))
@ -2611,12 +2691,12 @@ func (m *LokiInstantRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0x20
}
n3, err3 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.TimeTs, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.TimeTs):])
if err3 != nil {
return 0, err3
n5, err5 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.TimeTs, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.TimeTs):])
if err5 != nil {
return 0, err5
}
i -= n3
i = encodeVarintQueryrange(dAtA, i, uint64(n3))
i -= n5
i = encodeVarintQueryrange(dAtA, i, uint64(n5))
i--
dAtA[i] = 0x1a
if m.Limit != 0 {
@ -2634,6 +2714,36 @@ func (m *LokiInstantRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *Plan) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Plan) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Plan) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Raw) > 0 {
i -= len(m.Raw)
copy(dAtA[i:], m.Raw)
i = encodeVarintQueryrange(dAtA, i, uint64(len(m.Raw)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *LokiResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
@ -2763,20 +2873,20 @@ func (m *LokiSeriesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0x22
}
n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTs, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTs):])
if err6 != nil {
return 0, err6
n8, err8 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndTs, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndTs):])
if err8 != nil {
return 0, err8
}
i -= n6
i = encodeVarintQueryrange(dAtA, i, uint64(n6))
i -= n8
i = encodeVarintQueryrange(dAtA, i, uint64(n8))
i--
dAtA[i] = 0x1a
n7, err7 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTs, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTs):])
if err7 != nil {
return 0, err7
n9, err9 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartTs, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartTs):])
if err9 != nil {
return 0, err9
}
i -= n7
i = encodeVarintQueryrange(dAtA, i, uint64(n7))
i -= n9
i = encodeVarintQueryrange(dAtA, i, uint64(n9))
i--
dAtA[i] = 0x12
if len(m.Match) > 0 {
@ -3681,6 +3791,19 @@ func (m *LokiInstantRequest) Size() (n int) {
return n
}
func (m *Plan) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Raw)
if l > 0 {
n += 1 + l + sovQueryrange(uint64(l))
}
return n
}
func (m *LokiResponse) Size() (n int) {
if m == nil {
return 0
@ -4166,6 +4289,16 @@ func (this *LokiInstantRequest) String() string {
}, "")
return s
}
func (this *Plan) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&Plan{`,
`Raw:` + fmt.Sprintf("%v", this.Raw) + `,`,
`}`,
}, "")
return s
}
func (this *LokiResponse) String() string {
if this == nil {
return "nil"
@ -4748,7 +4881,7 @@ func (m *LokiRequest) Unmarshal(dAtA []byte) error {
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Plan", wireType)
}
var byteLen int
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQueryrange
@ -4758,23 +4891,24 @@ func (m *LokiRequest) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
if msglen < 0 {
return ErrInvalidLengthQueryrange
}
postIndex := iNdEx + byteLen
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthQueryrange
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
var v github_com_grafana_loki_pkg_querier_plan.QueryPlan
m.Plan = &v
if m.Plan == nil {
m.Plan = &github_com_grafana_loki_pkg_querier_plan.QueryPlan{}
}
if err := m.Plan.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
@ -5003,6 +5137,95 @@ func (m *LokiInstantRequest) Unmarshal(dAtA []byte) error {
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Plan", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQueryrange
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthQueryrange
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthQueryrange
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Plan == nil {
m.Plan = &github_com_grafana_loki_pkg_querier_plan.QueryPlan{}
}
if err := m.Plan.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipQueryrange(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthQueryrange
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthQueryrange
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *Plan) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQueryrange
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Plan: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Plan: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Raw", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
@ -5028,10 +5251,9 @@ func (m *LokiInstantRequest) Unmarshal(dAtA []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
var v github_com_grafana_loki_pkg_querier_plan.QueryPlan
m.Plan = &v
if err := m.Plan.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
m.Raw = append(m.Raw[:0], dAtA[iNdEx:postIndex]...)
if m.Raw == nil {
m.Raw = []byte{}
}
iNdEx = postIndex
default:

@ -18,7 +18,7 @@ option (gogoproto.sizer_all) = true;
option (gogoproto.unmarshaler_all) = true;
message LokiRequest {
string query = 1;
string query = 1; // mark as reserved once we've fully migrated to plan.
uint32 limit = 2;
int64 step = 3;
int64 interval = 9;
@ -33,7 +33,7 @@ message LokiRequest {
logproto.Direction direction = 6;
string path = 7;
repeated string shards = 8 [(gogoproto.jsontag) = "shards"];
bytes plan = 10 [(gogoproto.customtype) = "github.com/grafana/loki/pkg/querier/plan.QueryPlan"];
Plan plan = 10 [(gogoproto.customtype) = "github.com/grafana/loki/pkg/querier/plan.QueryPlan"];
}
message LokiInstantRequest {
@ -46,7 +46,11 @@ message LokiInstantRequest {
logproto.Direction direction = 4;
string path = 5;
repeated string shards = 6 [(gogoproto.jsontag) = "shards"];
bytes plan = 7 [(gogoproto.customtype) = "github.com/grafana/loki/pkg/querier/plan.QueryPlan"];
Plan plan = 7 [(gogoproto.customtype) = "github.com/grafana/loki/pkg/querier/plan.QueryPlan"];
}
message Plan {
bytes raw = 1;
}
message LokiResponse {

@ -22,6 +22,8 @@ import (
"time"
"github.com/pkg/errors"
"github.com/grafana/dskit/flagext"
)
const day = 24 * time.Hour
@ -33,11 +35,12 @@ var PassthroughMiddleware = MiddlewareFunc(func(next Handler) Handler {
// Config for query_range middleware chain.
type Config struct {
AlignQueriesWithStep bool `yaml:"align_queries_with_step"`
ResultsCacheConfig ResultsCacheConfig `yaml:"results_cache"`
CacheResults bool `yaml:"cache_results"`
MaxRetries int `yaml:"max_retries"`
ShardedQueries bool `yaml:"parallelise_shardable_queries"`
AlignQueriesWithStep bool `yaml:"align_queries_with_step"`
ResultsCacheConfig ResultsCacheConfig `yaml:"results_cache"`
CacheResults bool `yaml:"cache_results"`
MaxRetries int `yaml:"max_retries"`
ShardedQueries bool `yaml:"parallelise_shardable_queries"`
ShardAggregations flagext.StringSliceCSV `yaml:"shard_aggregations"`
}
// RegisterFlags adds the flags required to config this to the given FlagSet.
@ -47,6 +50,10 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
f.BoolVar(&cfg.CacheResults, "querier.cache-results", false, "Cache query results.")
f.BoolVar(&cfg.ShardedQueries, "querier.parallelise-shardable-queries", true, "Perform query parallelisations based on storage sharding configuration and query ASTs. This feature is supported only by the chunks storage engine.")
cfg.ShardAggregations = []string{}
f.Var(&cfg.ShardAggregations, "querier.shard-aggregation",
"A comma-separated list of LogQL vector and range aggregations that should be sharded")
cfg.ResultsCacheConfig.RegisterFlags(f)
}
@ -57,6 +64,11 @@ func (cfg *Config) Validate() error {
return errors.Wrap(err, "invalid results_cache config")
}
}
if len(cfg.ShardAggregations) > 0 && !cfg.ShardedQueries {
return errors.New("shard_aggregation requires parallelise_shardable_queries=true")
}
return nil
}

@ -41,6 +41,7 @@ func NewQueryShardMiddleware(
limits Limits,
maxShards int,
statsHandler queryrangebase.Handler,
shardAggregation []string,
) queryrangebase.Middleware {
noshards := !hasShards(confs)
@ -54,7 +55,7 @@ func NewQueryShardMiddleware(
}
mapperware := queryrangebase.MiddlewareFunc(func(next queryrangebase.Handler) queryrangebase.Handler {
return newASTMapperware(confs, engineOpts, next, statsHandler, logger, shardingMetrics, limits, maxShards)
return newASTMapperware(confs, engineOpts, next, statsHandler, logger, shardingMetrics, limits, maxShards, shardAggregation)
})
return queryrangebase.MiddlewareFunc(func(next queryrangebase.Handler) queryrangebase.Handler {
@ -79,16 +80,18 @@ func newASTMapperware(
metrics *logql.MapperMetrics,
limits Limits,
maxShards int,
shardAggregation []string,
) *astMapperware {
ast := &astMapperware{
confs: confs,
logger: log.With(logger, "middleware", "QueryShard.astMapperware"),
limits: limits,
next: next,
statsHandler: next,
ng: logql.NewDownstreamEngine(engineOpts, DownstreamHandler{next: next, limits: limits}, limits, logger),
metrics: metrics,
maxShards: maxShards,
confs: confs,
logger: log.With(logger, "middleware", "QueryShard.astMapperware"),
limits: limits,
next: next,
statsHandler: next,
ng: logql.NewDownstreamEngine(engineOpts, DownstreamHandler{next: next, limits: limits}, limits, logger),
metrics: metrics,
maxShards: maxShards,
shardAggregation: shardAggregation,
}
if statsHandler != nil {
@ -107,6 +110,10 @@ type astMapperware struct {
ng *logql.DownstreamEngine
metrics *logql.MapperMetrics
maxShards int
// Feature flag for sharding range and vector aggregations such as
// quantile_ver_time with probabilistic data structures.
shardAggregation []string
}
func (ast *astMapperware) checkQuerySizeLimit(ctx context.Context, bytesPerShard uint64, notShardable bool) error {
@ -143,7 +150,12 @@ func (ast *astMapperware) Do(ctx context.Context, r queryrangebase.Request) (que
util_log.WithContext(ctx, ast.logger),
)
maxRVDuration, maxOffset, err := maxRangeVectorAndOffsetDuration(r.GetQuery())
params, err := ParamsFromRequest(r)
if err != nil {
return nil, err
}
maxRVDuration, maxOffset, err := maxRangeVectorAndOffsetDuration(params.GetExpression())
if err != nil {
level.Warn(logger).Log("err", err.Error(), "msg", "failed to get range-vector and offset duration so skipped AST mapper for request")
return ast.next.Do(ctx, r)
@ -183,12 +195,7 @@ func (ast *astMapperware) Do(ctx context.Context, r queryrangebase.Request) (que
return ast.next.Do(ctx, r)
}
mapper := logql.NewShardMapper(resolver, ast.metrics)
params, err := ParamsFromRequest(r)
if err != nil {
return nil, err
}
mapper := logql.NewShardMapper(resolver, ast.metrics, ast.shardAggregation)
noop, bytesPerShard, parsed, err := mapper.Parse(params.GetExpression())
if err != nil {

@ -172,6 +172,7 @@ func Test_astMapper(t *testing.T) {
nilShardingMetrics,
fakeLimits{maxSeries: math.MaxInt32, maxQueryParallelism: 1, queryTimeout: time.Second},
0,
[]string{},
)
req := defaultReq()
@ -316,6 +317,7 @@ func Test_astMapper_QuerySizeLimits(t *testing.T) {
maxQuerierBytesRead: tc.maxQuerierBytesSize,
},
0,
[]string{},
)
req := defaultReq()
@ -354,6 +356,7 @@ func Test_ShardingByPass(t *testing.T) {
nilShardingMetrics,
fakeLimits{maxSeries: math.MaxInt32, maxQueryParallelism: 1},
0,
[]string{},
)
req := defaultReq()
@ -434,7 +437,9 @@ func Test_InstantSharding(t *testing.T) {
queryTimeout: time.Second,
},
0,
nil)
nil,
[]string{},
)
response, err := sharding.Wrap(queryrangebase.HandlerFunc(func(c context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
lock.Lock()
defer lock.Unlock()
@ -722,6 +727,7 @@ func TestShardingAcrossConfigs_ASTMapper(t *testing.T) {
nilShardingMetrics,
fakeLimits{maxSeries: math.MaxInt32, maxQueryParallelism: 1, queryTimeout: time.Second},
0,
[]string{},
)
// currently all the tests call `defaultReq()` which creates an instance of the type LokiRequest
@ -856,6 +862,7 @@ func Test_ASTMapper_MaxLookBackPeriod(t *testing.T) {
nilShardingMetrics,
fakeLimits{maxSeries: math.MaxInt32, tsdbMaxQueryParallelism: 1, queryTimeout: time.Second},
0,
[]string{},
)
q := `{cluster="dev-us-central-0"}`

@ -242,11 +242,6 @@ func (r roundTripper) Do(ctx context.Context, req base.Request) (base.Response,
switch op := req.(type) {
case *LokiRequest:
expr, err := syntax.ParseExpr(op.Query)
if err != nil {
return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
}
queryHash := util.HashedQuery(op.Query)
level.Info(logger).Log(
"msg", "executing query",
@ -261,7 +256,11 @@ func (r roundTripper) Do(ctx context.Context, req base.Request) (base.Response,
"query_hash", queryHash,
)
switch e := expr.(type) {
if op.Plan == nil {
return nil, errors.New("query plan is empty")
}
switch e := op.Plan.AST.(type) {
case syntax.SampleExpr:
// The error will be handled later.
groups, err := e.MatcherGroups()
@ -302,15 +301,10 @@ func (r roundTripper) Do(ctx context.Context, req base.Request) (base.Response,
return r.labels.Do(ctx, req)
case *LokiInstantRequest:
expr, err := syntax.ParseExpr(op.Query)
if err != nil {
return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
}
queryHash := util.HashedQuery(op.Query)
level.Info(logger).Log("msg", "executing query", "type", "instant", "query", op.Query, "query_hash", queryHash)
switch expr.(type) {
switch op.Plan.AST.(type) {
case syntax.SampleExpr:
return r.instantMetric.Do(ctx, req)
default:
@ -440,6 +434,7 @@ func NewLogFilterTripperware(
limits,
0, // 0 is unlimited shards
statsHandler,
cfg.ShardAggregations,
),
)
} else {
@ -664,6 +659,7 @@ func NewMetricTripperware(
limits,
0, // 0 is unlimited shards
statsHandler,
cfg.ShardAggregations,
),
)
} else {
@ -728,6 +724,7 @@ func NewInstantMetricTripperware(
limits,
0, // 0 is unlimited shards
statsHandler,
cfg.ShardAggregations,
),
)
}

@ -199,6 +199,9 @@ func TestMetricsTripperware(t *testing.T) {
EndTs: testTime,
Direction: logproto.FORWARD,
Path: "/query_range",
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`rate({app="foo"} |= "foo"[1m])`),
},
}
ctx := user.InjectOrgID(context.Background(), "1")
@ -282,6 +285,9 @@ func TestLogFilterTripperware(t *testing.T) {
EndTs: testTime,
Direction: logproto.FORWARD,
Path: "/loki/api/v1/query_range",
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{app="foo"} |= "foo"`),
},
}
ctx := user.InjectOrgID(context.Background(), "1")
@ -806,6 +812,9 @@ func TestLogNoFilter(t *testing.T) {
EndTs: testTime,
Direction: logproto.FORWARD,
Path: "/loki/api/v1/query_range",
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{app="foo"}`),
},
}
ctx := user.InjectOrgID(context.Background(), "1")
@ -817,7 +826,12 @@ func TestLogNoFilter(t *testing.T) {
}
func TestPostQueries(t *testing.T) {
lreq := &LokiRequest{Query: `{app="foo"} |~ "foo"`}
lreq := &LokiRequest{
Query: `{app="foo"} |~ "foo"`,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{app="foo"} |~ "foo"`),
},
}
ctx := user.InjectOrgID(context.Background(), "1")
handler := base.HandlerFunc(func(context.Context, base.Request) (base.Response, error) {
t.Error("unexpected default roundtripper called")
@ -855,6 +869,9 @@ func TestTripperware_EntriesLimit(t *testing.T) {
EndTs: testTime,
Direction: logproto.FORWARD,
Path: "/loki/api/v1/query_range",
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{app="foo"}`),
},
}
ctx := user.InjectOrgID(context.Background(), "1")
@ -902,6 +919,9 @@ func TestTripperware_RequiredLabels(t *testing.T) {
EndTs: testTime,
Direction: logproto.FORWARD,
Path: "/loki/api/v1/query_range",
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(test.qs),
},
}
// See loghttp.step
step := time.Duration(int(math.Max(math.Floor(lreq.EndTs.Sub(lreq.StartTs).Seconds()/250), 1))) * time.Second
@ -1007,6 +1027,9 @@ func TestTripperware_RequiredNumberLabels(t *testing.T) {
EndTs: testTime,
Direction: logproto.FORWARD,
Path: "/loki/api/v1/query_range",
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(tc.query),
},
}
// See loghttp.step
step := time.Duration(int(math.Max(math.Floor(lreq.EndTs.Sub(lreq.StartTs).Seconds()/250), 1))) * time.Second

@ -311,13 +311,17 @@ func splitByTime(req queryrangebase.Request, interval time.Duration) ([]queryran
return reqs, nil
}
// maxRangeVectorAndOffsetDuration returns the maximum range vector and offset duration within a LogQL query.
func maxRangeVectorAndOffsetDuration(q string) (time.Duration, time.Duration, error) {
expr, err := syntax.ParseExpr(q)
// maxRangeVectorAndOffsetDurationFromQueryString
func maxRangeVectorAndOffsetDurationFromQueryString(q string) (time.Duration, time.Duration, error) {
parsed, err := syntax.ParseExpr(q)
if err != nil {
return 0, 0, err
}
return maxRangeVectorAndOffsetDuration(parsed)
}
// maxRangeVectorAndOffsetDuration returns the maximum range vector and offset duration within a LogQL query.
func maxRangeVectorAndOffsetDuration(expr syntax.Expr) (time.Duration, time.Duration, error) {
if _, ok := expr.(syntax.SampleExpr); !ok {
return 0, 0, nil
}
@ -338,8 +342,8 @@ func maxRangeVectorAndOffsetDuration(q string) (time.Duration, time.Duration, er
// reduceSplitIntervalForRangeVector reduces the split interval for a range query based on the duration of the range vector.
// Large range vector durations will not be split into smaller intervals because it can cause the queries to be slow by over-processing data.
func reduceSplitIntervalForRangeVector(r queryrangebase.Request, interval time.Duration) (time.Duration, error) {
maxRange, _, err := maxRangeVectorAndOffsetDuration(r.GetQuery())
func reduceSplitIntervalForRangeVector(r *LokiRequest, interval time.Duration) (time.Duration, error) {
maxRange, _, err := maxRangeVectorAndOffsetDuration(r.Plan.AST)
if err != nil {
return 0, err
}
@ -352,13 +356,13 @@ func reduceSplitIntervalForRangeVector(r queryrangebase.Request, interval time.D
func splitMetricByTime(r queryrangebase.Request, interval time.Duration) ([]queryrangebase.Request, error) {
var reqs []queryrangebase.Request
interval, err := reduceSplitIntervalForRangeVector(r, interval)
lokiReq := r.(*LokiRequest)
interval, err := reduceSplitIntervalForRangeVector(lokiReq, interval)
if err != nil {
return nil, err
}
lokiReq := r.(*LokiRequest)
// step align start and end time of the query. Start time is rounded down and end time is rounded up.
stepNs := r.GetStep() * 1e6
startNs := lokiReq.StartTs.UnixNano()

@ -228,7 +228,7 @@ func Test_splitMetricQuery(t *testing.T) {
const seconds = 1e3 // 1e3 milliseconds per second.
for i, tc := range []struct {
input queryrangebase.Request
input *LokiRequest
expected []queryrangebase.Request
interval time.Duration
}{
@ -600,6 +600,17 @@ func Test_splitMetricQuery(t *testing.T) {
interval: 15 * time.Minute,
},
} {
// Set query plans
tc.input.Plan = &plan.QueryPlan{
AST: syntax.MustParseExpr(tc.input.Query),
}
for _, e := range tc.expected {
e.(*LokiRequest).Plan = &plan.QueryPlan{
AST: syntax.MustParseExpr(e.GetQuery()),
}
}
t.Run(strconv.Itoa(i), func(t *testing.T) {
splits, err := splitMetricByTime(tc.input, tc.interval)
require.NoError(t, err)

@ -25,7 +25,9 @@ import (
"github.com/grafana/loki/pkg/iter"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/querier/astmapper"
"github.com/grafana/loki/pkg/querier/plan"
"github.com/grafana/loki/pkg/storage/chunk"
"github.com/grafana/loki/pkg/storage/chunk/client/local"
"github.com/grafana/loki/pkg/storage/config"
@ -494,6 +496,10 @@ func Test_store_SelectLogs(t *testing.T) {
chunkMetrics: NilMetrics,
}
tt.req.Plan = &plan.QueryPlan{
AST: syntax.MustParseExpr(tt.req.Selector),
}
ctx = user.InjectOrgID(context.Background(), "test-user")
it, err := s.SelectLogs(ctx, logql.SelectLogParams{QueryRequest: tt.req})
if err != nil {
@ -818,6 +824,10 @@ func Test_store_SelectSample(t *testing.T) {
chunkMetrics: NilMetrics,
}
tt.req.Plan = &plan.QueryPlan{
AST: syntax.MustParseExpr(tt.req.Selector),
}
ctx = user.InjectOrgID(context.Background(), "test-user")
it, err := s.SelectSamples(ctx, logql.SelectSampleParams{SampleQueryRequest: tt.req})
if err != nil {
@ -1385,6 +1395,9 @@ func Test_OverlappingChunks(t *testing.T) {
Direction: logproto.BACKWARD,
Start: time.Unix(0, 0),
End: time.Unix(0, 10),
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(`{foo="bar"}`),
},
}})
if err != nil {
t.Errorf("store.SelectLogs() error = %v", err)
@ -1497,6 +1510,15 @@ func Test_GetSeries(t *testing.T) {
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
if tt.req.Selector != "" {
tt.req.Plan = &plan.QueryPlan{
AST: syntax.MustParseExpr(tt.req.Selector),
}
} else {
tt.req.Plan = &plan.QueryPlan{
AST: nil,
}
}
series, err := store.SelectSeries(ctx, tt.req)
require.NoError(t, err)
require.Equal(t, tt.expectedSeries, series)

@ -17,6 +17,7 @@ import (
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/logqlmodel/stats"
"github.com/grafana/loki/pkg/querier/astmapper"
"github.com/grafana/loki/pkg/querier/plan"
"github.com/grafana/loki/pkg/storage/chunk"
"github.com/grafana/loki/pkg/storage/chunk/cache"
chunkclient "github.com/grafana/loki/pkg/storage/chunk/client"
@ -135,6 +136,9 @@ func newQuery(query string, start, end time.Time, shards []astmapper.ShardAnnota
End: end,
Direction: logproto.FORWARD,
Deletes: deletes,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(query),
},
}
for _, shard := range shards {
req.Shards = append(req.Shards, shard.String())
@ -148,6 +152,9 @@ func newSampleQuery(query string, start, end time.Time, deletes []*logproto.Dele
Start: start,
End: end,
Deletes: deletes,
Plan: &plan.QueryPlan{
AST: syntax.MustParseExpr(query),
},
}
return req
}

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -e

@ -108,6 +108,7 @@ schema_config:
object_store: s3
schema: v11
store: boltdb-shipper
row_shards: 4
server:
graceful_shutdown_timeout: 5s
grpc_server_max_concurrent_streams: 1000

Loading…
Cancel
Save