Skip to content

Commit 6f6a498

Browse files
committed
more
1 parent 9b75d60 commit 6f6a498

File tree

11 files changed

+166
-16
lines changed

11 files changed

+166
-16
lines changed

src/Components/Components/src/RenderTree/Renderer.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree;
2525
// dispatching events to them, and notifying when the user interface is being updated.
2626
public abstract partial class Renderer : IDisposable, IAsyncDisposable
2727
{
28-
private readonly RenderingMetrics? _renderingMetrics;
2928
private readonly object _lockObject = new();
3029
private readonly IServiceProvider _serviceProvider;
3130
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
@@ -35,6 +34,7 @@ public abstract partial class Renderer : IDisposable, IAsyncDisposable
3534
private readonly Dictionary<ulong, ulong> _eventHandlerIdReplacements = new Dictionary<ulong, ulong>();
3635
private readonly ILogger _logger;
3736
private readonly ComponentFactory _componentFactory;
37+
private readonly RenderingMetrics? _renderingMetrics;
3838
private Dictionary<int, ParameterView>? _rootComponentsLatestParameters;
3939
private Task? _ongoingQuiescenceTask;
4040

@@ -92,6 +92,7 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory,
9292
_logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Components.RenderTree.Renderer");
9393
_componentFactory = new ComponentFactory(componentActivator, this);
9494

95+
// TODO register RenderingMetrics as singleton in DI
9596
var meterFactory = serviceProvider.GetService<IMeterFactory>();
9697
_renderingMetrics = meterFactory != null ? new RenderingMetrics(meterFactory) : null;
9798

@@ -931,7 +932,7 @@ private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
931932
{
932933
var componentState = renderQueueEntry.ComponentState;
933934
Log.RenderingComponent(_logger, componentState);
934-
var startTime = _renderingMetrics != null ? Stopwatch.GetTimestamp() : 0;
935+
var startTime = ((bool)_renderingMetrics?.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0;
935936
_renderingMetrics?.RenderStart(componentState.Component.GetType().FullName);
936937
componentState.RenderIntoBatch(_batchBuilder, renderQueueEntry.RenderFragment, out var renderFragmentException);
937938
if (renderFragmentException != null)

src/Components/Components/src/Rendering/RenderingMetrics.cs

+21-8
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,22 @@ internal sealed class RenderingMetrics : IDisposable
1212
public const string MeterName = "Microsoft.AspNetCore.Components.Rendering";
1313

1414
private readonly Meter _meter;
15-
private readonly Counter<long> _renderCounter;
15+
private readonly Counter<long> _renderTotalCounter;
16+
private readonly UpDownCounter<long> _renderActiveCounter;
1617
private readonly Histogram<double> _renderDuration;
1718

1819
public RenderingMetrics(IMeterFactory meterFactory)
1920
{
2021
_meter = meterFactory.Create(MeterName);
2122

22-
_renderCounter = _meter.CreateCounter<long>(
23+
_renderTotalCounter = _meter.CreateCounter<long>(
2324
"aspnetcore.components.rendering.count",
24-
unit: "{render}",
25+
unit: "{renders}",
26+
description: "Number of component renders performed.");
27+
28+
_renderActiveCounter = _meter.CreateUpDownCounter<long>(
29+
"aspnetcore.components.rendering.active_renders",
30+
unit: "{renders}",
2531
description: "Number of component renders performed.");
2632

2733
_renderDuration = _meter.CreateHistogram<double>(
@@ -36,18 +42,25 @@ public void RenderStart(string componentType)
3642
var tags = new TagList();
3743
tags = InitializeRequestTags(componentType, tags);
3844

39-
_renderCounter.Add(1, tags);
45+
if (_renderActiveCounter.Enabled)
46+
{
47+
_renderActiveCounter.Add(1, tags);
48+
}
49+
if (_renderTotalCounter.Enabled)
50+
{
51+
_renderTotalCounter.Add(1, tags);
52+
}
4053
}
4154

4255
public void RenderEnd(string componentType, Exception? exception, long startTimestamp, long currentTimestamp)
4356
{
57+
// Tags must match request start.
4458
var tags = new TagList();
4559
tags = InitializeRequestTags(componentType, tags);
4660

47-
// Tags must match request start.
48-
if (_renderCounter.Enabled)
61+
if (_renderActiveCounter.Enabled)
4962
{
50-
_renderCounter.Add(-1, tags);
63+
_renderActiveCounter.Add(-1, tags);
5164
}
5265

5366
if (_renderDuration.Enabled)
@@ -68,7 +81,7 @@ private static TagList InitializeRequestTags(string componentType, TagList tags)
6881
return tags;
6982
}
7083

71-
public bool IsEnabled() => _renderCounter.Enabled || _renderDuration.Enabled;
84+
public bool IsDurationEnabled() => _renderDuration.Enabled;
7285

7386
public void Dispose()
7487
{

src/Components/Server/src/Circuits/CircuitFactory.cs

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics.Metrics;
45
using System.Linq;
56
using System.Security.Claims;
67
using Microsoft.AspNetCore.Components.Infrastructure;
@@ -20,18 +21,22 @@ internal sealed partial class CircuitFactory : ICircuitFactory
2021
private readonly CircuitIdFactory _circuitIdFactory;
2122
private readonly CircuitOptions _options;
2223
private readonly ILogger _logger;
24+
private readonly CircuitMetrics? _circuitMetrics;
2325

2426
public CircuitFactory(
2527
IServiceScopeFactory scopeFactory,
2628
ILoggerFactory loggerFactory,
2729
CircuitIdFactory circuitIdFactory,
30+
CircuitMetrics? circuitMetrics,
2831
IOptions<CircuitOptions> options)
2932
{
3033
_scopeFactory = scopeFactory;
3134
_loggerFactory = loggerFactory;
3235
_circuitIdFactory = circuitIdFactory;
3336
_options = options.Value;
3437
_logger = _loggerFactory.CreateLogger<CircuitFactory>();
38+
39+
_circuitMetrics = circuitMetrics;
3540
}
3641

3742
public async ValueTask<CircuitHost> CreateCircuitHostAsync(
@@ -104,6 +109,7 @@ public async ValueTask<CircuitHost> CreateCircuitHostAsync(
104109
jsRuntime,
105110
navigationManager,
106111
circuitHandlers,
112+
_circuitMetrics,
107113
_loggerFactory.CreateLogger<CircuitHost>());
108114
Log.CreatedCircuit(_logger, circuitHost);
109115

src/Components/Server/src/Circuits/CircuitHost.cs

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
5+
using System.Diagnostics.Metrics;
46
using System.Globalization;
57
using System.Linq;
68
using System.Security.Claims;
@@ -23,11 +25,13 @@ internal partial class CircuitHost : IAsyncDisposable
2325
private readonly CircuitOptions _options;
2426
private readonly RemoteNavigationManager _navigationManager;
2527
private readonly ILogger _logger;
28+
private readonly CircuitMetrics? _circuitMetrics;
2629
private Func<Func<Task>, Task> _dispatchInboundActivity;
2730
private CircuitHandler[] _circuitHandlers;
2831
private bool _initialized;
2932
private bool _isFirstUpdate = true;
3033
private bool _disposed;
34+
private long _startTime;
3135

3236
// This event is fired when there's an unrecoverable exception coming from the circuit, and
3337
// it need so be torn down. The registry listens to this even so that the circuit can
@@ -47,6 +51,7 @@ public CircuitHost(
4751
RemoteJSRuntime jsRuntime,
4852
RemoteNavigationManager navigationManager,
4953
CircuitHandler[] circuitHandlers,
54+
CircuitMetrics circuitMetrics,
5055
ILogger logger)
5156
{
5257
CircuitId = circuitId;
@@ -64,6 +69,7 @@ public CircuitHost(
6469
JSRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime));
6570
_navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager));
6671
_circuitHandlers = circuitHandlers ?? throw new ArgumentNullException(nameof(circuitHandlers));
72+
_circuitMetrics = circuitMetrics;
6773
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
6874

6975
Services = scope.ServiceProvider;
@@ -230,6 +236,8 @@ await Renderer.Dispatcher.InvokeAsync(async () =>
230236
private async Task OnCircuitOpenedAsync(CancellationToken cancellationToken)
231237
{
232238
Log.CircuitOpened(_logger, CircuitId);
239+
_startTime = ((bool)_circuitMetrics?.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0;
240+
_circuitMetrics?.OnCircuitOpened();
233241

234242
Renderer.Dispatcher.AssertAccess();
235243

@@ -259,6 +267,7 @@ private async Task OnCircuitOpenedAsync(CancellationToken cancellationToken)
259267
public async Task OnConnectionUpAsync(CancellationToken cancellationToken)
260268
{
261269
Log.ConnectionUp(_logger, CircuitId, Client.ConnectionId);
270+
_circuitMetrics?.OnConnectionUp();
262271

263272
Renderer.Dispatcher.AssertAccess();
264273

@@ -288,6 +297,7 @@ public async Task OnConnectionUpAsync(CancellationToken cancellationToken)
288297
public async Task OnConnectionDownAsync(CancellationToken cancellationToken)
289298
{
290299
Log.ConnectionDown(_logger, CircuitId, Client.ConnectionId);
300+
_circuitMetrics?.OnConnectionDown();
291301

292302
Renderer.Dispatcher.AssertAccess();
293303

@@ -317,6 +327,7 @@ public async Task OnConnectionDownAsync(CancellationToken cancellationToken)
317327
private async Task OnCircuitDownAsync(CancellationToken cancellationToken)
318328
{
319329
Log.CircuitClosed(_logger, CircuitId);
330+
_circuitMetrics?.OnCircuitDown(_startTime, Stopwatch.GetTimestamp());
320331

321332
List<Exception> exceptions = null;
322333

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Diagnostics.Metrics;
6+
using Microsoft.AspNetCore.Http;
7+
8+
namespace Microsoft.AspNetCore.Components.Server.Circuits;
9+
10+
internal sealed class CircuitMetrics : IDisposable
11+
{
12+
public const string MeterName = "Microsoft.AspNetCore.Components.Server.Circuits";
13+
14+
private readonly Meter _meter;
15+
private readonly Counter<long> _circuitTotalCounter;
16+
private readonly UpDownCounter<long> _circuitActiveCounter;
17+
private readonly UpDownCounter<long> _circuitConnectedCounter;
18+
private readonly Histogram<double> _circuitDuration;
19+
20+
public CircuitMetrics(IMeterFactory meterFactory)
21+
{
22+
_meter = meterFactory.Create(MeterName);
23+
24+
_circuitTotalCounter = _meter.CreateCounter<long>(
25+
"aspnetcore.components.circuits.count",
26+
unit: "{circuits}",
27+
description: "Number of active circuits.");
28+
29+
_circuitActiveCounter = _meter.CreateUpDownCounter<long>(
30+
"aspnetcore.components.circuits.active_circuits",
31+
unit: "{circuits}",
32+
description: "Number of active circuits.");
33+
34+
_circuitConnectedCounter = _meter.CreateUpDownCounter<long>(
35+
"aspnetcore.components.circuits.connected_circuits",
36+
unit: "{circuits}",
37+
description: "Number of disconnected circuits.");
38+
39+
_circuitDuration = _meter.CreateHistogram<double>(
40+
"aspnetcore.components.circuits.duration",
41+
unit: "s",
42+
description: "Duration of circuit.",
43+
advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = MetricsConstants.VeryLongSecondsBucketBoundaries });
44+
}
45+
46+
public void OnCircuitOpened()
47+
{
48+
var tags = new TagList();
49+
50+
if (_circuitActiveCounter.Enabled)
51+
{
52+
_circuitActiveCounter.Add(1, tags);
53+
}
54+
if (_circuitTotalCounter.Enabled)
55+
{
56+
_circuitTotalCounter.Add(1, tags);
57+
}
58+
}
59+
60+
public void OnConnectionUp()
61+
{
62+
var tags = new TagList();
63+
64+
if (_circuitConnectedCounter.Enabled)
65+
{
66+
_circuitConnectedCounter.Add(1, tags);
67+
}
68+
}
69+
70+
public void OnConnectionDown()
71+
{
72+
var tags = new TagList();
73+
74+
if (_circuitConnectedCounter.Enabled)
75+
{
76+
_circuitConnectedCounter.Add(-1, tags);
77+
}
78+
}
79+
80+
public void OnCircuitDown(long startTimestamp, long currentTimestamp)
81+
{
82+
// Tags must match request start.
83+
var tags = new TagList();
84+
85+
if (_circuitActiveCounter.Enabled)
86+
{
87+
_circuitActiveCounter.Add(-1, tags);
88+
}
89+
90+
if (_circuitConnectedCounter.Enabled)
91+
{
92+
_circuitConnectedCounter.Add(-1, tags);
93+
}
94+
95+
if (_circuitDuration.Enabled)
96+
{
97+
var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp);
98+
_circuitDuration.Record(duration.TotalSeconds, tags);
99+
}
100+
}
101+
102+
public bool IsDurationEnabled() => _circuitDuration.Enabled;
103+
104+
public void Dispose()
105+
{
106+
_meter.Dispose();
107+
}
108+
}

src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics.CodeAnalysis;
5+
using System.Diagnostics.Metrics;
56
using Microsoft.AspNetCore.Components;
67
using Microsoft.AspNetCore.Components.Authorization;
78
using Microsoft.AspNetCore.Components.Forms;
@@ -60,6 +61,12 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti
6061
// Here we add a bunch of services that don't vary in any way based on the
6162
// user's configuration. So even if the user has multiple independent server-side
6263
// Components entrypoints, this lot is the same and repeated registrations are a no-op.
64+
65+
services.TryAddSingleton(s =>
66+
{
67+
var meterFactory = s.GetService<IMeterFactory>();
68+
return meterFactory != null ? new CircuitMetrics(meterFactory) : null;
69+
});
6370
services.TryAddSingleton<ICircuitFactory, CircuitFactory>();
6471
services.TryAddSingleton<ICircuitHandleRegistry, CircuitHandleRegistry>();
6572
services.TryAddSingleton<RootTypeCache>();

src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626

2727
<Compile Include="$(SharedSourceRoot)ValueStopwatch\*.cs" />
2828
<Compile Include="$(SharedSourceRoot)LinkerFlags.cs" LinkBase="Shared" />
29-
<Compile Include="$(SharedSourceRoot)\PooledArrayBufferWriter.cs" LinkBase="Shared" />
29+
<Compile Include="$(SharedSourceRoot)PooledArrayBufferWriter.cs" LinkBase="Shared" />
30+
<Compile Include="$(SharedSourceRoot)Metrics\MetricsConstants.cs" LinkBase="Shared" />
3031

3132
<!-- Add a project dependency without reference output assemblies to enforce build order -->
3233
<!-- Applying workaround for https://github.com/microsoft/msbuild/issues/2661 and https://github.com/dotnet/sdk/issues/952 -->

src/JSInterop/Microsoft.JSInterop.JS/src/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@microsoft/dotnet-js-interop",
3-
"version": "9.0.0-dev",
3+
"version": "10.0.0-dev",
44
"description": "Provides abstractions and features for interop between .NET and JavaScript code.",
55
"main": "dist/src/Microsoft.JSInterop.js",
66
"types": "dist/src/Microsoft.JSInterop.d.ts",

src/Shared/Metrics/MetricsConstants.cs

+3
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ internal static class MetricsConstants
1010

1111
// Not based on a standard. Larger bucket sizes for longer lasting operations, e.g. HTTP connection duration. See https://github.com/open-telemetry/semantic-conventions/issues/336
1212
public static readonly IReadOnlyList<double> LongSecondsBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300];
13+
14+
// For Blazor/signalR sessions, which can last a long time.
15+
public static readonly IReadOnlyList<double> VeryLongSecondsBucketBoundaries = [0.5, 1, 2, 5, 10, 30, 60, 120, 300, 600, 1500, 60*60, 2 * 60 * 60, 4 * 60 * 60];
1316
}

src/SignalR/clients/ts/signalr-protocol-msgpack/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@microsoft/signalr-protocol-msgpack",
3-
"version": "5.0.0-dev",
3+
"version": "10.0.0-dev",
44
"description": "MsgPack Protocol support for ASP.NET Core SignalR",
55
"main": "./dist/cjs/index.js",
66
"module": "./dist/esm/index.js",
@@ -41,11 +41,11 @@
4141
"src/**/*"
4242
],
4343
"dependencies": {
44-
"@microsoft/signalr": "*",
44+
"@microsoft/signalr": ">=10.0.0-dev",
4545
"@msgpack/msgpack": "^2.7.0"
4646
},
4747
"overrides": {
4848
"ws": ">=7.4.6",
4949
"tough-cookie": ">=4.1.3"
5050
}
51-
}
51+
}

src/SignalR/clients/ts/signalr/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@microsoft/signalr",
3-
"version": "5.0.0-dev",
3+
"version": "10.0.0-dev",
44
"description": "ASP.NET Core SignalR Client",
55
"main": "./dist/cjs/index.js",
66
"module": "./dist/esm/index.js",

0 commit comments

Comments
 (0)