using System; using System.Diagnostics.Metrics; namespace StellaOps.Cli.Telemetry; internal static class CliMetrics { private static readonly Meter Meter = new("StellaOps.Cli", "1.0.0"); private static readonly Counter ScannerDownloadCounter = Meter.CreateCounter("stellaops.cli.scanner.download.count"); private static readonly Counter ScannerInstallCounter = Meter.CreateCounter("stellaops.cli.scanner.install.count"); private static readonly Counter ScanRunCounter = Meter.CreateCounter("stellaops.cli.scan.run.count"); private static readonly Counter OfflineKitDownloadCounter = Meter.CreateCounter("stellaops.cli.offline.kit.download.count"); private static readonly Counter OfflineKitImportCounter = Meter.CreateCounter("stellaops.cli.offline.kit.import.count"); private static readonly Counter PolicySimulationCounter = Meter.CreateCounter("stellaops.cli.policy.simulate.count"); private static readonly Counter PolicyActivationCounter = Meter.CreateCounter("stellaops.cli.policy.activate.count"); private static readonly Counter SourcesDryRunCounter = Meter.CreateCounter("stellaops.cli.sources.dryrun.count"); private static readonly Counter AocVerifyCounter = Meter.CreateCounter("stellaops.cli.aoc.verify.count"); private static readonly Counter PolicyFindingsListCounter = Meter.CreateCounter("stellaops.cli.policy.findings.list.count"); private static readonly Counter PolicyFindingsGetCounter = Meter.CreateCounter("stellaops.cli.policy.findings.get.count"); private static readonly Counter PolicyFindingsExplainCounter = Meter.CreateCounter("stellaops.cli.policy.findings.explain.count"); private static readonly Histogram CommandDurationHistogram = Meter.CreateHistogram("stellaops.cli.command.duration.ms"); public static void RecordScannerDownload(string channel, bool fromCache) => ScannerDownloadCounter.Add(1, new KeyValuePair[] { new("channel", channel), new("cache", fromCache ? "hit" : "miss") }); public static void RecordScannerInstall(string channel) => ScannerInstallCounter.Add(1, new KeyValuePair[] { new("channel", channel) }); public static void RecordScanRun(string runner, int exitCode) => ScanRunCounter.Add(1, new KeyValuePair[] { new("runner", runner), new("exit_code", exitCode) }); public static void RecordOfflineKitDownload(string kind, bool fromCache) => OfflineKitDownloadCounter.Add(1, new KeyValuePair[] { new("kind", string.IsNullOrWhiteSpace(kind) ? "unknown" : kind), new("cache", fromCache ? "hit" : "miss") }); public static void RecordOfflineKitImport(string? status) => OfflineKitImportCounter.Add(1, new KeyValuePair[] { new("status", string.IsNullOrWhiteSpace(status) ? "queued" : status) }); public static void RecordPolicySimulation(string outcome) => PolicySimulationCounter.Add(1, new KeyValuePair[] { new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) }); public static void RecordPolicyActivation(string outcome) => PolicyActivationCounter.Add(1, new KeyValuePair[] { new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) }); public static void RecordSourcesDryRun(string status) => SourcesDryRunCounter.Add(1, new KeyValuePair[] { new("status", string.IsNullOrWhiteSpace(status) ? "unknown" : status) }); public static void RecordAocVerify(string outcome) => AocVerifyCounter.Add(1, new KeyValuePair[] { new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) }); public static void RecordPolicyFindingsList(string outcome) => PolicyFindingsListCounter.Add(1, new KeyValuePair[] { new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) }); public static void RecordPolicyFindingsGet(string outcome) => PolicyFindingsGetCounter.Add(1, new KeyValuePair[] { new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) }); public static void RecordPolicyFindingsExplain(string outcome) => PolicyFindingsExplainCounter.Add(1, new KeyValuePair[] { new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) }); public static IDisposable MeasureCommandDuration(string command) { var start = DateTime.UtcNow; return new DurationScope(command, start); } private sealed class DurationScope : IDisposable { private readonly string _command; private readonly DateTime _start; private bool _disposed; public DurationScope(string command, DateTime start) { _command = command; _start = start; } public void Dispose() { if (_disposed) { return; } _disposed = true; var elapsed = (DateTime.UtcNow - _start).TotalMilliseconds; CommandDurationHistogram.Record(elapsed, new KeyValuePair[] { new("command", _command) }); } } }