< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Diagnostics.AgentMetrics
Assembly: NexusLabs.Needlr.AgentFramework
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework/Diagnostics/AgentMetrics.cs
Line coverage
100%
Covered lines: 67
Uncovered lines: 0
Coverable lines: 67
Total lines: 148
Line coverage: 100%
Branch coverage
100%
Covered branches: 14
Total branches: 14
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%
RecordRunStarted(...)100%11100%
RecordRunCompleted(...)100%66100%
RecordToolCall(...)100%44100%
RecordChatCompletion(...)100%44100%
Dispose()100%11100%

File(s)

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

#LineLine coverage
 1using System.Diagnostics;
 2using System.Diagnostics.Metrics;
 3
 4namespace NexusLabs.Needlr.AgentFramework.Diagnostics;
 5
 6/// <summary>
 7/// Default <see cref="IAgentMetrics"/> implementation using <see cref="Meter"/>
 8/// for counters/histograms and <see cref="System.Diagnostics.ActivitySource"/> for
 9/// distributed tracing spans. Compatible with OpenTelemetry — both metrics and traces
 10/// are exported when listeners are registered.
 11/// </summary>
 12/// <remarks>
 13/// Source names default to <c>"NexusLabs.Needlr.AgentFramework"</c> but can be
 14/// overridden via <see cref="AgentFrameworkMetricsOptions.MeterName"/> and
 15/// <see cref="AgentFrameworkMetricsOptions.ActivitySourceName"/> to match consumers'
 16/// existing dashboard queries.
 17/// </remarks>
 18[DoNotAutoRegister]
 19internal sealed class AgentMetrics : IAgentMetrics, 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> _tokensUsed;
 27    private readonly Counter<long> _toolCallsCompleted;
 28    private readonly Histogram<double> _toolCallDuration;
 29    private readonly Histogram<double> _chatCompletionDuration;
 30
 5031    public AgentMetrics() : this(new AgentFrameworkMetricsOptions()) { }
 32
 8733    public AgentMetrics(AgentFrameworkMetricsOptions options)
 34    {
 8735        ArgumentNullException.ThrowIfNull(options);
 36
 8737        _meter = new Meter(options.MeterName);
 8738        _activitySource = new ActivitySource(options.ResolvedActivitySourceName);
 39
 8740        _runsStarted = _meter.CreateCounter<long>(
 8741            "agent.run.started",
 8742            description: "Agent runs started");
 43
 8744        _runsCompleted = _meter.CreateCounter<long>(
 8745            "agent.run.completed",
 8746            description: "Agent runs completed");
 47
 8748        _runDuration = _meter.CreateHistogram<double>(
 8749            "agent.run.duration",
 8750            unit: "s",
 8751            description: "Agent run execution duration");
 52
 8753        _tokensUsed = _meter.CreateCounter<long>(
 8754            "agent.tokens.used",
 8755            description: "Tokens consumed by agent runs");
 56
 8757        _toolCallsCompleted = _meter.CreateCounter<long>(
 8758            "agent.tool.completed",
 8759            description: "Agent tool calls completed");
 60
 8761        _toolCallDuration = _meter.CreateHistogram<double>(
 8762            "agent.tool.duration",
 8763            unit: "s",
 8764            description: "Agent tool call execution duration");
 65
 8766        _chatCompletionDuration = _meter.CreateHistogram<double>(
 8767            "agent.chat.duration",
 8768            unit: "s",
 8769            description: "Agent chat completion duration");
 8770    }
 71
 72    /// <inheritdoc />
 13873    public ActivitySource ActivitySource => _activitySource;
 74
 75    /// <inheritdoc />
 76    public void RecordRunStarted(string agentName) =>
 6077        _runsStarted.Add(1, new KeyValuePair<string, object?>("agent_name", agentName));
 78
 79    /// <inheritdoc />
 80    public void RecordRunCompleted(IAgentRunDiagnostics diagnostics)
 81    {
 5982        var status = diagnostics.Succeeded ? "success" : "failed";
 5983        KeyValuePair<string, object?> agentTag = new("agent_name", diagnostics.AgentName);
 5984        KeyValuePair<string, object?> statusTag = new("status", status);
 85
 5986        _runsCompleted.Add(1, agentTag, statusTag);
 5987        _runDuration.Record(diagnostics.TotalDuration.TotalSeconds, agentTag, statusTag);
 88
 5989        if (diagnostics.AggregateTokenUsage.InputTokens > 0)
 90        {
 2991            _tokensUsed.Add(
 2992                diagnostics.AggregateTokenUsage.InputTokens,
 2993                agentTag, new KeyValuePair<string, object?>("direction", "input"));
 94        }
 95
 5996        if (diagnostics.AggregateTokenUsage.OutputTokens > 0)
 97        {
 2998            _tokensUsed.Add(
 2999                diagnostics.AggregateTokenUsage.OutputTokens,
 29100                agentTag, new KeyValuePair<string, object?>("direction", "output"));
 101        }
 59102    }
 103
 104    /// <inheritdoc />
 105    public void RecordToolCall(string toolName, TimeSpan duration, bool succeeded, string? agentName = null)
 106    {
 16107        var status = succeeded ? "success" : "failed";
 16108        KeyValuePair<string, object?> toolTag = new("tool_name", toolName);
 16109        KeyValuePair<string, object?> statusTag = new("status", status);
 110
 16111        if (agentName is not null)
 112        {
 12113            KeyValuePair<string, object?> agentTag = new("agent_name", agentName);
 12114            _toolCallsCompleted.Add(1, toolTag, statusTag, agentTag);
 12115            _toolCallDuration.Record(duration.TotalSeconds, toolTag, statusTag, agentTag);
 116        }
 117        else
 118        {
 4119            _toolCallsCompleted.Add(1, toolTag, statusTag);
 4120            _toolCallDuration.Record(duration.TotalSeconds, toolTag, statusTag);
 121        }
 4122    }
 123
 124    /// <inheritdoc />
 125    public void RecordChatCompletion(string model, TimeSpan duration, bool succeeded, string? agentName = null)
 126    {
 78127        var status = succeeded ? "success" : "failed";
 78128        KeyValuePair<string, object?> modelTag = new("model", model);
 78129        KeyValuePair<string, object?> statusTag = new("status", status);
 130
 78131        if (agentName is not null)
 132        {
 61133            KeyValuePair<string, object?> agentTag = new("agent_name", agentName);
 61134            _chatCompletionDuration.Record(duration.TotalSeconds, modelTag, statusTag, agentTag);
 135        }
 136        else
 137        {
 17138            _chatCompletionDuration.Record(duration.TotalSeconds, modelTag, statusTag);
 139        }
 17140    }
 141
 142    /// <summary>Disposes the underlying <see cref="Meter"/> and <see cref="System.Diagnostics.ActivitySource"/>.</summa
 143    public void Dispose()
 144    {
 21145        _meter.Dispose();
 21146        _activitySource.Dispose();
 21147    }
 148}