github.com/etecs-ru/ristretto@v0.9.1/metrics.go (about) 1 /* 2 * Copyright 2021 Dgraph Labs, Inc. and Contributors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package ristretto 18 19 import ( 20 "bytes" 21 "fmt" 22 "sync" 23 "sync/atomic" 24 25 "github.com/etecs-ru/ristretto/z" 26 ) 27 28 type metricType int 29 30 const ( 31 // The following 2 keep track of hits and misses. 32 hit = iota 33 miss 34 // The following 3 keep track of number of keys added, updated and evicted. 35 keyAdd 36 keyUpdate 37 keyEvict 38 // The following 2 keep track of cost of keys added and evicted. 39 costAdd 40 costEvict 41 // The following keep track of how many sets were dropped or rejected later. 42 dropSets 43 rejectSets 44 // The following 2 keep track of how many gets were kept and dropped on the 45 // floor. 46 dropGets 47 keepGets 48 // This should be the final enum. Other enums should be set before this. 49 doNotUse 50 ) 51 52 func stringFor(t metricType) string { 53 switch t { 54 case hit: 55 return "hit" 56 case miss: 57 return "miss" 58 case keyAdd: 59 return "keys-added" 60 case keyUpdate: 61 return "keys-updated" 62 case keyEvict: 63 return "keys-evicted" 64 case costAdd: 65 return "cost-added" 66 case costEvict: 67 return "cost-evicted" 68 case dropSets: 69 return "sets-dropped" 70 case rejectSets: 71 return "sets-rejected" // by policy. 72 case dropGets: 73 return "gets-dropped" 74 case keepGets: 75 return "gets-kept" 76 default: 77 return "unidentified" 78 } 79 } 80 81 // Metrics is a snapshot of performance statistics for the lifetime of a cache instance. 82 type Metrics struct { 83 life *z.HistogramData 84 all [doNotUse][]*uint64 85 mu sync.RWMutex 86 } 87 88 // collectMetrics just creates a new *Metrics instance and adds the pointers 89 // to the cache and policy instances. 90 func (c *Cache) collectMetrics() { 91 c.Metrics = newMetrics() 92 c.policy.CollectMetrics(c.Metrics) 93 } 94 95 func newMetrics() *Metrics { 96 s := &Metrics{ 97 life: z.NewHistogramData(z.HistogramBounds(1, 16)), 98 } 99 for i := 0; i < doNotUse; i++ { 100 s.all[i] = make([]*uint64, 256) 101 slice := s.all[i] 102 for j := range slice { 103 slice[j] = new(uint64) 104 } 105 } 106 return s 107 } 108 109 func (p *Metrics) add(t metricType, hash, delta uint64) { 110 if p == nil { 111 return 112 } 113 valp := p.all[t] 114 // Avoid false sharing by padding at least 64 bytes of space between two 115 // atomic counters which would be incremented. 116 idx := (hash % 25) * 10 117 atomic.AddUint64(valp[idx], delta) 118 } 119 120 func (p *Metrics) get(t metricType) uint64 { 121 if p == nil { 122 return 0 123 } 124 valp := p.all[t] 125 var total uint64 126 for i := range valp { 127 total += atomic.LoadUint64(valp[i]) 128 } 129 return total 130 } 131 132 // Hits is the number of Get calls where a value was found for the corresponding key. 133 func (p *Metrics) Hits() uint64 { 134 return p.get(hit) 135 } 136 137 // Misses is the number of Get calls where a value was not found for the corresponding key. 138 func (p *Metrics) Misses() uint64 { 139 return p.get(miss) 140 } 141 142 // KeysAdded is the total number of Set calls where a new key-value item was added. 143 func (p *Metrics) KeysAdded() uint64 { 144 return p.get(keyAdd) 145 } 146 147 // KeysUpdated is the total number of Set calls where the value was updated. 148 func (p *Metrics) KeysUpdated() uint64 { 149 return p.get(keyUpdate) 150 } 151 152 // KeysEvicted is the total number of keys evicted. 153 func (p *Metrics) KeysEvicted() uint64 { 154 return p.get(keyEvict) 155 } 156 157 // CostAdded is the sum of costs that have been added (successful Set calls). 158 func (p *Metrics) CostAdded() uint64 { 159 return p.get(costAdd) 160 } 161 162 // CostEvicted is the sum of all costs that have been evicted. 163 func (p *Metrics) CostEvicted() uint64 { 164 return p.get(costEvict) 165 } 166 167 // SetsDropped is the number of Set calls that don't make it into internal 168 // buffers (due to contention or some other reason). 169 func (p *Metrics) SetsDropped() uint64 { 170 return p.get(dropSets) 171 } 172 173 // SetsRejected is the number of Set calls rejected by the policy (TinyLFU). 174 func (p *Metrics) SetsRejected() uint64 { 175 return p.get(rejectSets) 176 } 177 178 // GetsDropped is the number of Get counter increments that are dropped 179 // internally. 180 func (p *Metrics) GetsDropped() uint64 { 181 return p.get(dropGets) 182 } 183 184 // GetsKept is the number of Get counter increments that are kept. 185 func (p *Metrics) GetsKept() uint64 { 186 return p.get(keepGets) 187 } 188 189 // Ratio is the number of Hits over all accesses (Hits + Misses). This is the 190 // percentage of successful Get calls. 191 func (p *Metrics) Ratio() float64 { 192 if p == nil { 193 return 0.0 194 } 195 hits, misses := p.get(hit), p.get(miss) 196 if hits == 0 && misses == 0 { 197 return 0.0 198 } 199 return float64(hits) / float64(hits+misses) 200 } 201 202 func (p *Metrics) trackEviction(numSeconds int64) { 203 if p == nil { 204 return 205 } 206 p.mu.Lock() 207 defer p.mu.Unlock() 208 p.life.Update(numSeconds) 209 } 210 211 func (p *Metrics) LifeExpectancySeconds() *z.HistogramData { 212 if p == nil { 213 return nil 214 } 215 p.mu.RLock() 216 defer p.mu.RUnlock() 217 return p.life.Copy() 218 } 219 220 // Clear resets all the metrics. 221 func (p *Metrics) Clear() { 222 if p == nil { 223 return 224 } 225 for i := 0; i < doNotUse; i++ { 226 for j := range p.all[i] { 227 atomic.StoreUint64(p.all[i][j], 0) 228 } 229 } 230 p.mu.Lock() 231 p.life = z.NewHistogramData(z.HistogramBounds(1, 16)) 232 p.mu.Unlock() 233 } 234 235 // String returns a string representation of the metrics. 236 func (p *Metrics) String() string { 237 if p == nil { 238 return "" 239 } 240 var buf bytes.Buffer 241 for i := 0; i < doNotUse; i++ { 242 t := metricType(i) 243 fmt.Fprintf(&buf, "%s: %d ", stringFor(t), p.get(t)) 244 } 245 fmt.Fprintf(&buf, "gets-total: %d ", p.get(hit)+p.get(miss)) 246 fmt.Fprintf(&buf, "hit-ratio: %.2f", p.Ratio()) 247 return buf.String() 248 }