220 lines
7.5 KiB
C#
220 lines
7.5 KiB
C#
// -----------------------------------------------------------------------------
|
|
// ResolutionTelemetry.cs
|
|
// Sprint: SPRINT_1227_0001_0002_BE_resolution_api
|
|
// Task: T11 - Telemetry for resolution API
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.Metrics;
|
|
|
|
namespace StellaOps.BinaryIndex.WebService.Telemetry;
|
|
|
|
/// <summary>
|
|
/// OpenTelemetry instrumentation for binary resolution API.
|
|
/// </summary>
|
|
public sealed class ResolutionTelemetry : IDisposable
|
|
{
|
|
public const string ServiceName = "StellaOps.BinaryIndex.Resolution";
|
|
public const string MeterName = "StellaOps.BinaryIndex.Resolution";
|
|
public const string ActivitySourceName = "StellaOps.BinaryIndex.Resolution";
|
|
|
|
private readonly Meter _meter;
|
|
|
|
// Counters
|
|
private readonly Counter<long> _requestsTotal;
|
|
private readonly Counter<long> _cacheHitsTotal;
|
|
private readonly Counter<long> _cacheMissesTotal;
|
|
private readonly Counter<long> _resolutionsTotal;
|
|
private readonly Counter<long> _errorsTotal;
|
|
private readonly Counter<long> _rateLimitedTotal;
|
|
|
|
// Histograms
|
|
private readonly Histogram<double> _requestDurationMs;
|
|
private readonly Histogram<double> _cacheLatencyMs;
|
|
private readonly Histogram<double> _fingerprintMatchDurationMs;
|
|
private readonly Histogram<int> _batchSize;
|
|
private readonly Histogram<double> _confidenceScore;
|
|
|
|
// Gauges
|
|
private readonly UpDownCounter<long> _requestsInProgress;
|
|
|
|
public static readonly ActivitySource ActivitySource = new(ActivitySourceName);
|
|
|
|
public ResolutionTelemetry(IMeterFactory? meterFactory = null)
|
|
{
|
|
_meter = meterFactory?.Create(MeterName) ?? new Meter(MeterName);
|
|
|
|
_requestsTotal = _meter.CreateCounter<long>(
|
|
"binaryindex.resolution.requests.total",
|
|
unit: "{request}",
|
|
description: "Total resolution API requests");
|
|
|
|
_cacheHitsTotal = _meter.CreateCounter<long>(
|
|
"binaryindex.resolution.cache.hits.total",
|
|
unit: "{hit}",
|
|
description: "Total cache hits");
|
|
|
|
_cacheMissesTotal = _meter.CreateCounter<long>(
|
|
"binaryindex.resolution.cache.misses.total",
|
|
unit: "{miss}",
|
|
description: "Total cache misses");
|
|
|
|
_resolutionsTotal = _meter.CreateCounter<long>(
|
|
"binaryindex.resolution.resolutions.total",
|
|
unit: "{resolution}",
|
|
description: "Total successful resolutions");
|
|
|
|
_errorsTotal = _meter.CreateCounter<long>(
|
|
"binaryindex.resolution.errors.total",
|
|
unit: "{error}",
|
|
description: "Total resolution errors");
|
|
|
|
_rateLimitedTotal = _meter.CreateCounter<long>(
|
|
"binaryindex.resolution.rate_limited.total",
|
|
unit: "{request}",
|
|
description: "Total rate-limited requests");
|
|
|
|
_requestDurationMs = _meter.CreateHistogram<double>(
|
|
"binaryindex.resolution.request.duration.ms",
|
|
unit: "ms",
|
|
description: "Request duration in milliseconds");
|
|
|
|
_cacheLatencyMs = _meter.CreateHistogram<double>(
|
|
"binaryindex.resolution.cache.latency.ms",
|
|
unit: "ms",
|
|
description: "Cache lookup latency in milliseconds");
|
|
|
|
_fingerprintMatchDurationMs = _meter.CreateHistogram<double>(
|
|
"binaryindex.resolution.fingerprint_match.duration.ms",
|
|
unit: "ms",
|
|
description: "Fingerprint matching duration in milliseconds");
|
|
|
|
_batchSize = _meter.CreateHistogram<int>(
|
|
"binaryindex.resolution.batch.size",
|
|
unit: "{item}",
|
|
description: "Batch request size");
|
|
|
|
_confidenceScore = _meter.CreateHistogram<double>(
|
|
"binaryindex.resolution.confidence",
|
|
unit: "1",
|
|
description: "Resolution confidence score distribution");
|
|
|
|
_requestsInProgress = _meter.CreateUpDownCounter<long>(
|
|
"binaryindex.resolution.requests.in_progress",
|
|
unit: "{request}",
|
|
description: "Requests currently in progress");
|
|
}
|
|
|
|
public void RecordRequest(string method, string status, TimeSpan duration, bool cacheHit)
|
|
{
|
|
var tags = new TagList
|
|
{
|
|
{ ResolutionTelemetryTags.Method, method },
|
|
{ ResolutionTelemetryTags.Status, status },
|
|
{ ResolutionTelemetryTags.CacheHit, cacheHit.ToString().ToLowerInvariant() }
|
|
};
|
|
|
|
_requestsTotal.Add(1, tags);
|
|
_requestDurationMs.Record(duration.TotalMilliseconds, tags);
|
|
|
|
if (cacheHit)
|
|
_cacheHitsTotal.Add(1, tags);
|
|
else
|
|
_cacheMissesTotal.Add(1, tags);
|
|
}
|
|
|
|
public void RecordResolution(string matchType, string resolutionStatus, decimal confidence)
|
|
{
|
|
var tags = new TagList
|
|
{
|
|
{ ResolutionTelemetryTags.MatchType, matchType },
|
|
{ ResolutionTelemetryTags.ResolutionStatus, resolutionStatus }
|
|
};
|
|
|
|
_resolutionsTotal.Add(1, tags);
|
|
_confidenceScore.Record((double)confidence, tags);
|
|
}
|
|
|
|
public void RecordError(string errorCode, string method)
|
|
{
|
|
_errorsTotal.Add(1, new TagList
|
|
{
|
|
{ ResolutionTelemetryTags.ErrorCode, errorCode },
|
|
{ ResolutionTelemetryTags.Method, method }
|
|
});
|
|
}
|
|
|
|
public void RecordRateLimited(string tenantId)
|
|
{
|
|
_rateLimitedTotal.Add(1, new TagList
|
|
{
|
|
{ ResolutionTelemetryTags.TenantId, tenantId }
|
|
});
|
|
}
|
|
|
|
public void RecordBatchRequest(int size)
|
|
{
|
|
_batchSize.Record(size);
|
|
}
|
|
|
|
public void RecordCacheLatency(TimeSpan latency, bool hit)
|
|
{
|
|
_cacheLatencyMs.Record(latency.TotalMilliseconds, new TagList
|
|
{
|
|
{ ResolutionTelemetryTags.CacheHit, hit.ToString().ToLowerInvariant() }
|
|
});
|
|
}
|
|
|
|
public void RecordFingerprintMatchDuration(TimeSpan duration, string algorithm)
|
|
{
|
|
_fingerprintMatchDurationMs.Record(duration.TotalMilliseconds, new TagList
|
|
{
|
|
{ ResolutionTelemetryTags.Algorithm, algorithm }
|
|
});
|
|
}
|
|
|
|
public void IncrementInProgress() => _requestsInProgress.Add(1);
|
|
public void DecrementInProgress() => _requestsInProgress.Add(-1);
|
|
|
|
public static Activity? StartResolveActivity(string package, string? cveId)
|
|
{
|
|
var activity = ActivitySource.StartActivity("Resolution.Resolve");
|
|
activity?.SetTag("package", package);
|
|
if (cveId != null) activity?.SetTag("cve_id", cveId);
|
|
return activity;
|
|
}
|
|
|
|
public static Activity? StartBatchResolveActivity(int count)
|
|
{
|
|
var activity = ActivitySource.StartActivity("Resolution.ResolveBatch");
|
|
activity?.SetTag("batch_size", count);
|
|
return activity;
|
|
}
|
|
|
|
public void Dispose() => _meter.Dispose();
|
|
}
|
|
|
|
public static class ResolutionTelemetryTags
|
|
{
|
|
public const string Method = "method";
|
|
public const string Status = "status";
|
|
public const string CacheHit = "cache_hit";
|
|
public const string MatchType = "match_type";
|
|
public const string ResolutionStatus = "resolution_status";
|
|
public const string ErrorCode = "error_code";
|
|
public const string TenantId = "tenant_id";
|
|
public const string Algorithm = "algorithm";
|
|
}
|
|
|
|
public static class ResolutionTelemetryExtensions
|
|
{
|
|
public static IServiceCollection AddResolutionTelemetry(this IServiceCollection services)
|
|
{
|
|
services.TryAddSingleton<ResolutionTelemetry>();
|
|
return services;
|
|
}
|
|
}
|