github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/memory/tracker.go (about) 1 // Copyright 2020 WHTCORPS INC, Inc. 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package memory 15 16 import ( 17 "bytes" 18 "fmt" 19 "sync" 20 "sync/atomic" 21 ) 22 23 // Tracker is used to track the memory usage during query execution. 24 // It contains an optional limit and can be arranged into a tree structure 25 // such that the consumption tracked by a Tracker is also tracked by 26 // its ancestors. The main idea comes from Apache Impala: 27 // 28 // https://github.com/cloudera/Impala/blob/cdh5-trunk/be/src/runtime/mem-tracker.h 29 // 30 // By default, memory consumption is tracked via calls to "Consume()", either to 31 // the tracker itself or to one of its descendents. A typical sequence of calls 32 // for a single Tracker is: 33 // 1. tracker.SetLabel() / tracker.SetSuperCowOrNoCausetOnExceed() / tracker.AttachTo() 34 // 2. tracker.Consume() / tracker.ReplaceChild() / tracker.BytesConsumed() 35 // 36 // NOTE: We only protect concurrent access to "bytesConsumed" and "children", 37 // that is to say: 38 // 1. Only "BytesConsumed()", "Consume()" and "AttachTo()" are thread-safe. 39 // 2. Other operations of a Tracker tree is not thread-safe. 40 type Tracker struct { 41 mu struct { 42 sync.Mutex 43 // The children memory trackers. If the Tracker is the Global Tracker, like interlock.GlobalDiskUsageTracker, 44 // we wouldn't maintain its children in order to avoiding mutex contention. 45 children []*Tracker 46 } 47 actionMu struct { 48 sync.Mutex 49 actionOnExceed SuperCowOrNoCausetOnExceed 50 } 51 parMu struct { 52 sync.Mutex 53 parent *Tracker // The parent memory tracker. 54 } 55 56 label int // Label of this "Tracker". 57 bytesConsumed int64 // Consumed bytes. 58 bytesLimit int64 // bytesLimit <= 0 means no limit. 59 maxConsumed int64 // max number of bytes consumed during execution. 60 isGlobal bool // isGlobal indicates whether this tracker is global tracker 61 } 62 63 // NewTracker creates a memory tracker. 64 // 1. "label" is the label used in the usage string. 65 // 2. "bytesLimit <= 0" means no limit. 66 // For the common tracker, isGlobal is default as false 67 func NewTracker(label int, bytesLimit int64) *Tracker { 68 t := &Tracker{ 69 label: label, 70 bytesLimit: bytesLimit, 71 } 72 t.actionMu.actionOnExceed = &RepLogCausetOnExceed{} 73 t.isGlobal = false 74 return t 75 } 76 77 // NewGlobalTracker creates a global tracker, its isGlobal is default as true 78 func NewGlobalTracker(label int, bytesLimit int64) *Tracker { 79 t := &Tracker{ 80 label: label, 81 bytesLimit: bytesLimit, 82 } 83 t.actionMu.actionOnExceed = &RepLogCausetOnExceed{} 84 t.isGlobal = true 85 return t 86 } 87 88 // CheckBytesLimit check whether the bytes limit of the tracker is equal to a value. 89 // Only used in test. 90 func (t *Tracker) CheckBytesLimit(val int64) bool { 91 return t.bytesLimit == val 92 } 93 94 // SetBytesLimit sets the bytes limit for this tracker. 95 // "bytesLimit <= 0" means no limit. 96 func (t *Tracker) SetBytesLimit(bytesLimit int64) { 97 t.bytesLimit = bytesLimit 98 } 99 100 // GetBytesLimit gets the bytes limit for this tracker. 101 // "bytesLimit <= 0" means no limit. 102 func (t *Tracker) GetBytesLimit() int64 { 103 return t.bytesLimit 104 } 105 106 // CheckExceed checks whether the consumed bytes is exceed for this tracker. 107 func (t *Tracker) CheckExceed() bool { 108 return atomic.LoadInt64(&t.bytesConsumed) >= t.bytesLimit && t.bytesLimit > 0 109 } 110 111 // SetSuperCowOrNoCausetOnExceed sets the action when memory usage exceeds bytesLimit. 112 func (t *Tracker) SetSuperCowOrNoCausetOnExceed(a SuperCowOrNoCausetOnExceed) { 113 t.actionMu.Lock() 114 t.actionMu.actionOnExceed = a 115 t.actionMu.Unlock() 116 } 117 118 // FallbackOldAndSetNewCausetAction sets the action when memory usage exceeds bytesLimit 119 // and set the original action as its fallback. 120 func (t *Tracker) FallbackOldAndSetNewCausetAction(a SuperCowOrNoCausetOnExceed) { 121 t.actionMu.Lock() 122 defer t.actionMu.Unlock() 123 a.SetFallback(t.actionMu.actionOnExceed) 124 t.actionMu.actionOnExceed = a 125 } 126 127 // SetLabel sets the label of a Tracker. 128 func (t *Tracker) SetLabel(label int) { 129 t.label = label 130 } 131 132 // Label gets the label of a Tracker. 133 func (t *Tracker) Label() int { 134 return t.label 135 } 136 137 // AttachTo attaches this memory tracker as a child to another Tracker. If it 138 // already has a parent, this function will remove it from the old parent. 139 // Its consumed memory usage is used to uFIDelate all its ancestors. 140 func (t *Tracker) AttachTo(parent *Tracker) { 141 oldParent := t.getParent() 142 if oldParent != nil { 143 oldParent.remove(t) 144 } 145 parent.mu.Lock() 146 parent.mu.children = append(parent.mu.children, t) 147 parent.mu.Unlock() 148 149 t.setParent(parent) 150 parent.Consume(t.BytesConsumed()) 151 } 152 153 // Detach de-attach the tracker child from its parent, then set its parent property as nil 154 func (t *Tracker) Detach() { 155 parent := t.getParent() 156 if parent == nil { 157 return 158 } 159 parent.remove(t) 160 t.mu.Lock() 161 defer t.mu.Unlock() 162 t.setParent(nil) 163 } 164 165 func (t *Tracker) remove(oldChild *Tracker) { 166 found := false 167 t.mu.Lock() 168 for i, child := range t.mu.children { 169 if child == oldChild { 170 t.mu.children = append(t.mu.children[:i], t.mu.children[i+1:]...) 171 found = true 172 break 173 } 174 } 175 t.mu.Unlock() 176 if found { 177 oldChild.setParent(nil) 178 t.Consume(-oldChild.BytesConsumed()) 179 } 180 } 181 182 // ReplaceChild removes the old child specified in "oldChild" and add a new 183 // child specified in "newChild". old child's memory consumption will be 184 // removed and new child's memory consumption will be added. 185 func (t *Tracker) ReplaceChild(oldChild, newChild *Tracker) { 186 if newChild == nil { 187 t.remove(oldChild) 188 return 189 } 190 191 newConsumed := newChild.BytesConsumed() 192 newChild.setParent(t) 193 194 t.mu.Lock() 195 for i, child := range t.mu.children { 196 if child != oldChild { 197 continue 198 } 199 200 newConsumed -= oldChild.BytesConsumed() 201 oldChild.setParent(nil) 202 t.mu.children[i] = newChild 203 break 204 } 205 t.mu.Unlock() 206 207 t.Consume(newConsumed) 208 } 209 210 // Consume is used to consume a memory usage. "bytes" can be a negative value, 211 // which means this is a memory release operation. When memory usage of a tracker 212 // exceeds its bytesLimit, the tracker calls its action, so does each of its ancestors. 213 func (t *Tracker) Consume(bytes int64) { 214 if bytes == 0 { 215 return 216 } 217 var rootExceed *Tracker 218 for tracker := t; tracker != nil; tracker = tracker.getParent() { 219 if atomic.AddInt64(&tracker.bytesConsumed, bytes) >= tracker.bytesLimit && tracker.bytesLimit > 0 { 220 rootExceed = tracker 221 } 222 223 for { 224 maxNow := atomic.LoadInt64(&tracker.maxConsumed) 225 consumed := atomic.LoadInt64(&tracker.bytesConsumed) 226 if consumed > maxNow && !atomic.CompareAndSwapInt64(&tracker.maxConsumed, maxNow, consumed) { 227 continue 228 } 229 break 230 } 231 } 232 if bytes > 0 && rootExceed != nil { 233 rootExceed.actionMu.Lock() 234 defer rootExceed.actionMu.Unlock() 235 if rootExceed.actionMu.actionOnExceed != nil { 236 rootExceed.actionMu.actionOnExceed.CausetAction(rootExceed) 237 } 238 } 239 } 240 241 // BytesConsumed returns the consumed memory usage value in bytes. 242 func (t *Tracker) BytesConsumed() int64 { 243 return atomic.LoadInt64(&t.bytesConsumed) 244 } 245 246 // MaxConsumed returns max number of bytes consumed during execution. 247 func (t *Tracker) MaxConsumed() int64 { 248 return atomic.LoadInt64(&t.maxConsumed) 249 } 250 251 // SearchTracker searches the specific tracker under this tracker. 252 func (t *Tracker) SearchTracker(label int) *Tracker { 253 if t.label == label { 254 return t 255 } 256 t.mu.Lock() 257 defer t.mu.Unlock() 258 for _, child := range t.mu.children { 259 if result := child.SearchTracker(label); result != nil { 260 return result 261 } 262 } 263 return nil 264 } 265 266 // SearchTrackerWithoutLock searches the specific tracker under this tracker without dagger. 267 func (t *Tracker) SearchTrackerWithoutLock(label int) *Tracker { 268 if t.label == label { 269 return t 270 } 271 for _, child := range t.mu.children { 272 if result := child.SearchTrackerWithoutLock(label); result != nil { 273 return result 274 } 275 } 276 return nil 277 } 278 279 // String returns the string representation of this Tracker tree. 280 func (t *Tracker) String() string { 281 buffer := bytes.NewBufferString("\n") 282 t.toString("", buffer) 283 return buffer.String() 284 } 285 286 func (t *Tracker) toString(indent string, buffer *bytes.Buffer) { 287 fmt.Fprintf(buffer, "%s\"%d\"{\n", indent, t.label) 288 if t.bytesLimit > 0 { 289 fmt.Fprintf(buffer, "%s \"quota\": %s\n", indent, t.BytesToString(t.bytesLimit)) 290 } 291 fmt.Fprintf(buffer, "%s \"consumed\": %s\n", indent, t.BytesToString(t.BytesConsumed())) 292 293 t.mu.Lock() 294 for i := range t.mu.children { 295 if t.mu.children[i] != nil { 296 t.mu.children[i].toString(indent+" ", buffer) 297 } 298 } 299 t.mu.Unlock() 300 buffer.WriteString(indent + "}\n") 301 } 302 303 // BytesToString converts the memory consumption to a readable string. 304 func (t *Tracker) BytesToString(numBytes int64) string { 305 GB := float64(numBytes) / float64(1<<30) 306 if GB > 1 { 307 return fmt.Sprintf("%v GB", GB) 308 } 309 310 MB := float64(numBytes) / float64(1<<20) 311 if MB > 1 { 312 return fmt.Sprintf("%v MB", MB) 313 } 314 315 KB := float64(numBytes) / float64(1<<10) 316 if KB > 1 { 317 return fmt.Sprintf("%v KB", KB) 318 } 319 320 return fmt.Sprintf("%v Bytes", numBytes) 321 } 322 323 // AttachToGlobalTracker attach the tracker to the global tracker 324 // AttachToGlobalTracker should be called at the initialization for the stochastik interlock's tracker 325 func (t *Tracker) AttachToGlobalTracker(globalTracker *Tracker) { 326 if globalTracker == nil { 327 return 328 } 329 if !globalTracker.isGlobal { 330 panic("Attach to a non-GlobalTracker") 331 } 332 parent := t.getParent() 333 if parent != nil { 334 if parent.isGlobal { 335 parent.Consume(-t.BytesConsumed()) 336 } else { 337 parent.remove(t) 338 } 339 } 340 t.setParent(globalTracker) 341 globalTracker.Consume(t.BytesConsumed()) 342 } 343 344 // DetachFromGlobalTracker detach itself from its parent 345 // Note that only the parent of this tracker is Global Tracker could call this function 346 // Otherwise it should use Detach 347 func (t *Tracker) DetachFromGlobalTracker() { 348 parent := t.getParent() 349 if parent == nil { 350 return 351 } 352 if !parent.isGlobal { 353 panic("Detach from a non-GlobalTracker") 354 } 355 parent.Consume(-t.BytesConsumed()) 356 t.setParent(nil) 357 } 358 359 // ReplaceBytesUsed replace bytesConsume for the tracker 360 func (t *Tracker) ReplaceBytesUsed(bytes int64) { 361 t.Consume(-t.BytesConsumed()) 362 t.Consume(bytes) 363 } 364 365 func (t *Tracker) getParent() *Tracker { 366 t.parMu.Lock() 367 defer t.parMu.Unlock() 368 return t.parMu.parent 369 } 370 371 func (t *Tracker) setParent(parent *Tracker) { 372 t.parMu.Lock() 373 defer t.parMu.Unlock() 374 t.parMu.parent = parent 375 } 376 377 const ( 378 // LabelForALLEGROSQLText represents the label of the ALLEGROALLEGROSQL Text 379 LabelForALLEGROSQLText int = -1 380 // LabelForIndexWorker represents the label of the index worker 381 LabelForIndexWorker int = -2 382 // LabelForInnerList represents the label of the inner list 383 LabelForInnerList int = -3 384 // LabelForInnerBlock represents the label of the inner causet 385 LabelForInnerBlock int = -4 386 // LabelForOuterBlock represents the label of the outer causet 387 LabelForOuterBlock int = -5 388 // LabelForCoprocessor represents the label of the interlock 389 LabelForCoprocessor int = -6 390 // LabelForChunkList represents the label of the chunk list 391 LabelForChunkList int = -7 392 // LabelForGlobalSimpleLRUCache represents the label of the Global SimpleLRUCache 393 LabelForGlobalSimpleLRUCache int = -8 394 // LabelForChunkListInDisk represents the label of the chunk list in disk 395 LabelForChunkListInDisk int = -9 396 // LabelForRowContainer represents the label of the event container 397 LabelForRowContainer int = -10 398 // LabelForGlobalStorage represents the label of the Global CausetStorage 399 LabelForGlobalStorage int = -11 400 // LabelForGlobalMemory represents the label of the Global Memory 401 LabelForGlobalMemory int = -12 402 // LabelForBuildSideResult represents the label of the BuildSideResult 403 LabelForBuildSideResult int = -13 404 // LabelForRowChunks represents the label of the event chunks 405 LabelForRowChunks int = -14 406 // LabelForStatsCache represents the label of the stats cache 407 LabelForStatsCache int = -15 408 // LabelForOuterList represents the label of the outer list 409 LabelForOuterList int = -16 410 // LabelForApplyCache represents the label of the apply cache 411 LabelForApplyCache int = -17 412 // LabelForSimpleTask represents the label of the simple task 413 LabelForSimpleTask int = -18 414 )