KmemoMetrics

class KmemoMetrics @JvmOverloads constructor(tags: Iterable<Tag> = emptyList()) : MeterBinder, CacheListener

Publishes a SemanticCache's behaviour to a Micrometer registry — so the cache shows up in the same Prometheus / Datadog / CloudWatch dashboards as everything else your service runs.

It is both a MeterBinder (register it with your registry) and a CacheListener (give it to the cache), and the two halves are wired together by you:

val metrics = KmemoMetrics(tags = Tags.of("cache", "faq"))
val cache = SemanticCache(embedder, listeners = listOf(metrics))
metrics.bindTo(registry) // Spring Boot Actuator calls this for you

Every meter is driven by the CacheEvent stream, which is exact: the cache emits one event per counted lookup, so the counters here equal dev.kmemo.CacheStats to the event. Events that arrive before bindTo are ignored — metrics begin the moment you bind, which for a service is startup, before traffic. Meters registered:

MeterTypeTagsMeaning
kmemo.cache.lookupscountertotal lookups (hits + misses)
kmemo.cache.hitscounterlookups served from cache
kmemo.cache.missescounterreasonmisses, split by MissReason
kmemo.cache.guard.rejectionscounterguardguard rejections, split by guard name
kmemo.cache.writescounterentries written
kmemo.cache.evictionscountercauseevictions, split by EvictionCause (needs a store listener)
kmemo.cache.hit.ratiogaugehits / lookups
kmemo.cache.embedtimerdev.kmemo.Embedder latency per lookup
kmemo.cache.searchtimerdev.kmemo.CacheStore search latency per lookup
kmemo.cache.verifytimerdev.kmemo.Verifier latency, when one runs

Eviction counters only move if the store is also given this instance as its listener (evictions are the store's business); with InMemoryStore(listener = metrics) they light up too.

Cardinality

Meters here are not tagged by cache scope. Scope is caller-defined and often unbounded (one per tenant, per user, per model-version), and an unbounded tag is how a metrics bill or a Prometheus head-block blows up. The scope is on every CacheEvent for anyone who wants to build a deliberately bounded per-scope meter with their own CacheListener; this adapter stays safe by default.

Thread-safe: Micrometer meters are, and the listener does nothing else.

Parameters

tags

base tags added to every meter, e.g. the cache's name when a service runs several.

Constructors

Link copied to clipboard
constructor(tags: Iterable<Tag> = emptyList())

Functions

Link copied to clipboard
open override fun bindTo(registry: MeterRegistry)
Link copied to clipboard
open override fun onEvent(event: CacheEvent)