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 }