save development progress

This commit is contained in:
StellaOps Bot
2025-12-25 23:09:58 +02:00
parent d71853ad7e
commit aa70af062e
351 changed files with 37683 additions and 150156 deletions

View File

@@ -5,7 +5,7 @@ using System.Reflection;
namespace StellaOps.Findings.Ledger.Observability;
internal static class LedgerMetrics
public static class LedgerMetrics
{
private static readonly Meter Meter = new("StellaOps.Findings.Ledger");
@@ -492,4 +492,178 @@ internal static class LedgerMetrics
private static string NormalizeRole(string role) => string.IsNullOrWhiteSpace(role) ? "unspecified" : role.ToLowerInvariant();
private static string NormalizeTenant(string? tenantId) => string.IsNullOrWhiteSpace(tenantId) ? string.Empty : tenantId;
// SPRINT_8200_0012_0004: Evidence-Weighted Score (EWS) Metrics
private static readonly Counter<long> EwsCalculationsTotal = Meter.CreateCounter<long>(
"ews_calculations_total",
description: "Total number of EWS calculations by result and bucket.");
private static readonly Histogram<double> EwsCalculationDurationSeconds = Meter.CreateHistogram<double>(
"ews_calculation_duration_seconds",
unit: "s",
description: "Duration of EWS score calculations.");
private static readonly Counter<long> EwsBatchCalculationsTotal = Meter.CreateCounter<long>(
"ews_batch_calculations_total",
description: "Total number of EWS batch calculations.");
private static readonly Histogram<double> EwsBatchSizeHistogram = Meter.CreateHistogram<double>(
"ews_batch_size",
description: "Distribution of EWS batch sizes.");
private static readonly Counter<long> EwsCacheHitsTotal = Meter.CreateCounter<long>(
"ews_cache_hits_total",
description: "Total EWS cache hits.");
private static readonly Counter<long> EwsCacheMissesTotal = Meter.CreateCounter<long>(
"ews_cache_misses_total",
description: "Total EWS cache misses.");
private static readonly Counter<long> EwsWebhooksDeliveredTotal = Meter.CreateCounter<long>(
"ews_webhooks_delivered_total",
description: "Total webhooks delivered by status.");
private static readonly Histogram<double> EwsWebhookDeliveryDurationSeconds = Meter.CreateHistogram<double>(
"ews_webhook_delivery_duration_seconds",
unit: "s",
description: "Duration of webhook delivery attempts.");
private static readonly ConcurrentDictionary<string, BucketDistributionSnapshot> EwsBucketDistributionByTenant = new(StringComparer.Ordinal);
private static readonly ObservableGauge<long> EwsBucketActNowGauge =
Meter.CreateObservableGauge("ews_bucket_distribution_act_now", ObserveEwsBucketActNow,
description: "Current count of findings in ActNow bucket by tenant.");
private static readonly ObservableGauge<long> EwsBucketScheduleNextGauge =
Meter.CreateObservableGauge("ews_bucket_distribution_schedule_next", ObserveEwsBucketScheduleNext,
description: "Current count of findings in ScheduleNext bucket by tenant.");
private static readonly ObservableGauge<long> EwsBucketInvestigateGauge =
Meter.CreateObservableGauge("ews_bucket_distribution_investigate", ObserveEwsBucketInvestigate,
description: "Current count of findings in Investigate bucket by tenant.");
private static readonly ObservableGauge<long> EwsBucketWatchlistGauge =
Meter.CreateObservableGauge("ews_bucket_distribution_watchlist", ObserveEwsBucketWatchlist,
description: "Current count of findings in Watchlist bucket by tenant.");
/// <summary>Records an EWS calculation.</summary>
public static void RecordEwsCalculation(
TimeSpan duration,
string? tenantId,
string? policyDigest,
string bucket,
string result,
bool fromCache)
{
var tags = new KeyValuePair<string, object?>[]
{
new("tenant", NormalizeTenant(tenantId)),
new("policy_digest", policyDigest ?? string.Empty),
new("bucket", bucket),
new("result", result),
new("from_cache", fromCache)
};
EwsCalculationsTotal.Add(1, tags);
EwsCalculationDurationSeconds.Record(duration.TotalSeconds, tags);
}
/// <summary>Records an EWS batch calculation.</summary>
public static void RecordEwsBatchCalculation(
TimeSpan duration,
string? tenantId,
int batchSize,
int succeeded,
int failed)
{
var tags = new KeyValuePair<string, object?>[]
{
new("tenant", NormalizeTenant(tenantId)),
new("succeeded", succeeded),
new("failed", failed)
};
EwsBatchCalculationsTotal.Add(1, tags);
EwsBatchSizeHistogram.Record(batchSize, new KeyValuePair<string, object?>("tenant", NormalizeTenant(tenantId)));
EwsCalculationDurationSeconds.Record(duration.TotalSeconds, tags);
}
/// <summary>Records an EWS cache hit.</summary>
public static void RecordEwsCacheHit(string? tenantId, string findingId)
{
EwsCacheHitsTotal.Add(1, new KeyValuePair<string, object?>("tenant", NormalizeTenant(tenantId)));
}
/// <summary>Records an EWS cache miss.</summary>
public static void RecordEwsCacheMiss(string? tenantId, string findingId)
{
EwsCacheMissesTotal.Add(1, new KeyValuePair<string, object?>("tenant", NormalizeTenant(tenantId)));
}
/// <summary>Records a webhook delivery attempt.</summary>
public static void RecordWebhookDelivery(TimeSpan duration, Guid webhookId, string status, int attempt)
{
var tags = new KeyValuePair<string, object?>[]
{
new("webhook_id", webhookId.ToString()),
new("status", status),
new("attempt", attempt)
};
EwsWebhooksDeliveredTotal.Add(1, tags);
EwsWebhookDeliveryDurationSeconds.Record(duration.TotalSeconds, tags);
}
/// <summary>Updates the EWS bucket distribution for a tenant.</summary>
public static void UpdateEwsBucketDistribution(
string tenantId,
int actNow,
int scheduleNext,
int investigate,
int watchlist)
{
var key = NormalizeTenant(tenantId);
EwsBucketDistributionByTenant[key] = new BucketDistributionSnapshot(key, actNow, scheduleNext, investigate, watchlist);
}
private sealed record BucketDistributionSnapshot(
string TenantId,
int ActNow,
int ScheduleNext,
int Investigate,
int Watchlist);
private static IEnumerable<Measurement<long>> ObserveEwsBucketActNow()
{
foreach (var kvp in EwsBucketDistributionByTenant)
{
yield return new Measurement<long>(kvp.Value.ActNow,
new KeyValuePair<string, object?>("tenant", kvp.Value.TenantId));
}
}
private static IEnumerable<Measurement<long>> ObserveEwsBucketScheduleNext()
{
foreach (var kvp in EwsBucketDistributionByTenant)
{
yield return new Measurement<long>(kvp.Value.ScheduleNext,
new KeyValuePair<string, object?>("tenant", kvp.Value.TenantId));
}
}
private static IEnumerable<Measurement<long>> ObserveEwsBucketInvestigate()
{
foreach (var kvp in EwsBucketDistributionByTenant)
{
yield return new Measurement<long>(kvp.Value.Investigate,
new KeyValuePair<string, object?>("tenant", kvp.Value.TenantId));
}
}
private static IEnumerable<Measurement<long>> ObserveEwsBucketWatchlist()
{
foreach (var kvp in EwsBucketDistributionByTenant)
{
yield return new Measurement<long>(kvp.Value.Watchlist,
new KeyValuePair<string, object?>("tenant", kvp.Value.TenantId));
}
}
}