github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/sqlparse/tidbparser/dependency/util/memory/tracker.go (about) 1 // Copyright 2018 PingCAP, 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 ) 21 22 // Tracker is used to track the memory usage during query execution. 23 // It contains an optional limit and can be arranged into a tree structure 24 // such that the consumption tracked by a Tracker is also tracked by 25 // its ancestors. The main idea comes from Apache Impala: 26 // 27 // https://github.com/cloudera/Impala/blob/cdh5-trunk/be/src/runtime/mem-tracker.h 28 // 29 // By default, memory consumption is tracked via calls to "Consume()", either to 30 // the tracker itself or to one of its descendents. A typical sequence of calls 31 // for a single Tracker is: 32 // 1. tracker.SetLabel() / tracker.SetActionOnExceed() / tracker.AttachTo() 33 // 2. tracker.Consume() / tracker.ReplaceChild() / tracker.BytesConsumed() 34 // 35 // NOTE: 36 // 1. Only "BytesConsumed()" and "Consume()" are thread-safe. 37 // 2. Adjustment of Tracker tree is not thread-safe. 38 type Tracker struct { 39 label string // Label of this "Tracker". 40 mutex *sync.Mutex // For synchronization. 41 bytesConsumed int64 // Consumed bytes. 42 bytesLimit int64 // Negative value means no limit. 43 actionOnExceed ActionOnExceed 44 45 parent *Tracker // The parent memory tracker. 46 children []*Tracker // The children memory trackers. 47 } 48 49 // NewTracker creates a memory tracker. 50 // 1. "label" is the label used in the usage string. 51 // 2. "bytesLimit < 0" means no limit. 52 func NewTracker(label string, bytesLimit int64) *Tracker { 53 return &Tracker{ 54 label: label, 55 mutex: &sync.Mutex{}, 56 bytesConsumed: 0, 57 bytesLimit: bytesLimit, 58 actionOnExceed: &LogOnExceed{}, 59 parent: nil, 60 } 61 } 62 63 // SetActionOnExceed sets the action when memory usage is out of memory quota. 64 func (t *Tracker) SetActionOnExceed(a ActionOnExceed) { 65 t.actionOnExceed = a 66 } 67 68 // SetLabel sets the label of a Tracker. 69 func (t *Tracker) SetLabel(label string) { 70 t.label = label 71 } 72 73 // AttachTo attaches this memory tracker as a child to another Tracker. If it 74 // already has a parent, this function will remove it from the old parent. 75 // Its consumed memory usage is used to update all its ancestors. 76 func (t *Tracker) AttachTo(parent *Tracker) { 77 if t.parent != nil { 78 t.parent.ReplaceChild(t, nil) 79 } 80 parent.children = append(parent.children, t) 81 t.parent = parent 82 t.parent.Consume(t.BytesConsumed()) 83 } 84 85 // ReplaceChild removes the old child specified in "oldChild" and add a new 86 // child specified in "newChild". old child's memory consumption will be 87 // removed and new child's memory consumption will be added. 88 func (t *Tracker) ReplaceChild(oldChild, newChild *Tracker) { 89 for i, child := range t.children { 90 if child != oldChild { 91 continue 92 } 93 94 newConsumed := int64(0) 95 if newChild != nil { 96 newConsumed = newChild.BytesConsumed() 97 newChild.parent = t 98 } 99 newConsumed -= oldChild.BytesConsumed() 100 t.Consume(newConsumed) 101 102 oldChild.parent = nil 103 t.children[i] = newChild 104 return 105 } 106 } 107 108 // Consume is used to consume a memory usage. "bytes" can be a negative value, 109 // which means this is a memory release operation. 110 func (t *Tracker) Consume(bytes int64) { 111 var rootExceed *Tracker 112 for tracker := t; tracker != nil; tracker = tracker.parent { 113 tracker.mutex.Lock() 114 tracker.bytesConsumed += bytes 115 if tracker.bytesLimit > 0 && tracker.bytesConsumed >= tracker.bytesLimit { 116 rootExceed = tracker 117 } 118 tracker.mutex.Unlock() 119 } 120 if rootExceed != nil { 121 rootExceed.actionOnExceed.Action(rootExceed) 122 } 123 } 124 125 // BytesConsumed returns the consumed memory usage value in bytes. 126 func (t *Tracker) BytesConsumed() int64 { 127 t.mutex.Lock() 128 defer t.mutex.Unlock() 129 return t.bytesConsumed 130 } 131 132 // String returns the string representation of this Tracker tree. 133 func (t *Tracker) String() string { 134 buffer := bytes.NewBufferString("\n") 135 t.toString("", buffer) 136 return buffer.String() 137 } 138 139 func (t *Tracker) toString(indent string, buffer *bytes.Buffer) { 140 fmt.Fprintf(buffer, "%s\"%s\"{\n", indent, t.label) 141 if t.bytesLimit > 0 { 142 fmt.Fprintf(buffer, "%s \"quota\": %s\n", indent, t.bytesToString(t.bytesLimit)) 143 } 144 fmt.Fprintf(buffer, "%s \"consumed\": %s\n", indent, t.bytesToString(t.BytesConsumed())) 145 for i := range t.children { 146 if t.children[i] != nil { 147 t.children[i].toString(indent+" ", buffer) 148 } 149 } 150 buffer.WriteString(indent + "}\n") 151 } 152 153 func (t *Tracker) bytesToString(numBytes int64) string { 154 GB := float64(numBytes) / float64(1<<30) 155 if GB > 1 { 156 return fmt.Sprintf("%v GB", GB) 157 } 158 159 MB := float64(numBytes) / float64(1<<20) 160 if MB > 1 { 161 return fmt.Sprintf("%v MB", MB) 162 } 163 164 KB := float64(numBytes) / float64(1<<10) 165 if KB > 1 { 166 return fmt.Sprintf("%v KB", KB) 167 } 168 169 return fmt.Sprintf("%v Bytes", numBytes) 170 }