// ----------------------------------------------------------------------------- // ConcelierCacheMetrics.cs // Sprint: SPRINT_8200_0013_0001_GW_valkey_advisory_cache // Task: VCACHE-8200-027, VCACHE-8200-028 // Description: OpenTelemetry metrics for cache operations // ----------------------------------------------------------------------------- using System.Diagnostics; using System.Diagnostics.Metrics; namespace StellaOps.Concelier.Cache.Valkey; /// /// Metrics instrumentation for the Concelier advisory cache. /// public sealed class ConcelierCacheMetrics : IDisposable { /// /// Activity source name for cache operations. /// public const string ActivitySourceName = "StellaOps.Concelier.Cache"; /// /// Meter name for cache metrics. /// public const string MeterName = "StellaOps.Concelier.Cache"; private readonly Meter _meter; private readonly Counter _hitsCounter; private readonly Counter _missesCounter; private readonly Counter _evictionsCounter; private readonly Histogram _latencyHistogram; private readonly ObservableGauge _hotSetSizeGauge; private long _lastKnownHotSetSize; /// /// Activity source for tracing cache operations. /// public static ActivitySource ActivitySource { get; } = new(ActivitySourceName, "1.0.0"); /// /// Initializes a new instance of . /// public ConcelierCacheMetrics() { _meter = new Meter(MeterName, "1.0.0"); _hitsCounter = _meter.CreateCounter( "concelier_cache_hits_total", unit: "{hits}", description: "Total number of cache hits"); _missesCounter = _meter.CreateCounter( "concelier_cache_misses_total", unit: "{misses}", description: "Total number of cache misses"); _evictionsCounter = _meter.CreateCounter( "concelier_cache_evictions_total", unit: "{evictions}", description: "Total number of cache evictions"); _latencyHistogram = _meter.CreateHistogram( "concelier_cache_latency_ms", unit: "ms", description: "Cache operation latency in milliseconds"); _hotSetSizeGauge = _meter.CreateObservableGauge( "concelier_cache_hot_set_size", () => _lastKnownHotSetSize, unit: "{entries}", description: "Current number of entries in the hot advisory set"); } /// /// Records a cache hit. /// public void RecordHit() => _hitsCounter.Add(1); /// /// Records a cache miss. /// public void RecordMiss() => _missesCounter.Add(1); /// /// Records a cache eviction. /// /// The reason for eviction. public void RecordEviction(string reason = "ttl") { _evictionsCounter.Add(1, new KeyValuePair("reason", reason)); } /// /// Records operation latency. /// /// Latency in milliseconds. /// The operation type (get, set, invalidate). public void RecordLatency(double milliseconds, string operation) { _latencyHistogram.Record(milliseconds, new KeyValuePair("operation", operation)); } /// /// Updates the hot set size gauge. /// /// Current hot set size. public void UpdateHotSetSize(long size) { _lastKnownHotSetSize = size; } /// /// Starts an activity for tracing a cache operation. /// /// Name of the operation. /// The activity, or null if tracing is disabled. public static Activity? StartActivity(string operationName) { return ActivitySource.StartActivity(operationName, ActivityKind.Internal); } /// /// Starts an activity with tags. /// /// Name of the operation. /// Tags to add to the activity. /// The activity, or null if tracing is disabled. public static Activity? StartActivity(string operationName, params (string Key, object? Value)[] tags) { var activity = ActivitySource.StartActivity(operationName, ActivityKind.Internal); if (activity is not null) { foreach (var (key, value) in tags) { activity.SetTag(key, value); } } return activity; } /// public void Dispose() { _meter.Dispose(); } } /// /// Extension methods for timing cache operations. /// public static class CacheMetricsExtensions { /// /// Times an async operation and records the latency. /// public static async Task TimeAsync( this ConcelierCacheMetrics metrics, string operation, Func> action) { var sw = Stopwatch.StartNew(); try { return await action().ConfigureAwait(false); } finally { sw.Stop(); metrics.RecordLatency(sw.Elapsed.TotalMilliseconds, operation); } } /// /// Times an async operation and records the latency. /// public static async Task TimeAsync( this ConcelierCacheMetrics metrics, string operation, Func action) { var sw = Stopwatch.StartNew(); try { await action().ConfigureAwait(false); } finally { sw.Stop(); metrics.RecordLatency(sw.Elapsed.TotalMilliseconds, operation); } } }