329 lines
10 KiB
Go
329 lines
10 KiB
Go
|
// Copyright 2016 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,
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package perfschema
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"runtime"
|
||
|
"time"
|
||
|
|
||
|
"github.com/juju/errors"
|
||
|
"github.com/ngaut/log"
|
||
|
"github.com/pingcap/tidb/ast"
|
||
|
"github.com/pingcap/tidb/kv"
|
||
|
"github.com/pingcap/tidb/terror"
|
||
|
"github.com/pingcap/tidb/util/types"
|
||
|
)
|
||
|
|
||
|
// statementInfo defines statement instrument information.
|
||
|
type statementInfo struct {
|
||
|
// The registered statement key
|
||
|
key uint64
|
||
|
// The name of the statement instrument to register
|
||
|
name string
|
||
|
}
|
||
|
|
||
|
// StatementState provides temporary storage to a statement runtime statistics.
|
||
|
// TODO:
|
||
|
// 1. support statement digest.
|
||
|
// 2. support prepared statement.
|
||
|
type StatementState struct {
|
||
|
// Connection identifier
|
||
|
connID uint64
|
||
|
// Statement information
|
||
|
info *statementInfo
|
||
|
// Statement type
|
||
|
stmtType reflect.Type
|
||
|
// Source file and line number
|
||
|
source string
|
||
|
// Timer name
|
||
|
timerName enumTimerName
|
||
|
// Timer start
|
||
|
timerStart int64
|
||
|
// Timer end
|
||
|
timerEnd int64
|
||
|
// Locked time
|
||
|
lockTime int64
|
||
|
// SQL statement string
|
||
|
sqlText string
|
||
|
// Current schema name
|
||
|
schemaName string
|
||
|
// Number of errors
|
||
|
errNum uint32
|
||
|
// Number of warnings
|
||
|
warnNum uint32
|
||
|
// Rows affected
|
||
|
rowsAffected uint64
|
||
|
// Rows sent
|
||
|
rowsSent uint64
|
||
|
// Rows examined
|
||
|
rowsExamined uint64
|
||
|
// Metric, temporary tables created on disk
|
||
|
createdTmpDiskTables uint32
|
||
|
// Metric, temproray tables created
|
||
|
createdTmpTables uint32
|
||
|
// Metric, number of select full join
|
||
|
selectFullJoin uint32
|
||
|
// Metric, number of select full range join
|
||
|
selectFullRangeJoin uint32
|
||
|
// Metric, number of select range
|
||
|
selectRange uint32
|
||
|
// Metric, number of select range check
|
||
|
selectRangeCheck uint32
|
||
|
// Metric, number of select scan
|
||
|
selectScan uint32
|
||
|
// Metric, number of sort merge passes
|
||
|
sortMergePasses uint32
|
||
|
// Metric, number of sort merge
|
||
|
sortRange uint32
|
||
|
// Metric, number of sort rows
|
||
|
sortRows uint32
|
||
|
// Metric, number of sort scans
|
||
|
sortScan uint32
|
||
|
// Metric, no index used flag
|
||
|
noIndexUsed uint8
|
||
|
// Metric, no good index used flag
|
||
|
noGoodIndexUsed uint8
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
// Maximum allowed number of elements in table events_statements_history.
|
||
|
// TODO: make it configurable?
|
||
|
stmtsHistoryElemMax int = 1024
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
stmtInfos = make(map[reflect.Type]*statementInfo)
|
||
|
)
|
||
|
|
||
|
func (ps *perfSchema) RegisterStatement(category, name string, elem interface{}) {
|
||
|
instrumentName := fmt.Sprintf("%s%s/%s", statementInstrumentPrefix, category, name)
|
||
|
key, err := ps.addInstrument(instrumentName)
|
||
|
if err != nil {
|
||
|
// just ignore, do nothing else.
|
||
|
log.Errorf("Unable to register instrument %s", instrumentName)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
stmtInfos[reflect.TypeOf(elem)] = &statementInfo{
|
||
|
key: key,
|
||
|
name: instrumentName,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (ps *perfSchema) StartStatement(sql string, connID uint64, callerName EnumCallerName, elem interface{}) *StatementState {
|
||
|
stmtType := reflect.TypeOf(elem)
|
||
|
info, ok := stmtInfos[stmtType]
|
||
|
if !ok {
|
||
|
// just ignore, do nothing else.
|
||
|
log.Errorf("No instrument registered for statement %s", stmtType)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// check and apply the configuration parameter in table setup_timers.
|
||
|
timerName, err := ps.getTimerName(flagStatement)
|
||
|
if err != nil {
|
||
|
// just ignore, do nothing else.
|
||
|
log.Error("Unable to check setup_timers table")
|
||
|
return nil
|
||
|
}
|
||
|
var timerStart int64
|
||
|
switch timerName {
|
||
|
case timerNameNanosec:
|
||
|
timerStart = time.Now().UnixNano()
|
||
|
case timerNameMicrosec:
|
||
|
timerStart = time.Now().UnixNano() / int64(time.Microsecond)
|
||
|
case timerNameMillisec:
|
||
|
timerStart = time.Now().UnixNano() / int64(time.Millisecond)
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// TODO: check and apply the additional configuration parameters in:
|
||
|
// - table setup_actors
|
||
|
// - table setup_setup_consumers
|
||
|
// - table setup_instruments
|
||
|
// - table setup_objects
|
||
|
|
||
|
var source string
|
||
|
source, ok = callerNames[callerName]
|
||
|
if !ok {
|
||
|
_, fileName, fileLine, ok := runtime.Caller(1)
|
||
|
if !ok {
|
||
|
// just ignore, do nothing else.
|
||
|
log.Error("Unable to get runtime.Caller(1)")
|
||
|
return nil
|
||
|
}
|
||
|
source = fmt.Sprintf("%s:%d", fileName, fileLine)
|
||
|
callerNames[callerName] = source
|
||
|
}
|
||
|
|
||
|
return &StatementState{
|
||
|
connID: connID,
|
||
|
info: info,
|
||
|
stmtType: stmtType,
|
||
|
source: source,
|
||
|
timerName: timerName,
|
||
|
timerStart: timerStart,
|
||
|
sqlText: sql,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (ps *perfSchema) EndStatement(state *StatementState) {
|
||
|
if state == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
switch state.timerName {
|
||
|
case timerNameNanosec:
|
||
|
state.timerEnd = time.Now().UnixNano()
|
||
|
case timerNameMicrosec:
|
||
|
state.timerEnd = time.Now().UnixNano() / int64(time.Microsecond)
|
||
|
case timerNameMillisec:
|
||
|
state.timerEnd = time.Now().UnixNano() / int64(time.Millisecond)
|
||
|
default:
|
||
|
return
|
||
|
}
|
||
|
|
||
|
log.Debugf("EndStatement: sql %s, connection id %d, type %s", state.sqlText, state.connID, state.stmtType)
|
||
|
|
||
|
record := state2Record(state)
|
||
|
err := ps.updateEventsStmtsCurrent(state.connID, record)
|
||
|
if err != nil {
|
||
|
log.Error("Unable to update events_statements_current table")
|
||
|
}
|
||
|
err = ps.appendEventsStmtsHistory(record)
|
||
|
if err != nil {
|
||
|
log.Errorf("Unable to append to events_statements_history table %v", errors.ErrorStack(err))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func state2Record(state *StatementState) []types.Datum {
|
||
|
return types.MakeDatums(
|
||
|
state.connID, // THREAD_ID
|
||
|
state.info.key, // EVENT_ID
|
||
|
nil, // END_EVENT_ID
|
||
|
state.info.name, // EVENT_NAME
|
||
|
state.source, // SOURCE
|
||
|
uint64(state.timerStart), // TIMER_START
|
||
|
uint64(state.timerEnd), // TIMER_END
|
||
|
nil, // TIMER_WAIT
|
||
|
uint64(state.lockTime), // LOCK_TIME
|
||
|
state.sqlText, // SQL_TEXT
|
||
|
nil, // DIGEST
|
||
|
nil, // DIGEST_TEXT
|
||
|
state.schemaName, // CURRENT_SCHEMA
|
||
|
nil, // OBJECT_TYPE
|
||
|
nil, // OBJECT_SCHEMA
|
||
|
nil, // OBJECT_NAME
|
||
|
nil, // OBJECT_INSTANCE_BEGIN
|
||
|
nil, // MYSQL_ERRNO,
|
||
|
nil, // RETURNED_SQLSTATE
|
||
|
nil, // MESSAGE_TEXT
|
||
|
uint64(state.errNum), // ERRORS
|
||
|
uint64(state.warnNum), // WARNINGS
|
||
|
state.rowsAffected, // ROWS_AFFECTED
|
||
|
state.rowsSent, // ROWS_SENT
|
||
|
state.rowsExamined, // ROWS_EXAMINED
|
||
|
uint64(state.createdTmpDiskTables), // CREATED_TMP_DISK_TABLES
|
||
|
uint64(state.createdTmpTables), // CREATED_TMP_TABLES
|
||
|
uint64(state.selectFullJoin), // SELECT_FULL_JOIN
|
||
|
uint64(state.selectFullRangeJoin), // SELECT_FULL_RANGE_JOIN
|
||
|
uint64(state.selectRange), // SELECT_RANGE
|
||
|
uint64(state.selectRangeCheck), // SELECT_RANGE_CHECK
|
||
|
uint64(state.selectScan), // SELECT_SCAN
|
||
|
uint64(state.sortMergePasses), // SORT_MERGE_PASSES
|
||
|
uint64(state.sortRange), // SORT_RANGE
|
||
|
uint64(state.sortRows), // SORT_ROWS
|
||
|
uint64(state.sortScan), // SORT_SCAN
|
||
|
uint64(state.noIndexUsed), // NO_INDEX_USED
|
||
|
uint64(state.noGoodIndexUsed), // NO_GOOD_INDEX_USED
|
||
|
nil, // NESTING_EVENT_ID
|
||
|
nil, // NESTING_EVENT_TYPE
|
||
|
nil, // NESTING_EVENT_LEVEL
|
||
|
)
|
||
|
}
|
||
|
|
||
|
func (ps *perfSchema) updateEventsStmtsCurrent(connID uint64, record []types.Datum) error {
|
||
|
// Try AddRecord
|
||
|
tbl := ps.mTables[TableStmtsCurrent]
|
||
|
_, err := tbl.AddRecord(nil, record)
|
||
|
if err == nil {
|
||
|
return nil
|
||
|
}
|
||
|
if terror.ErrorNotEqual(err, kv.ErrKeyExists) {
|
||
|
return errors.Trace(err)
|
||
|
}
|
||
|
// Update it
|
||
|
handle := int64(connID)
|
||
|
err = tbl.UpdateRecord(nil, handle, nil, record, nil)
|
||
|
return errors.Trace(err)
|
||
|
}
|
||
|
|
||
|
func (ps *perfSchema) appendEventsStmtsHistory(record []types.Datum) error {
|
||
|
tbl := ps.mTables[TableStmtsHistory]
|
||
|
if len(ps.historyHandles) < stmtsHistoryElemMax {
|
||
|
h, err := tbl.AddRecord(nil, record)
|
||
|
if err == nil {
|
||
|
ps.historyHandles = append(ps.historyHandles, h)
|
||
|
return nil
|
||
|
}
|
||
|
if terror.ErrorNotEqual(err, kv.ErrKeyExists) {
|
||
|
return errors.Trace(err)
|
||
|
}
|
||
|
// THREAD_ID is PK
|
||
|
handle := int64(record[0].GetUint64())
|
||
|
err = tbl.UpdateRecord(nil, handle, nil, record, nil)
|
||
|
return errors.Trace(err)
|
||
|
|
||
|
}
|
||
|
// If histroy is full, replace old data
|
||
|
if ps.historyCursor >= len(ps.historyHandles) {
|
||
|
ps.historyCursor = 0
|
||
|
}
|
||
|
h := ps.historyHandles[ps.historyCursor]
|
||
|
ps.historyCursor++
|
||
|
err := tbl.UpdateRecord(nil, h, nil, record, nil)
|
||
|
return errors.Trace(err)
|
||
|
}
|
||
|
|
||
|
func registerStatements() {
|
||
|
// Existing instrument names are the same as MySQL 5.7
|
||
|
PerfHandle.RegisterStatement("sql", "alter_table", (*ast.AlterTableStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "begin", (*ast.BeginStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "commit", (*ast.CommitStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "create_db", (*ast.CreateDatabaseStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "create_index", (*ast.CreateIndexStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "create_table", (*ast.CreateTableStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "deallocate", (*ast.DeallocateStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "delete", (*ast.DeleteStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "do", (*ast.DoStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "drop_db", (*ast.DropDatabaseStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "drop_table", (*ast.DropTableStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "drop_index", (*ast.DropIndexStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "execute", (*ast.ExecuteStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "explain", (*ast.ExplainStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "insert", (*ast.InsertStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "prepare", (*ast.PrepareStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "rollback", (*ast.RollbackStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "select", (*ast.SelectStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "set", (*ast.SetStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "show", (*ast.ShowStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "truncate", (*ast.TruncateTableStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "union", (*ast.UnionStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "update", (*ast.UpdateStmt)(nil))
|
||
|
PerfHandle.RegisterStatement("sql", "use", (*ast.UseStmt)(nil))
|
||
|
}
|