mirror of https://github.com/status-im/consul.git
agent/cache: integrate go-metrics so the cache is debuggable
This commit is contained in:
parent
9f3dbf7b2a
commit
00e7ab3cd5
|
@ -17,6 +17,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/armon/go-metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate mockery -all -inpkg
|
//go:generate mockery -all -inpkg
|
||||||
|
@ -109,6 +111,9 @@ type RegisterOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterType registers a cacheable type.
|
// RegisterType registers a cacheable type.
|
||||||
|
//
|
||||||
|
// This makes the type available for Get but does not automatically perform
|
||||||
|
// any prefetching. In order to populate the cache, Get must be called.
|
||||||
func (c *Cache) RegisterType(n string, typ Type, opts *RegisterOptions) {
|
func (c *Cache) RegisterType(n string, typ Type, opts *RegisterOptions) {
|
||||||
c.typesLock.Lock()
|
c.typesLock.Lock()
|
||||||
defer c.typesLock.Unlock()
|
defer c.typesLock.Unlock()
|
||||||
|
@ -122,9 +127,18 @@ func (c *Cache) RegisterType(n string, typ Type, opts *RegisterOptions) {
|
||||||
//
|
//
|
||||||
// Multiple Get calls for the same Request (matching CacheKey value) will
|
// Multiple Get calls for the same Request (matching CacheKey value) will
|
||||||
// block on a single network request.
|
// block on a single network request.
|
||||||
|
//
|
||||||
|
// The timeout specified by the Request will be the timeout on the cache
|
||||||
|
// Get, and does not correspond to the timeout of any background data
|
||||||
|
// fetching. If the timeout is reached before data satisfying the minimum
|
||||||
|
// index is retrieved, the last known value (maybe nil) is returned. No
|
||||||
|
// error is returned on timeout. This matches the behavior of Consul blocking
|
||||||
|
// queries.
|
||||||
func (c *Cache) Get(t string, r Request) (interface{}, error) {
|
func (c *Cache) Get(t string, r Request) (interface{}, error) {
|
||||||
info := r.CacheInfo()
|
info := r.CacheInfo()
|
||||||
if info.Key == "" {
|
if info.Key == "" {
|
||||||
|
metrics.IncrCounter([]string{"consul", "cache", "bypass"}, 1)
|
||||||
|
|
||||||
// If no key is specified, then we do not cache this request.
|
// If no key is specified, then we do not cache this request.
|
||||||
// Pass directly through to the backend.
|
// Pass directly through to the backend.
|
||||||
return c.fetchDirect(t, r)
|
return c.fetchDirect(t, r)
|
||||||
|
@ -152,6 +166,7 @@ RETRY_GET:
|
||||||
if ok && entry.Valid {
|
if ok && entry.Valid {
|
||||||
if info.MinIndex == 0 || info.MinIndex < entry.Index {
|
if info.MinIndex == 0 || info.MinIndex < entry.Index {
|
||||||
if first {
|
if first {
|
||||||
|
metrics.IncrCounter([]string{"consul", "cache", t, "hit"}, 1)
|
||||||
atomic.AddUint64(&c.hits, 1)
|
atomic.AddUint64(&c.hits, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +177,15 @@ RETRY_GET:
|
||||||
if first {
|
if first {
|
||||||
// Record the miss if its our first time through
|
// Record the miss if its our first time through
|
||||||
atomic.AddUint64(&c.misses, 1)
|
atomic.AddUint64(&c.misses, 1)
|
||||||
|
|
||||||
|
// We increment two different counters for cache misses depending on
|
||||||
|
// whether we're missing because we didn't have the data at all,
|
||||||
|
// or if we're missing because we're blocking on a set index.
|
||||||
|
if info.MinIndex == 0 {
|
||||||
|
metrics.IncrCounter([]string{"consul", "cache", t, "miss_new"}, 1)
|
||||||
|
} else {
|
||||||
|
metrics.IncrCounter([]string{"consul", "cache", t, "miss_block"}, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No longer our first time through
|
// No longer our first time through
|
||||||
|
@ -196,6 +220,10 @@ func (c *Cache) entryKey(r *RequestInfo) string {
|
||||||
return fmt.Sprintf("%s/%s/%s", r.Datacenter, r.Token, r.Key)
|
return fmt.Sprintf("%s/%s/%s", r.Datacenter, r.Token, r.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetch triggers a new background fetch for the given Request. If a
|
||||||
|
// background fetch is already running for a matching Request, the waiter
|
||||||
|
// channel for that request is returned. The effect of this is that there
|
||||||
|
// is only ever one blocking query for any matching requests.
|
||||||
func (c *Cache) fetch(t, key string, r Request) (<-chan struct{}, error) {
|
func (c *Cache) fetch(t, key string, r Request) (<-chan struct{}, error) {
|
||||||
// Get the type that we're fetching
|
// Get the type that we're fetching
|
||||||
c.typesLock.RLock()
|
c.typesLock.RLock()
|
||||||
|
@ -205,6 +233,7 @@ func (c *Cache) fetch(t, key string, r Request) (<-chan struct{}, error) {
|
||||||
return nil, fmt.Errorf("unknown type in cache: %s", t)
|
return nil, fmt.Errorf("unknown type in cache: %s", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We acquire a write lock because we may have to set Fetching to true.
|
||||||
c.entriesLock.Lock()
|
c.entriesLock.Lock()
|
||||||
defer c.entriesLock.Unlock()
|
defer c.entriesLock.Unlock()
|
||||||
entry, ok := c.entries[key]
|
entry, ok := c.entries[key]
|
||||||
|
@ -226,6 +255,7 @@ func (c *Cache) fetch(t, key string, r Request) (<-chan struct{}, error) {
|
||||||
// perform multiple fetches.
|
// perform multiple fetches.
|
||||||
entry.Fetching = true
|
entry.Fetching = true
|
||||||
c.entries[key] = entry
|
c.entries[key] = entry
|
||||||
|
metrics.SetGauge([]string{"consul", "cache", "entries_count"}, float32(len(c.entries)))
|
||||||
|
|
||||||
// The actual Fetch must be performed in a goroutine.
|
// The actual Fetch must be performed in a goroutine.
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -234,6 +264,14 @@ func (c *Cache) fetch(t, key string, r Request) (<-chan struct{}, error) {
|
||||||
MinIndex: entry.Index,
|
MinIndex: entry.Index,
|
||||||
}, r)
|
}, r)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
metrics.IncrCounter([]string{"consul", "cache", "fetch_success"}, 1)
|
||||||
|
metrics.IncrCounter([]string{"consul", "cache", t, "fetch_success"}, 1)
|
||||||
|
} else {
|
||||||
|
metrics.IncrCounter([]string{"consul", "cache", "fetch_error"}, 1)
|
||||||
|
metrics.IncrCounter([]string{"consul", "cache", t, "fetch_error"}, 1)
|
||||||
|
}
|
||||||
|
|
||||||
var newEntry cacheEntry
|
var newEntry cacheEntry
|
||||||
if result.Value == nil {
|
if result.Value == nil {
|
||||||
// If no value was set, then we do not change the prior entry.
|
// If no value was set, then we do not change the prior entry.
|
||||||
|
@ -272,7 +310,9 @@ func (c *Cache) fetch(t, key string, r Request) (<-chan struct{}, error) {
|
||||||
return entry.Waiter, nil
|
return entry.Waiter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchDirect fetches the given request with no caching.
|
// fetchDirect fetches the given request with no caching. Because this
|
||||||
|
// bypasses the caching entirely, multiple matching requests will result
|
||||||
|
// in multiple actual RPC calls (unlike fetch).
|
||||||
func (c *Cache) fetchDirect(t string, r Request) (interface{}, error) {
|
func (c *Cache) fetchDirect(t string, r Request) (interface{}, error) {
|
||||||
// Get the type that we're fetching
|
// Get the type that we're fetching
|
||||||
c.typesLock.RLock()
|
c.typesLock.RLock()
|
||||||
|
@ -294,6 +334,8 @@ func (c *Cache) fetchDirect(t string, r Request) (interface{}, error) {
|
||||||
return result.Value, nil
|
return result.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refresh triggers a fetch for a specific Request according to the
|
||||||
|
// registration options.
|
||||||
func (c *Cache) refresh(opts *RegisterOptions, t string, key string, r Request) {
|
func (c *Cache) refresh(opts *RegisterOptions, t string, key string, r Request) {
|
||||||
// Sanity-check, we should not schedule anything that has refresh disabled
|
// Sanity-check, we should not schedule anything that has refresh disabled
|
||||||
if !opts.Refresh {
|
if !opts.Refresh {
|
||||||
|
|
Loading…
Reference in New Issue