163 lines
4.4 KiB
Go
163 lines
4.4 KiB
Go
// Copyright 2021 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 txninfo
|
|
|
|
import (
|
|
"container/list"
|
|
"encoding/json"
|
|
"fmt"
|
|
"hash/fnv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/tikv/client-go/v2/oracle"
|
|
)
|
|
|
|
func digest(digests []string) uint64 {
|
|
// We use FNV-1a hash to generate the 64bit digest
|
|
// since 64bit digest use less memory and FNV-1a is faster than most of other hash algorithms
|
|
// You can refer to https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed
|
|
hash := fnv.New64a()
|
|
for _, digest := range digests {
|
|
hash.Write([]byte(digest))
|
|
}
|
|
return hash.Sum64()
|
|
}
|
|
|
|
type trxSummaryEntry struct {
|
|
trxDigest uint64
|
|
digests []string
|
|
}
|
|
|
|
type trxSummaries struct {
|
|
capacity uint
|
|
|
|
// lru cache for digest -> trxSummaryEntry
|
|
elements map[uint64]*list.Element
|
|
cache *list.List
|
|
}
|
|
|
|
func newTrxSummaries(capacity uint) trxSummaries {
|
|
return trxSummaries{
|
|
capacity: capacity,
|
|
cache: list.New(),
|
|
elements: make(map[uint64]*list.Element),
|
|
}
|
|
}
|
|
|
|
func (s *trxSummaries) onTrxEnd(digests []string) {
|
|
key := digest(digests)
|
|
element, exists := s.elements[key]
|
|
if exists {
|
|
s.cache.MoveToFront(element)
|
|
return
|
|
}
|
|
e := trxSummaryEntry{
|
|
trxDigest: key,
|
|
digests: digests,
|
|
}
|
|
s.elements[key] = s.cache.PushFront(e)
|
|
if uint(s.cache.Len()) > s.capacity {
|
|
last := s.cache.Back()
|
|
delete(s.elements, last.Value.(trxSummaryEntry).trxDigest)
|
|
s.cache.Remove(last)
|
|
}
|
|
}
|
|
|
|
func (s *trxSummaries) dumpTrxSummary() [][]types.Datum {
|
|
var result [][]types.Datum
|
|
for element := s.cache.Front(); element != nil; element = element.Next() {
|
|
sqls := element.Value.(trxSummaryEntry).digests
|
|
// for consistency with other digests in TiDB, we calculate sum256 here to generate varchar(64) digest
|
|
digest := fmt.Sprintf("%x", element.Value.(trxSummaryEntry).trxDigest)
|
|
|
|
res, err := json.Marshal(sqls)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
result = append(result, []types.Datum{
|
|
types.NewDatum(digest),
|
|
types.NewDatum(string(res)),
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (s *trxSummaries) resize(capacity uint) {
|
|
s.capacity = capacity
|
|
for uint(s.cache.Len()) > s.capacity {
|
|
last := s.cache.Back()
|
|
delete(s.elements, last.Value.(trxSummaryEntry).trxDigest)
|
|
s.cache.Remove(last)
|
|
}
|
|
}
|
|
|
|
// TrxHistoryRecorder is a history recorder for transaction.
|
|
type TrxHistoryRecorder struct {
|
|
mu sync.Mutex
|
|
minDuration time.Duration
|
|
summaries trxSummaries
|
|
}
|
|
|
|
// DumpTrxSummary dumps the transaction summary to Datum for displaying in `TRX_SUMMARY` table.
|
|
func (recorder *TrxHistoryRecorder) DumpTrxSummary() [][]types.Datum {
|
|
recorder.mu.Lock()
|
|
defer recorder.mu.Unlock()
|
|
return recorder.summaries.dumpTrxSummary()
|
|
}
|
|
|
|
// OnTrxEnd should be called when a transaction ends, ie. leaves `TIDB_TRX` table.
|
|
func (recorder *TrxHistoryRecorder) OnTrxEnd(info *TxnInfo) {
|
|
now := time.Now()
|
|
startTime := time.UnixMilli(oracle.ExtractPhysical(info.StartTS))
|
|
recorder.mu.Lock()
|
|
defer recorder.mu.Unlock()
|
|
if now.Sub(startTime) < recorder.minDuration {
|
|
return
|
|
}
|
|
recorder.summaries.onTrxEnd(info.AllSQLDigests)
|
|
}
|
|
|
|
func newTrxHistoryRecorder(summariesCap uint) TrxHistoryRecorder {
|
|
return TrxHistoryRecorder{
|
|
summaries: newTrxSummaries(summariesCap),
|
|
minDuration: 1 * time.Second,
|
|
}
|
|
}
|
|
|
|
// Clean clears the history recorder. For test only.
|
|
func (recorder *TrxHistoryRecorder) Clean() {
|
|
recorder.summaries.cache = list.New()
|
|
}
|
|
|
|
// SetMinDuration sets the minimum duration for a transaction to be recorded.
|
|
func (recorder *TrxHistoryRecorder) SetMinDuration(d time.Duration) {
|
|
recorder.mu.Lock()
|
|
defer recorder.mu.Unlock()
|
|
recorder.minDuration = d
|
|
}
|
|
|
|
// ResizeSummaries resizes the summaries capacity.
|
|
func (recorder *TrxHistoryRecorder) ResizeSummaries(capacity uint) {
|
|
recorder.mu.Lock()
|
|
defer recorder.mu.Unlock()
|
|
recorder.summaries.resize(capacity)
|
|
}
|
|
|
|
// Recorder is the recorder instance.
|
|
var Recorder TrxHistoryRecorder = newTrxHistoryRecorder(0)
|