eko / gocache

☔️ A complete Go cache library that brings you multiple ways of managing your caches
https://vincent.composieux.fr/article/i-wrote-gocache-a-complete-and-extensible-go-cache-library/
MIT License
2.4k stars 193 forks source link

Multiple caches, one metric #244

Open twolf-adc opened 4 months ago

twolf-adc commented 4 months ago

I currently have multiple caches, that I don't want to join as one. However, in my Prometheus metrics I want all of these caches to use the same metric aka namespace, where each cache has its uniquely identifying label. This way I can easily query Prometheus, showing me the metric over all caches or filtered to (via the label) the one cache I want.

What I tried Attempting to call cachemetrics.NewPrometheus() multiple times with the same namespace but setting some label differently. This results in a runtime panic (panic: duplicate metrics collector registration attempted).

Other than that, I don't see a way to achieve this. Am I missing something, any suggestion?

Platforms:

alpine Linux

Versions:

v4.1.5

If this is not possible currently, is this a feature that you'd be willing to accept as a contribution from me?

semihbkgr commented 2 months ago

If you use two different type of storage, they should uniquely identified by the store label.

# HELP cache_collector This represent the number of items in cache
# TYPE cache_collector gauge
cache_collector{metric="delete_error",service="cache-app",store="go-cache"} 0
cache_collector{metric="delete_error",service="cache-app",store="hazelcast"} 0
cache_collector{metric="delete_success",service="cache-app",store="go-cache"} 0
cache_collector{metric="delete_success",service="cache-app",store="hazelcast"} 0
cache_collector{metric="hit_count",service="cache-app",store="go-cache"} 0
cache_collector{metric="hit_count",service="cache-app",store="hazelcast"} 0
cache_collector{metric="invalidate_error",service="cache-app",store="go-cache"} 0
cache_collector{metric="invalidate_error",service="cache-app",store="hazelcast"} 0
cache_collector{metric="invalidate_success",service="cache-app",store="go-cache"} 0
cache_collector{metric="invalidate_success",service="cache-app",store="hazelcast"} 0
cache_collector{metric="miss_count",service="cache-app",store="go-cache"} 0
cache_collector{metric="miss_count",service="cache-app",store="hazelcast"} 0
cache_collector{metric="set_error",service="cache-app",store="go-cache"} 0
cache_collector{metric="set_error",service="cache-app",store="hazelcast"} 0
cache_collector{metric="set_success",service="cache-app",store="go-cache"} 0
cache_collector{metric="set_success",service="cache-app",store="hazelcast"} 0
# HELP promhttp_metric_handler_errors_total Total number of internal errors encountered by the promhttp metric handler.
# TYPE promhttp_metric_handler_errors_total counter
promhttp_metric_handler_errors_total{cause="encoding"} 0
promhttp_metric_handler_errors_total{cause="gathering"} 0

If you use the same type of storage, it seems that the API doesn't provide direct support. However, there's a workaround you can use.

You can create a storage wrapper type to customize the store type modifying GetType function , so that the store value in the labels will be differentiated.

type StoreInterfaceWrapper struct {
    store.StoreInterface
    t string
}

func (c *StoreInterfaceWrapper) GetType() string {
    return fmt.Sprintf("%s-%s", c.StoreInterface.GetType(), c.t)
}
gocacheClientOne := gocache.New(5*time.Minute, 10*time.Minute)
gocacheStoreOne := gocache_store.NewGoCache(gocacheClientOne)
cacheManagerOne := cache.New[[]byte](&StoreInterfaceWrapper{StoreInterface: gocacheStoreOne, t: "one"})

gocacheClientTwo := gocache.New(5*time.Minute, 10*time.Minute)
gocacheStoreTwo := gocache_store.NewGoCache(gocacheClientTwo)
cacheManagerTwo := cache.New[[]byte](&StoreInterfaceWrapper{StoreInterface: gocacheStoreTwo, t: "two"})

reg := prometheus.NewRegistry()
p := metrics.NewPrometheus("cache-app", metrics.WithRegisterer(reg), metrics.WithNamespace("cache"))
go func() {
for {
    fmt.Println("sending metrics")
    p.RecordFromCodec(cacheManagerOne.GetCodec())
    p.RecordFromCodec(cacheManagerTwo.GetCodec())
    time.Sleep(time.Second * 5)
}
}()

http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))
# HELP cache_collector This represent the number of items in cache
# TYPE cache_collector gauge
cache_collector{metric="delete_error",service="cache-app",store="go-cache-one"} 0
cache_collector{metric="delete_error",service="cache-app",store="go-cache-two"} 0
cache_collector{metric="delete_success",service="cache-app",store="go-cache-one"} 0
cache_collector{metric="delete_success",service="cache-app",store="go-cache-two"} 0
cache_collector{metric="hit_count",service="cache-app",store="go-cache-one"} 0
cache_collector{metric="hit_count",service="cache-app",store="go-cache-two"} 0
cache_collector{metric="invalidate_error",service="cache-app",store="go-cache-one"} 0
cache_collector{metric="invalidate_error",service="cache-app",store="go-cache-two"} 0
cache_collector{metric="invalidate_success",service="cache-app",store="go-cache-one"} 0
cache_collector{metric="invalidate_success",service="cache-app",store="go-cache-two"} 0
cache_collector{metric="miss_count",service="cache-app",store="go-cache-one"} 0
cache_collector{metric="miss_count",service="cache-app",store="go-cache-two"} 0
cache_collector{metric="set_error",service="cache-app",store="go-cache-one"} 0
cache_collector{metric="set_error",service="cache-app",store="go-cache-two"} 0
cache_collector{metric="set_success",service="cache-app",store="go-cache-one"} 0
cache_collector{metric="set_success",service="cache-app",store="go-cache-two"} 0
# HELP promhttp_metric_handler_errors_total Total number of internal errors encountered by the promhttp metric handler.
# TYPE promhttp_metric_handler_errors_total counter
promhttp_metric_handler_errors_total{cause="encoding"} 0
promhttp_metric_handler_errors_total{cause="gathering"} 0
semihbkgr commented 2 months ago

@eko I think it should be supported in the API.