github.com/outcaste-io/ristretto@v0.2.3/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/outcaste-io/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 all [doNotUse][]*uint64 84 85 mu sync.RWMutex 86 life *z.HistogramData // Tracks the life expectancy of a key. 87 } 88 89 // collectMetrics just creates a new *Metrics instance and adds the pointers 90 // to the cache and policy instances. 91 func (c *Cache) collectMetrics() { 92 c.Metrics = newMetrics() 93 c.policy.CollectMetrics(c.Metrics) 94 } 95 96 func newMetrics() *Metrics { 97 s := &Metrics{ 98 life: z.NewHistogramData(z.HistogramBounds(1, 16)), 99 } 100 for i := 0; i < doNotUse; i++ { 101 s.all[i] = make([]*uint64, 256) 102 slice := s.all[i] 103 for j := range slice { 104 slice[j] = new(uint64) 105 } 106 } 107 return s 108 } 109 110 func (p *Metrics) add(t metricType, hash, delta uint64) { 111 if p == nil { 112 return 113 } 114 valp := p.all[t] 115 // Avoid false sharing by padding at least 64 bytes of space between two 116 // atomic counters which would be incremented. 117 idx := (hash % 25) * 10 118 atomic.AddUint64(valp[idx], delta) 119 } 120 121 func (p *Metrics) get(t metricType) uint64 { 122 if p == nil { 123 return 0 124 } 125 valp := p.all[t] 126 var total uint64 127 for i := range valp { 128 total += atomic.LoadUint64(valp[i]) 129 } 130 return total 131 } 132 133 // Hits is the number of Get calls where a value was found for the corresponding key. 134 func (p *Metrics) Hits() uint64 { 135 return p.get(hit) 136 } 137 138 // Misses is the number of Get calls where a value was not found for the corresponding key. 139 func (p *Metrics) Misses() uint64 { 140 return p.get(miss) 141 } 142 143 // KeysAdded is the total number of Set calls where a new key-value item was added. 144 func (p *Metrics) KeysAdded() uint64 { 145 return p.get(keyAdd) 146 } 147 148 // KeysUpdated is the total number of Set calls where the value was updated. 149 func (p *Metrics) KeysUpdated() uint64 { 150 return p.get(keyUpdate) 151 } 152 153 // KeysEvicted is the total number of keys evicted. 154 func (p *Metrics) KeysEvicted() uint64 { 155 return p.get(keyEvict) 156 } 157 158 // CostAdded is the sum of costs that have been added (successful Set calls). 159 func (p *Metrics) CostAdded() uint64 { 160 return p.get(costAdd) 161 } 162 163 // CostEvicted is the sum of all costs that have been evicted. 164 func (p *Metrics) CostEvicted() uint64 { 165 return p.get(costEvict) 166 } 167 168 // SetsDropped is the number of Set calls that don't make it into internal 169 // buffers (due to contention or some other reason). 170 func (p *Metrics) SetsDropped() uint64 { 171 return p.get(dropSets) 172 } 173 174 // SetsRejected is the number of Set calls rejected by the policy (TinyLFU). 175 func (p *Metrics) SetsRejected() uint64 { 176 return p.get(rejectSets) 177 } 178 179 // GetsDropped is the number of Get counter increments that are dropped 180 // internally. 181 func (p *Metrics) GetsDropped() uint64 { 182 return p.get(dropGets) 183 } 184 185 // GetsKept is the number of Get counter increments that are kept. 186 func (p *Metrics) GetsKept() uint64 { 187 return p.get(keepGets) 188 } 189 190 // Ratio is the number of Hits over all accesses (Hits + Misses). This is the 191 // percentage of successful Get calls. 192 func (p *Metrics) Ratio() float64 { 193 if p == nil { 194 return 0.0 195 } 196 hits, misses := p.get(hit), p.get(miss) 197 if hits == 0 && misses == 0 { 198 return 0.0 199 } 200 return float64(hits) / float64(hits+misses) 201 } 202 203 func (p *Metrics) trackEviction(numSeconds int64) { 204 if p == nil { 205 return 206 } 207 p.mu.Lock() 208 defer p.mu.Unlock() 209 p.life.Update(numSeconds) 210 } 211 212 func (p *Metrics) LifeExpectancySeconds() *z.HistogramData { 213 if p == nil { 214 return nil 215 } 216 p.mu.RLock() 217 defer p.mu.RUnlock() 218 return p.life.Copy() 219 } 220 221 // Clear resets all the metrics. 222 func (p *Metrics) Clear() { 223 if p == nil { 224 return 225 } 226 for i := 0; i < doNotUse; i++ { 227 for j := range p.all[i] { 228 atomic.StoreUint64(p.all[i][j], 0) 229 } 230 } 231 p.mu.Lock() 232 p.life = z.NewHistogramData(z.HistogramBounds(1, 16)) 233 p.mu.Unlock() 234 } 235 236 // String returns a string representation of the metrics. 237 func (p *Metrics) String() string { 238 if p == nil { 239 return "" 240 } 241 var buf bytes.Buffer 242 for i := 0; i < doNotUse; i++ { 243 t := metricType(i) 244 fmt.Fprintf(&buf, "%s: %d ", stringFor(t), p.get(t)) 245 } 246 fmt.Fprintf(&buf, "gets-total: %d ", p.get(hit)+p.get(miss)) 247 fmt.Fprintf(&buf, "hit-ratio: %.2f", p.Ratio()) 248 return buf.String() 249 }