github.com/KinWaiYuen/client-go/v2@v2.5.4/util/execdetails.go (about)

     1  // Copyright 2021 TiKV Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // NOTE: The code in this file is based on code from the
    16  // TiDB project, licensed under the Apache License v 2.0
    17  //
    18  // https://github.com/pingcap/tidb/tree/cc5e161ac06827589c4966674597c137cc9e809c/store/tikv/util/execdetails.go
    19  //
    20  
    21  // Copyright 2021 PingCAP, Inc.
    22  //
    23  // Licensed under the Apache License, Version 2.0 (the "License");
    24  // you may not use this file except in compliance with the License.
    25  // You may obtain a copy of the License at
    26  //
    27  //     http://www.apache.org/licenses/LICENSE-2.0
    28  //
    29  // Unless required by applicable law or agreed to in writing, software
    30  // distributed under the License is distributed on an "AS IS" BASIS,
    31  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    32  // See the License for the specific language governing permissions and
    33  // limitations under the License.
    34  
    35  package util
    36  
    37  import (
    38  	"bytes"
    39  	"math"
    40  	"strconv"
    41  	"sync"
    42  	"sync/atomic"
    43  	"time"
    44  
    45  	"github.com/pingcap/kvproto/pkg/kvrpcpb"
    46  )
    47  
    48  type commitDetailCtxKeyType struct{}
    49  type lockKeysDetailCtxKeyType struct{}
    50  type execDetailsCtxKeyType struct{}
    51  
    52  var (
    53  	// CommitDetailCtxKey presents CommitDetail info key in context.
    54  	CommitDetailCtxKey = commitDetailCtxKeyType{}
    55  
    56  	// LockKeysDetailCtxKey presents LockKeysDetail info key in context.
    57  	LockKeysDetailCtxKey = lockKeysDetailCtxKeyType{}
    58  
    59  	// ExecDetailsKey presents ExecDetail info key in context.
    60  	ExecDetailsKey = execDetailsCtxKeyType{}
    61  )
    62  
    63  // CommitDetails contains commit detail information.
    64  type CommitDetails struct {
    65  	GetCommitTsTime        time.Duration
    66  	PrewriteTime           time.Duration
    67  	WaitPrewriteBinlogTime time.Duration
    68  	CommitTime             time.Duration
    69  	LocalLatchTime         time.Duration
    70  	Mu                     struct {
    71  		sync.Mutex
    72  		CommitBackoffTime int64
    73  		BackoffTypes      []string
    74  	}
    75  	ResolveLockTime   int64
    76  	WriteKeys         int
    77  	WriteSize         int
    78  	PrewriteRegionNum int32
    79  	TxnRetry          int
    80  }
    81  
    82  // Merge merges commit details into itself.
    83  func (cd *CommitDetails) Merge(other *CommitDetails) {
    84  	cd.GetCommitTsTime += other.GetCommitTsTime
    85  	cd.PrewriteTime += other.PrewriteTime
    86  	cd.WaitPrewriteBinlogTime += other.WaitPrewriteBinlogTime
    87  	cd.CommitTime += other.CommitTime
    88  	cd.LocalLatchTime += other.LocalLatchTime
    89  	cd.ResolveLockTime += other.ResolveLockTime
    90  	cd.WriteKeys += other.WriteKeys
    91  	cd.WriteSize += other.WriteSize
    92  	cd.PrewriteRegionNum += other.PrewriteRegionNum
    93  	cd.TxnRetry += other.TxnRetry
    94  	cd.Mu.CommitBackoffTime += other.Mu.CommitBackoffTime
    95  	cd.Mu.BackoffTypes = append(cd.Mu.BackoffTypes, other.Mu.BackoffTypes...)
    96  }
    97  
    98  // Clone returns a deep copy of itself.
    99  func (cd *CommitDetails) Clone() *CommitDetails {
   100  	commit := &CommitDetails{
   101  		GetCommitTsTime:        cd.GetCommitTsTime,
   102  		PrewriteTime:           cd.PrewriteTime,
   103  		WaitPrewriteBinlogTime: cd.WaitPrewriteBinlogTime,
   104  		CommitTime:             cd.CommitTime,
   105  		LocalLatchTime:         cd.LocalLatchTime,
   106  		ResolveLockTime:        cd.ResolveLockTime,
   107  		WriteKeys:              cd.WriteKeys,
   108  		WriteSize:              cd.WriteSize,
   109  		PrewriteRegionNum:      cd.PrewriteRegionNum,
   110  		TxnRetry:               cd.TxnRetry,
   111  	}
   112  	commit.Mu.BackoffTypes = append([]string{}, cd.Mu.BackoffTypes...)
   113  	commit.Mu.CommitBackoffTime = cd.Mu.CommitBackoffTime
   114  	return commit
   115  }
   116  
   117  // LockKeysDetails contains pessimistic lock keys detail information.
   118  type LockKeysDetails struct {
   119  	TotalTime       time.Duration
   120  	RegionNum       int32
   121  	LockKeys        int32
   122  	ResolveLockTime int64
   123  	BackoffTime     int64
   124  	Mu              struct {
   125  		sync.Mutex
   126  		BackoffTypes []string
   127  	}
   128  	LockRPCTime  int64
   129  	LockRPCCount int64
   130  	RetryCount   int
   131  }
   132  
   133  // Merge merges lock keys execution details into self.
   134  func (ld *LockKeysDetails) Merge(lockKey *LockKeysDetails) {
   135  	ld.TotalTime += lockKey.TotalTime
   136  	ld.RegionNum += lockKey.RegionNum
   137  	ld.LockKeys += lockKey.LockKeys
   138  	ld.ResolveLockTime += lockKey.ResolveLockTime
   139  	ld.BackoffTime += lockKey.BackoffTime
   140  	ld.LockRPCTime += lockKey.LockRPCTime
   141  	ld.LockRPCCount += ld.LockRPCCount
   142  	ld.Mu.BackoffTypes = append(ld.Mu.BackoffTypes, lockKey.Mu.BackoffTypes...)
   143  	ld.RetryCount++
   144  }
   145  
   146  // Clone returns a deep copy of itself.
   147  func (ld *LockKeysDetails) Clone() *LockKeysDetails {
   148  	lock := &LockKeysDetails{
   149  		TotalTime:       ld.TotalTime,
   150  		RegionNum:       ld.RegionNum,
   151  		LockKeys:        ld.LockKeys,
   152  		ResolveLockTime: ld.ResolveLockTime,
   153  		BackoffTime:     ld.BackoffTime,
   154  		LockRPCTime:     ld.LockRPCTime,
   155  		LockRPCCount:    ld.LockRPCCount,
   156  		RetryCount:      ld.RetryCount,
   157  	}
   158  	lock.Mu.BackoffTypes = append([]string{}, ld.Mu.BackoffTypes...)
   159  	return lock
   160  }
   161  
   162  // ExecDetails contains execution detail info.
   163  type ExecDetails struct {
   164  	BackoffCount       int64
   165  	BackoffDuration    int64
   166  	WaitKVRespDuration int64
   167  	WaitPDRespDuration int64
   168  }
   169  
   170  // FormatDuration uses to format duration, this function will prune precision before format duration.
   171  // Pruning precision is for human readability. The prune rule is:
   172  // 1. if the duration was less than 1us, return the original string.
   173  // 2. readable value >=10, keep 1 decimal, otherwise, keep 2 decimal. such as:
   174  //    9.412345ms  -> 9.41ms
   175  //    10.412345ms -> 10.4ms
   176  //    5.999s      -> 6s
   177  //    100.45µs    -> 100.5µs
   178  func FormatDuration(d time.Duration) string {
   179  	if d <= time.Microsecond {
   180  		return d.String()
   181  	}
   182  	unit := getUnit(d)
   183  	if unit == time.Nanosecond {
   184  		return d.String()
   185  	}
   186  	integer := (d / unit) * unit
   187  	decimal := float64(d%unit) / float64(unit)
   188  	if d < 10*unit {
   189  		decimal = math.Round(decimal*100) / 100
   190  	} else {
   191  		decimal = math.Round(decimal*10) / 10
   192  	}
   193  	d = integer + time.Duration(decimal*float64(unit))
   194  	return d.String()
   195  }
   196  
   197  func getUnit(d time.Duration) time.Duration {
   198  	if d >= time.Second {
   199  		return time.Second
   200  	} else if d >= time.Millisecond {
   201  		return time.Millisecond
   202  	} else if d >= time.Microsecond {
   203  		return time.Microsecond
   204  	}
   205  	return time.Nanosecond
   206  }
   207  
   208  // ScanDetail contains coprocessor scan detail information.
   209  type ScanDetail struct {
   210  	// TotalKeys is the approximate number of MVCC keys meet during scanning. It includes
   211  	// deleted versions, but does not include RocksDB tombstone keys.
   212  	TotalKeys int64
   213  	// ProcessedKeys is the number of user keys scanned from the storage.
   214  	// It does not include deleted version or RocksDB tombstone keys.
   215  	// For Coprocessor requests, it includes keys that has been filtered out by Selection.
   216  	ProcessedKeys int64
   217  	// RocksdbDeleteSkippedCount is the total number of deletes and single deletes skipped over during
   218  	// iteration, i.e. how many RocksDB tombstones are skipped.
   219  	RocksdbDeleteSkippedCount uint64
   220  	// RocksdbKeySkippedCount it the total number of internal keys skipped over during iteration.
   221  	RocksdbKeySkippedCount uint64
   222  	// RocksdbBlockCacheHitCount is the total number of RocksDB block cache hits.
   223  	RocksdbBlockCacheHitCount uint64
   224  	// RocksdbBlockReadCount is the total number of block reads (with IO).
   225  	RocksdbBlockReadCount uint64
   226  	// RocksdbBlockReadByte is the total number of bytes from block reads.
   227  	RocksdbBlockReadByte uint64
   228  }
   229  
   230  // Merge merges scan detail execution details into self.
   231  func (sd *ScanDetail) Merge(scanDetail *ScanDetail) {
   232  	atomic.AddInt64(&sd.TotalKeys, scanDetail.TotalKeys)
   233  	atomic.AddInt64(&sd.ProcessedKeys, scanDetail.ProcessedKeys)
   234  	atomic.AddUint64(&sd.RocksdbDeleteSkippedCount, scanDetail.RocksdbDeleteSkippedCount)
   235  	atomic.AddUint64(&sd.RocksdbKeySkippedCount, scanDetail.RocksdbKeySkippedCount)
   236  	atomic.AddUint64(&sd.RocksdbBlockCacheHitCount, scanDetail.RocksdbBlockCacheHitCount)
   237  	atomic.AddUint64(&sd.RocksdbBlockReadCount, scanDetail.RocksdbBlockReadCount)
   238  	atomic.AddUint64(&sd.RocksdbBlockReadByte, scanDetail.RocksdbBlockReadByte)
   239  }
   240  
   241  var zeroScanDetail = ScanDetail{}
   242  
   243  // String implements the fmt.Stringer interface.
   244  func (sd *ScanDetail) String() string {
   245  	if sd == nil || *sd == zeroScanDetail {
   246  		return ""
   247  	}
   248  	buf := bytes.NewBuffer(make([]byte, 0, 16))
   249  	buf.WriteString("scan_detail: {")
   250  	buf.WriteString("total_process_keys: ")
   251  	buf.WriteString(strconv.FormatInt(sd.ProcessedKeys, 10))
   252  	buf.WriteString(", total_keys: ")
   253  	buf.WriteString(strconv.FormatInt(sd.TotalKeys, 10))
   254  	buf.WriteString(", rocksdb: {")
   255  	buf.WriteString("delete_skipped_count: ")
   256  	buf.WriteString(strconv.FormatUint(sd.RocksdbDeleteSkippedCount, 10))
   257  	buf.WriteString(", key_skipped_count: ")
   258  	buf.WriteString(strconv.FormatUint(sd.RocksdbKeySkippedCount, 10))
   259  	buf.WriteString(", block: {")
   260  	buf.WriteString("cache_hit_count: ")
   261  	buf.WriteString(strconv.FormatUint(sd.RocksdbBlockCacheHitCount, 10))
   262  	buf.WriteString(", read_count: ")
   263  	buf.WriteString(strconv.FormatUint(sd.RocksdbBlockReadCount, 10))
   264  	buf.WriteString(", read_byte: ")
   265  	buf.WriteString(FormatBytes(int64(sd.RocksdbBlockReadByte)))
   266  	buf.WriteString("}}}")
   267  	return buf.String()
   268  }
   269  
   270  // MergeFromScanDetailV2 merges scan detail from pb into itself.
   271  func (sd *ScanDetail) MergeFromScanDetailV2(scanDetail *kvrpcpb.ScanDetailV2) {
   272  	if scanDetail != nil {
   273  		sd.TotalKeys += int64(scanDetail.TotalVersions)
   274  		sd.ProcessedKeys += int64(scanDetail.ProcessedVersions)
   275  		sd.RocksdbDeleteSkippedCount += scanDetail.RocksdbDeleteSkippedCount
   276  		sd.RocksdbKeySkippedCount += scanDetail.RocksdbKeySkippedCount
   277  		sd.RocksdbBlockCacheHitCount += scanDetail.RocksdbBlockCacheHitCount
   278  		sd.RocksdbBlockReadCount += scanDetail.RocksdbBlockReadCount
   279  		sd.RocksdbBlockReadByte += scanDetail.RocksdbBlockReadByte
   280  	}
   281  }
   282  
   283  // TimeDetail contains coprocessor time detail information.
   284  type TimeDetail struct {
   285  	// WaitWallTimeMs is the off-cpu wall time which is elapsed in TiKV side. Usually this includes queue waiting time and
   286  	// other kind of waitings in series.
   287  	ProcessTime time.Duration
   288  	// Off-cpu and on-cpu wall time elapsed to actually process the request payload. It does not
   289  	// include `wait_wall_time`.
   290  	// This field is very close to the CPU time in most cases. Some wait time spend in RocksDB
   291  	// cannot be excluded for now, like Mutex wait time, which is included in this field, so that
   292  	// this field is called wall time instead of CPU time.
   293  	WaitTime time.Duration
   294  	// KvReadWallTimeMs is the time used in KV Scan/Get.
   295  	KvReadWallTimeMs time.Duration
   296  }
   297  
   298  // String implements the fmt.Stringer interface.
   299  func (td *TimeDetail) String() string {
   300  	if td == nil {
   301  		return ""
   302  	}
   303  	buf := bytes.NewBuffer(make([]byte, 0, 16))
   304  	if td.ProcessTime > 0 {
   305  		buf.WriteString("total_process_time: ")
   306  		buf.WriteString(FormatDuration(td.ProcessTime))
   307  	}
   308  	if td.WaitTime > 0 {
   309  		if buf.Len() > 0 {
   310  			buf.WriteString(", ")
   311  		}
   312  		buf.WriteString("total_wait_time: ")
   313  		buf.WriteString(FormatDuration(td.WaitTime))
   314  	}
   315  	return buf.String()
   316  }
   317  
   318  // MergeFromTimeDetail merges time detail from pb into itself.
   319  func (td *TimeDetail) MergeFromTimeDetail(timeDetail *kvrpcpb.TimeDetail) {
   320  	if timeDetail != nil {
   321  		td.WaitTime += time.Duration(timeDetail.WaitWallTimeMs) * time.Millisecond
   322  		td.ProcessTime += time.Duration(timeDetail.ProcessWallTimeMs) * time.Millisecond
   323  		td.KvReadWallTimeMs += time.Duration(timeDetail.KvReadWallTimeMs) * time.Millisecond
   324  	}
   325  }