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 }