Skip to content

Commit 4921480

Browse files
fix: emit QueryExecutionsByTable for success cases only (#18584)
Signed-off-by: Harshit Gangal <[email protected]>
1 parent e65734c commit 4921480

File tree

4 files changed

+189
-3
lines changed

4 files changed

+189
-3
lines changed

go/test/endtoend/vtgate/misc_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,38 @@ func TestQueryProcessedMetric(t *testing.T) {
10081008
}
10091009
}
10101010

1011+
// TestQueryProcessedMetric verifies that query metrics are correctly published.
1012+
func TestMetricForExplain(t *testing.T) {
1013+
conn, closer := start(t)
1014+
defer closer()
1015+
1016+
initialQP := getQPMetric(t, "QueryExecutions")
1017+
initialQT := getQPMetric(t, "QueryExecutionsByTable")
1018+
t.Run("explain t1", func(t *testing.T) {
1019+
utils.Exec(t, conn, "explain t1")
1020+
updatedQP := getQPMetric(t, "QueryExecutions")
1021+
updatedQT := getQPMetric(t, "QueryExecutionsByTable")
1022+
assert.EqualValuesf(t, 1, getValue(updatedQP, "EXPLAIN.Passthrough.PRIMARY")-getValue(initialQP, "EXPLAIN.Passthrough.PRIMARY"), "queryExecutions metric: %s", "explain")
1023+
assert.EqualValuesf(t, 1, getValue(updatedQT, "EXPLAIN.ks_t1")-getValue(initialQT, "EXPLAIN.ks_t1"), "queryExecutionsByTable metric: %s", "asdasd")
1024+
})
1025+
1026+
t.Run("explain `select id1, id2 from t1`", func(t *testing.T) {
1027+
utils.ExecAllowError(t, conn, "explain `select id1, id2 from t1`")
1028+
updatedQP := getQPMetric(t, "QueryExecutions")
1029+
updatedQT := getQPMetric(t, "QueryExecutionsByTable")
1030+
assert.EqualValuesf(t, 1, getValue(updatedQP, "EXPLAIN.Passthrough.PRIMARY")-getValue(initialQP, "EXPLAIN.Passthrough.PRIMARY"), "queryExecutions metric: %s", "explain")
1031+
assert.EqualValuesf(t, 1, getValue(updatedQT, "EXPLAIN.ks_t1")-getValue(initialQT, "EXPLAIN.ks_t1"), "queryExecutionsByTable metric: %s", "asdasd")
1032+
})
1033+
1034+
t.Run("explain select id1, id2 from t1", func(t *testing.T) {
1035+
utils.Exec(t, conn, "explain select id1, id2 from t1")
1036+
updatedQP := getQPMetric(t, "QueryExecutions")
1037+
updatedQT := getQPMetric(t, "QueryExecutionsByTable")
1038+
assert.EqualValuesf(t, 2, getValue(updatedQP, "EXPLAIN.Passthrough.PRIMARY")-getValue(initialQP, "EXPLAIN.Passthrough.PRIMARY"), "queryExecutions metric: %s", "explain")
1039+
assert.EqualValuesf(t, 2, getValue(updatedQT, "EXPLAIN.ks_t1")-getValue(initialQT, "EXPLAIN.ks_t1"), "queryExecutionsByTable metric: %s", "asdasd")
1040+
})
1041+
}
1042+
10111043
func getQPMetric(t *testing.T, metric string) map[string]any {
10121044
t.Helper()
10131045

go/test/endtoend/vtgate/unsharded/main_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,3 +459,70 @@ func execMulti(t *testing.T, conn *mysql.Conn, query string) []*sqltypes.Result
459459
}
460460
return res
461461
}
462+
463+
// TestMetricForExplain verifies that query metrics are correctly published for explain queries.
464+
func TestMetricForExplain(t *testing.T) {
465+
ctx := context.Background()
466+
conn, err := mysql.Connect(ctx, &vtParams)
467+
require.NoError(t, err)
468+
defer conn.Close()
469+
470+
initialQP := getQPMetric(t, "QueryExecutions")
471+
initialQT := getQPMetric(t, "QueryExecutionsByTable")
472+
473+
t.Run("explain t1", func(t *testing.T) {
474+
utils.Exec(t, conn, "explain t1")
475+
updatedQP := getQPMetric(t, "QueryExecutions")
476+
updatedQT := getQPMetric(t, "QueryExecutionsByTable")
477+
assert.EqualValues(t, 1, getValue(updatedQP, "EXPLAIN.Passthrough.PRIMARY")-getValue(initialQP, "EXPLAIN.Passthrough.PRIMARY"))
478+
assert.EqualValues(t, 1, getValue(updatedQT, "EXPLAIN.customer_t1")-getValue(initialQT, "EXPLAIN.customer_t1"))
479+
})
480+
481+
t.Run("explain `select c1, c2 from t1`", func(t *testing.T) {
482+
utils.ExecAllowError(t, conn, "explain `select c1, c2 from t1`")
483+
updatedQP := getQPMetric(t, "QueryExecutions")
484+
updatedQT := getQPMetric(t, "QueryExecutionsByTable")
485+
assert.EqualValues(t, 2, getValue(updatedQP, "EXPLAIN.Passthrough.PRIMARY")-getValue(initialQP, "EXPLAIN.Passthrough.PRIMARY"))
486+
assert.EqualValues(t, 1, getValue(updatedQT, "EXPLAIN.customer_t1")-getValue(initialQT, "EXPLAIN.customer_t1"))
487+
})
488+
489+
t.Run("explain select c1, c2 from t1", func(t *testing.T) {
490+
utils.Exec(t, conn, "explain select c1, c2 from t1")
491+
updatedQP := getQPMetric(t, "QueryExecutions")
492+
updatedQT := getQPMetric(t, "QueryExecutionsByTable")
493+
assert.EqualValues(t, 3, getValue(updatedQP, "EXPLAIN.Passthrough.PRIMARY")-getValue(initialQP, "EXPLAIN.Passthrough.PRIMARY"))
494+
assert.EqualValues(t, 2, getValue(updatedQT, "EXPLAIN.customer_t1")-getValue(initialQT, "EXPLAIN.customer_t1"))
495+
})
496+
}
497+
498+
func getQPMetric(t *testing.T, metric string) map[string]any {
499+
t.Helper()
500+
501+
vars := clusterInstance.VtgateProcess.GetVars()
502+
require.NotNil(t, vars)
503+
504+
qpVars, exists := vars[metric]
505+
if !exists {
506+
return nil
507+
}
508+
509+
qpMap, ok := qpVars.(map[string]any)
510+
require.True(t, ok, "query queryMetric vars is not a map")
511+
512+
return qpMap
513+
}
514+
515+
func getValue(m map[string]any, key string) float64 {
516+
if m == nil {
517+
return 0
518+
}
519+
val, exists := m[key]
520+
if !exists {
521+
return 0
522+
}
523+
f, ok := val.(float64)
524+
if !ok {
525+
return 0
526+
}
527+
return f
528+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
Copyright 2025 The Vitess Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package vtgate
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
24+
"vitess.io/vitess/go/sqltypes"
25+
vtgatepb "vitess.io/vitess/go/vt/proto/vtgate"
26+
vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
27+
econtext "vitess.io/vitess/go/vt/vtgate/executorcontext"
28+
)
29+
30+
// TestQueryExecutionsByTable_OnError verifies that queryExecutionsByTable counters
31+
// are incremented on successful execution but NOT incremented on execution failure.
32+
func TestQueryExecutionsByTable_OnError(t *testing.T) {
33+
executor, sbc1, _, _, ctx := createExecutorEnv(t)
34+
35+
// Set up successful result first
36+
sbc1.SetResults([]*sqltypes.Result{sqltypes.MakeTestResult(sqltypes.MakeTestFields("id", "int64"), "1")})
37+
38+
// Get initial counter values
39+
initialCounts := getCurrentQueryExecutionsByTableCounts()
40+
41+
// Execute a query successfully first
42+
session := econtext.NewSafeSession(&vtgatepb.Session{TargetString: KsTestSharded})
43+
result, err := executorExecSession(ctx, executor, session, "select id from user where id = 1", nil)
44+
45+
// Verify successful execution
46+
assert.NoError(t, err, "Expected query execution to succeed")
47+
assert.NotNil(t, result, "Expected valid result")
48+
49+
// Get counter values after successful execution
50+
successCounts := getCurrentQueryExecutionsByTableCounts()
51+
52+
// Check the specific counter that should be incremented for SELECT on user table
53+
selectUserKey := "SELECT.TestExecutor_user"
54+
initialUserCount := initialCounts[selectUserKey]
55+
successUserCount := successCounts[selectUserKey]
56+
57+
// Verify counter was incremented on successful execution
58+
assert.Equal(t, initialUserCount+1, successUserCount,
59+
"queryExecutionsByTable counter should be incremented on successful execution")
60+
61+
// Now set up the sandbox connection to return an error on next execution
62+
sbc1.MustFailCodes[vtrpcpb.Code_INTERNAL] = 1
63+
64+
// Execute the same query again, but this time it should fail
65+
_, err = executorExecSession(ctx, executor, session, "select id from user where id = 1", nil)
66+
67+
// Verify that the execution failed
68+
assert.Error(t, err, "Expected query execution to fail")
69+
70+
// Get counter values after failed execution
71+
finalCounts := getCurrentQueryExecutionsByTableCounts()
72+
73+
// Verify that queryExecutionsByTable counter was NOT incremented on execution error
74+
// The counter should remain the same as after the successful execution
75+
finalUserCount := finalCounts[selectUserKey]
76+
assert.Equal(t, successUserCount, finalUserCount,
77+
"queryExecutionsByTable counter should not be incremented on execution error")
78+
}
79+
80+
// getCurrentQueryExecutionsByTableCounts returns the current values of all queryExecutionsByTable counters
81+
func getCurrentQueryExecutionsByTableCounts() map[string]int64 {
82+
// queryExecutionsByTable is a global variable, so we can use its Counts() method
83+
// to get all counter values. The keys are already formatted as "query.table"
84+
return queryExecutionsByTable.Counts()
85+
}

go/vt/vtgate/plan_execute.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,6 @@ func (e *Executor) rollbackPartialExec(ctx context.Context, safeSession *econtex
409409
func (e *Executor) setLogStats(logStats *logstats.LogStats, plan *engine.Plan, vcursor *econtext.VCursorImpl, execStart time.Time, err error, qr *sqltypes.Result) {
410410
logStats.StmtType = plan.QueryType.String()
411411
logStats.ActiveKeyspace = vcursor.GetKeyspace()
412-
logStats.TablesUsed = plan.TablesUsed
413412
logStats.TabletType = vcursor.TabletType().String()
414413
errCount := e.logExecutionEnd(logStats, execStart, plan, vcursor, err, qr)
415414
plan.AddStats(1, time.Since(logStats.StartTime), logStats.ShardQueries, logStats.RowsAffected, logStats.RowsReturned, errCount)
@@ -418,16 +417,19 @@ func (e *Executor) setLogStats(logStats *logstats.LogStats, plan *engine.Plan, v
418417
func (e *Executor) logExecutionEnd(logStats *logstats.LogStats, execStart time.Time, plan *engine.Plan, vcursor *econtext.VCursorImpl, err error, qr *sqltypes.Result) uint64 {
419418
logStats.ExecuteTime = time.Since(execStart)
420419

421-
e.updateQueryStats(plan.QueryType.String(), plan.Type.String(), vcursor.TabletType().String(), int64(logStats.ShardQueries), plan.TablesUsed)
422-
423420
var errCount uint64
424421
if err != nil {
425422
logStats.Error = err
426423
errCount = 1
427424
} else {
428425
logStats.RowsAffected = qr.RowsAffected
429426
logStats.RowsReturned = uint64(len(qr.Rows))
427+
// log the tables used in the plan for successful query execution.
428+
logStats.TablesUsed = plan.TablesUsed
430429
}
430+
431+
e.updateQueryStats(plan.QueryType.String(), plan.Type.String(), vcursor.TabletType().String(), int64(logStats.ShardQueries), logStats.TablesUsed)
432+
431433
return errCount
432434
}
433435

0 commit comments

Comments
 (0)