184 lines
6.0 KiB
Go
184 lines
6.0 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 executor
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pingcap/tidb/pkg/keyspace"
|
|
"github.com/pingcap/tidb/pkg/parser/ast"
|
|
"github.com/pingcap/tidb/pkg/sessionctx/variable"
|
|
"github.com/tikv/client-go/v2/util"
|
|
)
|
|
|
|
func init() {
|
|
variable.SlowLogRuleFieldAccessors[strings.ToLower(variable.SlowLogPlanDigest)] = variable.SlowLogFieldAccessor{
|
|
Parse: variable.ParseString,
|
|
Setter: func(_ context.Context, seVars *variable.SessionVars, items *variable.SlowQueryLogItems) {
|
|
_, planDigest := GetPlanDigest(seVars.StmtCtx)
|
|
items.PlanDigest = planDigest.String()
|
|
},
|
|
Match: func(_ *variable.SessionVars, items *variable.SlowQueryLogItems, threshold any) bool {
|
|
return variable.MatchEqual(threshold, strings.ToLower(items.PlanDigest))
|
|
},
|
|
}
|
|
}
|
|
|
|
// PrepareSlowLogItemsForRules builds a SlowQueryLogItems containing only the fields referenced by current session's SlowLogRules.
|
|
// These pre-collected fields are later used for matching SQL execution details against the rules.
|
|
func PrepareSlowLogItemsForRules(ctx context.Context, seVars *variable.SessionVars) *variable.SlowQueryLogItems {
|
|
items := &variable.SlowQueryLogItems{}
|
|
for field := range seVars.SlowLogRules.AllConditionFields {
|
|
gs := variable.SlowLogRuleFieldAccessors[field]
|
|
if gs.Setter != nil {
|
|
gs.Setter(ctx, seVars, items)
|
|
}
|
|
}
|
|
|
|
return items
|
|
}
|
|
|
|
// Match checks whether the given SlowQueryLogItems satisfies any of the
|
|
// current session's slow log trigger rules.
|
|
//
|
|
// Matching is evaluated in two levels of logical relationship:
|
|
// - Rules are combined with OR: if any rule is satisfied, the function returns true.
|
|
// - Conditions inside a rule are combined with AND: all conditions must be met for that rule.
|
|
//
|
|
// Returns true if any rule matches, false otherwise.
|
|
func Match(seVars *variable.SessionVars, items *variable.SlowQueryLogItems) bool {
|
|
rules := seVars.SlowLogRules
|
|
// Or logical relationship
|
|
for _, rule := range rules.Rules {
|
|
matched := true
|
|
|
|
// And logical relationship
|
|
for _, condition := range rule.Conditions {
|
|
accessor := variable.SlowLogRuleFieldAccessors[strings.ToLower(condition.Field)]
|
|
if ok := accessor.Match(seVars, items, condition.Threshold); !ok {
|
|
matched = false
|
|
break
|
|
}
|
|
}
|
|
if matched {
|
|
return matched
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// CompleteSlowLogItemsForRules fills in the remaining SlowQueryLogItems fields
|
|
// that are relevant to triggering the current session's SlowLogRules.
|
|
// It's exporting for testing.
|
|
func CompleteSlowLogItemsForRules(ctx context.Context, seVars *variable.SessionVars, items *variable.SlowQueryLogItems) {
|
|
if items == nil {
|
|
return
|
|
}
|
|
|
|
for field, sg := range variable.SlowLogRuleFieldAccessors {
|
|
if _, ok := seVars.SlowLogRules.AllConditionFields[field]; ok {
|
|
continue
|
|
}
|
|
|
|
if sg.Setter != nil {
|
|
sg.Setter(ctx, seVars, items)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetSlowLogItems fills the remaining fields of SlowQueryLogItems after SQL execution.
|
|
func SetSlowLogItems(a *ExecStmt, txnTS uint64, hasMoreResults bool, items *variable.SlowQueryLogItems) {
|
|
CompleteSlowLogItemsForRules(a.GoCtx, a.Ctx.GetSessionVars(), items)
|
|
|
|
sessVars := a.Ctx.GetSessionVars()
|
|
stmtCtx := sessVars.StmtCtx
|
|
var indexNames string
|
|
if len(stmtCtx.IndexNames) > 0 {
|
|
// remove duplicate index.
|
|
idxMap := make(map[string]struct{})
|
|
buf := bytes.NewBuffer(make([]byte, 0, 4))
|
|
buf.WriteByte('[')
|
|
for _, idx := range stmtCtx.IndexNames {
|
|
_, ok := idxMap[idx]
|
|
if ok {
|
|
continue
|
|
}
|
|
idxMap[idx] = struct{}{}
|
|
if buf.Len() > 1 {
|
|
buf.WriteByte(',')
|
|
}
|
|
buf.WriteString(idx)
|
|
}
|
|
buf.WriteByte(']')
|
|
indexNames = buf.String()
|
|
}
|
|
|
|
var ruDetails *util.RUDetails
|
|
if ruDetailsVal := a.GoCtx.Value(util.RUDetailsCtxKey); ruDetailsVal != nil {
|
|
ruDetails = ruDetailsVal.(*util.RUDetails)
|
|
} else {
|
|
ruDetails = util.NewRUDetails()
|
|
}
|
|
|
|
binaryPlan := ""
|
|
if variable.GenerateBinaryPlan.Load() {
|
|
binaryPlan = getBinaryPlan(a.Ctx)
|
|
if len(binaryPlan) > 0 {
|
|
binaryPlan = variable.SlowLogBinaryPlanPrefix + binaryPlan + variable.SlowLogPlanSuffix
|
|
}
|
|
}
|
|
|
|
var keyspaceID uint32
|
|
keyspaceName := keyspace.GetKeyspaceNameBySettings()
|
|
if !keyspace.IsKeyspaceNameEmpty(keyspaceName) {
|
|
keyspaceID = uint32(a.Ctx.GetStore().GetCodec().GetKeyspaceID())
|
|
}
|
|
|
|
items.TxnTS = txnTS
|
|
items.KeyspaceName = keyspaceName
|
|
items.KeyspaceID = keyspaceID
|
|
items.SQL = FormatSQL(a.GetTextToLog(true)).String()
|
|
items.IndexNames = indexNames
|
|
items.Plan = getPlanTree(stmtCtx)
|
|
items.BinaryPlan = binaryPlan
|
|
items.Prepared = a.isPreparedStmt
|
|
items.HasMoreResults = hasMoreResults
|
|
items.PlanFromCache = sessVars.FoundInPlanCache
|
|
items.PlanFromBinding = sessVars.FoundInBinding
|
|
items.RewriteInfo = sessVars.RewritePhaseInfo
|
|
items.ResultRows = stmtCtx.GetResultRowsCount()
|
|
items.IsExplicitTxn = sessVars.TxnCtx.IsExplicit
|
|
items.IsWriteCacheTable = stmtCtx.WaitLockLeaseTime > 0
|
|
items.UsedStats = stmtCtx.GetUsedStatsInfo(false)
|
|
items.IsSyncStatsFailed = stmtCtx.IsSyncStatsFailed
|
|
items.Warnings = variable.CollectWarningsForSlowLog(stmtCtx)
|
|
items.ResourceGroupName = stmtCtx.ResourceGroupName
|
|
items.RUDetails = ruDetails
|
|
items.CPUUsages = sessVars.SQLCPUUsages.GetCPUUsages()
|
|
items.StorageKV = stmtCtx.IsTiKV.Load()
|
|
items.StorageMPP = stmtCtx.IsTiFlash.Load()
|
|
|
|
if a.retryCount > 0 {
|
|
items.ExecRetryTime = items.TimeTotal - sessVars.DurationParse - sessVars.DurationCompile - time.Since(a.retryStartTime)
|
|
}
|
|
if _, ok := a.StmtNode.(*ast.CommitStmt); ok && sessVars.PrevStmt != nil {
|
|
items.PrevStmt = sessVars.PrevStmt.String()
|
|
}
|
|
}
|