Files
git.stella-ops.org/src/Concelier/__Libraries/StellaOps.Concelier.Cache.Valkey/ConcelierCacheMetrics.cs
StellaOps Bot 83c37243e0 save progress
2026-01-03 11:02:24 +02:00

195 lines
6.0 KiB
C#

// -----------------------------------------------------------------------------
// 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;
/// <summary>
/// Metrics instrumentation for the Concelier advisory cache.
/// </summary>
public sealed class ConcelierCacheMetrics : IDisposable
{
/// <summary>
/// Activity source name for cache operations.
/// </summary>
public const string ActivitySourceName = "StellaOps.Concelier.Cache";
/// <summary>
/// Meter name for cache metrics.
/// </summary>
public const string MeterName = "StellaOps.Concelier.Cache";
private readonly Meter _meter;
private readonly Counter<long> _hitsCounter;
private readonly Counter<long> _missesCounter;
private readonly Counter<long> _evictionsCounter;
private readonly Histogram<double> _latencyHistogram;
private readonly ObservableGauge<long> _hotSetSizeGauge;
private long _lastKnownHotSetSize;
/// <summary>
/// Activity source for tracing cache operations.
/// </summary>
public static ActivitySource ActivitySource { get; } = new(ActivitySourceName, "1.0.0");
/// <summary>
/// Initializes a new instance of <see cref="ConcelierCacheMetrics"/>.
/// </summary>
public ConcelierCacheMetrics()
{
_meter = new Meter(MeterName, "1.0.0");
_hitsCounter = _meter.CreateCounter<long>(
"concelier_cache_hits_total",
unit: "{hits}",
description: "Total number of cache hits");
_missesCounter = _meter.CreateCounter<long>(
"concelier_cache_misses_total",
unit: "{misses}",
description: "Total number of cache misses");
_evictionsCounter = _meter.CreateCounter<long>(
"concelier_cache_evictions_total",
unit: "{evictions}",
description: "Total number of cache evictions");
_latencyHistogram = _meter.CreateHistogram<double>(
"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");
}
/// <summary>
/// Records a cache hit.
/// </summary>
public void RecordHit() => _hitsCounter.Add(1);
/// <summary>
/// Records a cache miss.
/// </summary>
public void RecordMiss() => _missesCounter.Add(1);
/// <summary>
/// Records a cache eviction.
/// </summary>
/// <param name="reason">The reason for eviction.</param>
public void RecordEviction(string reason = "ttl")
{
_evictionsCounter.Add(1, new KeyValuePair<string, object?>("reason", reason));
}
/// <summary>
/// Records operation latency.
/// </summary>
/// <param name="milliseconds">Latency in milliseconds.</param>
/// <param name="operation">The operation type (get, set, invalidate).</param>
public void RecordLatency(double milliseconds, string operation)
{
_latencyHistogram.Record(milliseconds, new KeyValuePair<string, object?>("operation", operation));
}
/// <summary>
/// Updates the hot set size gauge.
/// </summary>
/// <param name="size">Current hot set size.</param>
public void UpdateHotSetSize(long size)
{
_lastKnownHotSetSize = size;
}
/// <summary>
/// Starts an activity for tracing a cache operation.
/// </summary>
/// <param name="operationName">Name of the operation.</param>
/// <returns>The activity, or null if tracing is disabled.</returns>
public static Activity? StartActivity(string operationName)
{
return ActivitySource.StartActivity(operationName, ActivityKind.Internal);
}
/// <summary>
/// Starts an activity with tags.
/// </summary>
/// <param name="operationName">Name of the operation.</param>
/// <param name="tags">Tags to add to the activity.</param>
/// <returns>The activity, or null if tracing is disabled.</returns>
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;
}
/// <inheritdoc />
public void Dispose()
{
_meter.Dispose();
}
}
/// <summary>
/// Extension methods for timing cache operations.
/// </summary>
public static class CacheMetricsExtensions
{
/// <summary>
/// Times an async operation and records the latency.
/// </summary>
public static async Task<T> TimeAsync<T>(
this ConcelierCacheMetrics metrics,
string operation,
Func<Task<T>> action)
{
var sw = Stopwatch.StartNew();
try
{
return await action().ConfigureAwait(false);
}
finally
{
sw.Stop();
metrics.RecordLatency(sw.Elapsed.TotalMilliseconds, operation);
}
}
/// <summary>
/// Times an async operation and records the latency.
/// </summary>
public static async Task TimeAsync(
this ConcelierCacheMetrics metrics,
string operation,
Func<Task> action)
{
var sw = Stopwatch.StartNew();
try
{
await action().ConfigureAwait(false);
}
finally
{
sw.Stop();
metrics.RecordLatency(sw.Elapsed.TotalMilliseconds, operation);
}
}
}