Files
git.stella-ops.org/src/Cli/StellaOps.Cli/Telemetry/CliMetrics.cs
StellaOps Bot 3b96b2e3ea
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
up
2025-11-27 23:45:09 +02:00

184 lines
8.7 KiB
C#

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<long> ScannerDownloadCounter = Meter.CreateCounter<long>("stellaops.cli.scanner.download.count");
private static readonly Counter<long> ScannerInstallCounter = Meter.CreateCounter<long>("stellaops.cli.scanner.install.count");
private static readonly Counter<long> ScanRunCounter = Meter.CreateCounter<long>("stellaops.cli.scan.run.count");
private static readonly Counter<long> OfflineKitDownloadCounter = Meter.CreateCounter<long>("stellaops.cli.offline.kit.download.count");
private static readonly Counter<long> OfflineKitImportCounter = Meter.CreateCounter<long>("stellaops.cli.offline.kit.import.count");
private static readonly Counter<long> PolicySimulationCounter = Meter.CreateCounter<long>("stellaops.cli.policy.simulate.count");
private static readonly Counter<long> TaskRunnerSimulationCounter = Meter.CreateCounter<long>("stellaops.cli.taskrunner.simulate.count");
private static readonly Counter<long> PolicyActivationCounter = Meter.CreateCounter<long>("stellaops.cli.policy.activate.count");
private static readonly Counter<long> SourcesDryRunCounter = Meter.CreateCounter<long>("stellaops.cli.sources.dryrun.count");
private static readonly Counter<long> AocVerifyCounter = Meter.CreateCounter<long>("stellaops.cli.aoc.verify.count");
private static readonly Counter<long> PolicyFindingsListCounter = Meter.CreateCounter<long>("stellaops.cli.policy.findings.list.count");
private static readonly Counter<long> PolicyFindingsGetCounter = Meter.CreateCounter<long>("stellaops.cli.policy.findings.get.count");
private static readonly Counter<long> PolicyFindingsExplainCounter = Meter.CreateCounter<long>("stellaops.cli.policy.findings.explain.count");
private static readonly Counter<long> AdvisoryRunCounter = Meter.CreateCounter<long>("stellaops.cli.advisory.run.count");
private static readonly Counter<long> NodeLockValidateCounter = Meter.CreateCounter<long>("stellaops.cli.node.lock_validate.count");
private static readonly Counter<long> PythonLockValidateCounter = Meter.CreateCounter<long>("stellaops.cli.python.lock_validate.count");
private static readonly Counter<long> JavaLockValidateCounter = Meter.CreateCounter<long>("stellaops.cli.java.lock_validate.count");
private static readonly Counter<long> RubyInspectCounter = Meter.CreateCounter<long>("stellaops.cli.ruby.inspect.count");
private static readonly Counter<long> RubyResolveCounter = Meter.CreateCounter<long>("stellaops.cli.ruby.resolve.count");
private static readonly Counter<long> PhpInspectCounter = Meter.CreateCounter<long>("stellaops.cli.php.inspect.count");
private static readonly Histogram<double> CommandDurationHistogram = Meter.CreateHistogram<double>("stellaops.cli.command.duration.ms");
public static void RecordScannerDownload(string channel, bool fromCache)
=> ScannerDownloadCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("channel", channel),
new("cache", fromCache ? "hit" : "miss")
});
public static void RecordScannerInstall(string channel)
=> ScannerInstallCounter.Add(1, new KeyValuePair<string, object?>[] { new("channel", channel) });
public static void RecordScanRun(string runner, int exitCode)
=> ScanRunCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("runner", runner),
new("exit_code", exitCode)
});
public static void RecordOfflineKitDownload(string kind, bool fromCache)
=> OfflineKitDownloadCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("kind", string.IsNullOrWhiteSpace(kind) ? "unknown" : kind),
new("cache", fromCache ? "hit" : "miss")
});
public static void RecordOfflineKitImport(string? status)
=> OfflineKitImportCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("status", string.IsNullOrWhiteSpace(status) ? "queued" : status)
});
public static void RecordPolicySimulation(string outcome)
=> PolicySimulationCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordTaskRunnerSimulation(string outcome)
=> TaskRunnerSimulationCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordPolicyActivation(string outcome)
=> PolicyActivationCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordAdvisoryRun(string taskType, string outcome)
=> AdvisoryRunCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("task", string.IsNullOrWhiteSpace(taskType) ? "unknown" : taskType.ToLowerInvariant()),
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordSourcesDryRun(string status)
=> SourcesDryRunCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("status", string.IsNullOrWhiteSpace(status) ? "unknown" : status)
});
public static void RecordAocVerify(string outcome)
=> AocVerifyCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordPolicyFindingsList(string outcome)
=> PolicyFindingsListCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordPolicyFindingsGet(string outcome)
=> PolicyFindingsGetCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordPolicyFindingsExplain(string outcome)
=> PolicyFindingsExplainCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordNodeLockValidate(string outcome)
=> NodeLockValidateCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordPythonLockValidate(string outcome)
=> PythonLockValidateCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordJavaLockValidate(string outcome)
=> JavaLockValidateCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordRubyInspect(string outcome)
=> RubyInspectCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordRubyResolve(string outcome)
=> RubyResolveCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordPhpInspect(string outcome)
=> PhpInspectCounter.Add(1, new KeyValuePair<string, object?>[]
{
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<string, object?>[] { new("command", _command) });
}
}
}