github.com/MikyChow/arbitrum-go-ethereum@v0.0.0-20230306102812-078da49636de/metrics/chunked_associative_array.go (about)

     1  package metrics
     2  
     3  // Ported from
     4  // https://github.com/dropwizard/metrics/blob/release/4.2.x/metrics-core/src/main/java/com/codahale/metrics/ChunkedAssociativeLongArray.java
     5  
     6  import (
     7  	"github.com/gammazero/deque"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  )
    12  
    13  const (
    14  	ChunkedAssociativeArrayDefaultChunkSize = 512
    15  	ChunkedAssociativeArrayMaxCacheSize     = 128
    16  )
    17  
    18  type ChunkedAssociativeArray struct {
    19  	defaultChunkSize int
    20  
    21  	/*
    22  	 * We use this ArrayDeque as cache to store chunks that are expired and removed from main data structure.
    23  	 * Then instead of allocating new AssociativeArrayChunk immediately we are trying to poll one from this deque.
    24  	 * So if you have constant or slowly changing load ChunkedAssociativeLongArray will never
    25  	 * throw away old chunks or allocate new ones which makes this data structure almost garbage free.
    26  	 */
    27  	chunksCache *deque.Deque[*AssociativeArrayChunk]
    28  	chunks      *deque.Deque[*AssociativeArrayChunk]
    29  }
    30  
    31  func NewChunkedAssociativeArray(chunkSize int) *ChunkedAssociativeArray {
    32  	return &ChunkedAssociativeArray{
    33  		defaultChunkSize: chunkSize,
    34  		chunksCache:      deque.New[*AssociativeArrayChunk](ChunkedAssociativeArrayMaxCacheSize, ChunkedAssociativeArrayMaxCacheSize),
    35  		chunks:           deque.New[*AssociativeArrayChunk](),
    36  	}
    37  }
    38  
    39  func (caa *ChunkedAssociativeArray) Clear() {
    40  	for i := 0; i < caa.chunks.Len(); i++ {
    41  		chunk := caa.chunks.PopBack()
    42  		caa.freeChunk(chunk)
    43  	}
    44  }
    45  
    46  func (caa *ChunkedAssociativeArray) AllocateChunk() *AssociativeArrayChunk {
    47  	if caa.chunksCache.Len() == 0 {
    48  		return NewAssociativeArrayChunk(caa.defaultChunkSize)
    49  	}
    50  
    51  	chunk := caa.chunksCache.PopBack()
    52  	chunk.cursor = 0
    53  	chunk.startIndex = 0
    54  	chunk.chunkSize = len(chunk.keys)
    55  
    56  	return chunk
    57  }
    58  
    59  func (caa *ChunkedAssociativeArray) freeChunk(chunk *AssociativeArrayChunk) {
    60  	if caa.chunksCache.Len() < ChunkedAssociativeArrayMaxCacheSize {
    61  		caa.chunksCache.PushBack(chunk)
    62  	}
    63  }
    64  
    65  func (caa *ChunkedAssociativeArray) Put(key int64, value int64) {
    66  	var activeChunk *AssociativeArrayChunk
    67  	if caa.chunks.Len() > 0 {
    68  		activeChunk = caa.chunks.Back()
    69  	}
    70  
    71  	if activeChunk != nil && activeChunk.cursor != 0 && activeChunk.keys[activeChunk.cursor-1] > key {
    72  		// Key must be the same as last inserted or bigger
    73  		key = activeChunk.keys[activeChunk.cursor-1] + 1
    74  	}
    75  	if activeChunk == nil || activeChunk.cursor-activeChunk.startIndex == activeChunk.chunkSize {
    76  		// The last chunk doesn't exist or full
    77  		activeChunk = caa.AllocateChunk()
    78  		caa.chunks.PushBack(activeChunk)
    79  	}
    80  	activeChunk.Append(key, value)
    81  }
    82  
    83  func (caa *ChunkedAssociativeArray) Values() []int64 {
    84  	valuesSize := caa.Size()
    85  	if valuesSize == 0 {
    86  		// Empty
    87  		return []int64{0}
    88  	}
    89  
    90  	values := make([]int64, 0, valuesSize)
    91  	caa.chunks.Index(func(chunk *AssociativeArrayChunk) bool {
    92  		values = append(values, chunk.values[chunk.startIndex:chunk.cursor]...)
    93  		return false
    94  	})
    95  
    96  	return values
    97  }
    98  
    99  func (caa *ChunkedAssociativeArray) Size() int {
   100  	var result int
   101  	caa.chunks.Index(func(chunk *AssociativeArrayChunk) bool {
   102  		result += chunk.cursor - chunk.startIndex
   103  		return false
   104  	})
   105  	return result
   106  }
   107  
   108  func (caa *ChunkedAssociativeArray) String() string {
   109  	var builder strings.Builder
   110  	first := true
   111  	caa.chunks.Index(func(chunk *AssociativeArrayChunk) bool {
   112  		if first {
   113  			first = false
   114  		} else {
   115  			builder.WriteString("->")
   116  		}
   117  		builder.WriteString("[")
   118  		for i := chunk.startIndex; i < chunk.cursor; i++ {
   119  			builder.WriteString("(")
   120  			builder.WriteString(strconv.FormatInt(chunk.keys[i], 10))
   121  			builder.WriteString(": ")
   122  			builder.WriteString(strconv.FormatInt(chunk.values[i], 10))
   123  			builder.WriteString(") ")
   124  		}
   125  		builder.WriteString("]")
   126  		return false
   127  	})
   128  
   129  	return builder.String()
   130  }
   131  
   132  // Trim tries to trim all beyond specified boundaries
   133  // startKey: the start value for which all elements less than it should be removed.
   134  // endKey:   the end value for which all elements greater/equals than it should be removed
   135  func (caa *ChunkedAssociativeArray) Trim(startKey int64, endKey int64) {
   136  	/*
   137  	 * [3, 4, 5, 9] -> [10, 13, 14, 15] -> [21, 24, 29, 30] -> [31] :: start layout
   138  	 *       |5______________________________23|                    :: trim(5, 23)
   139  	 *       [5, 9] -> [10, 13, 14, 15] -> [21]                     :: result layout
   140  	 */
   141  	// Remove elements that are too large
   142  	indexBeforeEndKey := caa.chunks.RIndex(func(chunk *AssociativeArrayChunk) bool {
   143  		if chunk.IsFirstElementEmptyOrGreaterEqualThanKey(endKey) {
   144  			return false
   145  		}
   146  
   147  		chunk.cursor = chunk.FindFirstIndexOfGreaterEqualElements(endKey)
   148  		return true
   149  	})
   150  
   151  	// Remove chunks that only contain elements that are too large
   152  	if indexBeforeEndKey >= 0 {
   153  		for i := caa.chunks.Len() - 1; i > indexBeforeEndKey; i-- {
   154  			chunk := caa.chunks.PopBack()
   155  			caa.freeChunk(chunk)
   156  		}
   157  	}
   158  
   159  	// Remove elements that are too small
   160  	indexAfterStartKey := caa.chunks.Index(func(chunk *AssociativeArrayChunk) bool {
   161  		if chunk.IsLastElementEmptyOrLessThanKey(startKey) {
   162  			return false
   163  		}
   164  
   165  		newStartIndex := chunk.FindFirstIndexOfGreaterEqualElements(startKey)
   166  		if chunk.startIndex != newStartIndex {
   167  			chunk.startIndex = newStartIndex
   168  			chunk.chunkSize = chunk.cursor - chunk.startIndex
   169  		}
   170  		return true
   171  	})
   172  
   173  	// Remove chunks that only contain elements that are too small
   174  	for i := 0; i < indexAfterStartKey; i++ {
   175  		chunk := caa.chunks.PopFront()
   176  		caa.freeChunk(chunk)
   177  	}
   178  }
   179  
   180  type AssociativeArrayChunk struct {
   181  	keys   []int64
   182  	values []int64
   183  
   184  	chunkSize  int
   185  	startIndex int
   186  	cursor     int
   187  }
   188  
   189  func NewAssociativeArrayChunk(chunkSize int) *AssociativeArrayChunk {
   190  	return &AssociativeArrayChunk{
   191  		keys:       make([]int64, chunkSize),
   192  		values:     make([]int64, chunkSize),
   193  		chunkSize:  chunkSize,
   194  		startIndex: 0,
   195  		cursor:     0,
   196  	}
   197  }
   198  
   199  func (c *AssociativeArrayChunk) Append(key int64, value int64) {
   200  	c.keys[c.cursor] = key
   201  	c.values[c.cursor] = value
   202  	c.cursor++
   203  }
   204  
   205  func (c *AssociativeArrayChunk) IsFirstElementEmptyOrGreaterEqualThanKey(key int64) bool {
   206  	return c.cursor == c.startIndex || c.keys[c.startIndex] >= key
   207  }
   208  
   209  func (c *AssociativeArrayChunk) IsLastElementEmptyOrLessThanKey(key int64) bool {
   210  	return c.cursor == c.startIndex || c.keys[c.cursor-1] < key
   211  }
   212  
   213  func (c *AssociativeArrayChunk) FindFirstIndexOfGreaterEqualElements(minKey int64) int {
   214  	if c.cursor == c.startIndex || c.keys[c.startIndex] >= minKey {
   215  		return c.startIndex
   216  	}
   217  	elements := c.keys[c.startIndex:c.cursor]
   218  	keyIndex := sort.Search(len(elements), func(i int) bool { return elements[i] >= minKey })
   219  
   220  	return c.startIndex + keyIndex
   221  }