github.com/matrixorigin/matrixone@v1.2.0/pkg/vm/engine/tae/common/stats.go (about) 1 // Copyright 2022 Matrix Origin 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 package common 16 17 import ( 18 "encoding/hex" 19 "fmt" 20 "math" 21 "math/rand" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "github.com/matrixorigin/matrixone/pkg/container/types" 27 "golang.org/x/exp/constraints" 28 ) 29 30 const ( 31 DefaultMinOsizeQualifiedMB = 110 // MB 32 DefaultMaxOsizeObjMB = 128 // MB 33 DefaultMinCNMergeSize = 80000 // MB 34 DefaultCNMergeMemControlHint = 8192 // MB 35 DefaultMaxMergeObjN = 16 36 37 Const1GBytes = 1 << 30 38 Const1MBytes = 1 << 20 39 ) 40 41 var ( 42 RuntimeMaxMergeObjN atomic.Int32 43 RuntimeOsizeRowsQualified atomic.Uint32 44 RuntimeMaxObjOsize atomic.Uint32 45 RuntimeMinCNMergeSize atomic.Uint64 46 RuntimeCNMergeMemControl atomic.Uint64 47 RuntimeCNTakeOverAll atomic.Bool 48 IsStandaloneBoost atomic.Bool 49 ShouldStandaloneCNTakeOver atomic.Bool 50 Epsilon float64 51 52 RuntimeOverallFlushMemCap atomic.Uint64 53 ) 54 55 func init() { 56 RuntimeMaxMergeObjN.Store(DefaultMaxMergeObjN) 57 RuntimeOsizeRowsQualified.Store(DefaultMinOsizeQualifiedMB * Const1MBytes) 58 RuntimeMaxObjOsize.Store(DefaultMaxOsizeObjMB * Const1MBytes) 59 Epsilon = math.Nextafter(1, 2) - 1 60 } 61 62 type Number interface { 63 constraints.Integer | constraints.Float 64 } 65 66 /////// 67 /// statistics component 68 /////// 69 70 type TrendKind int 71 type WorkloadKind int 72 73 const ( 74 TrendUnknown TrendKind = iota 75 TrendDecII 76 TrendDecI 77 TrendStable 78 TrendIncI 79 TrendIncII 80 ) 81 82 const ( 83 WorkUnknown WorkloadKind = iota 84 WorkQuiet 85 WorkApInsert 86 WorkApQuiet 87 WorkTpUpdate 88 WorkMixed 89 ) 90 91 func (t TrendKind) String() string { 92 switch t { 93 case TrendDecII: 94 return "DecII" 95 case TrendDecI: 96 return "DecI" 97 case TrendStable: 98 return "Stable" 99 case TrendIncI: 100 return "IncI" 101 case TrendIncII: 102 return "IncII" 103 default: 104 return "UnknownTrend" 105 } 106 } 107 108 type HistorySampler[T Number] interface { 109 Append(x T) 110 V() T 111 QueryTrend() (TrendKind, TrendKind, TrendKind) 112 String() string 113 } 114 115 // general sampler 116 type SampleIII[T Number] struct { 117 new, sample, v1, v2, v3 T 118 119 lastSampleT time.Time 120 121 // f1 = 1.0 122 f2, f3 float64 123 124 // config 125 d1, d2, d3 time.Duration 126 } 127 128 var F2 = 0.7 129 var F3 = 0.4 130 var D1 = 30 * time.Second 131 var D2 = 2 * time.Minute 132 var D3 = 5 * time.Minute 133 134 // optimize for saving space 135 type FixedSampleIII[T Number] struct { 136 new, sample, v1, v2, v3 T 137 lastSampleT time.Time 138 } 139 140 func (s *FixedSampleIII[T]) tick(x T) { 141 s.new = x 142 // init 143 if s.lastSampleT.IsZero() { 144 s.lastSampleT = time.Now() 145 s.sample = x 146 s.v1 = x 147 s.v2 = x 148 s.v3 = x 149 return 150 } 151 now := time.Now() 152 span := now.Sub(s.lastSampleT) 153 if span < D1 { 154 return 155 } 156 // rotate and sample this point 157 158 // cnt history values need to be pushed back 159 // cnt := int(span / s.samplePeriod) 160 if span > D3 { 161 s.v3 = s.sample 162 s.v2 = s.sample 163 s.v1 = s.sample 164 } else if span > D2 { 165 s.v3 = moveAvg(s.v3, s.v2, F2) 166 s.v2 = s.sample 167 s.v1 = s.sample 168 } else { 169 s.v3 = moveAvg(s.v3, s.v2, F3) 170 s.v2 = moveAvg(s.v2, s.v1, F2) 171 s.v1 = s.sample 172 } 173 174 s.lastSampleT = now 175 s.sample = x 176 } 177 178 func (s *FixedSampleIII[T]) Append(x T) { 179 s.tick(x) 180 } 181 182 func (s *FixedSampleIII[T]) V() T { 183 return s.new 184 } 185 186 func (s *FixedSampleIII[T]) QueryTrend() (TrendKind, TrendKind, TrendKind) { 187 judgeTrend := func(vprev, vnow T) TrendKind { 188 if roundZero(vprev) { 189 if vnow > 0 { 190 return TrendIncI 191 } else if vnow < 0 { 192 return TrendDecI 193 } else { 194 return TrendStable 195 } 196 } 197 delta := float64(vnow - vprev) 198 deltaPercent := math.Abs(delta / float64(vprev)) 199 if math.Signbit(delta) { 200 deltaPercent = -deltaPercent 201 } 202 203 if deltaPercent < -0.4 { 204 return TrendDecII 205 } else if deltaPercent < -0.01 { 206 return TrendDecI 207 } else if deltaPercent < 0.01 { 208 return TrendStable 209 } else if deltaPercent < 0.4 { 210 return TrendIncI 211 } else { 212 return TrendIncII 213 } 214 } 215 s.tick(s.new) 216 return judgeTrend(s.v1, s.new), judgeTrend(s.v2, s.new), judgeTrend(s.v3, s.new) 217 } 218 219 func (s *FixedSampleIII[T]) String() string { 220 x, m, l := s.QueryTrend() 221 return fmt.Sprintf( 222 "Sample(%v/%v/{%v,%v,%v}/%v,%v,%v)", 223 s.new, s.lastSampleT.Format("2006-01-02_15:04:05"), 224 s.v1, s.v2, s.v3, 225 x, m, l, 226 ) 227 } 228 229 func NewSmapler35m[T Number]() FixedSampleIII[T] { 230 return FixedSampleIII[T]{} 231 } 232 233 func selectFactor(cnt int) float64 { 234 list := []int{2, 3, 4, 6, 7, 10, 13, 21, 44} 235 for i, v := range list { 236 if cnt <= v { 237 return float64(9-i) / 10.0 238 } 239 } 240 return 0.05 241 } 242 243 func NewSampleIII[T Number](sp, d2, d3 time.Duration) *SampleIII[T] { 244 return &SampleIII[T]{ 245 d1: sp, 246 d2: d2, 247 d3: d3, 248 f2: selectFactor(int(d2 / sp)), 249 f3: selectFactor(int(d3 / sp)), 250 } 251 } 252 253 func (s *SampleIII[T]) tick(x T) { 254 s.new = x 255 // init 256 if s.lastSampleT.IsZero() { 257 s.lastSampleT = time.Now() 258 s.sample = x 259 s.v1 = x 260 s.v2 = x 261 s.v3 = x 262 return 263 } 264 now := time.Now() 265 span := now.Sub(s.lastSampleT) 266 if span < s.d1 { 267 return 268 } 269 // rotate and sample this point 270 271 // cnt history values need to be pushed back 272 // cnt := int(span / s.samplePeriod) 273 if span > s.d3 { 274 s.v3 = s.sample 275 s.v2 = s.sample 276 s.v1 = s.sample 277 } else if span > s.d2 { 278 s.v3 = moveAvg(s.v3, s.v2, s.f2) 279 s.v2 = s.sample 280 s.v1 = s.sample 281 } else { 282 s.v3 = moveAvg(s.v3, s.v2, s.f3) 283 s.v2 = moveAvg(s.v2, s.v1, s.f2) 284 s.v1 = s.sample 285 } 286 287 s.lastSampleT = now 288 s.sample = x 289 } 290 291 func (s *SampleIII[T]) Append(x T) { 292 s.tick(x) 293 } 294 295 func (s *SampleIII[T]) V() T { 296 return s.new 297 } 298 299 func (s *SampleIII[T]) QueryTrend() (TrendKind, TrendKind, TrendKind) { 300 judgeTrend := func(vprev, vnow T) TrendKind { 301 if roundZero(vprev) { 302 if vnow > 0 { 303 return TrendIncI 304 } else if vnow < 0 { 305 return TrendDecI 306 } else { 307 return TrendStable 308 } 309 } 310 delta := float64(vnow - vprev) 311 deltaPercent := math.Abs(delta / float64(vprev)) 312 if math.Signbit(delta) { 313 deltaPercent = -deltaPercent 314 } 315 316 if deltaPercent < -0.4 { 317 return TrendDecII 318 } else if deltaPercent < -0.01 { 319 return TrendDecI 320 } else if deltaPercent < 0.01 { 321 return TrendStable 322 } else if deltaPercent < 0.4 { 323 return TrendIncI 324 } else { 325 return TrendIncII 326 } 327 } 328 s.tick(s.new) 329 return judgeTrend(s.v1, s.new), judgeTrend(s.v2, s.new), judgeTrend(s.v3, s.new) 330 } 331 332 func (s *SampleIII[T]) String() string { 333 x, m, l := s.QueryTrend() 334 return fmt.Sprintf( 335 "Sample(%v/%v/{%v@-%v,%v@-%v(%.2f),%v@-%v(%.2f)}/%v,%v,%v)", 336 s.new, s.lastSampleT.Format("2006-01-02_15:04:05"), 337 s.v1, s.d1, 338 s.v2, s.d2, s.f2, 339 s.v3, s.d3, s.f3, 340 x, m, l, 341 ) 342 } 343 344 type MergeHistory struct { 345 LastTime time.Time 346 OSize int 347 NObj int 348 NBlk int 349 } 350 351 func (h *MergeHistory) Add(osize, nobj, nblk int) { 352 h.OSize = osize 353 h.NObj = nobj 354 h.NBlk = nblk 355 h.LastTime = time.Now() 356 } 357 358 func (h *MergeHistory) IsLastBefore(d time.Duration) bool { 359 return h.LastTime.Before(time.Now().Add(-d)) 360 } 361 362 func (h *MergeHistory) String() string { 363 return fmt.Sprintf( 364 "(%v) no%v nb%v osize%v", 365 h.LastTime.Format("2006-01-02_15:04:05"), 366 h.NObj, h.NBlk, 367 HumanReadableBytes(h.OSize), 368 ) 369 } 370 371 /// 372 /// Table statistics 373 /// 374 375 type TableCompactStat struct { 376 sync.RWMutex 377 378 Inited bool 379 380 // Configs 381 382 // how often to flush table tail 383 // this duration will be add some random value to avoid flush many tables at the same time 384 FlushGapDuration time.Duration 385 // if the size of table tail, in bytes, exceeds FlushMemCapacity, flush it immediately 386 FlushMemCapacity int 387 388 // Status 389 390 // dirty end range flushed by last flush txn. If we are waiting for a ckp [a, b], and all dirty tables' LastFlush are greater than b, 391 // the checkpoint is ready to collect data and write all down. 392 LastFlush types.TS 393 // FlushDeadline is the deadline to flush table tail 394 FlushDeadline time.Time 395 396 WorkloadGuess WorkloadKind 397 WorkloadStreak int 398 RowCnt FixedSampleIII[int] 399 RowDel FixedSampleIII[int] 400 MergeHist MergeHistory 401 } 402 403 func NewTableCompactStat() *TableCompactStat { 404 return &TableCompactStat{ 405 RowCnt: FixedSampleIII[int]{}, 406 RowDel: FixedSampleIII[int]{}, 407 } 408 } 409 410 func (s *TableCompactStat) ResetDeadlineWithLock() { 411 // add random +/- 10% 412 factor := 1.0 + float64(rand.Intn(21)-10)/100.0 413 s.FlushDeadline = time.Now().Add(time.Duration(factor * float64(s.FlushGapDuration))) 414 } 415 416 func (s *TableCompactStat) InitWithLock(durationHint time.Duration) { 417 s.FlushGapDuration = durationHint * 5 418 s.FlushMemCapacity = 20 * 1024 * 1024 419 s.Inited = true 420 } 421 422 func (s *TableCompactStat) AddMerge(osize, nobj, nblk int) { 423 s.Lock() 424 defer s.Unlock() 425 s.MergeHist.Add(osize, nobj, nblk) 426 } 427 428 func (s *TableCompactStat) GetLastFlush() types.TS { 429 s.RLock() 430 defer s.RUnlock() 431 return s.LastFlush 432 } 433 434 func (s *TableCompactStat) GetLastMerge() *MergeHistory { 435 s.RLock() 436 defer s.RUnlock() 437 return &s.MergeHist 438 } 439 440 func (s *TableCompactStat) AddRowStat(rows, dels int) { 441 s.Lock() 442 defer s.Unlock() 443 s.RowCnt.Append(rows) 444 s.RowDel.Append(dels) 445 446 rs, rm, rl := s.RowCnt.QueryTrend() 447 ds, dm, dl := s.RowDel.QueryTrend() 448 449 guess := WorkUnknown 450 if IsApLikeDel(ds, dm, dl, dels) && s.RowCnt.V() > 1000*1000 { 451 if IsLongStable(rs, rm, rl) { 452 guess = WorkApQuiet 453 } else { 454 guess = WorkApInsert 455 } 456 } 457 458 // force a bigger merge 459 if s.WorkloadGuess == WorkApInsert && s.WorkloadStreak > 20 { 460 guess = WorkApQuiet 461 } 462 463 if s.WorkloadGuess == guess { 464 s.WorkloadStreak++ 465 } else { 466 s.WorkloadGuess = guess 467 s.WorkloadStreak = 0 468 } 469 } 470 471 func (s *TableCompactStat) GetWorkloadGuess() WorkloadKind { 472 s.RLock() 473 defer s.RUnlock() 474 return s.WorkloadGuess 475 } 476 477 func IsApLikeDel(s, m, l TrendKind, val int) bool { 478 return s == TrendStable && m == TrendStable && l == TrendStable && val == 0 479 } 480 481 func IsLongStable(s, m, l TrendKind) bool { 482 return s == TrendStable && m == TrendStable && l <= TrendIncI && l >= TrendDecI 483 } 484 485 //// 486 // Other utils 487 //// 488 489 func HumanReadableBytes(bytes int) string { 490 if bytes < 1024 { 491 return fmt.Sprintf("%dB", bytes) 492 } 493 if bytes < Const1MBytes { 494 return fmt.Sprintf("%.2fKB", float64(bytes)/1024) 495 } 496 if bytes < Const1GBytes { 497 return fmt.Sprintf("%.2fMB", float64(bytes)/1024/1024) 498 } 499 return fmt.Sprintf("%.2fGB", float64(bytes)/1024/1024/1024) 500 } 501 502 func ShortObjId(x types.Objectid) string { 503 var shortuuid [12]byte 504 hex.Encode(shortuuid[:], x[10:16]) 505 return string(shortuuid[:]) 506 } 507 508 func moveAvg[T Number](prev, now T, f float64) T { 509 return T((1-f)*float64(prev) + f*float64(now)) 510 } 511 512 func roundZero[T Number](v T) bool { 513 return math.Abs(float64(v)) < Epsilon 514 }