Skip to content

Commit 2b2b5df

Browse files
sbroenneStefan BroennerCopilot
authored
fix(mcp-server): drop noisy HttpClient telemetry metrics (#725)
* fix(mcp-server): drop noisy HttpClient telemetry metrics Application Insights WorkerService SDK 3.x unconditionally subscribes to the .NET 8+ built-in 'System.Net.Http' meter with no opt-out via ApplicationInsightsServiceOptions. ExcelMcp.McpServer makes at most one lightweight HttpClient call per process (NuGetVersionChecker), so the connection-pool gauges/histograms were pure noise driving the large majority of AppMetrics ingestion cost. Adds OpenTelemetry metric Views (MetricStreamConfiguration.Drop) for the five noisy http.client.* instruments via ConfigureOpenTelemetryMeterProvider, which appends to the same MeterProviderBuilder already registered by AddApplicationInsightsTelemetryWorkerService. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 906eafcb-aa80-4690-83e0-0f98d21fbbe8 * fix(mcp-server): address Copilot review feedback - Make DroppedHttpClientMetricNames private (only used internally) - Assert ForceFlush() return value in ProgramTelemetryMetricsTests --------- Co-authored-by: Stefan Broenner <stbrnner@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 9f78255 commit 2b2b5df

5 files changed

Lines changed: 114 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"excelmcp": patch
3+
---
4+
5+
**Reduced MCP Server telemetry noise and cost.** The MCP Server no longer reports the .NET runtime's built-in HTTP-client connection-pool metrics (`http.client.open_connections`, `http.client.active_requests`, `http.client.connection.duration`, `http.client.request.time_in_queue`, `http.client.request.duration`) to Application Insights. These were emitted automatically by the telemetry SDK regardless of actual traffic and accounted for the large majority of telemetry ingestion volume, without providing any useful signal for this tool.

Directory.Packages.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
<PackageVersion Include="Microsoft.ApplicationInsights" Version="3.1.2" />
2424
<PackageVersion Include="OpenTelemetry.Api" Version="1.16.0" />
2525
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.16.0" />
26+
<!-- Test-only: verifies dropped-metric View configuration in ExcelMcp.McpServer.Tests -->
27+
<PackageVersion Include="OpenTelemetry" Version="1.16.0" />
28+
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.16.0" />
2629
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.16.0" />
2730
<PackageVersion Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.16.0" />
2831
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.8.1" />

src/ExcelMcp.McpServer/Program.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.Extensions.Hosting;
88
using Microsoft.Extensions.Logging;
99
using Microsoft.Extensions.Logging.Console;
10+
using OpenTelemetry.Metrics;
1011
using Sbroenne.ExcelMcp.McpServer.Telemetry;
1112

1213
namespace Sbroenne.ExcelMcp.McpServer;
@@ -349,10 +350,45 @@ private static void ConfigureTelemetry(HostApplicationBuilder builder)
349350

350351
builder.Services.AddApplicationInsightsTelemetryWorkerService(aiOptions);
351352

353+
// AddApplicationInsightsTelemetryWorkerService() unconditionally subscribes to the .NET 8+
354+
// built-in "System.Net.Http" meter (via UseApplicationInsightsTelemetry -> AddHttpClientMetrics),
355+
// with no opt-out exposed on ApplicationInsightsServiceOptions. ExcelMcp.McpServer makes at most
356+
// one lightweight HttpClient call per process (NuGetVersionChecker), so these connection-pool
357+
// gauges/histograms are pure noise - they were driving ~96% of billed AppMetrics ingestion.
358+
// ConfigureOpenTelemetryMeterProvider appends to the same MeterProviderBuilder already
359+
// registered above, so this reliably drops the instruments regardless of registration order.
360+
builder.Services.ConfigureOpenTelemetryMeterProvider(ConfigureDroppedHttpClientMetrics);
361+
352362
// Application Insights 3.x no longer exposes the classic telemetry initializer abstraction.
353363
// ExcelMcpTelemetry enriches each telemetry item with user/session/version context before sending.
354364
}
355365

366+
/// <summary>
367+
/// Names of the noisy .NET built-in HttpClient meter instruments that ExcelMcp.McpServer never
368+
/// consumes. See <see cref="ConfigureTelemetry"/> for why these must be dropped explicitly.
369+
/// </summary>
370+
private static readonly string[] DroppedHttpClientMetricNames =
371+
[
372+
"http.client.open_connections",
373+
"http.client.active_requests",
374+
"http.client.connection.duration",
375+
"http.client.request.time_in_queue",
376+
"http.client.request.duration",
377+
];
378+
379+
/// <summary>
380+
/// Adds metric Views that drop the noisy HttpClient instruments before export/aggregation.
381+
/// Exposed internally (rather than inlined) so it can be exercised directly in unit tests
382+
/// without needing a full Application Insights host pipeline.
383+
/// </summary>
384+
internal static void ConfigureDroppedHttpClientMetrics(MeterProviderBuilder metrics)
385+
{
386+
foreach (var name in DroppedHttpClientMetricNames)
387+
{
388+
metrics.AddView(name, MetricStreamConfiguration.Drop);
389+
}
390+
}
391+
356392
/// <summary>
357393
/// Registers global exception handlers to capture unhandled exceptions.
358394
/// </summary>

tests/ExcelMcp.McpServer.Tests/ExcelMcp.McpServer.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2424
</PackageReference>
2525
<PackageReference Include="Microsoft.NET.Test.Sdk" />
26+
<!-- Test-only: verifies OpenTelemetry metric View drop configuration for noisy HttpClient meters -->
27+
<PackageReference Include="OpenTelemetry" />
28+
<PackageReference Include="OpenTelemetry.Exporter.InMemory" />
2629
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers">
2730
<PrivateAssets>all</PrivateAssets>
2831
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System.Diagnostics.Metrics;
2+
using OpenTelemetry;
3+
using OpenTelemetry.Metrics;
4+
using Xunit;
5+
6+
namespace Sbroenne.ExcelMcp.McpServer.Tests.Unit;
7+
8+
/// <summary>
9+
/// Verifies that the noisy .NET built-in "System.Net.Http" meter instruments
10+
/// (http.client.open_connections, http.client.active_requests, etc.) are dropped
11+
/// from the OpenTelemetry metrics pipeline before export.
12+
///
13+
/// Background: Microsoft.ApplicationInsights.WorkerService 3.x unconditionally calls
14+
/// meterProviderBuilder.AddMeter("System.Net.Http") with no opt-out via
15+
/// ApplicationInsightsServiceOptions. Since ExcelMcp.McpServer makes at most one
16+
/// lightweight HttpClient call per process (NuGetVersionChecker), these connection-pool
17+
/// gauges/histograms were driving ~96% of billed Application Insights ingestion.
18+
/// </summary>
19+
[Trait("Layer", "McpServer")]
20+
[Trait("Category", "Unit")]
21+
[Trait("Feature", "Telemetry")]
22+
[Trait("Speed", "Fast")]
23+
public sealed class ProgramTelemetryMetricsTests
24+
{
25+
[Fact]
26+
public void ConfigureDroppedHttpClientMetrics_DropsAllNoisyHttpClientInstruments()
27+
{
28+
using var meter = new Meter(nameof(ConfigureDroppedHttpClientMetrics_DropsAllNoisyHttpClientInstruments));
29+
30+
var openConnections = meter.CreateUpDownCounter<long>("http.client.open_connections");
31+
var activeRequests = meter.CreateUpDownCounter<long>("http.client.active_requests");
32+
var requestDuration = meter.CreateHistogram<double>("http.client.request.duration");
33+
var connectionDuration = meter.CreateHistogram<double>("http.client.connection.duration");
34+
var timeInQueue = meter.CreateHistogram<double>("http.client.request.time_in_queue");
35+
var keptMetric = meter.CreateCounter<long>("tool.invocations");
36+
37+
var exportedMetrics = new List<Metric>();
38+
39+
var builder = Sdk.CreateMeterProviderBuilder()
40+
.AddMeter(meter.Name);
41+
42+
Program.ConfigureDroppedHttpClientMetrics(builder);
43+
44+
using var provider = builder
45+
.AddInMemoryExporter(exportedMetrics)
46+
.Build();
47+
48+
openConnections.Add(1);
49+
activeRequests.Add(1);
50+
requestDuration.Record(12.3);
51+
connectionDuration.Record(45.6);
52+
timeInQueue.Record(7.8);
53+
keptMetric.Add(1);
54+
55+
var flushed = provider.ForceFlush();
56+
Assert.True(flushed, "ForceFlush should succeed; a false result would make the metric assertions below misleading.");
57+
58+
var exportedNames = exportedMetrics.Select(m => m.Name).ToList();
59+
60+
Assert.DoesNotContain("http.client.open_connections", exportedNames);
61+
Assert.DoesNotContain("http.client.active_requests", exportedNames);
62+
Assert.DoesNotContain("http.client.request.duration", exportedNames);
63+
Assert.DoesNotContain("http.client.connection.duration", exportedNames);
64+
Assert.DoesNotContain("http.client.request.time_in_queue", exportedNames);
65+
Assert.Contains("tool.invocations", exportedNames);
66+
}
67+
}

0 commit comments

Comments
 (0)