Skip to content

Commit f944439

Browse files
committed
Address feedback
1 parent 493a7fb commit f944439

File tree

4 files changed

+94
-34
lines changed

4 files changed

+94
-34
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Diagnostics;
2+
using System.Diagnostics.Metrics;
3+
4+
namespace ModelContextProtocol;
5+
6+
internal static class Diagnostics
7+
{
8+
internal static ActivitySource ActivitySource { get; } = new("ModelContextProtocol");
9+
10+
/// <summary>
11+
/// Follows boundaries from http.server.request.duration/http.client.request.duration
12+
/// </summary>
13+
internal static InstrumentAdvice<double> ShortSecondsBucketBoundaries { get; } = new()
14+
{
15+
HistogramBucketBoundaries = [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10],
16+
};
17+
18+
/// <summary>
19+
/// Not based on a standard. Larger bucket sizes for longer lasting operations, e.g. HTTP connection duration.
20+
/// See https://github.com/open-telemetry/semantic-conventions/issues/336
21+
/// </summary>
22+
internal static InstrumentAdvice<double> LongSecondsBucketBoundaries { get; } = new()
23+
{
24+
HistogramBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300],
25+
};
26+
27+
internal static Meter Meter { get; } = new("ModelContextProtocol");
28+
29+
}

src/ModelContextProtocol/Protocol/Messages/RequestId.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ public RequestId(long value)
3434
/// <summary>Gets whether the identifier is uninitialized.</summary>
3535
public bool IsDefault => _id is null;
3636

37+
/// <summary>Gets the underlying object for this id.</summary>
38+
/// <remarks>This will either be a <see cref="string"/>, a boxed <see cref="long"/>, or <see langword="null"/>.</remarks>
39+
public object? Id => _id;
40+
3741
/// <inheritdoc />
3842
public override string ToString() =>
3943
_id is string stringValue ? stringValue :

src/ModelContextProtocol/Shared/McpSession.cs

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,15 @@ namespace ModelContextProtocol.Shared;
1919
/// </summary>
2020
internal sealed class McpSession : IDisposable
2121
{
22-
private static readonly ActivitySource s_activitySource = new("ModelContextProtocol");
22+
private static readonly Histogram<double> s_clientSessionDuration = Diagnostics.Meter.CreateHistogram<double>(
23+
"mcp.client.session.duration", "s", "Measures the duration of a client session.", advice: Diagnostics.LongSecondsBucketBoundaries);
24+
private static readonly Histogram<double> s_serverSessionDuration = Diagnostics.Meter.CreateHistogram<double>(
25+
"mcp.server.session.duration", "s", "Measures the duration of a server session.", advice: Diagnostics.LongSecondsBucketBoundaries);
2326

24-
private static readonly InstrumentAdvice<double> s_shortSecondsBucketBoundaries = new()
25-
{
26-
// Follows boundaries from http.server.request.duration/http.client.request.duration
27-
HistogramBucketBoundaries = [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10],
28-
};
29-
30-
private static readonly InstrumentAdvice<double> s_longSecondsBucketBoundaries = new()
31-
{
32-
// Not based on a standard. Larger bucket sizes for longer lasting operations, e.g. HTTP connection duration.
33-
// See https://github.com/open-telemetry/semantic-conventions/issues/336
34-
HistogramBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300],
35-
};
36-
37-
private static readonly Meter s_meter = new("ModelContextProtocol");
38-
private static readonly Histogram<double> s_clientSessionDuration = s_meter.CreateHistogram<double>(
39-
"mcp.client.session.duration", "s", "Measures the duration of a client session.", advice: s_longSecondsBucketBoundaries);
40-
private static readonly Histogram<double> s_serverSessionDuration = s_meter.CreateHistogram<double>(
41-
"mcp.server.session.duration", "s", "Measures the duration of a server session.", advice: s_longSecondsBucketBoundaries);
42-
private static readonly Histogram<double> s_serverRequestDuration = s_meter.CreateHistogram<double>(
43-
"rpc.server.duration", "s", "Measures the duration of inbound RPC.", advice: s_shortSecondsBucketBoundaries);
44-
private static readonly Histogram<double> s_clientRequestDuration = s_meter.CreateHistogram<double>(
45-
"rpc.client.duration", "s", "Measures the duration of outbound RPC.", advice: s_shortSecondsBucketBoundaries);
27+
private static readonly Histogram<double> s_serverRequestDuration = Diagnostics.Meter.CreateHistogram<double>(
28+
"rpc.server.duration", "s", "Measures the duration of inbound RPC.", advice: Diagnostics.ShortSecondsBucketBoundaries);
29+
private static readonly Histogram<double> s_clientRequestDuration = Diagnostics.Meter.CreateHistogram<double>(
30+
"rpc.client.duration", "s", "Measures the duration of outbound RPC.", advice: Diagnostics.ShortSecondsBucketBoundaries);
4631

4732
private readonly bool _isServer;
4833
private readonly string _transportKind;
@@ -198,12 +183,12 @@ private async Task HandleMessageAsync(IJsonRpcMessage message, CancellationToken
198183
string method = GetMethodName(message);
199184

200185
long? startingTimestamp = durationMetric.Enabled ? Stopwatch.GetTimestamp() : null;
201-
Activity? activity = s_activitySource.HasListeners() ?
202-
s_activitySource.StartActivity(CreateActivityName(method)) :
186+
Activity? activity = Diagnostics.ActivitySource.HasListeners() ?
187+
Diagnostics.ActivitySource.StartActivity(CreateActivityName(method)) :
203188
null;
204189

205190
TagList tags = default;
206-
bool addTags = activity is not null || startingTimestamp is not null;
191+
bool addTags = activity is { IsAllDataRequested: true } || startingTimestamp is not null;
207192
try
208193
{
209194
if (addTags)
@@ -341,8 +326,8 @@ public async Task<TResult> SendRequestAsync<TResult>(JsonRpcRequest request, Can
341326
string method = request.Method;
342327

343328
long? startingTimestamp = durationMetric.Enabled ? Stopwatch.GetTimestamp() : null;
344-
using Activity? activity = s_activitySource.HasListeners() ?
345-
s_activitySource.StartActivity(CreateActivityName(method)) :
329+
using Activity? activity = Diagnostics.ActivitySource.HasListeners() ?
330+
Diagnostics.ActivitySource.StartActivity(CreateActivityName(method)) :
346331
null;
347332

348333
// Set request ID
@@ -352,7 +337,7 @@ public async Task<TResult> SendRequestAsync<TResult>(JsonRpcRequest request, Can
352337
}
353338

354339
TagList tags = default;
355-
bool addTags = activity is not null || startingTimestamp is not null;
340+
bool addTags = activity is { IsAllDataRequested: true } || startingTimestamp is not null;
356341

357342
var tcs = new TaskCompletionSource<IJsonRpcMessage>(TaskCreationOptions.RunContinuationsAsynchronously);
358343
_pendingRequests[request.Id] = tcs;
@@ -434,12 +419,12 @@ public async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken ca
434419
string method = GetMethodName(message);
435420

436421
long? startingTimestamp = durationMetric.Enabled ? Stopwatch.GetTimestamp() : null;
437-
using Activity? activity = s_activitySource.HasListeners() ?
438-
s_activitySource.StartActivity(CreateActivityName(method)) :
422+
using Activity? activity = Diagnostics.ActivitySource.HasListeners() ?
423+
Diagnostics.ActivitySource.StartActivity(CreateActivityName(method)) :
439424
null;
440425

441426
TagList tags = default;
442-
bool addTags = activity is not null || startingTimestamp is not null;
427+
bool addTags = activity is { IsAllDataRequested: true } || startingTimestamp is not null;
443428

444429
try
445430
{
@@ -516,6 +501,7 @@ private string GetMethodName(IJsonRpcMessage message) =>
516501

517502
private void AddStandardTags(ref TagList tags, string method)
518503
{
504+
tags.Add("session.id", _id);
519505
tags.Add("rpc.system", "jsonrpc");
520506
tags.Add("rpc.jsonrpc.version", "2.0");
521507
tags.Add("rpc.method", method);
@@ -581,7 +567,7 @@ private static void FinalizeDiagnostics(
581567
durationMetric.Record(GetElapsed(startingTimestamp.Value).TotalSeconds, tags);
582568
}
583569

584-
if (activity is not null)
570+
if (activity is { IsAllDataRequested: true })
585571
{
586572
foreach (var tag in tags)
587573
{
@@ -600,7 +586,10 @@ public void Dispose()
600586
Histogram<double> durationMetric = _isServer ? s_serverSessionDuration : s_clientSessionDuration;
601587
if (durationMetric.Enabled)
602588
{
603-
durationMetric.Record(GetElapsed(_sessionStartingTimestamp).TotalSeconds);
589+
TagList tags = default;
590+
tags.Add("session.id", _id);
591+
tags.Add("network.transport", _transportKind);
592+
durationMetric.Record(GetElapsed(_sessionStartingTimestamp).TotalSeconds, tags);
604593
}
605594

606595
// Complete all pending requests with cancellation
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using ModelContextProtocol.Protocol.Messages;
2+
using System.Text.Json;
3+
4+
namespace ModelContextProtocol.Tests.Protocol;
5+
6+
public class RequestIdTests
7+
{
8+
[Fact]
9+
public void StringCtor_Roundtrips()
10+
{
11+
RequestId id = new("test-id");
12+
Assert.Equal("test-id", id.ToString());
13+
Assert.Equal("\"test-id\"", JsonSerializer.Serialize(id));
14+
Assert.Same("test-id", id.Id);
15+
16+
Assert.True(id.Equals(new("test-id")));
17+
Assert.False(id.Equals(new("tEst-id")));
18+
Assert.Equal("test-id".GetHashCode(), id.GetHashCode());
19+
20+
Assert.Equal(id, JsonSerializer.Deserialize<RequestId>(JsonSerializer.Serialize(id)));
21+
}
22+
23+
[Fact]
24+
public void Int64Ctor_Roundtrips()
25+
{
26+
RequestId id = new(42);
27+
Assert.Equal("42", id.ToString());
28+
Assert.Equal("42", JsonSerializer.Serialize(id));
29+
Assert.Equal(42, Assert.IsType<long>(id.Id));
30+
31+
Assert.True(id.Equals(new(42)));
32+
Assert.False(id.Equals(new(43)));
33+
Assert.False(id.Equals(new("42")));
34+
Assert.Equal(42L.GetHashCode(), id.GetHashCode());
35+
36+
Assert.Equal(id, JsonSerializer.Deserialize<RequestId>(JsonSerializer.Serialize(id)));
37+
}
38+
}

0 commit comments

Comments
 (0)