Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2a349a7

Browse filesBrowse files
sverdelEraYaN
sverdel
authored andcommitted
add histogram to grpc
1 parent ea794f6 commit 2a349a7
Copy full SHA for 2a349a7

10 files changed

+183
-3
lines changed

‎Benchmark.NetCore/Benchmark.NetCore.csproj

Copy file name to clipboardExpand all lines: Benchmark.NetCore/Benchmark.NetCore.csproj
+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
</ItemGroup>
3131

3232
<ItemGroup>
33+
<ProjectReference Include="..\Prometheus.AspNetCore.Grpc\Prometheus.AspNetCore.Grpc.csproj" />
3334
<ProjectReference Include="..\Prometheus.AspNetCore\Prometheus.AspNetCore.csproj" />
3435
</ItemGroup>
3536

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using BenchmarkDotNet.Attributes;
2+
using Microsoft.AspNetCore.Http;
3+
using Prometheus;
4+
using System.Threading.Tasks;
5+
using Grpc.AspNetCore.Server;
6+
using Grpc.Core;
7+
8+
namespace Benchmark.NetCore
9+
{
10+
[MemoryDiagnoser]
11+
public class GrpcExporterBenchmarks
12+
{
13+
private CollectorRegistry _registry;
14+
private MetricFactory _factory;
15+
private GrpcRequestCountMiddleware _countMiddleware;
16+
private GrpcRequestDurationMiddleware _durationMiddleware;
17+
private DefaultHttpContext _ctx;
18+
19+
[Params(1000, 10000)]
20+
public int RequestCount { get; set; }
21+
22+
[GlobalSetup]
23+
public void Setup()
24+
{
25+
_ctx = new DefaultHttpContext();
26+
_ctx.SetEndpoint(new Endpoint(
27+
ctx => Task.CompletedTask,
28+
new EndpointMetadataCollection(new GrpcMethodMetadata(typeof(int),
29+
new Method<object, object>(MethodType.Unary,
30+
"test",
31+
"test",
32+
new Marshaller<object>(o => new byte[0], c => null),
33+
new Marshaller<object>(o => new byte[0], c => null)))),
34+
"test"));
35+
_registry = Metrics.NewCustomRegistry();
36+
_factory = Metrics.WithCustomRegistry(_registry);
37+
38+
_countMiddleware = new GrpcRequestCountMiddleware(next => Task.CompletedTask, new GrpcRequestCountOptions
39+
{
40+
Counter = _factory.CreateCounter("count", "help")
41+
});
42+
_durationMiddleware = new GrpcRequestDurationMiddleware(next => Task.CompletedTask, new GrpcRequestDurationOptions
43+
{
44+
Histogram = _factory.CreateHistogram("duration", "help")
45+
});
46+
}
47+
48+
[Benchmark]
49+
public async Task GrpcRequestCount()
50+
{
51+
for (var i = 0; i < RequestCount; i++)
52+
await _countMiddleware.Invoke(_ctx);
53+
}
54+
55+
[Benchmark]
56+
public async Task GrpcRequestDuration()
57+
{
58+
for (var i = 0; i < RequestCount; i++)
59+
await _durationMiddleware.Invoke(_ctx);
60+
}
61+
}
62+
}

‎Prometheus.AspNetCore.Grpc/GrpcMetricsMiddlewareExtensions.cs

Copy file name to clipboardExpand all lines: Prometheus.AspNetCore.Grpc/GrpcMetricsMiddlewareExtensions.cs
+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public static IApplicationBuilder UseGrpcMetrics(this IApplicationBuilder app,
2929
app.UseMiddleware<GrpcRequestCountMiddleware>(options.RequestCount);
3030
}
3131

32+
if (options.RequestDuration.Enabled)
33+
{
34+
app.UseMiddleware<GrpcRequestDurationMiddleware>(options.RequestDuration);
35+
}
36+
3237
return app;
3338
}
3439
}

‎Prometheus.AspNetCore.Grpc/GrpcMiddlewareExporterOptions.cs

Copy file name to clipboardExpand all lines: Prometheus.AspNetCore.Grpc/GrpcMiddlewareExporterOptions.cs
+2
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
public sealed class GrpcMiddlewareExporterOptions
44
{
55
public GrpcRequestCountOptions RequestCount { get; set; } = new GrpcRequestCountOptions();
6+
7+
public GrpcRequestDurationOptions RequestDuration { get; set; } = new GrpcRequestDurationOptions();
68
}
79
}

‎Prometheus.AspNetCore.Grpc/GrpcRequestCountMiddleware.cs

Copy file name to clipboardExpand all lines: Prometheus.AspNetCore.Grpc/GrpcRequestCountMiddleware.cs
+11-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ public GrpcRequestCountMiddleware(RequestDelegate next, GrpcRequestCountOptions?
1717

1818
public async Task Invoke(HttpContext context)
1919
{
20-
CreateChild(context)?.Inc();
21-
22-
await _next(context);
20+
try
21+
{
22+
await _next(context);
23+
}
24+
finally
25+
{
26+
// We need to record metrics after inner handler execution because routing data in
27+
// ASP.NET Core 2 is only available *after* executing the next request delegate.
28+
// So we would not have the right labels if we tried to create the child early on.
29+
CreateChild(context)?.Inc();
30+
}
2331
}
2432

2533
protected override string[] DefaultLabels => GrpcRequestLabelNames.All;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Http;
5+
using Prometheus.HttpMetrics;
6+
7+
namespace Prometheus
8+
{
9+
internal sealed class GrpcRequestDurationMiddleware : GrpcRequestMiddlewareBase<ICollector<IHistogram>, IHistogram>
10+
{
11+
private readonly RequestDelegate _next;
12+
13+
public GrpcRequestDurationMiddleware(RequestDelegate next, GrpcRequestDurationOptions? options)
14+
: base(options, options?.Histogram)
15+
{
16+
_next = next ?? throw new ArgumentNullException(nameof(next));
17+
}
18+
19+
public async Task Invoke(HttpContext context)
20+
{
21+
var stopWatch = Stopwatch.StartNew();
22+
23+
// We need to write this out in long form instead of using a timer because routing data in
24+
// ASP.NET Core 2 is only available *after* executing the next request delegate.
25+
// So we would not have the right labels if we tried to create the child early on.
26+
try
27+
{
28+
await _next(context);
29+
}
30+
finally
31+
{
32+
stopWatch.Stop();
33+
34+
CreateChild(context)?.Observe(stopWatch.Elapsed.TotalSeconds);
35+
}
36+
}
37+
38+
protected override string[] DefaultLabels => GrpcRequestLabelNames.NoStatusSpecific;
39+
40+
protected override ICollector<IHistogram> CreateMetricInstance(string[] labelNames) => MetricFactory.CreateHistogram(
41+
"grpc_request_duration_seconds",
42+
"The duration of gRPC requests processed by an ASP.NET Core application.",
43+
new HistogramConfiguration
44+
{
45+
// 1 ms to 32K ms buckets
46+
Buckets = Histogram.ExponentialBuckets(0.001, 2, 16),
47+
LabelNames = labelNames
48+
});
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Prometheus
2+
{
3+
public sealed class GrpcRequestDurationOptions : GrpcMetricsOptionsBase
4+
{
5+
/// <summary>
6+
/// Set this to use a custom metric instead of the default.
7+
/// </summary>
8+
public ICollector<IHistogram>? Histogram { get; set; }
9+
}
10+
}

‎Prometheus.AspNetCore.Grpc/GrpcRequestLabelNames.cs

Copy file name to clipboardExpand all lines: Prometheus.AspNetCore.Grpc/GrpcRequestLabelNames.cs
+8
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ public static class GrpcRequestLabelNames
77
{
88
public const string Service = "service";
99
public const string Method = "method";
10+
public const string Status = "status";
1011

1112
public static readonly string[] All =
13+
{
14+
Service,
15+
Method,
16+
Status,
17+
};
18+
19+
public static readonly string[] NoStatusSpecific =
1220
{
1321
Service,
1422
Method,

‎Prometheus.AspNetCore.Grpc/GrpcRequestMiddlewareBase.cs

Copy file name to clipboardExpand all lines: Prometheus.AspNetCore.Grpc/GrpcRequestMiddlewareBase.cs
+6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using Grpc.AspNetCore.Server;
2+
using Grpc.Core;
3+
24
using Microsoft.AspNetCore.Http;
35

46
namespace Prometheus
@@ -76,6 +78,10 @@ protected TChild CreateChild(HttpContext context, GrpcMethodMetadata metadata)
7678
case GrpcRequestLabelNames.Method:
7779
labelValues[i] = metadata.Method.Name;
7880
break;
81+
case GrpcRequestLabelNames.Status:
82+
labelValues[i] = context.Response?.GetStatusCode().ToString() ?? StatusCode.OK.ToString();
83+
break;
84+
7985
default:
8086
// Should never reach this point because we validate in ctor.
8187
throw new NotSupportedException($"Unexpected label name on {_metric.Name}: {_metric.LabelNames[i]}");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Linq;
2+
using Grpc.Core;
3+
using Microsoft.AspNetCore.Http;
4+
5+
namespace Prometheus
6+
{
7+
internal static class HttpResponseExtensions
8+
{
9+
private const string _grpcStatus = "grpc-status";
10+
11+
public static StatusCode GetStatusCode(this HttpResponse response)
12+
{
13+
var headerExists = response.Headers.TryGetValue(_grpcStatus, out var header);
14+
15+
if (!headerExists && response.StatusCode == StatusCodes.Status200OK)
16+
{
17+
return StatusCode.OK;
18+
}
19+
20+
if (header.Any() && int.TryParse(header.FirstOrDefault(), out var status))
21+
{
22+
return (StatusCode)status;
23+
}
24+
25+
return StatusCode.OK;
26+
}
27+
}
28+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.