diff --git a/docs/workflow/trimming/feature-switches.md b/docs/workflow/trimming/feature-switches.md
index 964869833b5f6..2c25a36230d4d 100644
--- a/docs/workflow/trimming/feature-switches.md
+++ b/docs/workflow/trimming/feature-switches.md
@@ -13,6 +13,7 @@ configurations but their defaults might vary as any SDK can set the defaults dif
| EnableUnsafeBinaryFormatterSerialization | System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization | BinaryFormatter serialization support is trimmed when set to false |
| EventSourceSupport | System.Diagnostics.Tracing.EventSource.IsSupported | Any EventSource related code or logic is trimmed when set to false |
| InvariantGlobalization | System.Globalization.Invariant | All globalization specific code and data is trimmed when set to true |
+| MetricsSupport | System.Diagnostics.Metrics.Meter.IsSupported | Any Metrics related code or logic is trimmed when set to false |
| PredefinedCulturesOnly | System.Globalization.PredefinedCulturesOnly | Don't allow creating a culture for which the platform does not have data |
| HybridGlobalization | System.Globalization.Hybrid | Properties connected with the mixed: platform-specific + icu-based globalization will be trimmed |
| UseSystemResourceKeys | System.Resources.UseSystemResourceKeys | Any localizable resources for system assemblies is trimmed when set to true |
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/ILLink/ILLink.Substitutions.Shared.xml b/src/libraries/System.Diagnostics.DiagnosticSource/src/ILLink/ILLink.Substitutions.Shared.xml
new file mode 100644
index 0000000000000..b67ac8623c402
--- /dev/null
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/ILLink/ILLink.Substitutions.Shared.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
index b565a8f55b201..2afba2938d0d8 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
@@ -20,6 +20,10 @@ System.Diagnostics.DiagnosticSource
true
+
+
+
+
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs
index 0cb50f523f028..c1e2fcb199b3a 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs
@@ -64,6 +64,12 @@ protected Instrument(Meter meter, string name, string? unit, string? description
///
protected void Publish()
{
+ // All instruments call Publish when they are created. We don't want to publish the instrument if the Meter is not supported.
+ if (!Meter.IsSupported)
+ {
+ return;
+ }
+
List? allListeners = null;
lock (Instrument.SyncObject)
{
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
index a5722aa7b6c4d..60314b1d5b4c0 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
@@ -17,6 +17,11 @@ public class Meter : IDisposable
private Dictionary> _nonObservableInstrumentsCache = new();
internal bool Disposed { get; private set; }
+ internal static bool IsSupported { get; } = InitializeIsSupported();
+
+ private static bool InitializeIsSupported() =>
+ AppContext.TryGetSwitch("System.Diagnostics.Metrics.Meter.IsSupported", out bool isSupported) ? isSupported : true;
+
///
/// Initialize a new instance of the Meter using the .
///
@@ -77,6 +82,11 @@ private void Initialize(string name, string? version, IEnumerable
- /// A delegate to represent the Meterlistener callbacks used in measurements recording operation.
+ /// A delegate to represent the MeterListener callbacks used in measurements recording operation.
///
public delegate void MeasurementCallback(Instrument instrument, T measurement, ReadOnlySpan> tags, object? state) where T : struct;
@@ -56,6 +56,11 @@ public MeterListener() { }
/// A state object which will be passed back to the callback getting measurements events.
public void EnableMeasurementEvents(Instrument instrument, object? state = null)
{
+ if (!Meter.IsSupported)
+ {
+ return;
+ }
+
bool oldStateStored = false;
bool enabled = false;
object? oldState = null;
@@ -92,6 +97,11 @@ public void EnableMeasurementEvents(Instrument instrument, object? state = null)
/// The state object originally passed to method.
public object? DisableMeasurementEvents(Instrument instrument)
{
+ if (!Meter.IsSupported)
+ {
+ return default;
+ }
+
object? state = null;
lock (Instrument.SyncObject)
{
@@ -114,6 +124,11 @@ public void EnableMeasurementEvents(Instrument instrument, object? state = null)
/// The callback which can be used to get measurement recording of numeric type T.
public void SetMeasurementEventCallback(MeasurementCallback? measurementCallback) where T : struct
{
+ if (!Meter.IsSupported)
+ {
+ return;
+ }
+
measurementCallback ??= (instrument, measurement, tags, state) => { /* no-op */};
if (typeof(T) == typeof(byte))
@@ -155,6 +170,11 @@ public void SetMeasurementEventCallback(MeasurementCallback? measurementCa
///
public void Start()
{
+ if (!Meter.IsSupported)
+ {
+ return;
+ }
+
List? publishedInstruments = null;
lock (Instrument.SyncObject)
{
@@ -184,6 +204,11 @@ public void Start()
///
public void RecordObservableInstruments()
{
+ if (!Meter.IsSupported)
+ {
+ return;
+ }
+
List? exceptionsList = null;
DiagNode? current = _enabledMeasurementInstruments.First;
while (current is not null)
@@ -215,6 +240,11 @@ public void RecordObservableInstruments()
///
public void Dispose()
{
+ if (!Meter.IsSupported)
+ {
+ return;
+ }
+
Dictionary? callbacksArguments = null;
Action? measurementsCompleted = MeasurementsCompleted;
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj
index 7e8136e0a1a6b..29a8e55dc2fc0 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj
@@ -11,6 +11,7 @@
+
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestNotSupported.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestNotSupported.cs
new file mode 100644
index 0000000000000..43fae74995720
--- /dev/null
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestNotSupported.cs
@@ -0,0 +1,56 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.DotNet.RemoteExecutor;
+using System.Diagnostics.Metrics;
+using Xunit;
+
+namespace System.Diagnostics.Metrics.Tests
+{
+ public class MetricsNotSupportedTest
+ {
+ ///
+ /// Tests using Metrics when the System.Diagnostics.Metrics.Meter.IsSupported
+ /// feature switch is set to disable all metrics operations.
+ ///
+ [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void IsSupportedSwitch(bool value)
+ {
+ RemoteInvokeOptions options = new RemoteInvokeOptions();
+ options.RuntimeConfigurationOptions.Add("System.Diagnostics.Metrics.Meter.IsSupported", value);
+
+ RemoteExecutor.Invoke((val) =>
+ {
+ bool isSupported = bool.Parse(val);
+
+ Meter meter = new Meter("IsSupportedTest");
+ Counter counter = meter.CreateCounter("counter");
+ bool instrumentsPublished = false;
+ bool instrumentCompleted = false;
+ long counterValue = 100;
+
+ using (MeterListener listener = new MeterListener
+ {
+ InstrumentPublished = (instruments, theListener) => instrumentsPublished = true,
+ MeasurementsCompleted = (instruments, state) => instrumentCompleted = true
+ })
+ {
+ listener.EnableMeasurementEvents(counter, null);
+ listener.SetMeasurementEventCallback((inst, measurement, tags, state) => counterValue = measurement);
+ listener.Start();
+
+ Assert.Equal(isSupported, counter.Enabled);
+
+ counter.Add(20);
+ }
+ meter.Dispose();
+
+ Assert.Equal(isSupported, instrumentsPublished);
+ Assert.Equal(isSupported, instrumentCompleted);
+ Assert.Equal(isSupported ? 20 : 100, counterValue);
+ }, value.ToString(), options).Dispose();
+ }
+ }
+}