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  }