165 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			165 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System.Collections.Generic;
 | |
| using System.Diagnostics.Metrics;
 | |
| 
 | |
| namespace StellaOps.Concelier.Connector.Ghsa.Internal;
 | |
| 
 | |
| public sealed class GhsaDiagnostics : IDisposable
 | |
| {
 | |
|     private const string MeterName = "StellaOps.Concelier.Connector.Ghsa";
 | |
|     private const string MeterVersion = "1.0.0";
 | |
| 
 | |
|     private readonly Meter _meter;
 | |
|     private readonly Counter<long> _fetchAttempts;
 | |
|     private readonly Counter<long> _fetchDocuments;
 | |
|     private readonly Counter<long> _fetchFailures;
 | |
|     private readonly Counter<long> _fetchUnchanged;
 | |
|     private readonly Counter<long> _parseSuccess;
 | |
|     private readonly Counter<long> _parseFailures;
 | |
|     private readonly Counter<long> _parseQuarantine;
 | |
|     private readonly Counter<long> _mapSuccess;
 | |
|     private readonly Histogram<long> _rateLimitRemaining;
 | |
|     private readonly Histogram<long> _rateLimitLimit;
 | |
|     private readonly Histogram<double> _rateLimitResetSeconds;
 | |
|     private readonly Histogram<double> _rateLimitHeadroomPct;
 | |
|     private readonly ObservableGauge<double> _rateLimitHeadroomGauge;
 | |
|     private readonly Counter<long> _rateLimitExhausted;
 | |
|     private readonly Counter<long> _canonicalMetricFallbacks;
 | |
|     private readonly object _rateLimitLock = new();
 | |
|     private GhsaRateLimitSnapshot? _lastRateLimitSnapshot;
 | |
|     private readonly Dictionary<(string Phase, string? Resource), GhsaRateLimitSnapshot> _rateLimitSnapshots = new();
 | |
| 
 | |
|     public GhsaDiagnostics()
 | |
|     {
 | |
|         _meter = new Meter(MeterName, MeterVersion);
 | |
|         _fetchAttempts = _meter.CreateCounter<long>("ghsa.fetch.attempts", unit: "operations");
 | |
|         _fetchDocuments = _meter.CreateCounter<long>("ghsa.fetch.documents", unit: "documents");
 | |
|         _fetchFailures = _meter.CreateCounter<long>("ghsa.fetch.failures", unit: "operations");
 | |
|         _fetchUnchanged = _meter.CreateCounter<long>("ghsa.fetch.unchanged", unit: "operations");
 | |
|         _parseSuccess = _meter.CreateCounter<long>("ghsa.parse.success", unit: "documents");
 | |
|         _parseFailures = _meter.CreateCounter<long>("ghsa.parse.failures", unit: "documents");
 | |
|         _parseQuarantine = _meter.CreateCounter<long>("ghsa.parse.quarantine", unit: "documents");
 | |
|         _mapSuccess = _meter.CreateCounter<long>("ghsa.map.success", unit: "advisories");
 | |
|         _rateLimitRemaining = _meter.CreateHistogram<long>("ghsa.ratelimit.remaining", unit: "requests");
 | |
|         _rateLimitLimit = _meter.CreateHistogram<long>("ghsa.ratelimit.limit", unit: "requests");
 | |
|         _rateLimitResetSeconds = _meter.CreateHistogram<double>("ghsa.ratelimit.reset_seconds", unit: "s");
 | |
|         _rateLimitHeadroomPct = _meter.CreateHistogram<double>("ghsa.ratelimit.headroom_pct", unit: "percent");
 | |
|         _rateLimitHeadroomGauge = _meter.CreateObservableGauge("ghsa.ratelimit.headroom_pct_current", ObserveHeadroom, unit: "percent");
 | |
|         _rateLimitExhausted = _meter.CreateCounter<long>("ghsa.ratelimit.exhausted", unit: "events");
 | |
|         _canonicalMetricFallbacks = _meter.CreateCounter<long>("ghsa.map.canonical_metric_fallbacks", unit: "advisories");
 | |
|     }
 | |
| 
 | |
|     public void FetchAttempt() => _fetchAttempts.Add(1);
 | |
| 
 | |
|     public void FetchDocument() => _fetchDocuments.Add(1);
 | |
| 
 | |
|     public void FetchFailure() => _fetchFailures.Add(1);
 | |
| 
 | |
|     public void FetchUnchanged() => _fetchUnchanged.Add(1);
 | |
| 
 | |
|     public void ParseSuccess() => _parseSuccess.Add(1);
 | |
| 
 | |
|     public void ParseFailure() => _parseFailures.Add(1);
 | |
| 
 | |
|     public void ParseQuarantine() => _parseQuarantine.Add(1);
 | |
| 
 | |
|     public void MapSuccess(long count) => _mapSuccess.Add(count);
 | |
| 
 | |
|     internal void RecordRateLimit(GhsaRateLimitSnapshot snapshot)
 | |
|     {
 | |
|         var tags = new KeyValuePair<string, object?>[]
 | |
|         {
 | |
|             new("phase", snapshot.Phase),
 | |
|             new("resource", snapshot.Resource ?? "unknown")
 | |
|         };
 | |
| 
 | |
|         if (snapshot.Limit.HasValue)
 | |
|         {
 | |
|             _rateLimitLimit.Record(snapshot.Limit.Value, tags);
 | |
|         }
 | |
| 
 | |
|         if (snapshot.Remaining.HasValue)
 | |
|         {
 | |
|             _rateLimitRemaining.Record(snapshot.Remaining.Value, tags);
 | |
|         }
 | |
| 
 | |
|         if (snapshot.ResetAfter.HasValue)
 | |
|         {
 | |
|             _rateLimitResetSeconds.Record(snapshot.ResetAfter.Value.TotalSeconds, tags);
 | |
|         }
 | |
| 
 | |
|         if (TryCalculateHeadroom(snapshot, out var headroom))
 | |
|         {
 | |
|             _rateLimitHeadroomPct.Record(headroom, tags);
 | |
|         }
 | |
| 
 | |
|         lock (_rateLimitLock)
 | |
|         {
 | |
|             _lastRateLimitSnapshot = snapshot;
 | |
|             _rateLimitSnapshots[(snapshot.Phase, snapshot.Resource)] = snapshot;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     internal void RateLimitExhausted(string phase)
 | |
|         => _rateLimitExhausted.Add(1, new KeyValuePair<string, object?>("phase", phase));
 | |
| 
 | |
|     public void CanonicalMetricFallback(string canonicalMetricId, string severity)
 | |
|         => _canonicalMetricFallbacks.Add(
 | |
|             1,
 | |
|             new KeyValuePair<string, object?>("canonical_metric_id", canonicalMetricId),
 | |
|             new KeyValuePair<string, object?>("severity", severity),
 | |
|             new KeyValuePair<string, object?>("reason", "no_cvss"));
 | |
| 
 | |
|     internal GhsaRateLimitSnapshot? GetLastRateLimitSnapshot()
 | |
|     {
 | |
|         lock (_rateLimitLock)
 | |
|         {
 | |
|             return _lastRateLimitSnapshot;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private IEnumerable<Measurement<double>> ObserveHeadroom()
 | |
|     {
 | |
|         lock (_rateLimitLock)
 | |
|         {
 | |
|             if (_rateLimitSnapshots.Count == 0)
 | |
|             {
 | |
|                 yield break;
 | |
|             }
 | |
| 
 | |
|             foreach (var snapshot in _rateLimitSnapshots.Values)
 | |
|             {
 | |
|                 if (TryCalculateHeadroom(snapshot, out var headroom))
 | |
|                 {
 | |
|                     yield return new Measurement<double>(
 | |
|                         headroom,
 | |
|                         new KeyValuePair<string, object?>("phase", snapshot.Phase),
 | |
|                         new KeyValuePair<string, object?>("resource", snapshot.Resource ?? "unknown"));
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private static bool TryCalculateHeadroom(in GhsaRateLimitSnapshot snapshot, out double headroomPct)
 | |
|     {
 | |
|         headroomPct = 0;
 | |
|         if (!snapshot.Limit.HasValue || !snapshot.Remaining.HasValue)
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         var limit = snapshot.Limit.Value;
 | |
|         if (limit <= 0)
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         headroomPct = (double)snapshot.Remaining.Value / limit * 100d;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     public void Dispose()
 | |
|     {
 | |
|         _meter.Dispose();
 | |
|     }
 | |
| }
 |