195 lines
6.0 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|