< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Diagnostics.PipelineMetrics
Assembly: NexusLabs.Needlr.AgentFramework
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/Diagnostics/PipelineMetrics.cs
Line coverage
100%
Covered lines: 76
Uncovered lines: 0
Coverable lines: 76
Total lines: 166
Line coverage: 100%
Branch coverage
100%
Covered branches: 18
Total branches: 18
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
.ctor(...)100%11100%
get_ActivitySource()100%11100%
RecordPipelineStarted(...)100%11100%
RecordPipelineCompleted(...)100%22100%
RecordStageCompleted(...)100%1010100%
EmitTokenCounts(...)100%11100%
EmitTokenKind(...)100%22100%
EmitFailedToolCalls(...)100%44100%
Dispose()100%11100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/Diagnostics/PipelineMetrics.cs

#LineLine coverage
 1using System.Diagnostics;
 2using System.Diagnostics.Metrics;
 3
 4namespace NexusLabs.Needlr.AgentFramework.Diagnostics;
 5
 6/// <summary>
 7/// Default <see cref="IPipelineMetrics"/> implementation using <see cref="Meter"/>
 8/// for counters/histograms and <see cref="ActivitySource"/> for distributed
 9/// tracing spans. Compatible with OpenTelemetry — both metrics and traces are
 10/// exported when listeners are registered against the configured source name.
 11/// </summary>
 12/// <remarks>
 13/// Source names default to <c>"NexusLabs.Needlr.AgentFramework.Pipelines"</c> but
 14/// can be overridden via <see cref="PipelineMetricsOptions.MeterName"/> and
 15/// <see cref="PipelineMetricsOptions.ActivitySourceName"/> to match consumers'
 16/// existing dashboard queries.
 17/// </remarks>
 18[DoNotAutoRegister]
 19internal sealed class PipelineMetrics : IPipelineMetrics, IDisposable
 20{
 21    private readonly Meter _meter;
 22    private readonly ActivitySource _activitySource;
 23    private readonly Counter<long> _runsStarted;
 24    private readonly Counter<long> _runsCompleted;
 25    private readonly Histogram<double> _runDuration;
 26    private readonly Counter<long> _stagesCompleted;
 27    private readonly Histogram<double> _stageDuration;
 28    private readonly Counter<long> _stageTokens;
 29    private readonly Counter<long> _stageToolFailed;
 30
 631    public PipelineMetrics() : this(new PipelineMetricsOptions()) { }
 32
 3233    public PipelineMetrics(PipelineMetricsOptions options)
 34    {
 3235        ArgumentNullException.ThrowIfNull(options);
 36
 3237        _meter = new Meter(options.MeterName);
 3238        _activitySource = new ActivitySource(options.ResolvedActivitySourceName);
 39
 3240        _runsStarted = _meter.CreateCounter<long>(
 3241            "pipeline.run.started",
 3242            description: "Pipeline runs started");
 43
 3244        _runsCompleted = _meter.CreateCounter<long>(
 3245            "pipeline.run.completed",
 3246            description: "Pipeline runs completed");
 47
 3248        _runDuration = _meter.CreateHistogram<double>(
 3249            "pipeline.run.duration",
 3250            unit: "s",
 3251            description: "Pipeline run execution duration");
 52
 3253        _stagesCompleted = _meter.CreateCounter<long>(
 3254            "pipeline.stage.completed",
 3255            description: "Pipeline stages completed");
 56
 3257        _stageDuration = _meter.CreateHistogram<double>(
 3258            "pipeline.stage.duration",
 3259            unit: "s",
 3260            description: "Pipeline stage execution duration");
 61
 3262        _stageTokens = _meter.CreateCounter<long>(
 3263            "pipeline.stage.tokens",
 3264            description: "Tokens consumed by a pipeline stage, broken down by token kind");
 65
 3266        _stageToolFailed = _meter.CreateCounter<long>(
 3267            "pipeline.stage.tool.failed",
 3268            description: "Failed tool invocations in a pipeline stage");
 3269    }
 70
 71    /// <inheritdoc />
 1072    public ActivitySource ActivitySource => _activitySource;
 73
 74    /// <inheritdoc />
 75    public void RecordPipelineStarted(string pipelineName) =>
 376        _runsStarted.Add(1, new KeyValuePair<string, object?>("pipeline_name", pipelineName));
 77
 78    /// <inheritdoc />
 79    public void RecordPipelineCompleted(string pipelineName, bool succeeded, TimeSpan duration)
 80    {
 481        var pipelineTag = new KeyValuePair<string, object?>("pipeline_name", pipelineName);
 482        var outcomeTag = new KeyValuePair<string, object?>("outcome", succeeded ? "Succeeded" : "Failed");
 83
 484        _runsCompleted.Add(1, pipelineTag, outcomeTag);
 485        _runDuration.Record(duration.TotalSeconds, pipelineTag, outcomeTag);
 486    }
 87
 88    /// <inheritdoc />
 89    public void RecordStageCompleted(string pipelineName, IAgentStageResult stage, TimeSpan duration)
 90    {
 2491        ArgumentNullException.ThrowIfNull(stage);
 92
 2393        var pipelineTag = new KeyValuePair<string, object?>("pipeline_name", pipelineName);
 2394        var stageTag = new KeyValuePair<string, object?>("stage_name", stage.AgentName);
 2395        var phaseTag = new KeyValuePair<string, object?>("phase_name", stage.PhaseName ?? "(none)");
 2396        var outcomeTag = new KeyValuePair<string, object?>("outcome", stage.Outcome.ToString());
 2397        var terminationTag = new KeyValuePair<string, object?>(
 2398            "termination_cause",
 2399            stage.Termination?.ToTagValue() ?? "Unspecified");
 100
 23101        _stagesCompleted.Add(1, pipelineTag, stageTag, outcomeTag, terminationTag, phaseTag);
 102
 23103        if (stage.Outcome == StageOutcome.Skipped)
 2104            return;
 105
 21106        _stageDuration.Record(duration.TotalSeconds, pipelineTag, stageTag, outcomeTag, phaseTag);
 107
 21108        if (stage.Diagnostics is { } diagnostics)
 109        {
 3110            EmitTokenCounts(diagnostics.AggregateTokenUsage, pipelineTag, stageTag);
 3111            EmitFailedToolCalls(diagnostics.ToolCalls, pipelineTag, stageTag);
 112        }
 21113    }
 114
 115    private void EmitTokenCounts(
 116        TokenUsage tokens,
 117        KeyValuePair<string, object?> pipelineTag,
 118        KeyValuePair<string, object?> stageTag)
 119    {
 3120        EmitTokenKind(tokens.InputTokens, "input", pipelineTag, stageTag);
 3121        EmitTokenKind(tokens.OutputTokens, "output", pipelineTag, stageTag);
 3122        EmitTokenKind(tokens.CachedInputTokens, "cached_input", pipelineTag, stageTag);
 3123        EmitTokenKind(tokens.ReasoningTokens, "reasoning", pipelineTag, stageTag);
 3124    }
 125
 126    private void EmitTokenKind(
 127        long count,
 128        string kind,
 129        KeyValuePair<string, object?> pipelineTag,
 130        KeyValuePair<string, object?> stageTag)
 131    {
 12132        if (count <= 0)
 7133            return;
 134
 5135        _stageTokens.Add(
 5136            count,
 5137            pipelineTag,
 5138            stageTag,
 5139            new KeyValuePair<string, object?>("token_kind", kind));
 5140    }
 141
 142    private void EmitFailedToolCalls(
 143        IReadOnlyList<ToolCallDiagnostics> toolCalls,
 144        KeyValuePair<string, object?> pipelineTag,
 145        KeyValuePair<string, object?> stageTag)
 146    {
 14147        foreach (var tool in toolCalls)
 148        {
 4149            if (tool.Succeeded)
 150                continue;
 151
 3152            _stageToolFailed.Add(
 3153                1,
 3154                pipelineTag,
 3155                stageTag,
 3156                new KeyValuePair<string, object?>("tool_name", tool.ToolName));
 157        }
 3158    }
 159
 160    /// <summary>Disposes the underlying <see cref="Meter"/> and <see cref="ActivitySource"/>.</summary>
 161    public void Dispose()
 162    {
 30163        _meter.Dispose();
 30164        _activitySource.Dispose();
 30165    }
 166}