<commit_before>// 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))
	if now.Sub(startTime) < recorder.minDuration {
		return
	}
	recorder.mu.Lock()
	defer recorder.mu.Unlock()
	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()
}<commit_msg>session: fix data race in the TrxHistoryRecorder.OnTrxEnd (#37476)<commit_after>// 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()
}
{'fraction_non_alphanumeric': 0.12116316639741519, 'fraction_numerical': 0.010065863054554492, 'mean_word_length': 4.162283515073765, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '487240', 'n_tokens_mistral': 2917, 'n_tokens_neox': 2760, 'n_words': 719}