cache.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. /*
  2. * SPDX-FileCopyrightText: © 2017-2025 Istari Digital, Inc.
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. // Ristretto is a fast, fixed size, in-memory cache with a dual focus on
  6. // throughput and hit ratio performance. You can easily add Ristretto to an
  7. // existing system and keep the most valuable data where you need it.
  8. package ristretto
  9. import (
  10. "bytes"
  11. "errors"
  12. "fmt"
  13. "sync"
  14. "sync/atomic"
  15. "time"
  16. "unsafe"
  17. "github.com/dgraph-io/ristretto/v2/z"
  18. )
  19. var (
  20. // TODO: find the optimal value for this or make it configurable
  21. setBufSize = 32 * 1024
  22. )
  23. const itemSize = int64(unsafe.Sizeof(storeItem[any]{}))
  24. func zeroValue[T any]() T {
  25. var zero T
  26. return zero
  27. }
  28. // Key is the generic type to represent the keys type in key-value pair of the cache.
  29. type Key = z.Key
  30. // Cache is a thread-safe implementation of a hashmap with a TinyLFU admission
  31. // policy and a Sampled LFU eviction policy. You can use the same Cache instance
  32. // from as many goroutines as you want.
  33. type Cache[K Key, V any] struct {
  34. // storedItems is the central concurrent hashmap where key-value items are stored.
  35. storedItems store[V]
  36. // cachePolicy determines what gets let in to the cache and what gets kicked out.
  37. cachePolicy *defaultPolicy[V]
  38. // getBuf is a custom ring buffer implementation that gets pushed to when
  39. // keys are read.
  40. getBuf *ringBuffer
  41. // setBuf is a buffer allowing us to batch/drop Sets during times of high
  42. // contention.
  43. setBuf chan *Item[V]
  44. // onEvict is called for item evictions.
  45. onEvict func(*Item[V])
  46. // onReject is called when an item is rejected via admission policy.
  47. onReject func(*Item[V])
  48. // onExit is called whenever a value goes out of scope from the cache.
  49. onExit (func(V))
  50. // KeyToHash function is used to customize the key hashing algorithm.
  51. // Each key will be hashed using the provided function. If keyToHash value
  52. // is not set, the default keyToHash function is used.
  53. keyToHash func(K) (uint64, uint64)
  54. // stop is used to stop the processItems goroutine.
  55. stop chan struct{}
  56. done chan struct{}
  57. // indicates whether cache is closed.
  58. isClosed atomic.Bool
  59. // cost calculates cost from a value.
  60. cost func(value V) int64
  61. // ignoreInternalCost dictates whether to ignore the cost of internally storing
  62. // the item in the cost calculation.
  63. ignoreInternalCost bool
  64. // cleanupTicker is used to periodically check for entries whose TTL has passed.
  65. cleanupTicker *time.Ticker
  66. // Metrics contains a running log of important statistics like hits, misses,
  67. // and dropped items.
  68. Metrics *Metrics
  69. }
  70. // Config is passed to NewCache for creating new Cache instances.
  71. type Config[K Key, V any] struct {
  72. // NumCounters determines the number of counters (keys) to keep that hold
  73. // access frequency information. It's generally a good idea to have more
  74. // counters than the max cache capacity, as this will improve eviction
  75. // accuracy and subsequent hit ratios.
  76. //
  77. // For example, if you expect your cache to hold 1,000,000 items when full,
  78. // NumCounters should be 10,000,000 (10x). Each counter takes up roughly
  79. // 3 bytes (4 bits for each counter * 4 copies plus about a byte per
  80. // counter for the bloom filter). Note that the number of counters is
  81. // internally rounded up to the nearest power of 2, so the space usage
  82. // may be a little larger than 3 bytes * NumCounters.
  83. //
  84. // We've seen good performance in setting this to 10x the number of items
  85. // you expect to keep in the cache when full.
  86. NumCounters int64
  87. // MaxCost is how eviction decisions are made. For example, if MaxCost is
  88. // 100 and a new item with a cost of 1 increases total cache cost to 101,
  89. // 1 item will be evicted.
  90. //
  91. // MaxCost can be considered as the cache capacity, in whatever units you
  92. // choose to use.
  93. //
  94. // For example, if you want the cache to have a max capacity of 100MB, you
  95. // would set MaxCost to 100,000,000 and pass an item's number of bytes as
  96. // the `cost` parameter for calls to Set. If new items are accepted, the
  97. // eviction process will take care of making room for the new item and not
  98. // overflowing the MaxCost value.
  99. //
  100. // MaxCost could be anything as long as it matches how you're using the cost
  101. // values when calling Set.
  102. MaxCost int64
  103. // BufferItems determines the size of Get buffers.
  104. //
  105. // Unless you have a rare use case, using `64` as the BufferItems value
  106. // results in good performance.
  107. //
  108. // If for some reason you see Get performance decreasing with lots of
  109. // contention (you shouldn't), try increasing this value in increments of 64.
  110. // This is a fine-tuning mechanism and you probably won't have to touch this.
  111. BufferItems int64
  112. // Metrics is true when you want variety of stats about the cache.
  113. // There is some overhead to keeping statistics, so you should only set this
  114. // flag to true when testing or throughput performance isn't a major factor.
  115. Metrics bool
  116. // OnEvict is called for every eviction with the evicted item.
  117. OnEvict func(item *Item[V])
  118. // OnReject is called for every rejection done via the policy.
  119. OnReject func(item *Item[V])
  120. // OnExit is called whenever a value is removed from cache. This can be
  121. // used to do manual memory deallocation. Would also be called on eviction
  122. // as well as on rejection of the value.
  123. OnExit func(val V)
  124. // ShouldUpdate is called when a value already exists in cache and is being updated.
  125. // If ShouldUpdate returns true, the cache continues with the update (Set). If the
  126. // function returns false, no changes are made in the cache. If the value doesn't
  127. // already exist, the cache continue with setting that value for the given key.
  128. //
  129. // In this function, you can check whether the new value is valid. For example, if
  130. // your value has timestamp assosicated with it, you could check whether the new
  131. // value has the latest timestamp, preventing you from setting an older value.
  132. ShouldUpdate func(cur, prev V) bool
  133. // KeyToHash function is used to customize the key hashing algorithm.
  134. // Each key will be hashed using the provided function. If keyToHash value
  135. // is not set, the default keyToHash function is used.
  136. //
  137. // Ristretto has a variety of defaults depending on the underlying interface type
  138. // https://github.com/dgraph-io/ristretto/blob/main/z/z.go#L19-L41).
  139. //
  140. // Note that if you want 128bit hashes you should use the both the values
  141. // in the return of the function. If you want to use 64bit hashes, you can
  142. // just return the first uint64 and return 0 for the second uint64.
  143. KeyToHash func(key K) (uint64, uint64)
  144. // Cost evaluates a value and outputs a corresponding cost. This function is ran
  145. // after Set is called for a new item or an item is updated with a cost param of 0.
  146. //
  147. // Cost is an optional function you can pass to the Config in order to evaluate
  148. // item cost at runtime, and only whentthe Set call isn't going to be dropped. This
  149. // is useful if calculating item cost is particularly expensive and you don't want to
  150. // waste time on items that will be dropped anyways.
  151. //
  152. // To signal to Ristretto that you'd like to use this Cost function:
  153. // 1. Set the Cost field to a non-nil function.
  154. // 2. When calling Set for new items or item updates, use a `cost` of 0.
  155. Cost func(value V) int64
  156. // IgnoreInternalCost set to true indicates to the cache that the cost of
  157. // internally storing the value should be ignored. This is useful when the
  158. // cost passed to set is not using bytes as units. Keep in mind that setting
  159. // this to true will increase the memory usage.
  160. IgnoreInternalCost bool
  161. // TtlTickerDurationInSec sets the value of time ticker for cleanup keys on TTL expiry.
  162. TtlTickerDurationInSec int64
  163. }
  164. type itemFlag byte
  165. const (
  166. itemNew itemFlag = iota
  167. itemDelete
  168. itemUpdate
  169. )
  170. // Item is a full representation of what's stored in the cache for each key-value pair.
  171. type Item[V any] struct {
  172. flag itemFlag
  173. Key uint64
  174. Conflict uint64
  175. Value V
  176. Cost int64
  177. Expiration time.Time
  178. wait chan struct{}
  179. }
  180. // NewCache returns a new Cache instance and any configuration errors, if any.
  181. func NewCache[K Key, V any](config *Config[K, V]) (*Cache[K, V], error) {
  182. switch {
  183. case config.NumCounters == 0:
  184. return nil, errors.New("NumCounters can't be zero")
  185. case config.NumCounters < 0:
  186. return nil, errors.New("NumCounters can't be negative")
  187. case config.MaxCost == 0:
  188. return nil, errors.New("MaxCost can't be zero")
  189. case config.MaxCost < 0:
  190. return nil, errors.New("MaxCost can't be negative")
  191. case config.BufferItems == 0:
  192. return nil, errors.New("BufferItems can't be zero")
  193. case config.BufferItems < 0:
  194. return nil, errors.New("BufferItems can't be negative")
  195. case config.TtlTickerDurationInSec == 0:
  196. config.TtlTickerDurationInSec = bucketDurationSecs
  197. }
  198. policy := newPolicy[V](config.NumCounters, config.MaxCost)
  199. cache := &Cache[K, V]{
  200. storedItems: newStore[V](),
  201. cachePolicy: policy,
  202. getBuf: newRingBuffer(policy, config.BufferItems),
  203. setBuf: make(chan *Item[V], setBufSize),
  204. keyToHash: config.KeyToHash,
  205. stop: make(chan struct{}),
  206. done: make(chan struct{}),
  207. cost: config.Cost,
  208. ignoreInternalCost: config.IgnoreInternalCost,
  209. cleanupTicker: time.NewTicker(time.Duration(config.TtlTickerDurationInSec) * time.Second / 2),
  210. }
  211. cache.storedItems.SetShouldUpdateFn(config.ShouldUpdate)
  212. cache.onExit = func(val V) {
  213. if config.OnExit != nil {
  214. config.OnExit(val)
  215. }
  216. }
  217. cache.onEvict = func(item *Item[V]) {
  218. if config.OnEvict != nil {
  219. config.OnEvict(item)
  220. }
  221. cache.onExit(item.Value)
  222. }
  223. cache.onReject = func(item *Item[V]) {
  224. if config.OnReject != nil {
  225. config.OnReject(item)
  226. }
  227. cache.onExit(item.Value)
  228. }
  229. if cache.keyToHash == nil {
  230. cache.keyToHash = z.KeyToHash[K]
  231. }
  232. if config.Metrics {
  233. cache.collectMetrics()
  234. }
  235. // NOTE: benchmarks seem to show that performance decreases the more
  236. // goroutines we have running cache.processItems(), so 1 should
  237. // usually be sufficient
  238. go cache.processItems()
  239. return cache, nil
  240. }
  241. // Wait blocks until all buffered writes have been applied. This ensures a call to Set()
  242. // will be visible to future calls to Get().
  243. func (c *Cache[K, V]) Wait() {
  244. if c == nil || c.isClosed.Load() {
  245. return
  246. }
  247. wait := make(chan struct{})
  248. c.setBuf <- &Item[V]{wait: wait}
  249. <-wait
  250. }
  251. // Get returns the value (if any) and a boolean representing whether the
  252. // value was found or not. The value can be nil and the boolean can be true at
  253. // the same time. Get will not return expired items.
  254. func (c *Cache[K, V]) Get(key K) (V, bool) {
  255. if c == nil || c.isClosed.Load() {
  256. return zeroValue[V](), false
  257. }
  258. keyHash, conflictHash := c.keyToHash(key)
  259. c.getBuf.Push(keyHash)
  260. value, ok := c.storedItems.Get(keyHash, conflictHash)
  261. if ok {
  262. c.Metrics.add(hit, keyHash, 1)
  263. } else {
  264. c.Metrics.add(miss, keyHash, 1)
  265. }
  266. return value, ok
  267. }
  268. // Set attempts to add the key-value item to the cache. If it returns false,
  269. // then the Set was dropped and the key-value item isn't added to the cache. If
  270. // it returns true, there's still a chance it could be dropped by the policy if
  271. // its determined that the key-value item isn't worth keeping, but otherwise the
  272. // item will be added and other items will be evicted in order to make room.
  273. //
  274. // To dynamically evaluate the items cost using the Config.Coster function, set
  275. // the cost parameter to 0 and Coster will be ran when needed in order to find
  276. // the items true cost.
  277. //
  278. // Set writes the value of type V as is. If type V is a pointer type, It is ok
  279. // to update the memory pointed to by the pointer. Updating the pointer itself
  280. // will not be reflected in the cache. Be careful when using slice types as the
  281. // value type V. Calling `append` may update the underlined array pointer which
  282. // will not be reflected in the cache.
  283. func (c *Cache[K, V]) Set(key K, value V, cost int64) bool {
  284. return c.SetWithTTL(key, value, cost, 0*time.Second)
  285. }
  286. // SetWithTTL works like Set but adds a key-value pair to the cache that will expire
  287. // after the specified TTL (time to live) has passed. A zero value means the value never
  288. // expires, which is identical to calling Set. A negative value is a no-op and the value
  289. // is discarded.
  290. //
  291. // See Set for more information.
  292. func (c *Cache[K, V]) SetWithTTL(key K, value V, cost int64, ttl time.Duration) bool {
  293. if c == nil || c.isClosed.Load() {
  294. return false
  295. }
  296. var expiration time.Time
  297. switch {
  298. case ttl == 0:
  299. // No expiration.
  300. break
  301. case ttl < 0:
  302. // Treat this a no-op.
  303. return false
  304. default:
  305. expiration = time.Now().Add(ttl)
  306. }
  307. keyHash, conflictHash := c.keyToHash(key)
  308. i := &Item[V]{
  309. flag: itemNew,
  310. Key: keyHash,
  311. Conflict: conflictHash,
  312. Value: value,
  313. Cost: cost,
  314. Expiration: expiration,
  315. }
  316. // cost is eventually updated. The expiration must also be immediately updated
  317. // to prevent items from being prematurely removed from the map.
  318. if prev, ok := c.storedItems.Update(i); ok {
  319. c.onExit(prev)
  320. i.flag = itemUpdate
  321. }
  322. // Attempt to send item to cachePolicy.
  323. select {
  324. case c.setBuf <- i:
  325. return true
  326. default:
  327. if i.flag == itemUpdate {
  328. // Return true if this was an update operation since we've already
  329. // updated the storedItems. For all the other operations (set/delete), we
  330. // return false which means the item was not inserted.
  331. return true
  332. }
  333. c.Metrics.add(dropSets, keyHash, 1)
  334. return false
  335. }
  336. }
  337. // Del deletes the key-value item from the cache if it exists.
  338. func (c *Cache[K, V]) Del(key K) {
  339. if c == nil || c.isClosed.Load() {
  340. return
  341. }
  342. keyHash, conflictHash := c.keyToHash(key)
  343. // Delete immediately.
  344. _, prev := c.storedItems.Del(keyHash, conflictHash)
  345. c.onExit(prev)
  346. // If we've set an item, it would be applied slightly later.
  347. // So we must push the same item to `setBuf` with the deletion flag.
  348. // This ensures that if a set is followed by a delete, it will be
  349. // applied in the correct order.
  350. c.setBuf <- &Item[V]{
  351. flag: itemDelete,
  352. Key: keyHash,
  353. Conflict: conflictHash,
  354. }
  355. }
  356. // GetTTL returns the TTL for the specified key and a bool that is true if the
  357. // item was found and is not expired.
  358. func (c *Cache[K, V]) GetTTL(key K) (time.Duration, bool) {
  359. if c == nil {
  360. return 0, false
  361. }
  362. keyHash, conflictHash := c.keyToHash(key)
  363. if _, ok := c.storedItems.Get(keyHash, conflictHash); !ok {
  364. // not found
  365. return 0, false
  366. }
  367. expiration := c.storedItems.Expiration(keyHash)
  368. if expiration.IsZero() {
  369. // found but no expiration
  370. return 0, true
  371. }
  372. if time.Now().After(expiration) {
  373. // found but expired
  374. return 0, false
  375. }
  376. return time.Until(expiration), true
  377. }
  378. // IterValues iterates the values of the Map, passing them to the callback.
  379. // It guarantees that any value in the Map will be visited only once.
  380. // The set of values visited by IterValues is non-deterministic.
  381. func (c *Cache[K, V]) IterValues(cb func(v V) (stop bool)) {
  382. if c == nil || c.isClosed.Load() {
  383. return
  384. }
  385. c.storedItems.IterValues(cb)
  386. }
  387. // Close stops all goroutines and closes all channels.
  388. func (c *Cache[K, V]) Close() {
  389. if c == nil || c.isClosed.Load() {
  390. return
  391. }
  392. c.Clear()
  393. // Block until processItems goroutine is returned.
  394. c.stop <- struct{}{}
  395. <-c.done
  396. close(c.stop)
  397. close(c.done)
  398. close(c.setBuf)
  399. c.cachePolicy.Close()
  400. c.cleanupTicker.Stop()
  401. c.isClosed.Store(true)
  402. }
  403. // Clear empties the hashmap and zeroes all cachePolicy counters. Note that this is
  404. // not an atomic operation (but that shouldn't be a problem as it's assumed that
  405. // Set/Get calls won't be occurring until after this).
  406. func (c *Cache[K, V]) Clear() {
  407. if c == nil || c.isClosed.Load() {
  408. return
  409. }
  410. // Block until processItems goroutine is returned.
  411. c.stop <- struct{}{}
  412. <-c.done
  413. // Clear out the setBuf channel.
  414. loop:
  415. for {
  416. select {
  417. case i := <-c.setBuf:
  418. if i.wait != nil {
  419. close(i.wait)
  420. continue
  421. }
  422. if i.flag != itemUpdate {
  423. // In itemUpdate, the value is already set in the storedItems. So, no need to call
  424. // onEvict here.
  425. c.onEvict(i)
  426. }
  427. default:
  428. break loop
  429. }
  430. }
  431. // Clear value hashmap and cachePolicy data.
  432. c.cachePolicy.Clear()
  433. c.storedItems.Clear(c.onEvict)
  434. // Only reset metrics if they're enabled.
  435. if c.Metrics != nil {
  436. c.Metrics.Clear()
  437. }
  438. // Restart processItems goroutine.
  439. go c.processItems()
  440. }
  441. // MaxCost returns the max cost of the cache.
  442. func (c *Cache[K, V]) MaxCost() int64 {
  443. if c == nil {
  444. return 0
  445. }
  446. return c.cachePolicy.MaxCost()
  447. }
  448. // UpdateMaxCost updates the maxCost of an existing cache.
  449. func (c *Cache[K, V]) UpdateMaxCost(maxCost int64) {
  450. if c == nil {
  451. return
  452. }
  453. c.cachePolicy.UpdateMaxCost(maxCost)
  454. }
  455. // RemainingCost returns the remaining cost capacity (MaxCost - Used) of an existing cache.
  456. func (c *Cache[K, V]) RemainingCost() int64 {
  457. if c == nil {
  458. return 0
  459. }
  460. return c.cachePolicy.Cap()
  461. }
  462. // processItems is ran by goroutines processing the Set buffer.
  463. func (c *Cache[K, V]) processItems() {
  464. startTs := make(map[uint64]time.Time)
  465. numToKeep := 100000 // TODO: Make this configurable via options.
  466. trackAdmission := func(key uint64) {
  467. if c.Metrics == nil {
  468. return
  469. }
  470. startTs[key] = time.Now()
  471. if len(startTs) > numToKeep {
  472. for k := range startTs {
  473. if len(startTs) <= numToKeep {
  474. break
  475. }
  476. delete(startTs, k)
  477. }
  478. }
  479. }
  480. onEvict := func(i *Item[V]) {
  481. if ts, has := startTs[i.Key]; has {
  482. c.Metrics.trackEviction(int64(time.Since(ts) / time.Second))
  483. delete(startTs, i.Key)
  484. }
  485. if c.onEvict != nil {
  486. c.onEvict(i)
  487. }
  488. }
  489. for {
  490. select {
  491. case i := <-c.setBuf:
  492. if i.wait != nil {
  493. close(i.wait)
  494. continue
  495. }
  496. // Calculate item cost value if new or update.
  497. if i.Cost == 0 && c.cost != nil && i.flag != itemDelete {
  498. i.Cost = c.cost(i.Value)
  499. }
  500. if !c.ignoreInternalCost {
  501. // Add the cost of internally storing the object.
  502. i.Cost += itemSize
  503. }
  504. switch i.flag {
  505. case itemNew:
  506. victims, added := c.cachePolicy.Add(i.Key, i.Cost)
  507. if added {
  508. c.storedItems.Set(i)
  509. c.Metrics.add(keyAdd, i.Key, 1)
  510. trackAdmission(i.Key)
  511. } else {
  512. c.onReject(i)
  513. }
  514. for _, victim := range victims {
  515. victim.Conflict, victim.Value = c.storedItems.Del(victim.Key, 0)
  516. onEvict(victim)
  517. }
  518. case itemUpdate:
  519. c.cachePolicy.Update(i.Key, i.Cost)
  520. case itemDelete:
  521. c.cachePolicy.Del(i.Key) // Deals with metrics updates.
  522. _, val := c.storedItems.Del(i.Key, i.Conflict)
  523. c.onExit(val)
  524. }
  525. case <-c.cleanupTicker.C:
  526. c.storedItems.Cleanup(c.cachePolicy, onEvict)
  527. case <-c.stop:
  528. c.done <- struct{}{}
  529. return
  530. }
  531. }
  532. }
  533. // collectMetrics just creates a new *Metrics instance and adds the pointers
  534. // to the cache and policy instances.
  535. func (c *Cache[K, V]) collectMetrics() {
  536. c.Metrics = newMetrics()
  537. c.cachePolicy.CollectMetrics(c.Metrics)
  538. }
  539. type metricType int
  540. const (
  541. // The following 2 keep track of hits and misses.
  542. hit = iota
  543. miss
  544. // The following 3 keep track of number of keys added, updated and evicted.
  545. keyAdd
  546. keyUpdate
  547. keyEvict
  548. // The following 2 keep track of cost of keys added and evicted.
  549. costAdd
  550. costEvict
  551. // The following keep track of how many sets were dropped or rejected later.
  552. dropSets
  553. rejectSets
  554. // The following 2 keep track of how many gets were kept and dropped on the
  555. // floor.
  556. dropGets
  557. keepGets
  558. // This should be the final enum. Other enums should be set before this.
  559. doNotUse
  560. )
  561. func stringFor(t metricType) string {
  562. switch t {
  563. case hit:
  564. return "hit"
  565. case miss:
  566. return "miss"
  567. case keyAdd:
  568. return "keys-added"
  569. case keyUpdate:
  570. return "keys-updated"
  571. case keyEvict:
  572. return "keys-evicted"
  573. case costAdd:
  574. return "cost-added"
  575. case costEvict:
  576. return "cost-evicted"
  577. case dropSets:
  578. return "sets-dropped"
  579. case rejectSets:
  580. return "sets-rejected" // by policy.
  581. case dropGets:
  582. return "gets-dropped"
  583. case keepGets:
  584. return "gets-kept"
  585. default:
  586. return "unidentified"
  587. }
  588. }
  589. // Metrics is a snapshot of performance statistics for the lifetime of a cache instance.
  590. type Metrics struct {
  591. all [doNotUse][]*uint64
  592. mu sync.RWMutex
  593. life *z.HistogramData // Tracks the life expectancy of a key.
  594. }
  595. func newMetrics() *Metrics {
  596. s := &Metrics{
  597. life: z.NewHistogramData(z.HistogramBounds(1, 16)),
  598. }
  599. for i := 0; i < doNotUse; i++ {
  600. s.all[i] = make([]*uint64, 256)
  601. slice := s.all[i]
  602. for j := range slice {
  603. slice[j] = new(uint64)
  604. }
  605. }
  606. return s
  607. }
  608. func (p *Metrics) add(t metricType, hash, delta uint64) {
  609. if p == nil {
  610. return
  611. }
  612. valp := p.all[t]
  613. // Avoid false sharing by padding at least 64 bytes of space between two
  614. // atomic counters which would be incremented.
  615. idx := (hash % 25) * 10
  616. atomic.AddUint64(valp[idx], delta)
  617. }
  618. func (p *Metrics) get(t metricType) uint64 {
  619. if p == nil {
  620. return 0
  621. }
  622. valp := p.all[t]
  623. var total uint64
  624. for i := range valp {
  625. total += atomic.LoadUint64(valp[i])
  626. }
  627. return total
  628. }
  629. // Hits is the number of Get calls where a value was found for the corresponding key.
  630. func (p *Metrics) Hits() uint64 {
  631. return p.get(hit)
  632. }
  633. // Misses is the number of Get calls where a value was not found for the corresponding key.
  634. func (p *Metrics) Misses() uint64 {
  635. return p.get(miss)
  636. }
  637. // KeysAdded is the total number of Set calls where a new key-value item was added.
  638. func (p *Metrics) KeysAdded() uint64 {
  639. return p.get(keyAdd)
  640. }
  641. // KeysUpdated is the total number of Set calls where the value was updated.
  642. func (p *Metrics) KeysUpdated() uint64 {
  643. return p.get(keyUpdate)
  644. }
  645. // KeysEvicted is the total number of keys evicted.
  646. func (p *Metrics) KeysEvicted() uint64 {
  647. return p.get(keyEvict)
  648. }
  649. // CostAdded is the sum of costs that have been added (successful Set calls).
  650. func (p *Metrics) CostAdded() uint64 {
  651. return p.get(costAdd)
  652. }
  653. // CostEvicted is the sum of all costs that have been evicted.
  654. func (p *Metrics) CostEvicted() uint64 {
  655. return p.get(costEvict)
  656. }
  657. // SetsDropped is the number of Set calls that don't make it into internal
  658. // buffers (due to contention or some other reason).
  659. func (p *Metrics) SetsDropped() uint64 {
  660. return p.get(dropSets)
  661. }
  662. // SetsRejected is the number of Set calls rejected by the policy (TinyLFU).
  663. func (p *Metrics) SetsRejected() uint64 {
  664. return p.get(rejectSets)
  665. }
  666. // GetsDropped is the number of Get counter increments that are dropped
  667. // internally.
  668. func (p *Metrics) GetsDropped() uint64 {
  669. return p.get(dropGets)
  670. }
  671. // GetsKept is the number of Get counter increments that are kept.
  672. func (p *Metrics) GetsKept() uint64 {
  673. return p.get(keepGets)
  674. }
  675. // Ratio is the number of Hits over all accesses (Hits + Misses). This is the
  676. // percentage of successful Get calls.
  677. func (p *Metrics) Ratio() float64 {
  678. if p == nil {
  679. return 0.0
  680. }
  681. hits, misses := p.get(hit), p.get(miss)
  682. if hits == 0 && misses == 0 {
  683. return 0.0
  684. }
  685. return float64(hits) / float64(hits+misses)
  686. }
  687. func (p *Metrics) trackEviction(numSeconds int64) {
  688. if p == nil {
  689. return
  690. }
  691. p.mu.Lock()
  692. defer p.mu.Unlock()
  693. p.life.Update(numSeconds)
  694. }
  695. func (p *Metrics) LifeExpectancySeconds() *z.HistogramData {
  696. if p == nil {
  697. return nil
  698. }
  699. p.mu.RLock()
  700. defer p.mu.RUnlock()
  701. return p.life.Copy()
  702. }
  703. // Clear resets all the metrics.
  704. func (p *Metrics) Clear() {
  705. if p == nil {
  706. return
  707. }
  708. for i := 0; i < doNotUse; i++ {
  709. for j := range p.all[i] {
  710. atomic.StoreUint64(p.all[i][j], 0)
  711. }
  712. }
  713. p.mu.Lock()
  714. p.life = z.NewHistogramData(z.HistogramBounds(1, 16))
  715. p.mu.Unlock()
  716. }
  717. // String returns a string representation of the metrics.
  718. func (p *Metrics) String() string {
  719. if p == nil {
  720. return ""
  721. }
  722. var buf bytes.Buffer
  723. for i := 0; i < doNotUse; i++ {
  724. t := metricType(i)
  725. fmt.Fprintf(&buf, "%s: %d ", stringFor(t), p.get(t))
  726. }
  727. fmt.Fprintf(&buf, "gets-total: %d ", p.get(hit)+p.get(miss))
  728. fmt.Fprintf(&buf, "hit-ratio: %.2f", p.Ratio())
  729. return buf.String()
  730. }