552 lines
24 KiB
Go
552 lines
24 KiB
Go
// Copyright 2025 PingCAP, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package variable_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pingcap/tidb/pkg/executor"
|
|
"github.com/pingcap/tidb/pkg/sessionctx"
|
|
"github.com/pingcap/tidb/pkg/sessionctx/slowlogrule"
|
|
"github.com/pingcap/tidb/pkg/sessionctx/stmtctx"
|
|
"github.com/pingcap/tidb/pkg/sessionctx/variable"
|
|
"github.com/pingcap/tidb/pkg/util/execdetails"
|
|
"github.com/pingcap/tidb/pkg/util/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tikv/client-go/v2/util"
|
|
)
|
|
|
|
func newMockCtx() sessionctx.Context {
|
|
ctx := mock.NewContext()
|
|
ctx.GetSessionVars().StmtCtx = stmtctx.NewStmtCtx()
|
|
ctx.GetSessionVars().SlowLogRules = slowlogrule.NewSessionSlowLogRules(&slowlogrule.SlowLogRules{
|
|
Rules: []*slowlogrule.SlowLogRule{},
|
|
Fields: make(map[string]struct{}),
|
|
})
|
|
return ctx
|
|
}
|
|
|
|
func TestSlowLogFieldAccessor(t *testing.T) {
|
|
for field, accessor := range variable.SlowLogRuleFieldAccessors {
|
|
require.Equalf(t, strings.ToLower(field), field, "field %q: field name should be all lowercase", field)
|
|
require.NotNilf(t, accessor.Parse, "field %q: Parse function is missing", field)
|
|
require.NotNilf(t, accessor.Match, "field %q: Match function is missing", field)
|
|
if accessor.Setter == nil {
|
|
if field == strings.ToLower(variable.SlowLogParseTimeStr) ||
|
|
field == strings.ToLower(variable.SlowLogCompileTimeStr) ||
|
|
field == strings.ToLower(variable.SlowLogOptimizeTimeStr) ||
|
|
field == strings.ToLower(variable.SlowLogWaitTSTimeStr) ||
|
|
field == strings.ToLower(variable.SlowLogIsInternalStr) ||
|
|
field == strings.ToLower(variable.SlowLogConnIDStr) ||
|
|
field == strings.ToLower(variable.SlowLogSessAliasStr) ||
|
|
field == strings.ToLower(variable.SlowLogDBStr) {
|
|
continue
|
|
}
|
|
require.NotNilf(t, accessor.Setter, "field %q: Setter function is missing", field)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMatchSingleRuleSingleCondition(t *testing.T) {
|
|
ctx := newMockCtx()
|
|
items := &variable.SlowQueryLogItems{MemMax: 200}
|
|
|
|
rule := &slowlogrule.SlowLogRule{
|
|
Conditions: []slowlogrule.SlowLogCondition{{
|
|
Field: variable.SlowLogMemMax,
|
|
Threshold: int64(100),
|
|
}},
|
|
}
|
|
ctx.GetSessionVars().SlowLogRules.Rules = []*slowlogrule.SlowLogRule{rule}
|
|
|
|
require.True(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules)) // 200 >= 100
|
|
items.MemMax = 50
|
|
require.False(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules)) // 50 < 100
|
|
}
|
|
|
|
func TestMatchSpecialTypeConditions(t *testing.T) {
|
|
ctx := newMockCtx()
|
|
items := &variable.SlowQueryLogItems{
|
|
Succ: true,
|
|
Digest: "abc",
|
|
ResourceGroupName: "rg_Test",
|
|
}
|
|
|
|
checkRet := func(expectMatch bool, condition slowlogrule.SlowLogCondition) {
|
|
rule := &slowlogrule.SlowLogRule{
|
|
Conditions: []slowlogrule.SlowLogCondition{
|
|
condition,
|
|
},
|
|
}
|
|
ctx.GetSessionVars().SlowLogRules.Rules = []*slowlogrule.SlowLogRule{rule}
|
|
|
|
if expectMatch {
|
|
require.True(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
} else {
|
|
require.False(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
}
|
|
}
|
|
|
|
t.Run("string type", func(t *testing.T) {
|
|
ctx.GetSessionVars().CurrentDB = "db_Test"
|
|
ctx.GetSessionVars().SessionAlias = "seA"
|
|
rule := &slowlogrule.SlowLogRule{
|
|
Conditions: []slowlogrule.SlowLogCondition{
|
|
{Field: variable.SlowLogSucc, Threshold: true},
|
|
{Field: variable.SlowLogDigestStr, Threshold: "abc"},
|
|
{Field: variable.SlowLogResourceGroup, Threshold: "rg_test"},
|
|
{Field: variable.SlowLogDBStr, Threshold: "db_test"},
|
|
{Field: variable.SlowLogSessAliasStr, Threshold: "seA"},
|
|
},
|
|
}
|
|
ctx.GetSessionVars().SlowLogRules.Rules = []*slowlogrule.SlowLogRule{rule}
|
|
|
|
require.True(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
|
|
// test SessionAlias
|
|
ctx.GetSessionVars().SessionAlias = "sea"
|
|
require.False(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
// test Digest
|
|
ctx.GetSessionVars().SessionAlias = "seA"
|
|
items.Digest = "abC"
|
|
require.False(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
|
|
// test Rewrite_time field
|
|
// test with zero value (RewriteInfo not set)
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: variable.SlowLogRewriteTimeStr, Threshold: 0.0})
|
|
checkRet(false, slowlogrule.SlowLogCondition{Field: variable.SlowLogRewriteTimeStr, Threshold: 0.00000001})
|
|
// set RewritePhaseInfo and use Setter to populate items.RewriteInfo
|
|
ctx.GetSessionVars().RewritePhaseInfo.DurationRewrite = 5 * time.Millisecond
|
|
accessor := variable.SlowLogRuleFieldAccessors[strings.ToLower(variable.SlowLogRewriteTimeStr)]
|
|
accessor.Setter(context.Background(), ctx.GetSessionVars(), items)
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: variable.SlowLogRewriteTimeStr, Threshold: 0.001})
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: variable.SlowLogRewriteTimeStr, Threshold: 0.0})
|
|
checkRet(false, slowlogrule.SlowLogCondition{Field: variable.SlowLogRewriteTimeStr, Threshold: 0.01})
|
|
// test reset DurationRewrite with smaller value (should not match larger threshold)
|
|
items.RewriteInfo.DurationRewrite = 500 * time.Microsecond
|
|
checkRet(false, slowlogrule.SlowLogCondition{Field: variable.SlowLogRewriteTimeStr, Threshold: 0.001})
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: variable.SlowLogRewriteTimeStr, Threshold: 0.0001})
|
|
})
|
|
|
|
t.Run("util.ExecDetails type", func(t *testing.T) {
|
|
checkRet(false, slowlogrule.SlowLogCondition{Field: variable.SlowLogKVTotal, Threshold: 0.00000001})
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: variable.SlowLogKVTotal, Threshold: 0.0})
|
|
tikvExecDetail := util.ExecDetails{
|
|
WaitKVRespDuration: (10 * time.Second).Nanoseconds(),
|
|
}
|
|
childCtx := context.WithValue(context.Background(), util.ExecDetailsKey, &tikvExecDetail)
|
|
accessor := variable.SlowLogRuleFieldAccessors[strings.ToLower(variable.SlowLogKVTotal)]
|
|
accessor.Setter(childCtx, nil, items)
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: variable.SlowLogKVTotal, Threshold: 0.00000001})
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: variable.SlowLogKVTotal, Threshold: 0.0})
|
|
})
|
|
|
|
t.Run("execdetails.ExecDetails type", func(t *testing.T) {
|
|
seVar := ctx.GetSessionVars()
|
|
seVar.StmtCtx.SyncExecDetails.Reset()
|
|
|
|
checkRet(false, slowlogrule.SlowLogCondition{Field: execdetails.ProcessTimeStr, Threshold: float64(1)})
|
|
checkRet(false, slowlogrule.SlowLogCondition{Field: execdetails.TotalKeysStr, Threshold: uint64(2)})
|
|
checkRet(false, slowlogrule.SlowLogCondition{Field: execdetails.PreWriteTimeStr, Threshold: 0.123})
|
|
checkRet(false, slowlogrule.SlowLogCondition{Field: execdetails.PrewriteRegionStr, Threshold: int64(4)})
|
|
// ExecDetail == nil
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: execdetails.ProcessTimeStr, Threshold: float64(0)})
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: execdetails.TotalKeysStr, Threshold: uint64(0)})
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: execdetails.PreWriteTimeStr, Threshold: 0.0})
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: execdetails.PrewriteRegionStr, Threshold: int64(0)})
|
|
// ExecDetail != nil && d.CommitDetail == nil && d.ScanDetail == nil
|
|
execDetail := &execdetails.ExecDetails{
|
|
CopExecDetails: execdetails.CopExecDetails{
|
|
BackoffTime: time.Millisecond,
|
|
},
|
|
}
|
|
seVar.StmtCtx.SyncExecDetails.MergeCopExecDetails(&execDetail.CopExecDetails, 0)
|
|
accessor := variable.SlowLogRuleFieldAccessors[strings.ToLower(execdetails.TotalKeysStr)]
|
|
accessor.Setter(context.Background(), seVar, items)
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: execdetails.ProcessTimeStr, Threshold: float64(0)})
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: execdetails.TotalKeysStr, Threshold: uint64(0)})
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: execdetails.PreWriteTimeStr, Threshold: 0.0})
|
|
checkRet(true, slowlogrule.SlowLogCondition{Field: execdetails.PrewriteRegionStr, Threshold: int64(0)})
|
|
})
|
|
}
|
|
|
|
func TestMatchSingleRuleMultipleConditions(t *testing.T) {
|
|
ctx := newMockCtx()
|
|
items := &variable.SlowQueryLogItems{
|
|
MemMax: 200,
|
|
Digest: "abc",
|
|
Succ: true,
|
|
WriteSQLRespTotal: 1.5e6, // time.Duration
|
|
}
|
|
|
|
rule := &slowlogrule.SlowLogRule{
|
|
Conditions: []slowlogrule.SlowLogCondition{
|
|
{Field: variable.SlowLogMemMax, Threshold: int64(100)},
|
|
{Field: variable.SlowLogDigestStr, Threshold: "abc"},
|
|
{Field: variable.SlowLogSucc, Threshold: true},
|
|
{Field: variable.SlowLogWriteSQLRespTotal, Threshold: 0.0015},
|
|
},
|
|
}
|
|
ctx.GetSessionVars().SlowLogRules.Rules = []*slowlogrule.SlowLogRule{rule}
|
|
|
|
require.True(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
|
|
items.Succ = false
|
|
require.False(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
}
|
|
|
|
func TestMatchMultipleRulesOR(t *testing.T) {
|
|
ctx := newMockCtx()
|
|
sessVars := ctx.GetSessionVars()
|
|
items := &variable.SlowQueryLogItems{}
|
|
items.ExecRetryCount = 5
|
|
items.Digest = "abc"
|
|
items.Succ = true
|
|
items.MemMax = 200
|
|
|
|
// rule 1: requires ExecRetryCount >= 3 AND Succ == true
|
|
rule1 := &slowlogrule.SlowLogRule{
|
|
Conditions: []slowlogrule.SlowLogCondition{
|
|
{Field: variable.SlowLogExecRetryCount, Threshold: uint64(3)},
|
|
{Field: variable.SlowLogSucc, Threshold: true},
|
|
},
|
|
}
|
|
// rule 2: requires MemMax >= 500 (not satisfied)
|
|
rule2 := &slowlogrule.SlowLogRule{
|
|
Conditions: []slowlogrule.SlowLogCondition{
|
|
{Field: variable.SlowLogMemMax, Threshold: int64(500)},
|
|
},
|
|
}
|
|
|
|
sessVars.SlowLogRules = slowlogrule.NewSessionSlowLogRules(&slowlogrule.SlowLogRules{Rules: []*slowlogrule.SlowLogRule{rule1, rule2}})
|
|
|
|
// should match rule1, return true
|
|
require.True(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
|
|
// change ExecRetryCount smaller -> no match
|
|
items.ExecRetryCount = 1
|
|
require.False(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
|
|
// test string matching
|
|
items.Digest = "plan_digest"
|
|
rule3 := &slowlogrule.SlowLogRule{
|
|
Conditions: []slowlogrule.SlowLogCondition{
|
|
{Field: variable.SlowLogDigestStr, Threshold: "plan_digest"},
|
|
},
|
|
}
|
|
sessVars.SlowLogRules = slowlogrule.NewSessionSlowLogRules(&slowlogrule.SlowLogRules{Rules: []*slowlogrule.SlowLogRule{rule3}})
|
|
require.True(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
}
|
|
|
|
func TestMatchDifferentTypesAfterParse(t *testing.T) {
|
|
ctx := newMockCtx()
|
|
items := &variable.SlowQueryLogItems{
|
|
MemMax: 123, // int64
|
|
DiskMax: 456, // int64
|
|
ExecRetryCount: uint64(789), // uint64
|
|
ResourceGroupName: "rg1", // string
|
|
Succ: true, // bool
|
|
TimeTotal: 3140 * time.Millisecond, // time.Duration
|
|
}
|
|
|
|
slowLogRules, err := variable.ParseSessionSlowLogRules(`Mem_max: 100, Exec_retry_count: 300, Succ: true, Query_time: 2.52, Resource_group: rg1`)
|
|
require.NoError(t, err)
|
|
ctx.GetSessionVars().SlowLogRules.SlowLogRules = slowLogRules
|
|
require.True(t, executor.Match(ctx.GetSessionVars(), items, ctx.GetSessionVars().SlowLogRules.SlowLogRules))
|
|
}
|
|
|
|
func TestParseSingleSlowLogField(t *testing.T) {
|
|
require.Equal(t, len(variable.SlowLogRuleFieldAccessors), 38)
|
|
accessor, ok := variable.SlowLogRuleFieldAccessors[strings.ToLower(variable.SlowLogPlanDigest)]
|
|
require.True(t, ok)
|
|
require.NotNil(t, accessor.Setter)
|
|
require.NotNil(t, accessor.Match)
|
|
|
|
// int64 fields
|
|
v, err := variable.ParseSlowLogFieldValue(variable.SlowLogMemMax, "123")
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(123), v)
|
|
|
|
_, err = variable.ParseSlowLogFieldValue(variable.SlowLogMemMax, "abc")
|
|
require.Error(t, err)
|
|
|
|
// uint64 fields
|
|
v, err = variable.ParseSlowLogFieldValue(variable.SlowLogConnIDStr, "456")
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(456), v)
|
|
|
|
_, err = variable.ParseSlowLogFieldValue(variable.SlowLogConnIDStr, "-1")
|
|
require.Error(t, err)
|
|
|
|
// float64 fields
|
|
v, err = variable.ParseSlowLogFieldValue(variable.SlowLogQueryTimeStr, "1.234")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1.234, v)
|
|
v, err = variable.ParseSlowLogFieldValue(variable.SlowLogQueryTimeStr, "1.5e6")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1.5e6, v)
|
|
|
|
_, err = variable.ParseSlowLogFieldValue(variable.SlowLogQueryTimeStr, "abc")
|
|
require.Error(t, err)
|
|
|
|
// string fields
|
|
v, err = variable.ParseSlowLogFieldValue(variable.SlowLogDBStr, "testdb")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "testdb", v)
|
|
|
|
// bool fields
|
|
v, err = variable.ParseSlowLogFieldValue(variable.SlowLogSucc, "true")
|
|
require.NoError(t, err)
|
|
require.Equal(t, true, v)
|
|
|
|
v, err = variable.ParseSlowLogFieldValue(variable.SlowLogSucc, "false")
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, v)
|
|
|
|
v, err = variable.ParseSlowLogFieldValue(variable.SlowLogSucc, "notabool")
|
|
require.Error(t, err)
|
|
require.Equal(t, false, v)
|
|
|
|
// unknown field
|
|
v, err = variable.ParseSlowLogFieldValue("NonExistField", "xxx")
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "unknown slow log field name")
|
|
require.Nil(t, v)
|
|
}
|
|
|
|
func compareConditionsUnordered(t *testing.T, got, want []slowlogrule.SlowLogCondition, rawRule string) {
|
|
require.Equal(t, len(got), len(want))
|
|
gotMap := make(map[string]any, len(got))
|
|
for _, c := range got {
|
|
gotMap[strings.ToLower(c.Field)] = c.Threshold
|
|
}
|
|
rawRule = strings.ToLower(rawRule)
|
|
for _, c := range want {
|
|
field := strings.ToLower(c.Field)
|
|
val, ok := gotMap[field]
|
|
require.True(t, ok)
|
|
kv := fmt.Sprintf("%s: %v", field, val)
|
|
require.True(t, strings.Contains(rawRule, kv),
|
|
fmt.Sprintf("rawRule:%v, substr: %s", rawRule, kv))
|
|
require.Equal(t, c.Threshold, val)
|
|
}
|
|
}
|
|
|
|
func TestParseSessionSlowLogRules(t *testing.T) {
|
|
// normal tests
|
|
// a rule that ends without a ';'
|
|
slowLogRules, err := variable.ParseSessionSlowLogRules(`Conn_ID: 123, DB: db1, Succ: true, Query_time: 0.5276, Resource_group: rg1`)
|
|
require.EqualError(t, err, "do not allow ConnID value:123")
|
|
require.Nil(t, slowLogRules)
|
|
rawRule := `Exec_retry_count: 10, DB: db1, Succ: true, Query_time: 0.5276, Resource_group: rg1`
|
|
slowLogRules, err = variable.ParseSessionSlowLogRules(rawRule)
|
|
require.NoError(t, err)
|
|
rules := []slowlogrule.SlowLogRule{
|
|
{
|
|
Conditions: []slowlogrule.SlowLogCondition{
|
|
{Field: strings.ToLower(variable.SlowLogExecRetryCount), Threshold: uint64(10)},
|
|
{Field: strings.ToLower(variable.SlowLogDBStr), Threshold: "db1"},
|
|
{Field: strings.ToLower(variable.SlowLogSucc), Threshold: true},
|
|
{Field: strings.ToLower(variable.SlowLogQueryTimeStr), Threshold: 0.5276},
|
|
{Field: strings.ToLower(variable.SlowLogResourceGroup), Threshold: "rg1"},
|
|
},
|
|
},
|
|
}
|
|
allConditionFields := map[string]struct{}{
|
|
strings.ToLower(variable.SlowLogExecRetryCount): {},
|
|
strings.ToLower(variable.SlowLogDBStr): {},
|
|
strings.ToLower(variable.SlowLogSucc): {},
|
|
strings.ToLower(variable.SlowLogQueryTimeStr): {},
|
|
strings.ToLower(variable.SlowLogResourceGroup): {},
|
|
}
|
|
compareConditionsUnordered(t, rules[0].Conditions, slowLogRules.Rules[0].Conditions, rawRule)
|
|
require.Equal(t, allConditionFields, slowLogRules.Fields)
|
|
// a rule that ends with a ';'
|
|
slowLogRules, err = variable.ParseSessionSlowLogRules(`Exec_retry_count: 10, DB: db1, Succ: true, Query_time: 0.5276, Resource_group: rg1;`)
|
|
require.NoError(t, err)
|
|
compareConditionsUnordered(t, rules[0].Conditions, slowLogRules.Rules[0].Conditions, rawRule)
|
|
require.Equal(t, allConditionFields, slowLogRules.Fields)
|
|
// some rules
|
|
rawRule1 := "Exec_retry_count: 123, DB: db1, Succ: true, Query_time: 0.5276, Resource_group: rg1;"
|
|
rawRule2 := "Exec_retry_count: 124, DB: db2, Succ: false, Query_time: 1.5276"
|
|
slowLogRules, err = variable.ParseSessionSlowLogRules(rawRule1 + rawRule2)
|
|
require.NoError(t, err)
|
|
rules = []slowlogrule.SlowLogRule{
|
|
{
|
|
Conditions: []slowlogrule.SlowLogCondition{
|
|
{Field: strings.ToLower(variable.SlowLogExecRetryCount), Threshold: uint64(123)},
|
|
{Field: strings.ToLower(variable.SlowLogDBStr), Threshold: "db1"},
|
|
{Field: strings.ToLower(variable.SlowLogSucc), Threshold: true},
|
|
{Field: strings.ToLower(variable.SlowLogQueryTimeStr), Threshold: 0.5276},
|
|
{Field: strings.ToLower(variable.SlowLogResourceGroup), Threshold: "rg1"},
|
|
},
|
|
},
|
|
{
|
|
Conditions: []slowlogrule.SlowLogCondition{
|
|
{Field: strings.ToLower(variable.SlowLogExecRetryCount), Threshold: uint64(124)},
|
|
{Field: strings.ToLower(variable.SlowLogDBStr), Threshold: "db2"},
|
|
{Field: strings.ToLower(variable.SlowLogSucc), Threshold: false},
|
|
{Field: strings.ToLower(variable.SlowLogQueryTimeStr), Threshold: 1.5276},
|
|
},
|
|
},
|
|
}
|
|
compareConditionsUnordered(t, rules[0].Conditions, slowLogRules.Rules[0].Conditions, rawRule1)
|
|
compareConditionsUnordered(t, rules[1].Conditions, slowLogRules.Rules[1].Conditions, rawRule2)
|
|
require.Equal(t, allConditionFields, slowLogRules.Fields)
|
|
|
|
// return nil
|
|
slowLogRules, err = variable.ParseSessionSlowLogRules(" ")
|
|
require.NoError(t, err)
|
|
require.Nil(t, slowLogRules)
|
|
slowLogRules, err = variable.ParseSessionSlowLogRules(" ; ; ")
|
|
require.NoError(t, err)
|
|
require.Nil(t, slowLogRules)
|
|
|
|
// exceeding the limit set by the rules
|
|
longRules := strings.Repeat("Conn_ID:1;", 11)
|
|
_, err = variable.ParseSessionSlowLogRules(longRules)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "invalid slow log rules count")
|
|
// the field has ','
|
|
slowLogRules, err = variable.ParseSessionSlowLogRules(`DB:"a,b", Succ:true`)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, len(slowLogRules.Rules[0].Conditions))
|
|
|
|
// Format error
|
|
_, err = variable.ParseSessionSlowLogRules("Exec_retry_count 123")
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "invalid slow log rule format:Exec_retry_count 123")
|
|
_, err = variable.ParseSessionSlowLogRules("Exec_retry_count > 123")
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "invalid slow log rule format:Exec_retry_count > 123")
|
|
|
|
// Field resetting
|
|
slowLogRules, err = variable.ParseSessionSlowLogRules("Mem_max:100,Succ:true,Succ:false,Mem_max:200")
|
|
require.NoError(t, err)
|
|
require.Len(t, slowLogRules.Rules, 1)
|
|
m := make(map[string]any)
|
|
for _, cond := range slowLogRules.Rules[0].Conditions {
|
|
m[cond.Field] = cond.Threshold
|
|
}
|
|
require.Equal(t, int64(200), m[strings.ToLower(variable.SlowLogMemMax)])
|
|
require.Equal(t, false, m[strings.ToLower(variable.SlowLogSucc)])
|
|
|
|
// empty fields in a single rule
|
|
slowLogRules, err = variable.ParseSessionSlowLogRules("Exec_retry_count:1, , DB:db")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, len(slowLogRules.Rules[0].Conditions))
|
|
}
|
|
|
|
func checkRuleByField(t *testing.T, rules []*slowlogrule.SlowLogRule, field, val string) {
|
|
for _, r := range rules {
|
|
for _, cond := range r.Conditions {
|
|
if cond.Field == field {
|
|
require.Equal(t, cond.Threshold, val)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseGlobalSlowLogRules(t *testing.T) {
|
|
// normal tests
|
|
rawRule := `Conn_ID: 123, Exec_retry_count: 10, DB: db1, Succ: true, Query_time: 0.5276, Resource_group: rg1`
|
|
slowLogRuleSet, err := variable.ParseGlobalSlowLogRules(rawRule)
|
|
require.NoError(t, err)
|
|
rules := []slowlogrule.SlowLogRule{
|
|
{
|
|
Conditions: []slowlogrule.SlowLogCondition{
|
|
{Field: strings.ToLower(variable.SlowLogConnIDStr), Threshold: uint64(123)},
|
|
{Field: strings.ToLower(variable.SlowLogExecRetryCount), Threshold: uint64(10)},
|
|
{Field: strings.ToLower(variable.SlowLogDBStr), Threshold: "db1"},
|
|
{Field: strings.ToLower(variable.SlowLogSucc), Threshold: true},
|
|
{Field: strings.ToLower(variable.SlowLogQueryTimeStr), Threshold: 0.5276},
|
|
{Field: strings.ToLower(variable.SlowLogResourceGroup), Threshold: "rg1"},
|
|
},
|
|
},
|
|
}
|
|
allConditionFields := map[string]struct{}{
|
|
strings.ToLower(variable.SlowLogConnIDStr): {},
|
|
strings.ToLower(variable.SlowLogExecRetryCount): {},
|
|
strings.ToLower(variable.SlowLogDBStr): {},
|
|
strings.ToLower(variable.SlowLogSucc): {},
|
|
strings.ToLower(variable.SlowLogQueryTimeStr): {},
|
|
strings.ToLower(variable.SlowLogResourceGroup): {},
|
|
}
|
|
require.NotNil(t, slowLogRuleSet)
|
|
require.Len(t, slowLogRuleSet.RulesMap, 1)
|
|
compareConditionsUnordered(t, rules[0].Conditions, slowLogRuleSet.RulesMap[123].Rules[0].Conditions, rawRule)
|
|
require.Equal(t, allConditionFields, slowLogRuleSet.RulesMap[123].Fields)
|
|
require.Nil(t, slowLogRuleSet.RulesMap[variable.UnsetConnID])
|
|
|
|
// empty raw rule returns empty map
|
|
slowLogRuleSet, err = variable.ParseGlobalSlowLogRules("")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, slowLogRuleSet)
|
|
require.Empty(t, slowLogRuleSet.RulesMap)
|
|
require.Equal(t, "", slowLogRuleSet.RawRules)
|
|
require.Equal(t, uint64(0x0), slowLogRuleSet.RawRulesHash)
|
|
|
|
// special ConnID with empty raw rule
|
|
slowLogRuleSet, err = variable.ParseGlobalSlowLogRules("Conn_id:123;")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, slowLogRuleSet)
|
|
require.Equal(t, slowLogRuleSet.RulesMap[123].Rules[0].Conditions[0].Threshold, uint64(123))
|
|
require.Equal(t, "conn_id:123", slowLogRuleSet.RawRules)
|
|
|
|
// invalid connID format
|
|
slowLogRuleSet, err = variable.ParseGlobalSlowLogRules("Conn_ID: -123, DB: db1;")
|
|
require.True(t, strings.Contains(err.Error(), "invalid slow log format"))
|
|
require.Nil(t, slowLogRuleSet)
|
|
|
|
// multiple rules with different ConnID
|
|
allConditionFields = map[string]struct{}{
|
|
strings.ToLower(variable.SlowLogConnIDStr): {},
|
|
strings.ToLower(variable.SlowLogDBStr): {},
|
|
}
|
|
rawRule = `Conn_ID: 123, DB: db1; Conn_ID: 456, DB: db2; DB: db3; Conn_ID: 789; Conn_ID: 123;;`
|
|
slowLogRuleSet, err = variable.ParseGlobalSlowLogRules(rawRule)
|
|
require.NoError(t, err)
|
|
// the result raw rule(in uncertain order): conn_id:123,db:db1;conn_id:123;conn_id:456,db:db2;db:db3;conn_id:789
|
|
require.True(t, strings.Contains(slowLogRuleSet.RawRules, "conn_id:123,db:db1;conn_id:123") ||
|
|
strings.Contains(slowLogRuleSet.RawRules, "db:db1,conn_id:123;conn_id:123"))
|
|
require.True(t, strings.Contains(slowLogRuleSet.RawRules, "conn_id:456,db:db2") ||
|
|
strings.Contains(slowLogRuleSet.RawRules, "db:db2,conn_id:456"))
|
|
require.True(t, strings.Contains(slowLogRuleSet.RawRules, "db:db3"))
|
|
require.True(t, strings.Contains(slowLogRuleSet.RawRules, "conn_id:789"))
|
|
require.Len(t, slowLogRuleSet.RulesMap, 4)
|
|
// Conn_ID: 123
|
|
checkRuleByField(t, slowLogRuleSet.RulesMap[123].Rules, variable.SlowLogDBStr, "db1")
|
|
checkRuleByField(t, slowLogRuleSet.RulesMap[123].Rules, variable.SlowLogConnIDStr, "123")
|
|
require.Equal(t, "", slowLogRuleSet.RulesMap[123].RawRules)
|
|
require.Equal(t, allConditionFields, slowLogRuleSet.RulesMap[123].Fields)
|
|
// Conn_ID: 456
|
|
checkRuleByField(t, slowLogRuleSet.RulesMap[456].Rules, variable.SlowLogDBStr, "db2")
|
|
require.Equal(t, "", slowLogRuleSet.RulesMap[456].RawRules)
|
|
require.Equal(t, allConditionFields, slowLogRuleSet.RulesMap[456].Fields)
|
|
// Conn_ID: -1
|
|
require.Equal(t, "db3", slowLogRuleSet.RulesMap[variable.UnsetConnID].Rules[0].Conditions[0].Threshold)
|
|
require.Equal(t, "", slowLogRuleSet.RulesMap[variable.UnsetConnID].RawRules)
|
|
allConditionFields = map[string]struct{}{
|
|
strings.ToLower(variable.SlowLogDBStr): {},
|
|
}
|
|
require.Equal(t, allConditionFields, slowLogRuleSet.RulesMap[variable.UnsetConnID].Fields)
|
|
// Conn_ID: 789
|
|
require.Equal(t, uint64(789), slowLogRuleSet.RulesMap[789].Rules[0].Conditions[0].Threshold)
|
|
}
|