/* * Copyright 2021 Dgraph Labs, Inc. and Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ristretto import ( "bytes" "fmt" "sync" "sync/atomic" "github.com/outcaste-io/ristretto/z" ) type metricType int const ( // The following 2 keep track of hits and misses. hit = iota miss // The following 3 keep track of number of keys added, updated and evicted. keyAdd keyUpdate keyEvict // The following 2 keep track of cost of keys added and evicted. costAdd costEvict // The following keep track of how many sets were dropped or rejected later. dropSets rejectSets // The following 2 keep track of how many gets were kept and dropped on the // floor. dropGets keepGets // This should be the final enum. Other enums should be set before this. doNotUse ) func stringFor(t metricType) string { switch t { case hit: return "hit" case miss: return "miss" case keyAdd: return "keys-added" case keyUpdate: return "keys-updated" case keyEvict: return "keys-evicted" case costAdd: return "cost-added" case costEvict: return "cost-evicted" case dropSets: return "sets-dropped" case rejectSets: return "sets-rejected" // by policy. case dropGets: return "gets-dropped" case keepGets: return "gets-kept" default: return "unidentified" } } // Metrics is a snapshot of performance statistics for the lifetime of a cache instance. type Metrics struct { all [doNotUse][]*uint64 mu sync.RWMutex life *z.HistogramData // Tracks the life expectancy of a key. } // collectMetrics just creates a new *Metrics instance and adds the pointers // to the cache and policy instances. func (c *Cache) collectMetrics() { c.Metrics = newMetrics() c.policy.CollectMetrics(c.Metrics) } func newMetrics() *Metrics { s := &Metrics{ life: z.NewHistogramData(z.HistogramBounds(1, 16)), } for i := 0; i < doNotUse; i++ { s.all[i] = make([]*uint64, 256) slice := s.all[i] for j := range slice { slice[j] = new(uint64) } } return s } func (p *Metrics) add(t metricType, hash, delta uint64) { if p == nil { return } valp := p.all[t] // Avoid false sharing by padding at least 64 bytes of space between two // atomic counters which would be incremented. idx := (hash % 25) * 10 atomic.AddUint64(valp[idx], delta) } func (p *Metrics) get(t metricType) uint64 { if p == nil { return 0 } valp := p.all[t] var total uint64 for i := range valp { total += atomic.LoadUint64(valp[i]) } return total } // Hits is the number of Get calls where a value was found for the corresponding key. func (p *Metrics) Hits() uint64 { return p.get(hit) } // Misses is the number of Get calls where a value was not found for the corresponding key. func (p *Metrics) Misses() uint64 { return p.get(miss) } // KeysAdded is the total number of Set calls where a new key-value item was added. func (p *Metrics) KeysAdded() uint64 { return p.get(keyAdd) } // KeysUpdated is the total number of Set calls where the value was updated. func (p *Metrics) KeysUpdated() uint64 { return p.get(keyUpdate) } // KeysEvicted is the total number of keys evicted. func (p *Metrics) KeysEvicted() uint64 { return p.get(keyEvict) } // CostAdded is the sum of costs that have been added (successful Set calls). func (p *Metrics) CostAdded() uint64 { return p.get(costAdd) } // CostEvicted is the sum of all costs that have been evicted. func (p *Metrics) CostEvicted() uint64 { return p.get(costEvict) } // SetsDropped is the number of Set calls that don't make it into internal // buffers (due to contention or some other reason). func (p *Metrics) SetsDropped() uint64 { return p.get(dropSets) } // SetsRejected is the number of Set calls rejected by the policy (TinyLFU). func (p *Metrics) SetsRejected() uint64 { return p.get(rejectSets) } // GetsDropped is the number of Get counter increments that are dropped // internally. func (p *Metrics) GetsDropped() uint64 { return p.get(dropGets) } // GetsKept is the number of Get counter increments that are kept. func (p *Metrics) GetsKept() uint64 { return p.get(keepGets) } // Ratio is the number of Hits over all accesses (Hits + Misses). This is the // percentage of successful Get calls. func (p *Metrics) Ratio() float64 { if p == nil { return 0.0 } hits, misses := p.get(hit), p.get(miss) if hits == 0 && misses == 0 { return 0.0 } return float64(hits) / float64(hits+misses) } func (p *Metrics) trackEviction(numSeconds int64) { if p == nil { return } p.mu.Lock() defer p.mu.Unlock() p.life.Update(numSeconds) } func (p *Metrics) LifeExpectancySeconds() *z.HistogramData { if p == nil { return nil } p.mu.RLock() defer p.mu.RUnlock() return p.life.Copy() } // Clear resets all the metrics. func (p *Metrics) Clear() { if p == nil { return } for i := 0; i < doNotUse; i++ { for j := range p.all[i] { atomic.StoreUint64(p.all[i][j], 0) } } p.mu.Lock() p.life = z.NewHistogramData(z.HistogramBounds(1, 16)) p.mu.Unlock() } // String returns a string representation of the metrics. func (p *Metrics) String() string { if p == nil { return "" } var buf bytes.Buffer for i := 0; i < doNotUse; i++ { t := metricType(i) fmt.Fprintf(&buf, "%s: %d ", stringFor(t), p.get(t)) } fmt.Fprintf(&buf, "gets-total: %d ", p.get(hit)+p.get(miss)) fmt.Fprintf(&buf, "hit-ratio: %.2f", p.Ratio()) return buf.String() }