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  }