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  }