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  )