Skip to content

Commit

Permalink
Query Analysis (thanos-io#6515)
Browse files Browse the repository at this point in the history
* Return Query Analysis in API

A param  is added to QueryAPI, if true then query analysis is
returned by the  method of the query having structure
 is returned in response.

Signed-off-by: nishchay-veer <[email protected]>

* Added analyze checkbox in Thanos UI

A analyze checkbox is added to the thanos query api, that requests for operator telemetry which includes CPU Time

Signed-off-by: nishchay-veer <[email protected]>

* Return Query Analysis in API

A param  is added to QueryAPI, if true then query analysis is
returned by the  method of the query having structure
 is returned in response.

Signed-off-by: nishchay-veer <[email protected]>

* Added analyze checkbox in Thanos UI

A analyze checkbox is added to the thanos query api, that requests for operator telemetry which includes CPU Time

Signed-off-by: nishchay-veer <[email protected]>

* Add query explain API

Signed-off-by: Saswata Mukherjee <[email protected]>

* Hooked queryTelemetry data into UI

Signed-off-by: nishchay-veer <[email protected]>

* /query_explain and /query_range_explain for explain-tree

Signed-off-by: nishchay-veer <[email protected]>

* update promql-engine

Signed-off-by: nishchay-veer <[email protected]>

* Execution time shows 0s

Signed-off-by: nishchay-veer <[email protected]>

* Show execution time of operators

Signed-off-by: nishchay-veer <[email protected]>

* Removing QueryExplainParam from query api

Signed-off-by: nishchay-veer <[email protected]>

* bad request format in Explain

Signed-off-by: nishchay-veer <[email protected]>

* Showing Expalin and Analyze Output

Signed-off-by: nishchay-veer <[email protected]>

* Added tooltip and different enpoints for table and graph queries

Signed-off-by: nishchay-veer <[email protected]>

* Linters pass

Signed-off-by: nishchay-veer <[email protected]>

* disable Explain when engine is 'prometheus'

Signed-off-by: nishchay-veer <[email protected]>

* passing query params to explain endpoints

Signed-off-by: nishchay-veer <[email protected]>

* fixed react test case failing

Signed-off-by: nishchay-veer <[email protected]>

* fix ui tests

Signed-off-by: nishchay-veer <[email protected]>

* fix some e2e test fails

Signed-off-by: nishchay-veer <[email protected]>

* added customised tooltip in place of Tooltip component

Signed-off-by: nishchay-veer <[email protected]>

* removed Tooltip from Panel

Signed-off-by: nishchay-veer <[email protected]>

* Linters pass

Signed-off-by: nishchay-veer <[email protected]>

* 4 arguments in QueryInstant

Signed-off-by: nishchay-veer <[email protected]>

* resolving conflicts -2

Signed-off-by: nishchay-veer <[email protected]>

* resolving conflicts in Panel.tsx

Signed-off-by: nishchay-veer <[email protected]>

* adding checkbox

Signed-off-by: nishchay-veer <[email protected]>

* fixing linters fail

Signed-off-by: nishchay-veer <[email protected]>

---------

Signed-off-by: nishchay-veer <[email protected]>
Signed-off-by: Saswata Mukherjee <[email protected]>
Signed-off-by: Nishchay Veer <[email protected]>
Co-authored-by: Saswata Mukherjee <[email protected]>
  • Loading branch information
nishchay-veer and saswatamcode authored Oct 10, 2023
1 parent b2b80b3 commit dfe0bbf
Show file tree
Hide file tree
Showing 17 changed files with 700 additions and 277 deletions.
340 changes: 298 additions & 42 deletions pkg/api/query/v1.go

Large diffs are not rendered by default.

148 changes: 148 additions & 0 deletions pkg/api/query/v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
"github.com/prometheus/prometheus/util/annotations"
promgate "github.com/prometheus/prometheus/util/gate"
"github.com/prometheus/prometheus/util/stats"
"github.com/thanos-io/promql-engine/engine"
baseAPI "github.com/thanos-io/thanos/pkg/api"
"github.com/thanos-io/thanos/pkg/compact"
"github.com/thanos-io/thanos/pkg/component"
Expand Down Expand Up @@ -338,6 +339,7 @@ func TestQueryEndpoints(t *testing.T) {
},
},
},

// Query endpoint with single deduplication label.
{
endpoint: api.query,
Expand Down Expand Up @@ -629,6 +631,152 @@ func TestQueryEndpoints(t *testing.T) {
}
}

func TestQueryExplainEndpoints(t *testing.T) {
db, err := e2eutil.NewTSDB()
defer func() { testutil.Ok(t, db.Close()) }()
testutil.Ok(t, err)

now := time.Now()
timeout := 100 * time.Second
ef := NewQueryEngineFactory(promql.EngineOpts{
Logger: nil,
Reg: nil,
MaxSamples: 10000,
Timeout: timeout,
}, nil)
api := &QueryAPI{
baseAPI: &baseAPI.BaseAPI{
Now: func() time.Time { return now },
},
queryableCreate: query.NewQueryableCreator(nil, nil, newProxyStoreWithTSDBStore(db), 2, timeout),
engineFactory: ef,
defaultEngine: PromqlEnginePrometheus,
lookbackDeltaCreate: func(m int64) time.Duration { return time.Duration(0) },
gate: gate.New(nil, 4, gate.Queries),
defaultRangeQueryStep: time.Second,
queryRangeHist: promauto.With(prometheus.NewRegistry()).NewHistogram(prometheus.HistogramOpts{
Name: "query_range_hist",
}),
seriesStatsAggregatorFactory: &store.NoopSeriesStatsAggregatorFactory{},
tenantHeader: "thanos-tenant",
defaultTenant: "default-tenant",
}

var tests = []endpointTestCase{
{
endpoint: api.queryExplain,
query: url.Values{
"query": []string{"2"},
"time": []string{"123.4"},
"engine": []string{"thanos"},
},
response: &engine.ExplainOutputNode{
OperatorName: "[*numberLiteralSelector] 2",
},
},
{
endpoint: api.queryRangeExplain,
query: url.Values{
"query": []string{"time()"},
"start": []string{"0"},
"end": []string{"500"},
"step": []string{"1"},
"engine": []string{"thanos"},
},
response: &engine.ExplainOutputNode{
OperatorName: "[*noArgFunctionOperator] time()",
},
},
}
for i, test := range tests {
if ok := testEndpoint(t, test, fmt.Sprintf("#%d %s", i, test.query.Encode()), reflect.DeepEqual); !ok {
return
}
}
}

func TestQueryAnalyzeEndpoints(t *testing.T) {
db, err := e2eutil.NewTSDB()
defer func() { testutil.Ok(t, db.Close()) }()
testutil.Ok(t, err)

now := time.Now()
timeout := 100 * time.Second
ef := NewQueryEngineFactory(promql.EngineOpts{
Logger: nil,
Reg: nil,
MaxSamples: 10000,
Timeout: timeout,
}, nil)
api := &QueryAPI{
baseAPI: &baseAPI.BaseAPI{
Now: func() time.Time { return now },
},
queryableCreate: query.NewQueryableCreator(nil, nil, newProxyStoreWithTSDBStore(db), 2, timeout),
engineFactory: ef,
defaultEngine: PromqlEnginePrometheus,
lookbackDeltaCreate: func(m int64) time.Duration { return time.Duration(0) },
gate: gate.New(nil, 4, gate.Queries),
defaultRangeQueryStep: time.Second,
queryRangeHist: promauto.With(prometheus.NewRegistry()).NewHistogram(prometheus.HistogramOpts{
Name: "query_range_hist",
}),
seriesStatsAggregatorFactory: &store.NoopSeriesStatsAggregatorFactory{},
tenantHeader: "thanos-tenant",
defaultTenant: "default-tenant",
}
start := time.Unix(0, 0)

var tests = []endpointTestCase{
{
endpoint: api.query,
query: url.Values{
"query": []string{"2"},
"time": []string{"123.4"},
"engine": []string{"thanos"},
},
response: &queryData{
ResultType: parser.ValueTypeScalar,
Result: promql.Scalar{
V: 2,
T: timestamp.FromTime(start.Add(123*time.Second + 400*time.Millisecond)),
},
QueryAnalysis: queryTelemetry{},
},
},
{
endpoint: api.queryRange,
query: url.Values{
"query": []string{"time()"},
"start": []string{"0"},
"end": []string{"500"},
"step": []string{"1"},
},
response: &queryData{
ResultType: parser.ValueTypeMatrix,
Result: promql.Matrix{
promql.Series{
Floats: func(end, step float64) []promql.FPoint {
var res []promql.FPoint
for v := float64(0); v <= end; v += step {
res = append(res, promql.FPoint{F: v, T: timestamp.FromTime(start.Add(time.Duration(v) * time.Second))})
}
return res
}(500, 1),
Metric: nil,
},
},
QueryAnalysis: queryTelemetry{},
},
},
}
for i, test := range tests {
if ok := testEndpoint(t, test, fmt.Sprintf("#%d %s", i, test.query.Encode()), reflect.DeepEqual); !ok {
return
}
}
}

func newProxyStoreWithTSDBStore(db store.TSDBReader) *store.ProxyStore {
c := &storetestutil.TestClient{
Name: "1",
Expand Down
2 changes: 2 additions & 0 deletions pkg/promclient/promclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ type QueryOptions struct {
MaxSourceResolution string
Engine string
Explain bool
Analyze bool
HTTPHeaders http.Header
}

Expand All @@ -377,6 +378,7 @@ func (p *QueryOptions) AddTo(values url.Values) error {
}

values.Add("explain", fmt.Sprintf("%v", p.Explain))
values.Add("analyze", fmt.Sprintf("%v", p.Analyze))
values.Add("engine", p.Engine)

var partialResponseValue string
Expand Down
4 changes: 2 additions & 2 deletions pkg/queryfrontend/queryinstant_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func (c queryInstantCodec) DecodeRequest(_ context.Context, r *http.Request, for

result.Query = r.FormValue("query")
result.Path = r.URL.Path
result.Explain = r.FormValue(queryv1.QueryExplainParam)
result.Analyze = r.FormValue("analyze")
result.Engine = r.FormValue("engine")

for _, header := range forwardHeaders {
Expand All @@ -176,7 +176,7 @@ func (c queryInstantCodec) EncodeRequest(ctx context.Context, r queryrange.Reque
"query": []string{thanosReq.Query},
queryv1.DedupParam: []string{strconv.FormatBool(thanosReq.Dedup)},
queryv1.PartialResponseParam: []string{strconv.FormatBool(thanosReq.PartialResponse)},
queryv1.QueryExplainParam: []string{thanosReq.Explain},
queryv1.QueryAnalyzeParam: []string{thanosReq.Analyze},
queryv1.EngineParam: []string{thanosReq.Engine},
queryv1.ReplicaLabelsParam: thanosReq.ReplicaLabels,
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/queryfrontend/queryrange_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func (c queryRangeCodec) DecodeRequest(_ context.Context, r *http.Request, forwa
}

result.Query = r.FormValue("query")
result.Explain = r.FormValue(queryv1.QueryExplainParam)
result.Analyze = r.FormValue(queryv1.QueryAnalyzeParam)
result.Engine = r.FormValue(queryv1.EngineParam)
result.Path = r.URL.Path

Expand Down Expand Up @@ -161,7 +161,7 @@ func (c queryRangeCodec) EncodeRequest(ctx context.Context, r queryrange.Request
"end": []string{encodeTime(thanosReq.End)},
"step": []string{encodeDurationMillis(thanosReq.Step)},
"query": []string{thanosReq.Query},
queryv1.QueryExplainParam: []string{thanosReq.Explain},
queryv1.QueryAnalyzeParam: []string{thanosReq.Analyze},
queryv1.EngineParam: []string{thanosReq.Engine},
queryv1.DedupParam: []string{strconv.FormatBool(thanosReq.Dedup)},
queryv1.PartialResponseParam: []string{strconv.FormatBool(thanosReq.PartialResponse)},
Expand Down
4 changes: 2 additions & 2 deletions pkg/queryfrontend/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type ThanosQueryRangeRequest struct {
Stats string
ShardInfo *storepb.ShardInfo
LookbackDelta int64
Explain string
Analyze string
Engine string
}

Expand Down Expand Up @@ -156,7 +156,7 @@ type ThanosQueryInstantRequest struct {
Stats string
ShardInfo *storepb.ShardInfo
LookbackDelta int64 // in milliseconds.
Explain string
Analyze string
Engine string
}

Expand Down
116 changes: 58 additions & 58 deletions pkg/ui/bindata.go

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions pkg/ui/react-app/src/components/ListTree.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React, { useState } from 'react';
import { InputProps, Collapse, ListGroupItem, ListGroup } from 'reactstrap';
import { ExplainTree } from '../pages/graph/ExpressionInput';

export interface QueryTree {
name: string;
executionTime?: string;
children?: QueryTree[];
}

interface NodeProps extends InputProps {
node: QueryTree | null;
node: QueryTree | ExplainTree | null;
}

const ListTree: React.FC<NodeProps> = ({ id, node }) => {
Expand Down Expand Up @@ -41,7 +43,7 @@ const ListTree: React.FC<NodeProps> = ({ id, node }) => {
</div>
)}
<div id={id} style={{ cursor: `${node.children ? 'pointer' : 'inherit'}` }} onClick={toggle}>
{node.name}
{node.name} · {node.executionTime}
</div>
</div>
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/ui/react-app/src/pages/graph/ExpressionInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ describe('ExpressionInput', () => {
enableAutocomplete: true,
enableHighlighting: true,
enableLinter: true,
executeExplain: (): void => {
// Do nothing.
},
disableExplain: false,
};

let expressionInput: ReactWrapper;
Expand Down
12 changes: 11 additions & 1 deletion pkg/ui/react-app/src/pages/graph/ExpressionInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@ interface CMExpressionInputProps {
enableAutocomplete: boolean;
enableHighlighting: boolean;
enableLinter: boolean;
executeExplain: () => void;
disableExplain: boolean;
}

const dynamicConfigCompartment = new Compartment();

export interface ExplainTree {
name: string;
children?: ExplainTree[];
}
// Autocompletion strategy that wraps the main one and enriches
// it with past query items.
export class HistoryCompleteStrategy implements CompleteStrategy {
Expand Down Expand Up @@ -88,11 +94,12 @@ const ExpressionInput: FC<PathPrefixProps & CMExpressionInputProps> = ({
enableAutocomplete,
enableHighlighting,
enableLinter,
executeExplain,
disableExplain,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null);
const { theme } = useTheme();

// (Re)initialize editor based on settings / setting changes.
useEffect(() => {
// Build the dynamic part of the config.
Expand Down Expand Up @@ -205,6 +212,9 @@ const ExpressionInput: FC<PathPrefixProps & CMExpressionInputProps> = ({
Execute
</Button>
</InputGroupAddon>
<Button className="ml-2" color="info" onClick={executeExplain} disabled={disableExplain}>
Explain
</Button>
</InputGroup>
</>
);
Expand Down
8 changes: 4 additions & 4 deletions pkg/ui/react-app/src/pages/graph/Panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const defaultProps: PanelProps = {
usePartialResponse: false,
storeMatches: [],
engine: 'prometheus',
explain: false,
disableExplainCheckbox: false,
analyze: false,
disableAnalyzeCheckbox: false,
},
onOptionsChanged: (): void => {
// Do nothing.
Expand Down Expand Up @@ -99,8 +99,8 @@ describe('Panel', () => {
usePartialResponse: false,
storeMatches: [],
engine: 'prometheus',
explain: false,
disableExplainCheckbox: false,
analyze: false,
disableAnalyzeCheckbox: false,
};
const graphPanel = mount(<Panel {...defaultProps} options={options} />);
const controls = graphPanel.find(GraphControls);
Expand Down
Loading

0 comments on commit dfe0bbf

Please sign in to comment.