Skip to content

[browser] managed Diagnostics.Metrics and Diagnostics.Tracing single-threaded #113524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
40fe48e
Implementation
pavelsavara Mar 14, 2025
7cd04e2
fix
pavelsavara Mar 14, 2025
ed04043
move System.Net.Http.EnableMetrics to https://github.com/dotnet/runti…
pavelsavara Mar 14, 2025
88e5ba5
fix
pavelsavara Mar 14, 2025
0a22b09
fix
pavelsavara Mar 14, 2025
5d287d3
fix
pavelsavara Mar 14, 2025
b8d3f61
Merge branch 'main' into browser_managed_diag_events
pavelsavara Mar 15, 2025
bfdbda8
ActivityTracker_Instance_Enable
pavelsavara Mar 15, 2025
7a6f106
fix
pavelsavara Mar 15, 2025
04df34b
fix
pavelsavara Mar 17, 2025
d1a8712
more tests
pavelsavara Mar 18, 2025
46d63a9
Merge branch 'main' into browser_managed_diag_events
pavelsavara Mar 18, 2025
be60fbe
fix
pavelsavara Mar 18, 2025
963f14c
fix
pavelsavara Mar 18, 2025
948f7f6
Update src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tr…
pavelsavara Mar 18, 2025
84ec7ff
fix
pavelsavara Mar 18, 2025
ff65c1f
fix
pavelsavara Mar 18, 2025
60a60d0
Merge branch 'main' into browser_managed_diag_events
pavelsavara Mar 19, 2025
5003ac5
feedback
pavelsavara Mar 19, 2025
b059f30
feedbak - keep DiagnosticCounter not supported API
pavelsavara Mar 19, 2025
48be899
more
pavelsavara Mar 19, 2025
c97ce30
fix
pavelsavara Mar 19, 2025
ea72741
fix
pavelsavara Mar 19, 2025
d1d4f27
fix
pavelsavara Mar 19, 2025
f242d64
fix
pavelsavara Mar 19, 2025
f4cb864
fix
pavelsavara Mar 19, 2025
84af319
Merge branch 'main' into browser_managed_diag_events
pavelsavara Mar 20, 2025
52daddc
feedback
pavelsavara Mar 20, 2025
149746b
fix
pavelsavara Mar 20, 2025
4c4ef09
just browser
pavelsavara Mar 20, 2025
53a2188
Merge branch 'main' into browser_managed_diag_events
pavelsavara Mar 21, 2025
6fb21e9
disable RequestDuration_EnrichmentHandler_ContentLengthError_Recorded…
pavelsavara Mar 21, 2025
fedacae
Merge branch 'main' into browser_managed_diag_events
pavelsavara Mar 21, 2025
57c9692
RequestDuration_Success_Recorded not firefox
pavelsavara Mar 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ private static bool ComputeIsLinqSizeOptimized()
public static bool IsFirefox => IsEnvironmentVariableTrue("IsFirefox");
public static bool IsChromium => IsEnvironmentVariableTrue("IsChromium");
public static bool IsNotNodeJS => !IsNodeJS;
public static bool IsNotNodeJSOrFirefox => !IsNodeJS && !IsFirefox;
public static bool IsNodeJSOnWindows => GetNodeJSPlatform() == "win32";
public static bool LocalEchoServerIsNotAvailable => !LocalEchoServerIsAvailable;
public static bool LocalEchoServerIsAvailable => IsBrowser;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ public void NegativeTest()
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/93754", TestPlatforms.Browser)]
public void MeterDisposeTest()
{
ServiceCollection services = new ServiceCollection();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ private class FakeListener : IMetricsListener
public void MeasurementsCompleted(Instrument instrument, object? userState) => throw new NotImplementedException();
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
[Fact]
public void TestSubscriptionManagerDisposal()
{
var meter = new Meter("TestMeter");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkCurrent)</TargetFrameworks>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppCurrent)-browser;$(NetFrameworkCurrent)</TargetFrameworks>
<EnableDefaultItems>true</EnableDefaultItems>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetOS)' == 'browser'">
<!-- Enable diagnostic features. They will add appropriate RuntimeHostConfigurationOption values to runtime config and ILLink.
https://github.com/dotnet/docs/blob/main/docs/core/deploying/trimming/trimming-options.md#trim-framework-library-features
-->
<MetricsSupport>true</MetricsSupport>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\src\Metrics\Configuration\MetricsConfigureOptions.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ System.Diagnostics.DiagnosticSource</PackageDescription>
<!-- DesignTimeBuild requires all the TargetFramework Derived Properties to not be present in the first property group. -->
<PropertyGroup>
<DefineConstants Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETFramework'">$(DefineConstants);ENABLE_HTTP_HANDLER</DefineConstants>
<DefineConstants Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETCoreApp'">$(DefineConstants);W3C_DEFAULT_ID_FORMAT;MEMORYMARSHAL_SUPPORT;OS_ISBROWSER_SUPPORT</DefineConstants>
<DefineConstants Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETCoreApp'">$(DefineConstants);W3C_DEFAULT_ID_FORMAT;MEMORYMARSHAL_SUPPORT;OS_ISWASI_SUPPORT;OS_ISBROWSER_SUPPORT</DefineConstants>
<IncludePlatformAttributes>true</IncludePlatformAttributes>
<!-- TODO: Add package README file: https://github.com/dotnet/runtime/issues/99358 -->
<EnableDefaultPackageReadmeFile>false</EnableDefaultPackageReadmeFile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

namespace System.Diagnostics.Metrics
{
[UnsupportedOSPlatform("browser")]
[SecuritySafeCritical]
internal sealed class AggregationManager
{
Expand All @@ -29,6 +28,9 @@ internal sealed class AggregationManager
private readonly ConcurrentDictionary<Instrument, InstrumentState> _instrumentStates = new();
private readonly CancellationTokenSource _cts = new();
private Thread? _collectThread;
#if OS_ISBROWSER_SUPPORT
private Timer? _pollingTimer;
#endif
private readonly MeterListener _listener;
private int _currentTimeSeries;
private int _currentHistograms;
Expand All @@ -43,6 +45,9 @@ internal sealed class AggregationManager
private readonly Action _timeSeriesLimitReached;
private readonly Action _histogramLimitReached;
private readonly Action<Exception> _observableInstrumentCallbackError;
private DateTime _startTime;
private DateTime _intervalStartTime;
private DateTime _nextIntervalStartTime;

public AggregationManager(
int maxTimeSeries,
Expand Down Expand Up @@ -160,15 +165,28 @@ public void Start()
Debug.Assert(_collectThread == null && !_cts.IsCancellationRequested);
Debug.Assert(CollectionPeriod.TotalSeconds >= MinCollectionTimeSecs);

// This explicitly uses a Thread and not a Task so that metrics still work
// even when an app is experiencing thread-pool starvation. Although we
// can't make in-proc metrics robust to everything, this is a common enough
// problem in .NET apps that it feels worthwhile to take the precaution.
_collectThread = new Thread(() => CollectWorker(_cts.Token));
_collectThread.IsBackground = true;
_collectThread.Name = "MetricsEventSource CollectWorker";
_collectThread.Start();
_intervalStartTime = _nextIntervalStartTime = _startTime = DateTime.UtcNow;
#if OS_ISBROWSER_SUPPORT
if (OperatingSystem.IsBrowser())
{
TimeSpan delayTime = CalculateDelayTime(CollectionPeriod.TotalSeconds);
_pollingTimer = new Timer(CollectOnTimer, null, (int)delayTime.TotalMilliseconds, 0);
}
else
#endif
{
// This explicitly uses a Thread and not a Task so that metrics still work
// even when an app is experiencing thread-pool starvation. Although we
// can't make in-proc metrics robust to everything, this is a common enough
// problem in .NET apps that it feels worthwhile to take the precaution.
_collectThread = new Thread(CollectWorker);
_collectThread.IsBackground = true;
_collectThread.Name = "MetricsEventSource CollectWorker";
#pragma warning disable CA1416 // 'Thread.Start' is unsupported on: 'browser', there the actual implementation is in AggregationManager.Wasm.cs
_collectThread.Start();
#pragma warning restore CA1416

}
_listener.Start();
_initialInstrumentEnumerationComplete();
}
Expand All @@ -187,57 +205,64 @@ public void Update()
_initialInstrumentEnumerationComplete();
}

private void CollectWorker(CancellationToken cancelToken)
private TimeSpan CalculateDelayTime(double collectionIntervalSecs)
{
_intervalStartTime = _nextIntervalStartTime;

// intervals end at startTime + X*collectionIntervalSecs. Under normal
// circumstance X increases by 1 each interval, but if the time it
// takes to do collection is very large then we might need to skip
// ahead multiple intervals to catch back up.
//
DateTime now = DateTime.UtcNow;
double secsSinceStart = (now - _startTime).TotalSeconds;
double alignUpSecsSinceStart = Math.Ceiling(secsSinceStart / collectionIntervalSecs) *
collectionIntervalSecs;
_nextIntervalStartTime = _startTime.AddSeconds(alignUpSecsSinceStart);

// The delay timer precision isn't exact. We might have a situation
// where in the previous loop iterations intervalStartTime=20.00,
// nextIntervalStartTime=21.00, the timer was supposed to delay for 1s but
// it exited early so we looped around and DateTime.Now=20.99.
// Aligning up from DateTime.Now would give us 21.00 again so we also need to skip
// forward one time interval
DateTime minNextInterval = _intervalStartTime.AddSeconds(collectionIntervalSecs);
if (_nextIntervalStartTime <= minNextInterval)
{
_nextIntervalStartTime = minNextInterval;
}
return _nextIntervalStartTime - now;
}

private void CollectWorker()
{
try
{
double collectionIntervalSecs = -1;
CancellationToken cancelToken;
lock (this)
{
collectionIntervalSecs = CollectionPeriod.TotalSeconds;
cancelToken = _cts.Token;
}
Debug.Assert(collectionIntervalSecs >= MinCollectionTimeSecs);

DateTime startTime = DateTime.UtcNow;
DateTime intervalStartTime = startTime;
while (!cancelToken.IsCancellationRequested)
while (!_cts.Token.IsCancellationRequested)
{
// intervals end at startTime + X*collectionIntervalSecs. Under normal
// circumstance X increases by 1 each interval, but if the time it
// takes to do collection is very large then we might need to skip
// ahead multiple intervals to catch back up.
//
DateTime now = DateTime.UtcNow;
double secsSinceStart = (now - startTime).TotalSeconds;
double alignUpSecsSinceStart = Math.Ceiling(secsSinceStart / collectionIntervalSecs) *
collectionIntervalSecs;
DateTime nextIntervalStartTime = startTime.AddSeconds(alignUpSecsSinceStart);

// The delay timer precision isn't exact. We might have a situation
// where in the previous loop iterations intervalStartTime=20.00,
// nextIntervalStartTime=21.00, the timer was supposed to delay for 1s but
// it exited early so we looped around and DateTime.Now=20.99.
// Aligning up from DateTime.Now would give us 21.00 again so we also need to skip
// forward one time interval
DateTime minNextInterval = intervalStartTime.AddSeconds(collectionIntervalSecs);
if (nextIntervalStartTime <= minNextInterval)
{
nextIntervalStartTime = minNextInterval;
}

// pause until the interval is complete
TimeSpan delayTime = nextIntervalStartTime - now;
TimeSpan delayTime = CalculateDelayTime(collectionIntervalSecs);
if (cancelToken.WaitHandle.WaitOne(delayTime))
{
// don't do collection if timer may not have run to completion
break;
}

// collect statistics for the completed interval
_beginCollection(intervalStartTime, nextIntervalStartTime);
_beginCollection(_intervalStartTime, _nextIntervalStartTime);
Collect();
_endCollection(intervalStartTime, nextIntervalStartTime);
intervalStartTime = nextIntervalStartTime;
_endCollection(_intervalStartTime, _nextIntervalStartTime);
}
}
catch (Exception e)
Expand All @@ -246,12 +271,49 @@ private void CollectWorker(CancellationToken cancelToken)
}
}

#if OS_ISBROWSER_SUPPORT
private void CollectOnTimer(object? _)
{
try
{
// this is single-threaded so we don't need to lock
CancellationToken cancelToken = _cts.Token;
double collectionIntervalSecs = CollectionPeriod.TotalSeconds;

if (cancelToken.IsCancellationRequested)
{
return;
}

// collect statistics for the completed interval
_beginCollection(_intervalStartTime, _nextIntervalStartTime);
Collect();
_endCollection(_intervalStartTime, _nextIntervalStartTime);

TimeSpan delayTime = CalculateDelayTime(collectionIntervalSecs);
// schedule the next collection
_pollingTimer!.Change((int)delayTime.TotalMilliseconds, 0);
}
catch (Exception e)
{
_collectionError(e);
}
}
#endif

public void Dispose()
{
_cts.Cancel();
if (_collectThread != null)
#if OS_ISBROWSER_SUPPORT
if (OperatingSystem.IsBrowser())
{
_pollingTimer?.Dispose();
_pollingTimer = null;
}
else
#endif
{
_collectThread.Join();
_collectThread?.Join();
_collectThread = null;
}
_listener.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public void BeginInstrumentReporting(
string? meterTelemetrySchemaUrl)
{
WriteEvent(7, sessionId, meterName, meterVersion ?? "", instrumentName, instrumentType, unit ?? "", description ?? "",
instrumentTags, meterTags, meterScopeHash, instrumentId, meterTelemetrySchemaUrl);
instrumentTags, meterTags, meterScopeHash, instrumentId, meterTelemetrySchemaUrl ?? "");
}

// Sent when we stop monitoring the value of a instrument, either because new session filter arguments changed subscriptions
Expand All @@ -198,7 +198,7 @@ public void EndInstrumentReporting(
string? meterTelemetrySchemaUrl)
{
WriteEvent(8, sessionId, meterName, meterVersion ?? "", instrumentName, instrumentType, unit ?? "", description ?? "",
instrumentTags, meterTags, meterScopeHash, instrumentId, meterTelemetrySchemaUrl);
instrumentTags, meterTags, meterScopeHash, instrumentId, meterTelemetrySchemaUrl ?? "");
}

[Event(9, Keywords = Keywords.TimeSeriesValues | Keywords.Messages | Keywords.InstrumentPublishing)]
Expand Down Expand Up @@ -233,7 +233,7 @@ public void InstrumentPublished(
string? meterTelemetrySchemaUrl)
{
WriteEvent(11, sessionId, meterName, meterVersion ?? "", instrumentName, instrumentType, unit ?? "", description ?? "",
instrumentTags, meterTags, meterScopeHash, instrumentId, meterTelemetrySchemaUrl);
instrumentTags, meterTags, meterScopeHash, instrumentId, meterTelemetrySchemaUrl ?? "");
}

[Event(12, Keywords = Keywords.TimeSeriesValues)]
Expand Down Expand Up @@ -338,16 +338,16 @@ public void OnEventCommand(EventCommandEventArgs command)
{
try
{
#if OS_ISBROWSER_SUPPORT
if (OperatingSystem.IsBrowser() || OperatingSystem.IsWasi())
#if OS_ISWASI_SUPPORT
if (OperatingSystem.IsWasi())
{
// AggregationManager uses a dedicated thread to avoid losing data for apps experiencing threadpool starvation
// and browser doesn't support Thread.Start()
// and wasi doesn't support Thread.Start()
//
// This limitation shouldn't really matter because browser also doesn't support out-of-proc EventSource communication
// This limitation shouldn't really matter because wasi also doesn't support out-of-proc EventSource communication
// which is the intended scenario for this EventSource. If it matters in the future AggregationManager can be
// modified to have some other fallback path that works for browser.
Parent.Error("", "System.Diagnostics.Metrics EventSource not supported on browser and wasi");
// modified to have some other fallback path that works for wasi.
Parent.Error("", "System.Diagnostics.Metrics EventSource not supported on wasi");
return;
}
#endif
Expand Down Expand Up @@ -661,7 +661,6 @@ private bool LogError(Exception e)

private static readonly char[] s_instrumentSeparators = new char[] { '\r', '\n', ',', ';' };

[UnsupportedOSPlatform("browser")]
private void ParseSpecs(string? metricsSpecs)
{
if (metricsSpecs == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1416,7 +1416,7 @@ public void DiagnosticSourceStartStop()
/// <summary>
/// Tests that Activity.Current flows correctly within async methods
/// </summary>
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[Fact]
public async Task ActivityCurrentFlowsWithAsyncSimple()
{
Activity activity = new Activity("activity").Start();
Expand All @@ -1433,7 +1433,7 @@ await Task.Run(() =>
/// <summary>
/// Tests that Activity.Current flows correctly within async methods
/// </summary>
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[Fact]
public async Task ActivityCurrentFlowsWithAsyncComplex()
{
Activity originalActivity = Activity.Current;
Expand Down Expand Up @@ -1472,7 +1472,7 @@ public async Task ActivityCurrentFlowsWithAsyncComplex()
/// <summary>
/// Tests that Activity.Current could be set
/// </summary>
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[Fact]
public async Task ActivityCurrentSet()
{
Activity activity = new Activity("activity");
Expand Down
Loading
Loading