Skip to content

Commit 3aeefbd

Browse files
authored
Metrics Feature Switch (#91767)
1 parent b960f64 commit 3aeefbd

File tree

8 files changed

+119
-1
lines changed

8 files changed

+119
-1
lines changed

docs/workflow/trimming/feature-switches.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ configurations but their defaults might vary as any SDK can set the defaults dif
1313
| EnableUnsafeBinaryFormatterSerialization | System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization | BinaryFormatter serialization support is trimmed when set to false |
1414
| EventSourceSupport | System.Diagnostics.Tracing.EventSource.IsSupported | Any EventSource related code or logic is trimmed when set to false |
1515
| InvariantGlobalization | System.Globalization.Invariant | All globalization specific code and data is trimmed when set to true |
16+
| MetricsSupport | System.Diagnostics.Metrics.Meter.IsSupported | Any Metrics related code or logic is trimmed when set to false |
1617
| PredefinedCulturesOnly | System.Globalization.PredefinedCulturesOnly | Don't allow creating a culture for which the platform does not have data |
1718
| HybridGlobalization | System.Globalization.Hybrid | Properties connected with the mixed: platform-specific + icu-based globalization will be trimmed |
1819
| UseSystemResourceKeys | System.Resources.UseSystemResourceKeys | Any localizable resources for system assemblies is trimmed when set to true |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<linker>
2+
<assembly fullname="System.Diagnostics.DiagnosticSource" feature="System.Diagnostics.Metrics.Meter.IsSupported" featurevalue="false">
3+
<type fullname="System.Diagnostics.Metrics.Meter">
4+
<method signature="System.Boolean get_IsSupported()" body="stub" value="false" />
5+
</type>
6+
<type fullname="System.Diagnostics.Metrics.Instrument">
7+
<method signature="System.Boolean get_Enabled()" body="stub" value="false" />
8+
</type>
9+
</assembly>
10+
</linker>

src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ System.Diagnostics.DiagnosticSource</PackageDescription>
2020
<IncludePlatformAttributes>true</IncludePlatformAttributes>
2121
</PropertyGroup>
2222

23+
<ItemGroup>
24+
<ILLinkSubstitutionsXmls Include="ILLink/ILLink.Substitutions.Shared.xml" />
25+
</ItemGroup>
26+
2327
<ItemGroup>
2428
<Compile Include="System\Diagnostics\Activity.cs" />
2529
<Compile Include="System\Diagnostics\Activity.Current.net46.cs" />

src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs

+6
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ protected Instrument(Meter meter, string name, string? unit, string? description
6464
/// </summary>
6565
protected void Publish()
6666
{
67+
// All instruments call Publish when they are created. We don't want to publish the instrument if the Meter is not supported.
68+
if (!Meter.IsSupported)
69+
{
70+
return;
71+
}
72+
6773
List<MeterListener>? allListeners = null;
6874
lock (Instrument.SyncObject)
6975
{

src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public class Meter : IDisposable
1717
private Dictionary<string, List<Instrument>> _nonObservableInstrumentsCache = new();
1818
internal bool Disposed { get; private set; }
1919

20+
internal static bool IsSupported { get; } = InitializeIsSupported();
21+
22+
private static bool InitializeIsSupported() =>
23+
AppContext.TryGetSwitch("System.Diagnostics.Metrics.Meter.IsSupported", out bool isSupported) ? isSupported : true;
24+
2025
/// <summary>
2126
/// Initialize a new instance of the Meter using the <see cref="MeterOptions" />.
2227
/// </summary>
@@ -77,6 +82,11 @@ private void Initialize(string name, string? version, IEnumerable<KeyValuePair<s
7782
}
7883
Scope = scope;
7984

85+
if (!IsSupported)
86+
{
87+
return;
88+
}
89+
8090
lock (Instrument.SyncObject)
8191
{
8292
s_allMeters.Add(this);

src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MeterListener.cs

+31-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
namespace System.Diagnostics.Metrics
88
{
99
/// <summary>
10-
/// A delegate to represent the Meterlistener callbacks used in measurements recording operation.
10+
/// A delegate to represent the MeterListener callbacks used in measurements recording operation.
1111
/// </summary>
1212
public delegate void MeasurementCallback<T>(Instrument instrument, T measurement, ReadOnlySpan<KeyValuePair<string, object?>> tags, object? state) where T : struct;
1313

@@ -56,6 +56,11 @@ public MeterListener() { }
5656
/// <param name="state">A state object which will be passed back to the callback getting measurements events.</param>
5757
public void EnableMeasurementEvents(Instrument instrument, object? state = null)
5858
{
59+
if (!Meter.IsSupported)
60+
{
61+
return;
62+
}
63+
5964
bool oldStateStored = false;
6065
bool enabled = false;
6166
object? oldState = null;
@@ -92,6 +97,11 @@ public void EnableMeasurementEvents(Instrument instrument, object? state = null)
9297
/// <returns>The state object originally passed to <see cref="EnableMeasurementEvents" /> method.</returns>
9398
public object? DisableMeasurementEvents(Instrument instrument)
9499
{
100+
if (!Meter.IsSupported)
101+
{
102+
return default;
103+
}
104+
95105
object? state = null;
96106
lock (Instrument.SyncObject)
97107
{
@@ -114,6 +124,11 @@ public void EnableMeasurementEvents(Instrument instrument, object? state = null)
114124
/// <param name="measurementCallback">The callback which can be used to get measurement recording of numeric type T.</param>
115125
public void SetMeasurementEventCallback<T>(MeasurementCallback<T>? measurementCallback) where T : struct
116126
{
127+
if (!Meter.IsSupported)
128+
{
129+
return;
130+
}
131+
117132
measurementCallback ??= (instrument, measurement, tags, state) => { /* no-op */};
118133

119134
if (typeof(T) == typeof(byte))
@@ -155,6 +170,11 @@ public void SetMeasurementEventCallback<T>(MeasurementCallback<T>? measurementCa
155170
/// </summary>
156171
public void Start()
157172
{
173+
if (!Meter.IsSupported)
174+
{
175+
return;
176+
}
177+
158178
List<Instrument>? publishedInstruments = null;
159179
lock (Instrument.SyncObject)
160180
{
@@ -184,6 +204,11 @@ public void Start()
184204
/// </summary>
185205
public void RecordObservableInstruments()
186206
{
207+
if (!Meter.IsSupported)
208+
{
209+
return;
210+
}
211+
187212
List<Exception>? exceptionsList = null;
188213
DiagNode<Instrument>? current = _enabledMeasurementInstruments.First;
189214
while (current is not null)
@@ -215,6 +240,11 @@ public void RecordObservableInstruments()
215240
/// </summary>
216241
public void Dispose()
217242
{
243+
if (!Meter.IsSupported)
244+
{
245+
return;
246+
}
247+
218248
Dictionary<Instrument, object?>? callbacksArguments = null;
219249
Action<Instrument, object?>? measurementsCompleted = MeasurementsCompleted;
220250

src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
</ItemGroup>
1212
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
1313
<Compile Include="DiagnosticSourceEventSourceBridgeTests.cs" />
14+
<Compile Include="TestNotSupported.cs" />
1415
</ItemGroup>
1516
<ItemGroup>
1617
<Compile Include="..\src\System\Diagnostics\Metrics\Aggregator.cs" Link="Aggregator.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 Microsoft.DotNet.RemoteExecutor;
5+
using System.Diagnostics.Metrics;
6+
using Xunit;
7+
8+
namespace System.Diagnostics.Metrics.Tests
9+
{
10+
public class MetricsNotSupportedTest
11+
{
12+
/// <summary>
13+
/// Tests using Metrics when the System.Diagnostics.Metrics.Meter.IsSupported
14+
/// feature switch is set to disable all metrics operations.
15+
/// </summary>
16+
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
17+
[InlineData(false)]
18+
[InlineData(true)]
19+
public void IsSupportedSwitch(bool value)
20+
{
21+
RemoteInvokeOptions options = new RemoteInvokeOptions();
22+
options.RuntimeConfigurationOptions.Add("System.Diagnostics.Metrics.Meter.IsSupported", value);
23+
24+
RemoteExecutor.Invoke((val) =>
25+
{
26+
bool isSupported = bool.Parse(val);
27+
28+
Meter meter = new Meter("IsSupportedTest");
29+
Counter<long> counter = meter.CreateCounter<long>("counter");
30+
bool instrumentsPublished = false;
31+
bool instrumentCompleted = false;
32+
long counterValue = 100;
33+
34+
using (MeterListener listener = new MeterListener
35+
{
36+
InstrumentPublished = (instruments, theListener) => instrumentsPublished = true,
37+
MeasurementsCompleted = (instruments, state) => instrumentCompleted = true
38+
})
39+
{
40+
listener.EnableMeasurementEvents(counter, null);
41+
listener.SetMeasurementEventCallback<long>((inst, measurement, tags, state) => counterValue = measurement);
42+
listener.Start();
43+
44+
Assert.Equal(isSupported, counter.Enabled);
45+
46+
counter.Add(20);
47+
}
48+
meter.Dispose();
49+
50+
Assert.Equal(isSupported, instrumentsPublished);
51+
Assert.Equal(isSupported, instrumentCompleted);
52+
Assert.Equal(isSupported ? 20 : 100, counterValue);
53+
}, value.ToString(), options).Dispose();
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)