Introducing Metrics APIs (#52685)
authorTarek Mahmoud Sayed <tarekms@microsoft.com>
Sat, 15 May 2021 06:53:47 +0000 (23:53 -0700)
committerGitHub <noreply@github.com>
Sat, 15 May 2021 06:53:47 +0000 (23:53 -0700)
19 files changed:
src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx
src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LinkedList.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Counter.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Histogram.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.common.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.netcore.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.netfx.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Measurement.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MeterListener.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/ObservableCounter.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/ObservableGauge.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/ObservableInstrument.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs [new file with mode: 0644]
src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj

index 9402388..a5ac6cb 100644 (file)
@@ -13,13 +13,7 @@ namespace System.Diagnostics
         public System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, string?>> Baggage { get { throw null; } }
         public static System.Diagnostics.Activity? Current
         {
-#if ALLOW_PARTIALLY_TRUSTED_CALLERS
-            [System.Security.SecuritySafeCriticalAttribute]
-#endif
             get { throw null; }
-#if ALLOW_PARTIALLY_TRUSTED_CALLERS
-            [System.Security.SecuritySafeCriticalAttribute]
-#endif
             set { }
         }
         public static System.Diagnostics.ActivityIdFormat DefaultIdFormat { get { throw null; } set { } }
@@ -27,9 +21,6 @@ namespace System.Diagnostics
         public static bool ForceDefaultIdFormat { get { throw null; } set { } }
         public string? Id
         {
-#if ALLOW_PARTIALLY_TRUSTED_CALLERS
-        [System.Security.SecuritySafeCriticalAttribute]
-#endif
             get { throw null; }
         }
 
@@ -120,9 +111,6 @@ namespace System.Diagnostics
         Hierarchical = 1,
         W3C = 2,
     }
-#if ALLOW_PARTIALLY_TRUSTED_CALLERS
-    [System.Security.SecuritySafeCriticalAttribute]
-#endif
     public readonly partial struct ActivitySpanId : System.IEquatable<System.Diagnostics.ActivitySpanId>
     {
         private readonly object _dummy;
@@ -162,9 +150,6 @@ namespace System.Diagnostics
         None = 0,
         Recorded = 1,
     }
-#if ALLOW_PARTIALLY_TRUSTED_CALLERS
-    [System.Security.SecuritySafeCriticalAttribute]
-#endif
     public readonly partial struct ActivityTraceId : System.IEquatable<System.Diagnostics.ActivityTraceId>
     {
         private readonly object _dummy;
@@ -271,3 +256,125 @@ namespace System.Diagnostics
    }
 }
 
+namespace System.Diagnostics.Metrics
+{
+    public sealed class Counter<T> : Instrument<T> where T : struct
+    {
+        public void Add(T delta) {  throw null; }
+        public void Add(T delta, System.Collections.Generic.KeyValuePair<string, object?> tag)  {  throw null; }
+        public void Add(T delta, System.Collections.Generic.KeyValuePair<string, object?> tag1, System.Collections.Generic.KeyValuePair<string, object?> tag2)  {  throw null; }
+        public void Add(T delta, System.Collections.Generic.KeyValuePair<string, object?> tag1, System.Collections.Generic.KeyValuePair<string, object?> tag2, System.Collections.Generic.KeyValuePair<string, object?> tag3)  {  throw null; }
+        public void Add(T delta, ReadOnlySpan<System.Collections.Generic.KeyValuePair<string, object?>> tags) {  throw null; }
+        public void Add(T delta, params System.Collections.Generic.KeyValuePair<string, object?>[] tags) {  throw null; }
+        internal Counter(Meter meter, string name, string? unit, string? description) :
+                        base(meter, name, unit, description) {  throw null; }
+    }
+    public sealed class Histogram<T> : Instrument<T> where T : struct
+    {
+        internal Histogram(Meter meter, string name, string? unit, string? description) : base(meter, name, unit, description) { throw null; }
+        public void Record(T value) { throw null; }
+        public void Record(T value, System.Collections.Generic.KeyValuePair<string, object?> tag) { throw null; }
+        public void Record(T value, System.Collections.Generic.KeyValuePair<string, object?> tag1, System.Collections.Generic.KeyValuePair<string, object?> tag2) { throw null; }
+        public void Record(T value, System.Collections.Generic.KeyValuePair<string, object?> tag1, System.Collections.Generic.KeyValuePair<string, object?> tag2, System.Collections.Generic.KeyValuePair<string, object?> tag3) { throw null; }
+        public void Record(T value, ReadOnlySpan<System.Collections.Generic.KeyValuePair<string, object?>> tags) { throw null; }
+        public void Record(T value, params System.Collections.Generic.KeyValuePair<string, object?>[] tags) { throw null; }
+    }
+    public abstract class Instrument
+    {
+        public string? Description { get {throw null;} }
+        public bool Enabled { get  {throw null; } }
+        protected Instrument(Meter meter, string name, string? unit, string? description) {throw null;}
+        public virtual bool IsObservable { get  {throw null; } }
+        public Meter Meter { get {throw null;} }
+        public string Name { get {throw null;} }
+        protected void Publish() {throw null;}
+        public string? Unit { get {throw null; } }
+    }
+    public abstract class Instrument<T> : Instrument where T : struct
+    {
+        protected Instrument(Meter meter, string name, string? unit, string? description) : base(meter, name, unit, description) { throw null; }
+        protected void RecordMeasurement(T measurement)  { throw null; }
+        protected void RecordMeasurement(T measurement, System.Collections.Generic.KeyValuePair<string, object?> tag) { throw null; }
+        protected void RecordMeasurement(T measurement, System.Collections.Generic.KeyValuePair<string, object?> tag1, System.Collections.Generic.KeyValuePair<string, object?> tag2)  { throw null; }
+        protected void RecordMeasurement(T measurement, System.Collections.Generic.KeyValuePair<string, object?> tag1, System.Collections.Generic.KeyValuePair<string, object?> tag2, System.Collections.Generic.KeyValuePair<string, object?> tag3)  { throw null; }
+        protected void RecordMeasurement(T measurement, ReadOnlySpan<System.Collections.Generic.KeyValuePair<string, object?>> tags) { throw null; }
+    }
+    public readonly struct Measurement<T> where T : struct
+    {
+        public Measurement(T value) { throw null; }
+        public Measurement(T value, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags) { throw null; }
+        public Measurement(T value, params System.Collections.Generic.KeyValuePair<string, object?>[]? tags) { throw null; }
+        public Measurement(T value, ReadOnlySpan<System.Collections.Generic.KeyValuePair<string, object?>> tags) { throw null; }
+        public ReadOnlySpan<System.Collections.Generic.KeyValuePair<string, object?>> Tags { get { throw null; } }
+        public T Value { get { throw null; } }
+    }
+    public delegate void MeasurementCallback<T>(Instrument instrument, T measurement, ReadOnlySpan<System.Collections.Generic.KeyValuePair<string, object?>> tags, object? state);
+    public class Meter : IDisposable
+    {
+        public Counter<T> CreateCounter<T>(string name, string? unit = null, string? description = null) where T : struct  { throw null; }
+        public Histogram<T> CreateHistogram<T>(string name, string? unit = null, string? description = null) where T : struct { throw null; }
+        public ObservableCounter<T> CreateObservableCounter<T>(
+                            string name,
+                            Func<T> observeValue,
+                            string? unit = null,
+                            string? description = null) where T : struct { throw null; }
+        public ObservableCounter<T> CreateObservableCounter<T>(
+                            string name,
+                            Func<Measurement<T>> observeValue,
+                            string? unit = null,
+                            string? description = null) where T : struct { throw null; }
+        public ObservableCounter<T> CreateObservableCounter<T>(
+                            string name,
+                            Func<System.Collections.Generic.IEnumerable<Measurement<T>>> observeValues,
+                            string? unit = null,
+                            string? description = null) where T : struct { throw null; }
+        public ObservableGauge<T> CreateObservableGauge<T>(
+                            string name,
+                            Func<T> observeValue,
+                            string? unit = null,
+                            string? description = null) where T : struct { throw null; }
+        public ObservableGauge<T> CreateObservableGauge<T>(
+                            string name,
+                            Func<Measurement<T>> observeValue,
+                            string? unit = null,
+                            string? description = null) where T : struct { throw null; }
+        public ObservableGauge<T> CreateObservableGauge<T>(
+                            string name,
+                            Func<System.Collections.Generic.IEnumerable<Measurement<T>>> observeValues,
+                            string? unit = null,
+                            string? description = null) where T : struct { throw null; }
+        public void Dispose()  { throw null; }
+        public Meter(string name) { throw null; }
+        public Meter(string name, string? version)  { throw null; }
+        public string Name { get { throw null; }  }
+        public string? Version { get { throw null; } }
+    }
+    public sealed class MeterListener : IDisposable
+    {
+        public object? DisableMeasurementEvents(Instrument instrument) { throw null; }
+        public void Dispose() { throw null; }
+        public void EnableMeasurementEvents(Instrument instrument, object? state = null) { throw null; }
+        public Action<Instrument, MeterListener>? InstrumentPublished { get { throw null; } set { throw null; } }
+        public Action<Instrument, object?>? MeasurementsCompleted { get { throw null; } set { throw null; } }
+        public MeterListener() { throw null; }
+        public void RecordObservableInstruments() { throw null; }
+        public void SetMeasurementEventCallback<T>(MeasurementCallback<T>? measurementCallback) where T : struct { throw null; }
+        public void Start() { throw null; }
+    }
+    public sealed class ObservableCounter<T> : ObservableInstrument<T> where T : struct
+    {
+        internal ObservableCounter(Meter meter, string name, string? unit, string? description) : base(meter, name, unit, description) { throw null; }
+        protected override System.Collections.Generic.IEnumerable<Measurement<T>> Observe() { throw null;}
+    }
+    public sealed class ObservableGauge<T> : ObservableInstrument<T> where T : struct
+    {
+        internal ObservableGauge(Meter meter, string name, string? unit, string? description) : base(meter, name, unit, description) { throw null; }
+        protected override System.Collections.Generic.IEnumerable<Measurement<T>> Observe() { throw null; }
+    }
+    public abstract class ObservableInstrument<T> : Instrument where T : struct
+    {
+        public override bool IsObservable { get { throw null; } }
+        protected ObservableInstrument(Meter meter, string name, string? unit, string? description) : base(meter, name, unit, description) { throw null; }
+        protected abstract System.Collections.Generic.IEnumerable<Measurement<T>> Observe();
+    }
+}
index e11e37d..1006527 100644 (file)
   <data name="UnableToInitialize" xml:space="preserve">
     <value>Unable to initialize all required reflection objects</value>
   </data>
+  <data name="UnsupportedType" xml:space="preserve">
+    <value>{0} is unsupported type for this operation. The only supported types are byte, short, int, long, float, double and decimal.</value>
+  </data>
 </root>
\ No newline at end of file
index 25c8a38..32e49f4 100644 (file)
@@ -14,7 +14,7 @@
          (which is netstandard1.1).  Again we duplicate in a portable-* folder
          to work with older NuGet clients -->
     <PackageTargetFramework Condition="'$(TargetFramework)' == 'netstandard1.1'">netstandard1.1;portable-net45+win8+wpa81</PackageTargetFramework>
-    <DefineConstants Condition="'$(TargetFramework)' == 'netstandard1.1' or '$(TargetFramework)' == 'net45'">$(DefineConstants);NO_EVENTSOURCE_COMPLEX_TYPE_SUPPORT</DefineConstants>
+    <DefineConstants Condition="'$(TargetFramework)' == 'netstandard1.1' or '$(TargetFramework)' == 'net45'">$(DefineConstants);NO_EVENTSOURCE_COMPLEX_TYPE_SUPPORT;NO_ARRAY_EMPTY_SUPPORT</DefineConstants>
     <DefineConstants Condition="'$(TargetFramework)' != 'netstandard1.1'">$(DefineConstants);EVENTSOURCE_ACTIVITY_SUPPORT</DefineConstants>
     <DefineConstants Condition="'$(TargetFramework)' != 'netstandard1.1' and '$(TargetFramework)' != 'netstandard1.3'">$(DefineConstants);EVENTSOURCE_ENUMERATE_SUPPORT</DefineConstants>
     <DefineConstants Condition="$(TargetFramework.StartsWith('net4'))">$(DefineConstants);ALLOW_PARTIALLY_TRUSTED_CALLERS;ENABLE_HTTP_HANDLER</DefineConstants>
     <Compile Include="System\Diagnostics\ActivityListener.cs" />
     <Compile Include="System\Diagnostics\ActivitySource.cs" />
     <Compile Include="System\Diagnostics\DiagnosticSourceActivity.cs" />
+    <Compile Include="System\Diagnostics\LinkedList.cs" />
     <Compile Include="System\Diagnostics\RandomNumberGenerator.cs" />
+    <Compile Include="System\Diagnostics\Metrics\Counter.cs" />
+    <Compile Include="System\Diagnostics\Metrics\Histogram.cs" />
+    <Compile Include="System\Diagnostics\Metrics\Instrument.cs" />
+    <Compile Include="System\Diagnostics\Metrics\Instrument.common.cs" />
+    <Compile Include="System\Diagnostics\Metrics\Instrument.netcore.cs" Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)' Or '$(TargetFramework)' == 'net5.0'" />
+    <Compile Include="System\Diagnostics\Metrics\Instrument.netfx.cs" Condition="'$(TargetFramework)' != '$(NetCoreAppCurrent)' And '$(TargetFramework)' != 'net5.0'" />
+    <Compile Include="System\Diagnostics\Metrics\Measurement.cs" />
+    <Compile Include="System\Diagnostics\Metrics\Meter.cs" />
+    <Compile Include="System\Diagnostics\Metrics\MeterListener.cs" />
+    <Compile Include="System\Diagnostics\Metrics\ObservableCounter.cs" />
+    <Compile Include="System\Diagnostics\Metrics\ObservableGauge.cs" />
+    <Compile Include="System\Diagnostics\Metrics\ObservableInstrument.cs" />
     <None Include="ActivityUserGuide.md" />
   </ItemGroup>
   <ItemGroup Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETCoreApp'">
index b2d2cd0..8cd65c9 100644 (file)
@@ -1323,62 +1323,6 @@ namespace System.Diagnostics
             private set => _state = (_state & ~State.FormatFlags) | (State)((byte)value & (byte)State.FormatFlags);
         }
 
-        private sealed partial class LinkedListNode<T>
-        {
-            public LinkedListNode(T value) => Value = value;
-            public T Value;
-            public LinkedListNode<T>? Next;
-        }
-
-        // We are not using the public LinkedList<T> because we need to ensure thread safety operation on the list.
-        private sealed class LinkedList<T> : IEnumerable<T>
-        {
-            private LinkedListNode<T> _first;
-            private LinkedListNode<T> _last;
-
-            public LinkedList(T firstValue) => _last = _first = new LinkedListNode<T>(firstValue);
-
-            public LinkedList(IEnumerator<T> e)
-            {
-                _last = _first = new LinkedListNode<T>(e.Current);
-
-                while (e.MoveNext())
-                {
-                    _last.Next = new LinkedListNode<T>(e.Current);
-                    _last = _last.Next;
-                }
-            }
-
-            public LinkedListNode<T> First => _first;
-
-            public void Add(T value)
-            {
-                LinkedListNode<T> newNode = new LinkedListNode<T>(value);
-
-                lock (this)
-                {
-                    _last.Next = newNode;
-                    _last = newNode;
-                }
-            }
-
-            public void AddFront(T value)
-            {
-                LinkedListNode<T> newNode = new LinkedListNode<T>(value);
-
-                lock (this)
-                {
-                    newNode.Next = _first;
-                    _first = newNode;
-                }
-            }
-
-            // Note: Some consumers use this GetEnumerator dynamically to avoid allocations.
-            public Enumerator<T> GetEnumerator() => new Enumerator<T>(_first);
-            IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
-            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-        }
-
         private sealed class BaggageLinkedList : IEnumerable<KeyValuePair<string, string?>>
         {
             private LinkedListNode<KeyValuePair<string, string?>>? _first;
@@ -1662,42 +1606,6 @@ namespace System.Diagnostics
             }
         }
 
-        // Note: Some consumers use this Enumerator dynamically to avoid allocations.
-        private struct Enumerator<T> : IEnumerator<T>
-        {
-            private LinkedListNode<T>? _nextNode;
-            [AllowNull, MaybeNull] private T _currentItem;
-
-            public Enumerator(LinkedListNode<T>? head)
-            {
-                _nextNode = head;
-                _currentItem = default;
-            }
-
-            public T Current => _currentItem!;
-
-            object? IEnumerator.Current => Current;
-
-            public bool MoveNext()
-            {
-                if (_nextNode == null)
-                {
-                    _currentItem = default;
-                    return false;
-                }
-
-                _currentItem = _nextNode.Value;
-                _nextNode = _nextNode.Next;
-                return true;
-            }
-
-            public void Reset() => throw new NotSupportedException();
-
-            public void Dispose()
-            {
-            }
-        }
-
         [Flags]
         private enum State : byte
         {
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LinkedList.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LinkedList.cs
new file mode 100644 (file)
index 0000000..f6b32d0
--- /dev/null
@@ -0,0 +1,192 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Diagnostics
+{
+    internal sealed partial class LinkedListNode<T>
+    {
+        public LinkedListNode(T value) => Value = value;
+        public T Value;
+        public LinkedListNode<T>? Next;
+    }
+
+    // We are not using the public LinkedList<T> because we need to ensure thread safety operation on the list.
+    internal sealed class LinkedList<T> : IEnumerable<T>
+    {
+        private LinkedListNode<T>? _first;
+        private LinkedListNode<T>? _last;
+
+        public LinkedList() {}
+
+        public LinkedList(T firstValue) => _last = _first = new LinkedListNode<T>(firstValue);
+
+        public LinkedList(IEnumerator<T> e)
+        {
+            Debug.Assert(e is not null);
+            _last = _first = new LinkedListNode<T>(e.Current);
+
+            while (e.MoveNext())
+            {
+                _last.Next = new LinkedListNode<T>(e.Current);
+                _last = _last.Next;
+            }
+        }
+
+        public LinkedListNode<T>? First => _first;
+
+        public void Clear()
+        {
+            lock (this)
+            {
+                _first = _last = null;
+            }
+        }
+
+        private void UnsafeAdd(LinkedListNode<T> newNode)
+        {
+            if (_first is null)
+            {
+                _first = _last = newNode;
+                return;
+            }
+
+            Debug.Assert(_first is not null);
+            Debug.Assert(_last is not null);
+
+            _last!.Next = newNode;
+            _last = newNode;
+        }
+
+        public void Add(T value)
+        {
+            LinkedListNode<T> newNode = new LinkedListNode<T>(value);
+
+            lock (this)
+            {
+                UnsafeAdd(newNode);
+            }
+        }
+
+        public bool AddIfNotExist(T value, Func<T, T, bool> compare)
+        {
+            lock (this)
+            {
+                LinkedListNode<T>? current = _first;
+                while (current is not null)
+                {
+                    if (compare(value, current.Value))
+                    {
+                        return false;
+                    }
+
+                    current = current.Next;
+                }
+
+                LinkedListNode<T> newNode = new LinkedListNode<T>(value);
+                UnsafeAdd(newNode);
+
+                return true;
+            }
+        }
+
+        public T? Remove(T value, Func<T, T, bool> compare)
+        {
+            lock (this)
+            {
+                LinkedListNode<T>? previous = _first;
+                if (previous is null)
+                {
+                    return default;
+                }
+
+                if (compare(previous.Value, value))
+                {
+                    _first = previous.Next;
+                    if (_first is null)
+                    {
+                        _last = null;
+                    }
+                    return previous.Value;
+                }
+
+                LinkedListNode<T>? current = previous.Next;
+
+                while (current is not null)
+                {
+                    if (compare(current.Value, value))
+                    {
+                        previous.Next = current.Next;
+                        if (object.ReferenceEquals(_last, current))
+                        {
+                            _last = previous;
+                        }
+
+                        return current.Value;
+                    }
+
+                    previous = current;
+                    current = current.Next;
+                }
+
+                return default;
+            }
+        }
+
+        public void AddFront(T value)
+        {
+            LinkedListNode<T> newNode = new LinkedListNode<T>(value);
+
+            lock (this)
+            {
+                newNode.Next = _first;
+                _first = newNode;
+            }
+        }
+
+        // Note: Some consumers use this GetEnumerator dynamically to avoid allocations.
+        public Enumerator<T> GetEnumerator() => new Enumerator<T>(_first);
+        IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+    }
+
+    // Note: Some consumers use this Enumerator dynamically to avoid allocations.
+    internal struct Enumerator<T> : IEnumerator<T>
+    {
+        private LinkedListNode<T>? _nextNode;
+        [AllowNull, MaybeNull] private T _currentItem;
+
+        public Enumerator(LinkedListNode<T>? head)
+        {
+            _nextNode = head;
+            _currentItem = default;
+        }
+
+        public T Current => _currentItem!;
+
+        object? IEnumerator.Current => Current;
+
+        public bool MoveNext()
+        {
+            if (_nextNode == null)
+            {
+                _currentItem = default;
+                return false;
+            }
+
+            _currentItem = _nextNode.Value;
+            _nextNode = _nextNode.Next;
+            return true;
+        }
+
+        public void Reset() => throw new NotSupportedException();
+
+        public void Dispose()
+        {
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Counter.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Counter.cs
new file mode 100644 (file)
index 0000000..3e7b3d1
--- /dev/null
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// The counter is an instrument that supports adding non-negative values. For example you might call
+    /// counter.Add(1) each time a request is processed to track the total number of requests. Most metric viewers
+    /// will display counters using a rate by default (requests/sec) but can also display a cumulative total.
+    /// </summary>
+    /// <remarks>
+    /// This class supports only the following generic parameter types: <see cref="byte" />, <see cref="short" />, <see cref="int" />, <see cref="long" />, <see cref="float" />, <see cref="double" />, and <see cref="decimal" />
+    /// </remarks>
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+    [System.Security.SecuritySafeCriticalAttribute]
+#endif
+    public sealed class Counter<T> : Instrument<T> where T : struct
+    {
+        internal Counter(Meter meter, string name, string? unit, string? description) : base(meter, name, unit, description)
+        {
+            Publish();
+        }
+
+        /// <summary>
+        /// Record the increment value of the measurement.
+        /// </summary>
+        /// <param name="delta">The increment measurement.</param>
+        public void Add(T delta) => RecordMeasurement(delta);
+
+        /// <summary>
+        /// Record the increment value of the measurement.
+        /// </summary>
+        /// <param name="delta">The increment measurement.</param>
+        /// <param name="tag">A key-value pair tag associated with the measurement.</param>
+        public void Add(T delta, KeyValuePair<string, object?> tag) => RecordMeasurement(delta, tag);
+
+        /// <summary>
+        /// Record the increment value of the measurement.
+        /// </summary>
+        /// <param name="delta">The increment measurement.</param>
+        /// <param name="tag1">A first key-value pair tag associated with the measurement.</param>
+        /// <param name="tag2">A second key-value pair tag associated with the measurement.</param>
+        public void Add(T delta, KeyValuePair<string, object?> tag1, KeyValuePair<string, object?> tag2) => RecordMeasurement(delta, tag1, tag2);
+
+        /// <summary>
+        /// Record the increment value of the measurement.
+        /// </summary>
+        /// <param name="delta">The increment measurement.</param>
+        /// <param name="tag1">A first key-value pair tag associated with the measurement.</param>
+        /// <param name="tag2">A second key-value pair tag associated with the measurement.</param>
+        /// <param name="tag3">A third key-value pair tag associated with the measurement.</param>
+        public void Add(T delta, KeyValuePair<string, object?> tag1, KeyValuePair<string, object?> tag2, KeyValuePair<string, object?> tag3) => RecordMeasurement(delta, tag1, tag2, tag3);
+
+        /// <summary>
+        /// Record the increment value of the measurement.
+        /// </summary>
+        /// <param name="delta">The increment measurement.</param>
+        /// <param name="tags">A span of key-value pair tags associated with the measurement.</param>
+        public void Add(T delta, ReadOnlySpan<KeyValuePair<string, object?>> tags) => RecordMeasurement(delta, tags);
+
+        /// <summary>
+        /// Record the increment value of the measurement.
+        /// </summary>
+        /// <param name="delta">The increment measurement.</param>
+        /// <param name="tags">A list of key-value pair tags associated with the measurement.</param>
+        public void Add(T delta, params KeyValuePair<string, object?>[] tags) => RecordMeasurement(delta, tags.AsSpan());
+    }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Histogram.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Histogram.cs
new file mode 100644 (file)
index 0000000..6bb4711
--- /dev/null
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// The histogram is a metrics Instrument which can be used to report arbitrary values that are likely to be statistically meaningful.
+    /// e.g. the request duration.
+    /// Use <see cref="Meter.CreateHistogram" /> method to create the Histogram object.
+    /// </summary>
+    /// <remarks>
+    /// This class supports only the following generic parameter types: <see cref="byte" />, <see cref="short" />, <see cref="int" />, <see cref="long" />, <see cref="float" />, <see cref="double" />, and <see cref="decimal" />
+    /// </remarks>
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+        [System.Security.SecuritySafeCriticalAttribute]
+#endif
+    public sealed class Histogram<T> : Instrument<T> where T : struct
+    {
+        internal Histogram(Meter meter, string name, string? unit, string? description) : base(meter, name, unit, description)
+        {
+            Publish();
+        }
+
+        /// <summary>
+        /// Record a measurement value.
+        /// </summary>
+        /// <param name="value">The measurement value.</param>
+        public void Record(T value) => RecordMeasurement(value);
+
+        /// <summary>
+        /// Record a measurement value.
+        /// </summary>
+        /// <param name="value">The measurement value.</param>
+        /// <param name="tag">A key-value pair tag associated with the measurement.</param>
+        public void Record(T value, KeyValuePair<string, object?> tag) => RecordMeasurement(value, tag);
+
+        /// <summary>
+        /// Record a measurement value.
+        /// </summary>
+        /// <param name="value">The measurement value.</param>
+        /// <param name="tag1">A first key-value pair tag associated with the measurement.</param>
+        /// <param name="tag2">A second key-value pair tag associated with the measurement.</param>
+        public void Record(T value, KeyValuePair<string, object?> tag1, KeyValuePair<string, object?> tag2) => RecordMeasurement(value, tag1, tag2);
+
+        /// <summary>
+        /// Record a measurement value.
+        /// </summary>
+        /// <param name="value">The measurement value.</param>
+        /// <param name="tag1">A first key-value pair tag associated with the measurement.</param>
+        /// <param name="tag2">A second key-value pair tag associated with the measurement.</param>
+        /// <param name="tag3">A third key-value pair tag associated with the measurement.</param>
+        public void Record(T value, KeyValuePair<string, object?> tag1, KeyValuePair<string, object?> tag2, KeyValuePair<string, object?> tag3) => RecordMeasurement(value, tag1, tag2, tag3);
+
+        /// <summary>
+        /// Record a measurement value.
+        /// </summary>
+        /// <param name="value">The measurement value.</param>
+        /// <param name="tags">A span of key-value pair tags associated with the measurement.</param>
+        public void Record(T value, ReadOnlySpan<KeyValuePair<string, object?>> tags) => RecordMeasurement(value, tags);
+
+        /// <summary>
+        /// Record a measurement value.
+        /// </summary>
+        /// <param name="value">The measurement value.</param>
+        /// <param name="tags">A list of key-value pair tags associated with the measurement.</param>
+        public void Record(T value, params KeyValuePair<string, object?>[] tags) => RecordMeasurement(value, tags.AsSpan());
+    }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.common.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.common.cs
new file mode 100644 (file)
index 0000000..e869859
--- /dev/null
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// Instrument{T} is the base class for all non-observable instruments.
+    /// </summary>
+    /// <remarks>
+    /// This class supports only the following generic parameter types: <see cref="byte" />, <see cref="short" />, <see cref="int" />, <see cref="long" />, <see cref="float" />, <see cref="double" />, and <see cref="decimal" />
+    /// </remarks>
+    public abstract partial class Instrument<T> : Instrument where T : struct
+    {
+        /// <summary>
+        /// Create the metrics instrument using the properties meter, name, description, and unit.
+        /// All classes extending Instrument{T} need to call this constructor when constructing object of the extended class.
+        /// </summary>
+        /// <param name="meter">The meter that created the instrument.</param>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        protected Instrument(Meter meter, string name, string? unit, string? description) : base(meter, name, unit, description)
+        {
+            ValidateTypeParameter<T>();
+        }
+
+        /// <summary>
+        /// Record the measurement by notifying all <see cref="MeterListener" /> objects which listeneing to this instrument.
+        /// </summary>
+        /// <param name="measurement">The measurement value.</param>
+        protected void RecordMeasurement(T measurement) => RecordMeasurement(measurement, Instrument.EmptyTags.AsSpan());
+
+        /// <summary>
+        /// Record the measurement by notifying all <see cref="MeterListener" /> objects which listeneing to this instrument.
+        /// </summary>
+        /// <param name="measurement">The measurement value.</param>
+        /// <param name="tags">A span of key-value pair tags associated with the measurement.</param>
+        protected void RecordMeasurement(T measurement, ReadOnlySpan<KeyValuePair<string, object?>> tags)
+        {
+            LinkedListNode<ListenerSubscription>? current = _subscriptions.First;
+            while (current is not null)
+            {
+                current.Value.Listener.NotifyMeasurement(this, measurement, tags, current.Value.State);
+                current = current.Next;
+            }
+        }
+    }
+}
\ No newline at end of file
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
new file mode 100644 (file)
index 0000000..ec2112f
--- /dev/null
@@ -0,0 +1,151 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// Base class of all Metrics Instrument classes
+    /// </summary>
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+        [System.Security.SecuritySafeCriticalAttribute]
+#endif
+    public abstract class Instrument
+    {
+#if NO_ARRAY_EMPTY_SUPPORT
+        internal static KeyValuePair<string, object?>[] EmptyTags { get; } = new KeyValuePair<string, object?>[0];
+#else
+        internal static KeyValuePair<string, object?>[] EmptyTags { get; } = Array.Empty<KeyValuePair<string, object?>>();
+#endif // NO_ARRAY_EMPTY_SUPPORT
+
+        // We use LikedList here so we don't have to take any lock while iterating over the list as we always hold on a node which be either valid or null.
+        // LinkedList is thread safe for Add and Remove operations.
+        internal LinkedList<ListenerSubscription> _subscriptions = new LinkedList<ListenerSubscription>();
+
+        /// <summary>
+        /// Protected constructor to initialize the common instrument properties like the meter, name, description, and unit.
+        /// All classes extending Instrument need to call this constructor when constructing object of the extended class.
+        /// </summary>
+        /// <param name="meter">The meter that created the instrument.</param>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        protected Instrument(Meter meter, string name, string? unit, string? description)
+        {
+            if (meter == null)
+            {
+                throw new ArgumentNullException(nameof(meter));
+            }
+
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            Meter = meter;
+            Name = name;
+            Description = description;
+            Unit = unit;
+        }
+
+        /// <summary>
+        /// Publish is activating the instrument to start recording measurements and to allow listeners to start listening to such measurements.
+        /// </summary>
+        protected void Publish()
+        {
+            Meter.AddInstrument(this);
+            MeterListener.NotifyForPublishedInstrument(this);
+        }
+
+        /// <summary>
+        /// Gets the Meter which created the instrument.
+        /// </summary>
+        public Meter Meter { get; }
+
+        /// <summary>
+        /// Gets the instrument name.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets the instrument description.
+        /// </summary>
+        public string? Description { get; }
+
+        /// <summary>
+        /// Gets the instrument unit of measurements.
+        /// </summary>
+        public string? Unit { get; }
+
+        /// <summary>
+        /// Checks if there is any listeners for this instrument.
+        /// </summary>
+        public bool Enabled => _subscriptions.First is not null;
+
+        /// <summary>
+        /// A property tells if the instrument is an observable instrument.
+        /// </summary>
+        public virtual bool IsObservable => false;
+
+        // NotifyForUnpublishedInstrument is called from Meter.Dispose()
+        internal void NotifyForUnpublishedInstrument()
+        {
+            LinkedListNode<ListenerSubscription>? current = _subscriptions.First;
+            while (current is not null)
+            {
+                current.Value.Listener.DisableMeasurementEvents(this);
+                current = current.Next;
+            }
+
+            _subscriptions.Clear();
+        }
+
+        internal static void ValidateTypeParameter<T>()
+        {
+            Type type = typeof(T);
+            if (type != typeof(byte)   && type != typeof(short) && type != typeof(int) && type != typeof(long) &&
+                type != typeof(double) && type != typeof(float) && type != typeof(decimal))
+            {
+                throw new InvalidOperationException(SR.Format(SR.UnsupportedType, type));
+            }
+        }
+
+        // Called from MeterListener.EnableMeasurementEvents
+        internal void EnableMeasurement(ListenerSubscription subscription)
+        {
+            while (!_subscriptions.AddIfNotExist(subscription, (s1, s2) => object.ReferenceEquals(s1.Listener, s2.Listener)))
+            {
+                ListenerSubscription oldSubscription = _subscriptions.Remove(subscription, (s1, s2) => object.ReferenceEquals(s1.Listener, s2.Listener));
+                if (object.ReferenceEquals(oldSubscription.Listener, subscription.Listener))
+                {
+                    oldSubscription.Listener.MeasurementsCompleted?.Invoke(this, oldSubscription.State);
+                }
+            }
+        }
+
+        // Called from MeterListener.DisableMeasurementEvents
+        internal object? DisableMeasurements(MeterListener listener) => _subscriptions.Remove(new ListenerSubscription(listener), (s1, s2) => object.ReferenceEquals(s1.Listener, s2.Listener)).State;
+
+        internal virtual void Observe(MeterListener listener)
+        {
+            Debug.Assert(false);
+            throw new InvalidOperationException();
+        }
+
+        internal object? GetSubscriptionState(MeterListener listener)
+        {
+            LinkedListNode<ListenerSubscription>? current = _subscriptions.First;
+            while (current is not null)
+            {
+                if (object.ReferenceEquals(listener, current.Value.Listener))
+                {
+                    return current.Value.State;
+                }
+                current = current.Next;
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.netcore.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.netcore.cs
new file mode 100644 (file)
index 0000000..e937b82
--- /dev/null
@@ -0,0 +1,71 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+namespace System.Diagnostics.Metrics
+{
+    [StructLayout(LayoutKind.Sequential)]
+    internal struct TagsBag
+    {
+        internal KeyValuePair<string, object?> Tag1;
+        internal KeyValuePair<string, object?> Tag2;
+        internal KeyValuePair<string, object?> Tag3;
+    }
+
+    /// <summary>
+    /// Instrument{T} is the base class from which all non-observable instruments will inherit from.
+    /// </summary>
+    /// <remarks>
+    /// This class supports only the following generic parameter types: <see cref="byte" />, <see cref="short" />, <see cref="int" />, <see cref="long" />, <see cref="float" />, <see cref="double" />, and <see cref="decimal" />
+    /// </remarks>
+    public abstract partial class Instrument<T> : Instrument where T : struct
+    {
+        /// <summary>
+        /// Record the measurement by notifying all <see cref="MeterListener" /> objects which listeneing to this instrument.
+        /// </summary>
+        /// <param name="measurement">The measurement value.</param>
+        /// <param name="tag">A key-value pair tag associated with the measurement.</param>
+        protected void RecordMeasurement(T measurement, KeyValuePair<string, object?> tag)
+        {
+            TagsBag tags;
+            tags.Tag1 = tag;
+
+            RecordMeasurement(measurement, MemoryMarshal.CreateReadOnlySpan(ref tags.Tag1, 1));
+        }
+
+        /// <summary>
+        /// Record the measurement by notifying all <see cref="MeterListener" /> objects which listeneing to this instrument.
+        /// </summary>
+        /// <param name="measurement">The measurement value.</param>
+        /// <param name="tag1">A first key-value pair tag associated with the measurement.</param>
+        /// <param name="tag2">A second key-value pair tag associated with the measurement.</param>
+        protected void RecordMeasurement(T measurement, KeyValuePair<string, object?> tag1, KeyValuePair<string, object?> tag2)
+        {
+            TagsBag tags;
+            tags.Tag1 = tag1;
+            tags.Tag2 = tag2;
+
+            RecordMeasurement(measurement, MemoryMarshal.CreateReadOnlySpan(ref tags.Tag1, 2));
+        }
+
+        /// <summary>
+        /// Record the measurement by notifying all <see cref="MeterListener" /> objects which listeneing to this instrument.
+        /// </summary>
+        /// <param name="measurement">The measurement value.</param>
+        /// <param name="tag1">A first key-value pair tag associated with the measurement.</param>
+        /// <param name="tag2">A second key-value pair tag associated with the measurement.</param>
+        /// <param name="tag3">A third key-value pair tag associated with the measurement.</param>
+        protected void RecordMeasurement(T measurement, KeyValuePair<string, object?> tag1, KeyValuePair<string, object?> tag2, KeyValuePair<string, object?> tag3)
+        {
+            TagsBag tags;
+            tags.Tag1 = tag1;
+            tags.Tag2 = tag2;
+            tags.Tag3 = tag3;
+
+            RecordMeasurement(measurement, MemoryMarshal.CreateReadOnlySpan(ref tags.Tag1, 3));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.netfx.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.netfx.cs
new file mode 100644 (file)
index 0000000..66e94ba
--- /dev/null
@@ -0,0 +1,72 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Threading;
+using System.Collections.Generic;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// Instrument{T} is the base class from which all non-observable instruments will inherit from.
+    /// </summary>
+    /// <remarks>
+    /// This class supports only the following generic parameter types: <see cref="byte" />, <see cref="short" />, <see cref="int" />, <see cref="long" />, <see cref="float" />, <see cref="double" />, and <see cref="decimal" />
+    /// </remarks>
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+        [System.Security.SecuritySafeCriticalAttribute]
+#endif
+    public abstract partial class Instrument<T> : Instrument where T : struct
+    {
+        [ThreadStatic] private KeyValuePair<string, object?>[] ts_tags;
+
+        private const int MaxTagsCount = 3;
+
+        /// <summary>
+        /// Record the measurement by notifying all <see cref="MeterListener" /> objects which listeneing to this instrument.
+        /// </summary>
+        /// <param name="measurement">The measurement value.</param>
+        /// <param name="tag">A key-value pair tag associated with the measurement.</param>
+        protected void RecordMeasurement(T measurement, KeyValuePair<string, object?> tag)
+        {
+            var tags = ts_tags ?? new KeyValuePair<string, object?>[MaxTagsCount];
+            ts_tags = null;
+            tags[0] = tag;
+            RecordMeasurement(measurement, tags.AsSpan().Slice(0, 1));
+            ts_tags = tags;
+        }
+
+        /// <summary>
+        /// Record the measurement by notifying all <see cref="MeterListener" /> objects which listeneing to this instrument.
+        /// </summary>
+        /// <param name="measurement">The measurement value.</param>
+        /// <param name="tag1">A first key-value pair tag associated with the measurement.</param>
+        /// <param name="tag2">A second key-value pair tag associated with the measurement.</param>
+        protected void RecordMeasurement(T measurement, KeyValuePair<string, object?> tag1, KeyValuePair<string, object?> tag2)
+        {
+            var tags = ts_tags ?? new KeyValuePair<string, object?>[MaxTagsCount];
+            ts_tags = null;
+            tags[0] = tag1;
+            tags[1] = tag2;
+            RecordMeasurement(measurement, tags.AsSpan().Slice(0, 2));
+            ts_tags = tags;
+        }
+
+        /// <summary>
+        /// Record the measurement by notifying all <see cref="MeterListener" /> objects which listeneing to this instrument.
+        /// </summary>
+        /// <param name="measurement">The measurement value.</param>
+        /// <param name="tag1">A first key-value pair tag associated with the measurement.</param>
+        /// <param name="tag2">A second key-value pair tag associated with the measurement.</param>
+        /// <param name="tag3">A third key-value pair tag associated with the measurement.</param>
+        protected void RecordMeasurement(T measurement, KeyValuePair<string, object?> tag1, KeyValuePair<string, object?> tag2, KeyValuePair<string, object?> tag3)
+        {
+            var tags = ts_tags ?? new KeyValuePair<string, object?>[MaxTagsCount];
+            ts_tags = null;
+            tags[0] = tag1;
+            tags[1] = tag2;
+            tags[2] = tag3;
+            RecordMeasurement(measurement, tags.AsSpan().Slice(0, 3));
+            ts_tags = tags;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Measurement.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Measurement.cs
new file mode 100644 (file)
index 0000000..4c1e769
--- /dev/null
@@ -0,0 +1,118 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// Measurement stores one observed metrics value and its associated tags. This type is used by Observable instruments' Observe() method when reporting current measurements.
+    /// with the associated tags.
+    /// </summary>
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+        [System.Security.SecuritySafeCriticalAttribute]
+#endif
+    public readonly struct Measurement<T> where T : struct
+    {
+        private readonly KeyValuePair<string, object?>[] _tags;
+
+        /// <summary>
+        /// Initializes a new instance of the Measurement using the value and the list of tags.
+        /// </summary>
+        /// <param name="value">The measurement value.</param>
+        public Measurement(T value)
+        {
+            _tags = Instrument.EmptyTags;
+            Value = value;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the Measurement using the value and the list of tags.
+        /// </summary>
+        /// <param name="value">The measurement value.</param>
+        /// <param name="tags">The measurement associated tags list.</param>
+        public Measurement(T value, IEnumerable<KeyValuePair<string, object?>>? tags)
+        {
+            _tags = ToArray(tags);
+            Value = value;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the Measurement using the value and the list of tags.
+        /// </summary>
+        /// <param name="value">The measurement value.</param>
+        /// <param name="tags">The measurement associated tags list.</param>
+        public Measurement(T value, params KeyValuePair<string, object?>[]? tags)
+        {
+            if (tags is not null)
+            {
+                _tags = new KeyValuePair<string, object?>[tags.Length];
+                tags.CopyTo(_tags, 0);
+            }
+            else
+            {
+                _tags = Instrument.EmptyTags;
+            }
+
+            Value = value;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the Measurement using the value and the list of tags.
+        /// </summary>
+        /// <param name="value">The measurement value.</param>
+        /// <param name="tags">The measurement associated tags list.</param>
+        public Measurement(T value, ReadOnlySpan<KeyValuePair<string, object?>> tags)
+        {
+            _tags = tags.ToArray();
+            Value = value;
+        }
+
+        /// <summary>
+        /// Gets the measurement tags list.
+        /// </summary>
+        public ReadOnlySpan<KeyValuePair<string, object?>> Tags => _tags.AsSpan();
+
+        /// <summary>
+        /// Gets the measurement value.
+        /// </summary>
+        public T Value { get; }
+
+        // Private helper to copy IEnumerable to array. We have it to avoid adding dependencies on System.Linq
+        private static KeyValuePair<string, object?>[] ToArray(IEnumerable<KeyValuePair<string, object?>>? tags)
+        {
+            if (tags is null)
+            {
+                return Instrument.EmptyTags;
+            }
+
+            int count = 0;
+            using (IEnumerator<KeyValuePair<string, object?>> e = tags.GetEnumerator())
+            {
+                checked
+                {
+                    while (e.MoveNext())
+                    {
+                        count++;
+                    }
+                }
+            }
+
+            int index = 0;
+            KeyValuePair<string, object?>[] tagsArray = new KeyValuePair<string, object?>[count];
+            using (IEnumerator<KeyValuePair<string, object?>> e = tags.GetEnumerator())
+            {
+                checked
+                {
+                    while (e.MoveNext() && index < count)
+                    {
+                        tagsArray[index] = e.Current;
+                        index++;
+                    }
+                }
+            }
+
+            return tagsArray;
+        }
+    }
+}
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
new file mode 100644 (file)
index 0000000..edbf867
--- /dev/null
@@ -0,0 +1,204 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Diagnostics;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// Meter is the class responsible for creating and tracking the Instruments.
+    /// </summary>
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+        [System.Security.SecuritySafeCriticalAttribute]
+#endif
+    public class Meter : IDisposable
+    {
+        private static LinkedList<Meter> s_allMeters = new LinkedList<Meter>();
+        private LinkedList<Instrument>? _instruments;
+
+        /// <summary>
+        /// Initializes a new instance of the Meter using the meter name.
+        /// </summary>
+        /// <param name="name">The Meter name.</param>
+        public Meter(string name) : this (name, null) {}
+
+        /// <summary>
+        /// Initializes a new instance of the Meter using the meter name and version.
+        /// </summary>
+        /// <param name="name">The Meter name.</param>
+        /// <param name="version">The optional Meter version.</param>
+        public Meter(string name, string? version)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            Name = name;
+            Version = version;
+
+            s_allMeters.Add(this);
+        }
+
+        /// <summary>
+        /// Returns the Meter name.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Returns the Meter Version.
+        /// </summary>
+        public string? Version { get; }
+
+        /// <summary>
+        /// Create a metrics Counter object.
+        /// </summary>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        /// <remarks>
+        /// Counter is an Instrument which supports non-negative increments.
+        /// Example uses for Counter: count the number of bytes received, count the number of requests completed, count the number of accounts created, count the number of checkpoints run, and count the number of HTTP 5xx errors.
+        /// </remarks>
+        public Counter<T> CreateCounter<T>(string name, string? unit = null, string? description = null) where T : struct => new Counter<T>(this, name, unit, description);
+
+        /// <summary>
+        /// Histogram is an Instrument which can be used to report arbitrary values that are likely to be statistically meaningful. It is intended for statistics such as histograms, summaries, and percentile.
+        /// </summary>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        /// <remarks>
+        /// Example uses for Histogram: the request duration and the size of the response payload.
+        /// </remarks>
+        public Histogram<T> CreateHistogram<T>(string name, string? unit = null, string? description = null) where T : struct => new Histogram<T>(this, name, unit, description);
+
+        /// <summary>
+        /// ObservableCounter is an Instrument which reports monotonically increasing value(s) when the instrument is being observed.
+        /// </summary>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableCounter{t}.Observe" /> is called by <see cref="MeterListener.RecordObservableInstruments" />.</param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        /// <remarks>
+        /// Example uses for ObservableCounter: The number of page faults for each process.
+        /// </remarks>
+        public ObservableCounter<T> CreateObservableCounter<T>(string name, Func<T> observeValue, string? unit = null, string? description = null) where T : struct =>
+                                        new ObservableCounter<T>(this, name, observeValue, unit, description);
+
+
+        /// <summary>
+        /// ObservableCounter is an Instrument which reports monotonically increasing value(s) when the instrument is being observed.
+        /// </summary>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableCounter{t}.Observe" /> is called by <see cref="MeterListener.RecordObservableInstruments" /></param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        /// <remarks>
+        /// Example uses for ObservableCounter: The number of page faults for each process.
+        /// </remarks>
+        public ObservableCounter<T> CreateObservableCounter<T>(string name, Func<Measurement<T>> observeValue, string? unit = null, string? description = null) where T : struct =>
+                                        new ObservableCounter<T>(this, name, observeValue, unit, description);
+
+        /// <summary>
+        /// ObservableCounter is an Instrument which reports monotonically increasing value(s) when the instrument is being observed.
+        /// </summary>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="observeValues">The callback to call to get the measurements when the <see cref="ObservableCounter{t}.Observe" /> is called by <see cref="MeterListener.RecordObservableInstruments" />.</param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        /// <remarks>
+        /// Example uses for ObservableCounter: The number of page faults for each process.
+        /// </remarks>
+        public ObservableCounter<T> CreateObservableCounter<T>(string name, Func<IEnumerable<Measurement<T>>> observeValues, string? unit = null, string? description = null) where T : struct =>
+                                        new ObservableCounter<T>(this, name, observeValues, unit, description);
+
+        /// <summary>
+        /// ObservableGauge is an asynchronous Instrument which reports non-additive value(s) (e.g. the room temperature - it makes no sense to report the temperature value from multiple rooms and sum them up) when the instrument is being observed.
+        /// </summary>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableCounter{t}.Observe" /> is called by <see cref="MeterListener.RecordObservableInstruments" />.</param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        public ObservableGauge<T> CreateObservableGauge<T>(string name, Func<T> observeValue, string? unit = null, string? description = null) where T : struct =>
+                                        new ObservableGauge<T>(this, name, observeValue, unit, description);
+
+        /// <summary>
+        /// ObservableGauge is an asynchronous Instrument which reports non-additive value(s) (e.g. the room temperature - it makes no sense to report the temperature value from multiple rooms and sum them up) when the instrument is being observed.
+        /// </summary>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableCounter{t}.Observe" /> is called by <see cref="MeterListener.RecordObservableInstruments" />.</param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        public ObservableGauge<T> CreateObservableGauge<T>(string name, Func<Measurement<T>> observeValue, string? unit = null, string? description = null) where T : struct =>
+                                        new ObservableGauge<T>(this, name, observeValue, unit, description);
+
+        /// <summary>
+        /// ObservableGauge is an asynchronous Instrument which reports non-additive value(s) (e.g. the room temperature - it makes no sense to report the temperature value from multiple rooms and sum them up) when the instrument is being observed.
+        /// </summary>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="observeValues">The callback to call to get the measurements when the <see cref="ObservableCounter{t}.Observe" /> is called by <see cref="MeterListener.RecordObservableInstruments" />.</param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        public ObservableGauge<T> CreateObservableGauge<T>(string name, Func<IEnumerable<Measurement<T>>> observeValues, string? unit = null, string? description = null) where T : struct =>
+                                        new ObservableGauge<T>(this, name, observeValues, unit, description);
+
+        /// <summary>
+        /// Dispose the Meter which will disable all instruments created by this meter.
+        /// </summary>
+        public void Dispose()
+        {
+            s_allMeters.Remove(this, (meter1, meter2) => object.ReferenceEquals(meter1, meter2));
+
+            if (_instruments is not null)
+            {
+                LinkedListNode<Instrument>? current = _instruments.First;
+
+                while (current is not null)
+                {
+                    current.Value.NotifyForUnpublishedInstrument();
+                    current = current.Next;
+                }
+
+                _instruments.Clear();
+            }
+        }
+
+        // AddInstrument will be called when publishing the instrument (i.e. calling Instrument.Publish()).
+        internal void AddInstrument(Instrument instrument)
+        {
+            if (_instruments is null)
+            {
+                Interlocked.CompareExchange(ref _instruments, new LinkedList<Instrument>(), null);
+            }
+
+            Debug.Assert(_instruments is not null);
+
+            _instruments.AddIfNotExist(instrument, (instrument1, instrument2) => object.ReferenceEquals(instrument1, instrument2));
+        }
+
+        // Called from MeterListener.Start
+        internal static void NotifyListenerWithAllPublishedInstruments(MeterListener listener)
+        {
+            Action<Instrument, MeterListener>? instrumentPublished = listener.InstrumentPublished;
+            if (instrumentPublished is null)
+            {
+                return;
+            }
+
+            LinkedListNode<Meter>? current = s_allMeters.First;
+            while (current is not null)
+            {
+                LinkedListNode<Instrument>? currentInstrument = current.Value._instruments?.First;
+                while (currentInstrument is not null)
+                {
+                    instrumentPublished.Invoke(currentInstrument.Value, listener);
+                    currentInstrument = currentInstrument.Next;
+                }
+                current = current.Next;
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MeterListener.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MeterListener.cs
new file mode 100644 (file)
index 0000000..96c9568
--- /dev/null
@@ -0,0 +1,243 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// A delegate to represent the Meterlistener callbacks used in measurements recording operation.
+    /// </summary>
+    public delegate void MeasurementCallback<T>(Instrument instrument, T measurement, ReadOnlySpan<KeyValuePair<string, object?>> tags, object? state) where T : struct;
+
+    /// <summary>
+    /// MeterListener is class used to listen to the metrics instrument measurements recording.
+    /// </summary>
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+        [System.Security.SecuritySafeCriticalAttribute]
+#endif
+    public sealed class MeterListener : IDisposable
+    {
+        // We use LikedList here so we don't have to take any lock while iterating over the list as we always hold on a node which be either valid or null.
+        // LinkedList is thread safe for Add, Remove, and Clear operations.
+        private static LinkedList<MeterListener> s_allStartedListeners = new LinkedList<MeterListener>();
+
+        // List of the instruments which the current listener is listening to.
+        private LinkedList<Instrument> _enabledMeasurementInstruments = new LinkedList<Instrument>();
+        private bool _disposed;
+
+        // We initialize all measurement callback with no-op operations so we'll avoid the null checks during the execution;
+        private MeasurementCallback<byte>    _byteMeasurementCallback    = (instrument, measurement, tags, state) => { /* no-op */ };
+        private MeasurementCallback<short>   _shortMeasurementCallback   = (instrument, measurement, tags, state) => { /* no-op */ };
+        private MeasurementCallback<int>     _intMeasurementCallback     = (instrument, measurement, tags, state) => { /* no-op */ };
+        private MeasurementCallback<long>    _longMeasurementCallback    = (instrument, measurement, tags, state) => { /* no-op */ };
+        private MeasurementCallback<float>   _floatMeasurementCallback   = (instrument, measurement, tags, state) => { /* no-op */ };
+        private MeasurementCallback<double>  _doubleMeasurementCallback  = (instrument, measurement, tags, state) => { /* no-op */ };
+        private MeasurementCallback<decimal> _decimalMeasurementCallback = (instrument, measurement, tags, state) => { /* no-op */ };
+
+        /// <summary>
+        /// Creates a MeterListener object.
+        /// </summary>
+        public MeterListener() { }
+
+        /// <summary>
+        /// Callbacks to get notification when an instrument is published.
+        /// </summary>
+        public Action<Instrument, MeterListener>? InstrumentPublished { get; set; }
+
+        /// <summary>
+        /// Callbacks to get notification when stopping the measurement on some instrument.
+        /// This can happen when the Meter or the Listener is disposed or calling <see cref="Dispose" /> on the listener.
+        /// </summary>
+        public Action<Instrument, object?>? MeasurementsCompleted { get; set; }
+
+        /// <summary>
+        /// Start listening to a specific instrument measurement recording.
+        /// </summary>
+        /// <param name="instrument">The instrument to listen to.</param>
+        /// <param name="state">A state object which will be passed back to the callback getting measurements events.</param>
+        public void EnableMeasurementEvents(Instrument instrument, object? state = null)
+        {
+            if (instrument is null || _disposed)
+            {
+                return;
+            }
+
+            _enabledMeasurementInstruments.AddIfNotExist(instrument, (instrument1, instrument2) => object.ReferenceEquals(instrument1, instrument2));
+            instrument.EnableMeasurement(new ListenerSubscription(this, state));
+        }
+
+        /// <summary>
+        /// Stop listening to a specific instrument measurement recording.
+        /// </summary>
+        /// <param name="instrument">The instrument to stop listening to.</param>
+        /// <returns>The state object originally passed to <see cref="EnableMeasurementEvents" /> method.</returns>
+        public object? DisableMeasurementEvents(Instrument instrument)
+        {
+            if (instrument is null)
+            {
+                return default;
+            }
+
+            _enabledMeasurementInstruments.Remove(instrument, (instrument1, instrument2) => object.ReferenceEquals(instrument1, instrument2));
+            object? state =  instrument.DisableMeasurements(this);
+            MeasurementsCompleted?.Invoke(instrument!, state);
+            return state;
+        }
+
+        /// <summary>
+        /// Sets a callback for a specific numeric type to get the measurement recording notification from all instruments which enabled listening and was created with the same specified numeric type.
+        /// If a measurement of type T is recorded and a callback of type T is registered, that callback will be used.
+        /// </summary>
+        /// <param name="measurementCallback">The callback which can be used to get measurement recording of numeric type T.</param>
+        public void SetMeasurementEventCallback<T>(MeasurementCallback<T>? measurementCallback) where T : struct
+        {
+            if (measurementCallback is MeasurementCallback<byte> byteCallback)
+            {
+                _byteMeasurementCallback = (measurementCallback is null) ? ((instrument, measurement, tags, state) => { /* no-op */}) : byteCallback;
+            }
+            else if (measurementCallback is MeasurementCallback<int> intCallback)
+            {
+                _intMeasurementCallback = (measurementCallback is null) ? ((instrument, measurement, tags, state) => { /* no-op */}) : intCallback;
+            }
+            else if (measurementCallback is MeasurementCallback<float> floatCallback)
+            {
+                _floatMeasurementCallback = (measurementCallback is null) ? ((instrument, measurement, tags, state) => { /* no-op */}) : floatCallback;
+            }
+            else if (measurementCallback is MeasurementCallback<double> doubleCallback)
+            {
+                _doubleMeasurementCallback = (measurementCallback is null) ? ((instrument, measurement, tags, state) => { /* no-op */}) : doubleCallback;
+            }
+            else if (measurementCallback is MeasurementCallback<decimal> decimalCallback)
+            {
+                _decimalMeasurementCallback = (measurementCallback is null) ? ((instrument, measurement, tags, state) => { /* no-op */}) : decimalCallback;
+            }
+            else if (measurementCallback is MeasurementCallback<short> shortCallback)
+            {
+                _shortMeasurementCallback = (measurementCallback is null) ? ((instrument, measurement, tags, state) => { /* no-op */}) : shortCallback;
+            }
+            else if (measurementCallback is MeasurementCallback<long> longCallback)
+            {
+                _longMeasurementCallback = (measurementCallback is null) ? ((instrument, measurement, tags, state) => { /* no-op */}) : longCallback;
+            }
+            else
+            {
+                throw new InvalidOperationException(SR.Format(SR.UnsupportedType, typeof(T)));
+            }
+        }
+
+        /// <summary>
+        /// Enable the listener to start listeneing to instruments measurement recording.
+        /// </summary>
+        public void Start()
+        {
+            if (_disposed)
+            {
+                return;
+            }
+
+            if (s_allStartedListeners.AddIfNotExist(this, (listener1, listener2) => object.ReferenceEquals(listener1, listener2)))
+            {
+                Meter.NotifyListenerWithAllPublishedInstruments(this);
+            }
+        }
+
+        /// <summary>
+        /// Calls all Observable instruments which the listener is listening to then calls <see cref="SetMeasurementEventCallback" /> with every collected measurement.
+        /// </summary>
+        public void RecordObservableInstruments()
+        {
+            LinkedListNode<Instrument>? current = _enabledMeasurementInstruments.First;
+            while (current is not null)
+            {
+                if (current.Value.IsObservable)
+                {
+                    current.Value.Observe(this);
+                }
+
+                current = current.Next;
+            }
+        }
+
+        /// <summary>
+        /// Disposes the listeners which will stop it from listeneing to any instrument.
+        /// </summary>
+        public void Dispose()
+        {
+            if (_disposed)
+            {
+                return;
+            }
+            _disposed = true;
+            s_allStartedListeners.Remove(this, (listener1, listener2) => object.ReferenceEquals(listener1, listener2));
+
+            LinkedListNode<Instrument>? current = _enabledMeasurementInstruments.First;
+
+            while (current is not null)
+            {
+                object? state = current.Value.DisableMeasurements(this);
+                MeasurementsCompleted?.Invoke(current.Value, state);
+                current = current.Next;
+            }
+
+            _enabledMeasurementInstruments.Clear();
+        }
+
+        // NotifyForPublishedInstrument will be called every time publishing instrument
+        internal static void NotifyForPublishedInstrument(Instrument instrument)
+        {
+            LinkedListNode<MeterListener>? current = s_allStartedListeners.First;
+            while (current is not null)
+            {
+                current.Value.InstrumentPublished?.Invoke(instrument, current.Value);
+                current = current.Next;
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal void NotifyMeasurement<T>(Instrument instrument, T measurement, ReadOnlySpan<KeyValuePair<string, object?>> tags, object? state)
+        {
+            if (measurement is byte byteMeasurement)
+            {
+                _byteMeasurementCallback(instrument, byteMeasurement, tags, state);
+            }
+            else if (measurement is short shortMeasurement)
+            {
+                _shortMeasurementCallback(instrument, shortMeasurement, tags, state);
+            }
+            else if (measurement is int intMeasurement)
+            {
+                _intMeasurementCallback(instrument, intMeasurement, tags, state);
+            }
+            else if (measurement is long longMeasurement)
+            {
+                _longMeasurementCallback(instrument, longMeasurement, tags, state);
+            }
+            else if (measurement is float floatMeasurement)
+            {
+                _floatMeasurementCallback(instrument, floatMeasurement, tags, state);
+            }
+            else if (measurement is double doubleMeasurement)
+            {
+                _doubleMeasurementCallback(instrument, doubleMeasurement, tags, state);
+            }
+            else if (measurement is decimal decimalMeasurement)
+            {
+                _decimalMeasurementCallback(instrument, decimalMeasurement, tags, state);
+            }
+        }
+    }
+
+    internal readonly struct ListenerSubscription
+    {
+        internal ListenerSubscription(MeterListener listener, object? state = null)
+        {
+            Listener = listener;
+            State = state;
+        }
+
+        internal MeterListener Listener { get; }
+        internal object? State { get; }
+    }
+}
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/ObservableCounter.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/ObservableCounter.cs
new file mode 100644 (file)
index 0000000..0a77bdd
--- /dev/null
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// ObservableCounter is a metrics observable Instrument which reports monotonically increasing value(s) when the instrument is being observed.
+    /// e.g. CPU time (for different processes, threads, user mode or kernel mode).
+    /// Use Meter.CreateObservableCounter methods to create the observable counter object.
+    /// </summary>
+    /// <remarks>
+    /// This class supports only the following generic parameter types: <see cref="byte" />, <see cref="short" />, <see cref="int" />, <see cref="long" />, <see cref="float" />, <see cref="double" />, and <see cref="decimal" />
+    /// </remarks>
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+        [System.Security.SecuritySafeCriticalAttribute]
+#endif
+    public sealed class ObservableCounter<T> : ObservableInstrument<T> where T : struct
+    {
+        private object _callback;
+
+        internal ObservableCounter(Meter meter, string name, Func<T> observeValue, string? unit, string? description) : base(meter, name, unit, description)
+        {
+            if (observeValue is null)
+            {
+                throw new ArgumentNullException(nameof(observeValue));
+            }
+
+            _callback = observeValue;
+            Publish();
+        }
+
+        internal ObservableCounter(Meter meter, string name, Func<Measurement<T>> observeValue, string? unit, string? description) : base(meter, name, unit, description)
+        {
+            if (observeValue is null)
+            {
+                throw new ArgumentNullException(nameof(observeValue));
+            }
+
+            _callback = observeValue;
+            Publish();
+        }
+
+        internal ObservableCounter(Meter meter, string name, Func<IEnumerable<Measurement<T>>> observeValues, string? unit, string? description) : base(meter, name, unit, description)
+        {
+            if (observeValues is null)
+            {
+                throw new ArgumentNullException(nameof(observeValues));
+            }
+
+            _callback = observeValues;
+            Publish();
+        }
+
+        /// <summary>
+        /// Observe() fetches the current measurements being tracked by this observable counter.
+        /// </summary>
+        protected override IEnumerable<Measurement<T>> Observe() => Observe(_callback);
+    }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/ObservableGauge.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/ObservableGauge.cs
new file mode 100644 (file)
index 0000000..a6e519d
--- /dev/null
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// ObservableGauge is an observable Instrument that reports non-additive value(s) when the instrument is being observed.
+    /// e.g. the current room temperature
+    /// Use Meter.CreateObservableGauge methods to create the observable counter object.
+    /// </summary>
+    /// <remarks>
+    /// This class supports only the following generic parameter types: <see cref="byte" />, <see cref="short" />, <see cref="int" />, <see cref="long" />, <see cref="float" />, <see cref="double" />, and <see cref="decimal" />
+    /// </remarks>
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+        [System.Security.SecuritySafeCriticalAttribute]
+#endif
+    public sealed class ObservableGauge<T> : ObservableInstrument<T> where T : struct
+    {
+        private object _callback;
+
+        internal ObservableGauge(Meter meter, string name, Func<T> observeValue, string? unit, string? description) : base(meter, name, unit, description)
+        {
+            if (observeValue is null)
+            {
+                throw new ArgumentNullException(nameof(observeValue));
+            }
+
+            _callback = observeValue;
+            Publish();
+        }
+
+        internal ObservableGauge(Meter meter, string name, Func<Measurement<T>> observeValue, string? unit, string? description) : base(meter, name, unit, description)
+        {
+            if (observeValue is null)
+            {
+                throw new ArgumentNullException(nameof(observeValue));
+            }
+
+            _callback = observeValue;
+            Publish();
+        }
+
+        internal ObservableGauge(Meter meter, string name, Func<IEnumerable<Measurement<T>>> observeValues, string? unit, string? description) : base(meter, name, unit, description)
+        {
+            if (observeValues is null)
+            {
+                throw new ArgumentNullException(nameof(observeValues));
+            }
+
+            _callback = observeValues;
+            Publish();
+        }
+
+        /// <summary>
+        /// Observe() fetches the current measurements being tracked by this observable counter.
+        /// </summary>
+        protected override IEnumerable<Measurement<T>> Observe() => Observe(_callback);
+    }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/ObservableInstrument.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/ObservableInstrument.cs
new file mode 100644 (file)
index 0000000..1cbcd59
--- /dev/null
@@ -0,0 +1,87 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace System.Diagnostics.Metrics
+{
+    /// <summary>
+    /// ObservableInstrument{T} is the base class from which all metrics observable instruments will inherit from.
+    /// </summary>
+    /// <remarks>
+    /// This class supports only the following generic parameter types: <see cref="byte" />, <see cref="short" />, <see cref="int" />, <see cref="long" />, <see cref="float" />, <see cref="double" />, and <see cref="decimal" />
+    /// </remarks>
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+        [System.Security.SecuritySafeCriticalAttribute]
+#endif
+    public abstract class ObservableInstrument<T> : Instrument where T : struct
+    {
+        /// <summary>
+        /// Create the metrics observable instrument using the properties meter, name, description, and unit.
+        /// All classes extending ObservableInstrument{T} need to call this constructor when constructing object of the extended class.
+        /// </summary>
+        /// <param name="meter">The meter that created the instrument.</param>
+        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="unit">Optional instrument unit of measurements.</param>
+        /// <param name="description">Optional instrument description.</param>
+        protected ObservableInstrument(Meter meter, string name, string? unit, string? description) : base(meter, name, unit, description)
+        {
+            ValidateTypeParameter<T>();
+        }
+
+        /// <summary>
+        /// Observe() fetches the current measurements being tracked by this instrument. All classes extending ObservableInstrument{T} need to implement this method.
+        /// </summary>
+        protected abstract IEnumerable<Measurement<T>> Observe();
+
+        /// <summary>
+        /// A property tells if the instrument is an observable instrument. This property will return true for all metrics observable instruments.
+        /// </summary>
+        public override bool IsObservable => true;
+
+        // Will be called from MeterListener.RecordObservableInstruments for each observable instrument.
+#if ALLOW_PARTIALLY_TRUSTED_CALLERS
+        [System.Security.SecuritySafeCriticalAttribute]
+#endif
+        internal override void Observe(MeterListener listener)
+        {
+            object? state = GetSubscriptionState(listener);
+
+            IEnumerable<Measurement<T>> measurements = Observe();
+            if (measurements is null)
+            {
+                return;
+            }
+
+            foreach (Measurement<T> measurement in measurements)
+            {
+                listener.NotifyMeasurement(this, measurement.Value, measurement.Tags, state);
+            }
+        }
+
+        // Will be called from the concrete classes which extends ObservabilityInstrument<T> when calling Observe() method.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal IEnumerable<Measurement<T>> Observe(object callback)
+        {
+            if (callback is Func<T> valueOnlyFunc)
+            {
+                return new Measurement<T>[1] { new Measurement<T>(valueOnlyFunc()) };
+            }
+
+            if (callback is Func<Measurement<T>> measurementOnlyFunc)
+            {
+                return new Measurement<T>[1] { measurementOnlyFunc() };
+            }
+
+            if (callback is Func<IEnumerable<Measurement<T>>> listOfMeasurementsFunc)
+            {
+                return listOfMeasurementsFunc();
+            }
+
+            Debug.Assert(false, "Execution shouldn't reach this point");
+            return null;
+        }
+
+    }
+}
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
new file mode 100644 (file)
index 0000000..39fd23b
--- /dev/null
@@ -0,0 +1,856 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Diagnostics.Metrics;
+using Microsoft.DotNet.RemoteExecutor;
+using System.Collections.Generic;
+
+namespace System.Diagnostics.Metrics.Tests
+{
+    public class MetricsTests
+    {
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void MeterConstructionTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("meter1");
+                Assert.Equal("meter1", meter.Name);
+                Assert.Null(meter.Version);
+
+                meter = new Meter("meter2", "v1.0");
+                Assert.Equal("meter2", meter.Name);
+                Assert.Equal("v1.0", meter.Version);
+
+                Assert.Throws<ArgumentNullException>(() => new Meter(null));
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void InstrumentCreationTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("InstrumentCreationTest");
+
+                Counter<int> counter = meter.CreateCounter<int>("Counter", "seconds", "Seconds Counter");
+                ValidateInstrumentInfo(counter, "Counter", "seconds", "Seconds Counter", false, false);
+
+                Histogram<float> histogram = meter.CreateHistogram<float>("Histogram", "centimeters", "centimeters Histogram");
+                ValidateInstrumentInfo(histogram, "Histogram", "centimeters", "centimeters Histogram", false, false);
+
+                ObservableCounter<long> observableCounter = meter.CreateObservableCounter<long>("ObservableCounter", () => 10, "millisecond", "millisecond ObservableCounter");
+                ValidateInstrumentInfo(observableCounter, "ObservableCounter", "millisecond", "millisecond ObservableCounter", false, true);
+
+                ObservableGauge<double> observableGauge = meter.CreateObservableGauge<double>("ObservableGauge", () => 10, "Fahrenheit", "Fahrenheit ObservableGauge");
+                ValidateInstrumentInfo(observableGauge, "ObservableGauge", "Fahrenheit", "Fahrenheit ObservableGauge", false, true);
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void CreateInstrumentParametersTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("CreateInstrumentParametersTest");
+
+                Assert.Throws<ArgumentNullException>(() => meter.CreateCounter<byte>(null, "seconds", "Seconds Counter"));
+                Assert.Throws<ArgumentNullException>(() => meter.CreateHistogram<short>(null, "seconds", "Seconds Counter"));
+                Assert.Throws<ArgumentNullException>(() => meter.CreateObservableCounter<long>(null, () => 0, "seconds", "Seconds ObservableCounter"));
+                Assert.Throws<ArgumentNullException>(() => meter.CreateObservableGauge<double>(null, () => 0, "seconds", "Seconds ObservableGauge"));
+
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void SupportedGenericParameterTypesTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("SupportedGenericParameterTypesTest");
+
+                Counter<byte> counter1 = meter.CreateCounter<byte>("Counter1", "seconds", "Seconds Counter");
+                Counter<short> counter2 = meter.CreateCounter<short>("Counter2", "seconds", "Seconds Counter");
+                Counter<int> counter3 = meter.CreateCounter<int>("Counter3", "seconds", "Seconds Counter");
+                Counter<long> counter4 = meter.CreateCounter<long>("Counter4", "seconds", "Seconds Counter");
+                Counter<float> counter5 = meter.CreateCounter<float>("Counter5", "seconds", "Seconds Counter");
+                Counter<double> counter6 = meter.CreateCounter<double>("Counter6", "seconds", "Seconds Counter");
+                Counter<decimal> counter7 = meter.CreateCounter<decimal>("Counter7", "seconds", "Seconds Counter");
+
+                Histogram<byte> histogram1 = meter.CreateHistogram<byte>("histogram1", "seconds", "Seconds histogram");
+                Histogram<short> histogram2 = meter.CreateHistogram<short>("histogram2", "seconds", "Seconds histogram");
+                Histogram<int> histogram3 = meter.CreateHistogram<int>("histogram3", "seconds", "Seconds histogram");
+                Histogram<long> histogram4 = meter.CreateHistogram<long>("histogram4", "seconds", "Seconds histogram");
+                Histogram<float> histogram5 = meter.CreateHistogram<float>("histogram5", "seconds", "Seconds histogram");
+                Histogram<double> histogram6 = meter.CreateHistogram<double>("histogram6", "seconds", "Seconds histogram");
+                Histogram<decimal> histogram7 = meter.CreateHistogram<decimal>("histogram7", "seconds", "Seconds histogram");
+
+                ObservableCounter<byte> observableCounter1 = meter.CreateObservableCounter<byte>("observableCounter1", () => 0, "seconds", "Seconds ObservableCounter");
+                ObservableCounter<short> observableCounter2 = meter.CreateObservableCounter<short>("observableCounter2", () => 0, "seconds", "Seconds ObservableCounter");
+                ObservableCounter<int> observableCounter3 = meter.CreateObservableCounter<int>("observableCounter3", () => 0, "seconds", "Seconds ObservableCounter");
+                ObservableCounter<long> observableCounter4 = meter.CreateObservableCounter<long>("observableCounter4", () => 0, "seconds", "Seconds ObservableCounter");
+                ObservableCounter<float> observableCounter5 = meter.CreateObservableCounter<float>("observableCounter5", () => 0, "seconds", "Seconds ObservableCounter");
+                ObservableCounter<double> observableCounter6 = meter.CreateObservableCounter<double>("observableCounter6", () => 0, "seconds", "Seconds ObservableCounter");
+                ObservableCounter<decimal> observableCounter7 = meter.CreateObservableCounter<decimal>("observableCounter7", () => 0, "seconds", "Seconds ObservableCounter");
+
+                ObservableGauge<byte> observableGauge1 = meter.CreateObservableGauge<byte>("observableGauge1", () => 0, "seconds", "Seconds ObservableGauge");
+                ObservableGauge<short> observableGauge2 = meter.CreateObservableGauge<short>("observableGauge2", () => 0, "seconds", "Seconds ObservableGauge");
+                ObservableGauge<int> observableGauge3 = meter.CreateObservableGauge<int>("observableGauge3", () => 0, "seconds", "Seconds ObservableGauge");
+                ObservableGauge<long> observableGauge4 = meter.CreateObservableGauge<long>("observableGauge4", () => 0, "seconds", "Seconds ObservableGauge");
+                ObservableGauge<float> observableGauge5 = meter.CreateObservableGauge<float>("observableGauge5", () => 0, "seconds", "Seconds ObservableGauge");
+                ObservableGauge<double> observableGauge6 = meter.CreateObservableGauge<double>("observableGauge6", () => 0, "seconds", "Seconds ObservableGauge");
+                ObservableGauge<decimal> observableGauge7 = meter.CreateObservableGauge<decimal>("observableGauge7", () => 0, "seconds", "Seconds ObservableGauge");
+
+                Assert.Throws<InvalidOperationException>(() => meter.CreateCounter<uint>("Counter", "seconds", "Seconds Counter"));
+                Assert.Throws<InvalidOperationException>(() => meter.CreateHistogram<ulong>("histogram1", "seconds", "Seconds histogram"));
+                Assert.Throws<InvalidOperationException>(() => meter.CreateObservableCounter<sbyte>("observableCounter3", () => 0, "seconds", "Seconds ObservableCounter"));
+                Assert.Throws<InvalidOperationException>(() => meter.CreateObservableGauge<ushort>("observableGauge7", () => 0, "seconds", "Seconds ObservableGauge"));
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void ListeningToInstrumentsPublishingTest()
+        {
+            RemoteExecutor.Invoke(() => {
+
+                Meter meter = new Meter("ListeningToInstrumentsPublishingTest");
+
+                int instrumentsEncountered = 0;
+                using (MeterListener listener = new MeterListener())
+                {
+                    Counter<long> counter = meter.CreateCounter<long>("Counter4", "seconds", "Seconds Counter");
+                    ObservableGauge<byte> observableGauge = meter.CreateObservableGauge<byte>("observableGauge1", () => 0, "seconds", "Seconds ObservableGauge");
+
+                    // Listener is not enabled yet
+                    Assert.Equal(0, instrumentsEncountered);
+
+                    listener.InstrumentPublished = (instruments, theListener) => instrumentsEncountered++;
+
+                    // Listener still not started yet
+                    Assert.Equal(0, instrumentsEncountered);
+
+                    listener.Start();
+
+                    Assert.Equal(2, instrumentsEncountered);
+
+                    Histogram<byte> histogram = meter.CreateHistogram<byte>("histogram1", "seconds", "Seconds histogram");
+                    ObservableCounter<byte> observableCounter = meter.CreateObservableCounter<byte>("observableCounter1", () => 0, "seconds", "Seconds ObservableCounter");
+
+                    Assert.Equal(4, instrumentsEncountered);
+
+                    // Enable listening to the 4 instruments
+
+                    listener.EnableMeasurementEvents(counter, counter);
+                    listener.EnableMeasurementEvents(observableGauge, observableGauge);
+                    listener.EnableMeasurementEvents(histogram, histogram);
+                    listener.EnableMeasurementEvents(observableCounter, observableCounter);
+
+                    // Enable listening to instruments unpublished event
+                    listener.MeasurementsCompleted = (instruments, state) => { instrumentsEncountered--; Assert.Same(state, instruments); };
+
+                    // Should fire all MeasurementsCompleted event for all instruments in the Meter
+                    meter.Dispose();
+
+                    // MeasurementsCompleted should be called 4 times for every instrument.
+                    Assert.Equal(0, instrumentsEncountered);
+                }
+
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void InstrumentMeasurementTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("InstrumentMeasurementTest");
+
+                Counter<byte> counter = meter.CreateCounter<byte>("byteCounter");
+                InstruementMeasurementAggregationValidation(counter, (value, tags) => { counter.Add(value, tags); } );
+
+                Counter<short> counter1 = meter.CreateCounter<short>("shortCounter");
+                InstruementMeasurementAggregationValidation(counter1, (value, tags) => { counter1.Add(value, tags); } );
+
+                Counter<int> counter2 = meter.CreateCounter<int>("intCounter");
+                InstruementMeasurementAggregationValidation(counter2, (value, tags) => { counter2.Add(value, tags); } );
+
+                Counter<long> counter3 = meter.CreateCounter<long>("longCounter");
+                InstruementMeasurementAggregationValidation(counter3, (value, tags) => { counter3.Add(value, tags); } );
+
+                Counter<float> counter4 = meter.CreateCounter<float>("floatCounter");
+                InstruementMeasurementAggregationValidation(counter4, (value, tags) => { counter4.Add(value, tags); } );
+
+                Counter<double> counter5 = meter.CreateCounter<double>("doubleCounter");
+                InstruementMeasurementAggregationValidation(counter5, (value, tags) => { counter5.Add(value, tags); } );
+
+                Counter<decimal> counter6 = meter.CreateCounter<decimal>("decimalCounter");
+                InstruementMeasurementAggregationValidation(counter6, (value, tags) => { counter6.Add(value, tags); } );
+
+                Histogram<byte> histogram = meter.CreateHistogram<byte>("byteHistogram");
+                InstruementMeasurementAggregationValidation(histogram, (value, tags) => { histogram.Record(value, tags); } );
+
+                Histogram<short> histogram1 = meter.CreateHistogram<short>("shortHistogram");
+                InstruementMeasurementAggregationValidation(histogram1, (value, tags) => { histogram1.Record(value, tags); } );
+
+                Histogram<int> histogram2 = meter.CreateHistogram<int>("intHistogram");
+                InstruementMeasurementAggregationValidation(histogram2, (value, tags) => { histogram2.Record(value, tags); } );
+
+                Histogram<long> histogram3 = meter.CreateHistogram<long>("longHistogram");
+                InstruementMeasurementAggregationValidation(histogram3, (value, tags) => { histogram3.Record(value, tags); } );
+
+                Histogram<float> histogram4 = meter.CreateHistogram<float>("floatHistogram");
+                InstruementMeasurementAggregationValidation(histogram4, (value, tags) => { histogram4.Record(value, tags); } );
+
+                Histogram<double> histogram5 = meter.CreateHistogram<double>("doubleHistogram");
+                InstruementMeasurementAggregationValidation(histogram5, (value, tags) => { histogram5.Record(value, tags); } );
+
+                Histogram<decimal> histogram6 = meter.CreateHistogram<decimal>("decimalHistogram");
+                InstruementMeasurementAggregationValidation(histogram6, (value, tags) => { histogram6.Record(value, tags); } );
+
+            }).Dispose();
+        }
+
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void ObservableInstrumentMeasurementTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("ObservableInstrumentMeasurementTest");
+
+                //
+                // CreateObservableCounter using Func<T>
+                //
+                ObservableCounter<byte> observableCounter = meter.CreateObservableCounter<byte>("ByteObservableCounter", () => 50);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter, new Measurement<byte>[] { new Measurement<byte>(50)});
+                ObservableCounter<short> observableCounter1 = meter.CreateObservableCounter<short>("ShortObservableCounter", () => 30_000);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter1, new Measurement<short>[] { new Measurement<short>(30_000)});
+                ObservableCounter<int> observableCounter2 = meter.CreateObservableCounter<int>("IntObservableCounter", () => 1_000_000);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter2, new Measurement<int>[] { new Measurement<int>(1_000_000)});
+                ObservableCounter<long> observableCounter3 = meter.CreateObservableCounter<long>("longObservableCounter", () => 1_000_000_000);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter3, new Measurement<long>[] { new Measurement<long>(1_000_000_000)});
+                ObservableCounter<float> observableCounter4 = meter.CreateObservableCounter<float>("floatObservableCounter", () => 3.14f);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter4, new Measurement<float>[] { new Measurement<float>(3.14f)});
+                ObservableCounter<double> observableCounter5 = meter.CreateObservableCounter<double>("doubleObservableCounter", () => 1e6);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter5, new Measurement<double>[] { new Measurement<double>(1e6)});
+                ObservableCounter<decimal> observableCounter6 = meter.CreateObservableCounter<decimal>("decimalObservableCounter", () => 1.5E6m);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter6, new Measurement<decimal>[] { new Measurement<decimal>(1.5E6m)});
+
+                //
+                // CreateObservableGauge using Func<T>
+                //
+                ObservableGauge<byte> observableGauge = meter.CreateObservableGauge<byte>("ByteObservableGauge", () => 100);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge, new Measurement<byte>[] { new Measurement<byte>(100)});
+                ObservableGauge<short> observableGauge1 = meter.CreateObservableGauge<short>("ShortObservableGauge", () => 30_123);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge1, new Measurement<short>[] { new Measurement<short>(30_123)});
+                ObservableGauge<int> observableGauge2 = meter.CreateObservableGauge<int>("IntObservableGauge", () => 2_123_456);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge2, new Measurement<int>[] { new Measurement<int>(2_123_456)});
+                ObservableGauge<long> observableGauge3 = meter.CreateObservableGauge<long>("longObservableGauge", () => 3_123_456_789);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge3, new Measurement<long>[] { new Measurement<long>(3_123_456_789)});
+                ObservableGauge<float> observableGauge4 = meter.CreateObservableGauge<float>("floatObservableGauge", () => 1.6f);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge4, new Measurement<float>[] { new Measurement<float>(1.6f)});
+                ObservableGauge<double> observableGauge5 = meter.CreateObservableGauge<double>("doubleObservableGauge", () => 1e5);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge5, new Measurement<double>[] { new Measurement<double>(1e5)});
+                ObservableGauge<decimal> observableGauge6 = meter.CreateObservableGauge<decimal>("decimalObservableGauge", () => 2.5E7m);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge6, new Measurement<decimal>[] { new Measurement<decimal>(2.5E7m)});
+
+                //
+                // CreateObservableCounter using Func<Measurement<T>>
+                //
+                Measurement<byte> byteMeasurement = new Measurement<byte>(60, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T1", "V1"), new KeyValuePair<string, object?>("T2", "V2") });
+                ObservableCounter<byte> observableCounter7 = meter.CreateObservableCounter<byte>("ByteObservableCounter", () => byteMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter7, new Measurement<byte>[] { byteMeasurement });
+
+                Measurement<short> shortMeasurement = new Measurement<short>(20_000, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T3", "V3"), new KeyValuePair<string, object?>("T4", "V4") });
+                ObservableCounter<short> observableCounter8 = meter.CreateObservableCounter<short>("ShortObservableCounter", () => shortMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter8, new Measurement<short>[] { shortMeasurement });
+
+                Measurement<int> intMeasurement = new Measurement<int>(2_000_000, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T5", "V5"), new KeyValuePair<string, object?>("T6", "V6") });
+                ObservableCounter<int> observableCounter9 = meter.CreateObservableCounter<int>("IntObservableCounter", () => intMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter9, new Measurement<int>[] { intMeasurement });
+
+                Measurement<long> longMeasurement = new Measurement<long>(20_000_000_000, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T7", "V7"), new KeyValuePair<string, object?>("T8", "V8") });
+                ObservableCounter<long> observableCounter10 = meter.CreateObservableCounter<long>("longObservableCounter", () => longMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter10, new Measurement<long>[] { longMeasurement });
+
+                Measurement<float> floatMeasurement = new Measurement<float>(1e2f, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T9", "V10"), new KeyValuePair<string, object?>("T11", "V12") });
+                ObservableCounter<float> observableCounter11 = meter.CreateObservableCounter<float>("floatObservableCounter", () => 3.14f);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter11, new Measurement<float>[] { new Measurement<float>(3.14f)});
+
+                Measurement<double> doubleMeasurement = new Measurement<double>(2.5e7, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T13", "V14"), new KeyValuePair<string, object?>("T15", "V16") });
+                ObservableCounter<double> observableCounter12 = meter.CreateObservableCounter<double>("doubleObservableCounter", () => doubleMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter12, new Measurement<double>[] { doubleMeasurement });
+
+                Measurement<decimal> decimalMeasurement = new Measurement<decimal>(3.2e20m, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T17", "V18"), new KeyValuePair<string, object?>("T19", "V20") });
+                ObservableCounter<decimal> observableCounter13 = meter.CreateObservableCounter<decimal>("decimalObservableCounter", () => decimalMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter13, new Measurement<decimal>[] { decimalMeasurement });
+
+                //
+                // CreateObservableGauge using Func<Measurement<T>>
+                //
+                Measurement<byte> byteGaugeMeasurement = new Measurement<byte>(35, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T21", "V22"), new KeyValuePair<string, object?>("T23", "V24") });
+                ObservableGauge<byte> observableGauge7 = meter.CreateObservableGauge<byte>("ByteObservableGauge", () => byteGaugeMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge7, new Measurement<byte>[] { byteGaugeMeasurement });
+
+                Measurement<short> shortGaugeMeasurement = new Measurement<short>(23_987, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T25", "V26"), new KeyValuePair<string, object?>("T27", "V28") });
+                ObservableGauge<short> observableGauge8 = meter.CreateObservableGauge<short>("ShortObservableGauge", () => shortGaugeMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge8, new Measurement<short>[] { shortGaugeMeasurement });
+
+                Measurement<int> intGaugeMeasurement = new Measurement<int>(1_987_765, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T29", "V30"), new KeyValuePair<string, object?>("T31", "V32") });
+                ObservableGauge<int> observableGauge9 = meter.CreateObservableGauge<int>("IntObservableGauge", () => intGaugeMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge9, new Measurement<int>[] { intGaugeMeasurement });
+
+                Measurement<long> longGaugeMeasurement = new Measurement<long>(10_000_234_343, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T33", "V342"), new KeyValuePair<string, object?>("T35", "V36") });
+                ObservableGauge<long> observableGauge10 = meter.CreateObservableGauge<long>("longObservableGauge", () => longGaugeMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge10, new Measurement<long>[] { longGaugeMeasurement });
+
+                Measurement<float> floatGaugeMeasurement = new Measurement<float>(2.1f, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T37", "V38"), new KeyValuePair<string, object?>("T39", "V40") });
+                ObservableGauge<float> observableGauge11 = meter.CreateObservableGauge<float>("floatObservableGauge", () => floatGaugeMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge11, new Measurement<float>[] { floatGaugeMeasurement });
+
+                Measurement<double> doubleGaugeMeasurement = new Measurement<double>(1.5e30, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T41", "V42"), new KeyValuePair<string, object?>("T43", "V44") });
+                ObservableGauge<double> observableGauge12 = meter.CreateObservableGauge<double>("doubleObservableGauge", () => doubleGaugeMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge12, new Measurement<double>[] { doubleGaugeMeasurement });
+
+                Measurement<decimal> decimalGaugeMeasurement = new Measurement<decimal>(2.5e20m, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T45", "V46"), new KeyValuePair<string, object?>("T47", "V48") });
+                ObservableGauge<decimal> observableGauge13 = meter.CreateObservableGauge<decimal>("decimalObservableGauge", () => decimalGaugeMeasurement);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge13, new Measurement<decimal>[] { decimalGaugeMeasurement });
+
+                //
+                // CreateObservableCounter using Func<Measurement<T>>
+                //
+                Measurement<byte>[] byteGaugeMeasurementList = new Measurement<byte>[]
+                {
+                    new Measurement<byte>(0, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T48", "V49"), new KeyValuePair<string, object?>("T50", "V51") }),
+                    new Measurement<byte>(1, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T51", "V52"), new KeyValuePair<string, object?>("T53", "V54") }),
+                };
+                ObservableCounter<byte> observableCounter14 = meter.CreateObservableCounter<byte>("ByteObservableCounter", () => byteGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter14, byteGaugeMeasurementList);
+
+                Measurement<short>[] shortGaugeMeasurementList = new Measurement<short>[]
+                {
+                    new Measurement<short>(20_000, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T55", "V56"), new KeyValuePair<string, object?>("T57", "V58") }),
+                    new Measurement<short>(30_000, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T59", "V60"), new KeyValuePair<string, object?>("T61", "V62") }),
+                };
+                ObservableCounter<short> observableCounter15 = meter.CreateObservableCounter<short>("ShortObservableCounter", () => shortGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter15, shortGaugeMeasurementList);
+
+                Measurement<int>[] intGaugeMeasurementList = new Measurement<int>[]
+                {
+                    new Measurement<int>(1_000_001, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T63", "V64"), new KeyValuePair<string, object?>("T65", "V66") }),
+                    new Measurement<int>(1_000_002, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T67", "V68"), new KeyValuePair<string, object?>("T69", "V70") }),
+                };
+                ObservableCounter<int> observableCounter16 = meter.CreateObservableCounter<int>("IntObservableCounter", () => intGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter16, intGaugeMeasurementList);
+
+                Measurement<long>[] longGaugeMeasurementList = new Measurement<long>[]
+                {
+                    new Measurement<long>(1_000_001_001, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T71", "V72"), new KeyValuePair<string, object?>("T73", "V74") }),
+                    new Measurement<long>(1_000_002_002, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T75", "V76"), new KeyValuePair<string, object?>("T77", "V78") }),
+                };
+                ObservableCounter<long> observableCounter17 = meter.CreateObservableCounter<long>("longObservableCounter", () => longGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter17, longGaugeMeasurementList);
+
+                Measurement<float>[] floatGaugeMeasurementList = new Measurement<float>[]
+                {
+                    new Measurement<float>(68.15e8f, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T79", "V80"), new KeyValuePair<string, object?>("T81", "V82") }),
+                    new Measurement<float>(68.15e6f, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T83", "V84"), new KeyValuePair<string, object?>("T85", "V86") }),
+                };
+                ObservableCounter<float> observableCounter18 = meter.CreateObservableCounter<float>("floatObservableCounter", () => floatGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter18, floatGaugeMeasurementList);
+
+                Measurement<double>[] doubleGaugeMeasurementList = new Measurement<double>[]
+                {
+                    new Measurement<double>(68.15e20, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T87", "V88"), new KeyValuePair<string, object?>("T89", "V90") }),
+                    new Measurement<double>(68.15e21, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T91", "V92"), new KeyValuePair<string, object?>("T93", "V94") }),
+                };
+                ObservableCounter<double> observableCounter19 = meter.CreateObservableCounter<double>("doubleObservableCounter", () => doubleGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter19, doubleGaugeMeasurementList);
+
+                Measurement<decimal>[] decimalGaugeMeasurementList = new Measurement<decimal>[]
+                {
+                    new Measurement<decimal>(68.15e8m, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T95", "V96"), new KeyValuePair<string, object?>("T97", "V98") }),
+                    new Measurement<decimal>(68.15e6m, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("T99", "V100"), new KeyValuePair<string, object?>("T101", "V102") }),
+                };
+                ObservableCounter<decimal> observableCounter20 = meter.CreateObservableCounter<decimal>("decimalObservableCounter", () => decimalGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableCounter20, decimalGaugeMeasurementList);
+
+                //
+                // CreateObservableGauge using IEnumerable<Measurement<T>>
+                //
+                ObservableGauge<byte> observableGauge14 = meter.CreateObservableGauge<byte>("ByteObservableGauge", () => byteGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge14, byteGaugeMeasurementList);
+
+                ObservableGauge<short> observableGauge15 = meter.CreateObservableGauge<short>("ShortObservableGauge", () => shortGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge15, shortGaugeMeasurementList);
+
+                ObservableGauge<int> observableGauge16 = meter.CreateObservableGauge<int>("IntObservableGauge", () => intGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge16, intGaugeMeasurementList);
+
+                ObservableGauge<long> observableGauge17 = meter.CreateObservableGauge<long>("longObservableGauge", () => longGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge17, longGaugeMeasurementList);
+
+                ObservableGauge<float> observableGauge18 = meter.CreateObservableGauge<float>("floatObservableGauge", () => floatGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge18, floatGaugeMeasurementList);
+
+                ObservableGauge<double> observableGauge19 = meter.CreateObservableGauge<double>("doubleObservableGauge", () => doubleGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge19, doubleGaugeMeasurementList);
+
+                ObservableGauge<decimal> observableGauge20 = meter.CreateObservableGauge<decimal>("decimalObservableGauge", () => decimalGaugeMeasurementList);
+                ObservableInstruementMeasurementAggregationValidation(observableGauge20, decimalGaugeMeasurementList);
+
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void PassingVariableTagsParametersTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("PassingVariableTagsParametersTest");
+
+                InstrumentPassingVariableTagsParametersTest<byte>(meter.CreateCounter<byte>("Counter"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishCounterMeasurement(instrument as Counter<byte>, value, tags);
+                                                            return (byte)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<short>(meter.CreateCounter<short>("Counter"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishCounterMeasurement(instrument as Counter<short>, value, tags);
+                                                            return (short)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<int>(meter.CreateCounter<int>("Counter"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishCounterMeasurement(instrument as Counter<int>, value, tags);
+                                                            return (int)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<long>(meter.CreateCounter<long>("Counter"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishCounterMeasurement(instrument as Counter<long>, value, tags);
+                                                            return (long)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<float>(meter.CreateCounter<float>("Counter"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishCounterMeasurement(instrument as Counter<float>, value, tags);
+                                                            return (float)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<double>(meter.CreateCounter<double>("Counter"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishCounterMeasurement(instrument as Counter<double>, value, tags);
+                                                            return (double)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<decimal>(meter.CreateCounter<decimal>("Counter"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishCounterMeasurement(instrument as Counter<decimal>, value, tags);
+                                                            return (decimal)(value * 2);
+                                                        });
+
+                InstrumentPassingVariableTagsParametersTest<byte>(meter.CreateHistogram<byte>("Histogram"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishHistogramMeasurement(instrument as Histogram<byte>, value, tags);
+                                                            return (byte)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<short>(meter.CreateHistogram<short>("Histogram"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishHistogramMeasurement(instrument as Histogram<short>, value, tags);
+                                                            return (short)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<int>(meter.CreateHistogram<int>("Histogram"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishHistogramMeasurement(instrument as Histogram<int>, value, tags);
+                                                            return (int)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<long>(meter.CreateHistogram<long>("Histogram"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishHistogramMeasurement(instrument as Histogram<long>, value, tags);
+                                                            return (long)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<float>(meter.CreateHistogram<float>("Histogram"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishHistogramMeasurement(instrument as Histogram<float>, value, tags);
+                                                            return (float)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<double>(meter.CreateHistogram<double>("Histogram"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishHistogramMeasurement(instrument as Histogram<double>, value, tags);
+                                                            return (double)(value * 2);
+                                                        });
+                InstrumentPassingVariableTagsParametersTest<decimal>(meter.CreateHistogram<decimal>("Histogram"), (instrument, value, tags) =>
+                                                        {
+                                                            PublishHistogramMeasurement(instrument as Histogram<decimal>, value, tags);
+                                                            return (decimal)(value * 2);
+                                                        });
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void MeterDisposalsTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter1 = new Meter("MeterDisposalsTest1");
+                Meter meter2 = new Meter("MeterDisposalsTest2");
+                Meter meter3 = new Meter("MeterDisposalsTest3");
+                Meter meter4 = new Meter("MeterDisposalsTest4");
+
+                Counter<int> counter = meter1.CreateCounter<int>("Counter");
+                Histogram<double> histogram = meter2.CreateHistogram<double>("Histogram");
+                ObservableCounter<long> observableCounter = meter3.CreateObservableCounter<long>("ObservableCounter", () => new Measurement<long>(10, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("Key", "value")}));
+                ObservableGauge<decimal> observableGauge = meter4.CreateObservableGauge<decimal>("ObservableGauge", () => new Measurement<decimal>(5.7m, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("Key", "value")}));
+
+                using MeterListener listener = new MeterListener();
+                listener.InstrumentPublished = (theInstrument, theListener) => theListener.EnableMeasurementEvents(theInstrument, theInstrument);
+
+                int count = 0;
+
+                listener.SetMeasurementEventCallback<int>((inst, measurement, tags, state)     => count++);
+                listener.SetMeasurementEventCallback<double>((inst, measurement, tags, state)  => count++);
+                listener.SetMeasurementEventCallback<long>((inst, measurement, tags, state)    => count++);
+                listener.SetMeasurementEventCallback<decimal>((inst, measurement, tags, state) => count++);
+
+                listener.Start();
+
+                Assert.Equal(0, count);
+
+                counter.Add(1);
+                Assert.Equal(1, count);
+
+                histogram.Record(1);
+                Assert.Equal(2, count);
+
+                listener.RecordObservableInstruments();
+                Assert.Equal(4, count);
+
+                meter1.Dispose();
+                counter.Add(1);
+                Assert.Equal(4, count);
+
+                meter2.Dispose();
+                histogram.Record(1);
+                Assert.Equal(4, count);
+
+                listener.RecordObservableInstruments();
+                Assert.Equal(6, count);
+
+                meter3.Dispose();
+                listener.RecordObservableInstruments();
+                Assert.Equal(7, count);
+
+                meter4.Dispose();
+                listener.RecordObservableInstruments();
+                Assert.Equal(7, count);
+
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void ListenerDisposalsTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("ListenerDisposalsTest");
+
+                Counter<int> counter = meter.CreateCounter<int>("Counter");
+                Histogram<double> histogram = meter.CreateHistogram<double>("Histogram");
+                ObservableCounter<long> observableCounter = meter.CreateObservableCounter<long>("ObservableCounter", () => new Measurement<long>(10, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("Key", "value")}));
+                ObservableGauge<decimal> observableGauge = meter.CreateObservableGauge<decimal>("ObservableGauge", () => new Measurement<decimal>(5.7m, new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("Key", "value")}));
+
+                int completedMeasurements = 0;
+                MeterListener listener = new MeterListener();
+                listener.InstrumentPublished = (theInstrument, theListener) => theListener.EnableMeasurementEvents(theInstrument, theInstrument);
+                listener.MeasurementsCompleted = (theInstrument, state) => completedMeasurements++;
+
+                int count = 0;
+
+                listener.SetMeasurementEventCallback<int>((inst, measurement, tags, state)     => count++);
+                listener.SetMeasurementEventCallback<double>((inst, measurement, tags, state)  => count++);
+                listener.SetMeasurementEventCallback<long>((inst, measurement, tags, state)    => count++);
+                listener.SetMeasurementEventCallback<decimal>((inst, measurement, tags, state) => count++);
+
+                listener.Start();
+
+                Assert.Equal(0, count);
+
+                counter.Add(1);
+                Assert.Equal(1, count);
+
+                histogram.Record(1);
+                Assert.Equal(2, count);
+
+                listener.RecordObservableInstruments();
+                Assert.Equal(4, count);
+
+                listener.Dispose();
+                Assert.Equal(4, completedMeasurements);
+
+                counter.Add(1);
+                Assert.Equal(4, count);
+
+                histogram.Record(1);
+                Assert.Equal(4, count);
+
+                listener.RecordObservableInstruments();
+                Assert.Equal(4, count);
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void MultipleListenersTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("MultipleListenersTest");
+
+                Counter<int> counter = meter.CreateCounter<int>("Counter");
+
+                MeterListener listener1 = new MeterListener();
+                MeterListener listener2 = new MeterListener();
+                MeterListener listener3 = new MeterListener();
+
+                listener1.InstrumentPublished = listener2.InstrumentPublished = listener3.InstrumentPublished = (theInstrument, theListener) => theListener.EnableMeasurementEvents(theInstrument, theInstrument);
+
+                int count = 0;
+
+                listener1.SetMeasurementEventCallback<int>((inst, measurement, tags, state) => count++);
+                listener2.SetMeasurementEventCallback<int>((inst, measurement, tags, state) => count++);
+                listener3.SetMeasurementEventCallback<int>((inst, measurement, tags, state) => count++);
+
+                listener1.Start();
+                listener2.Start();
+                listener3.Start();
+
+                Assert.Equal(0, count);
+
+                counter.Add(1);
+                Assert.Equal(3, count);
+
+                counter.Add(1);
+                Assert.Equal(6, count);
+
+                counter.Add(1);
+                Assert.Equal(9, count);
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void EnableListeneingMultipleTimesWithDifferentState()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("EnableListeneingMultipleTimesWithDifferentState");
+
+                Counter<int> counter = meter.CreateCounter<int>("Counter");
+
+                MeterListener listener = new MeterListener();
+
+                string lastState = "1";
+                listener.InstrumentPublished = (theInstrument, theListener) => theListener.EnableMeasurementEvents(theInstrument, lastState);
+                int completedCount = 0;
+                listener.MeasurementsCompleted = (theInstrument, state) => { Assert.Equal(lastState, state); completedCount++; };
+                listener.Start();
+
+                string newState = "2";
+                listener.EnableMeasurementEvents(counter, newState);
+                Assert.Equal(1, completedCount);
+                lastState = newState;
+
+                newState = "3";
+                listener.EnableMeasurementEvents(counter, newState);
+                Assert.Equal(2, completedCount);
+                lastState = newState;
+
+                newState = null;
+                listener.EnableMeasurementEvents(counter, newState);
+                Assert.Equal(3, completedCount);
+                lastState = newState;
+
+                listener.Dispose();
+                Assert.Equal(4, completedCount);
+            }).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void ParallelRunningTest()
+        {
+            RemoteExecutor.Invoke(() => {
+                Meter meter = new Meter("ParallelRunningTest");
+
+                Counter<int> counter = meter.CreateCounter<int>("Counter");
+                Histogram<int> histogram = meter.CreateHistogram<int>("Histogram");
+                ObservableCounter<int> observableCounter = meter.CreateObservableCounter<int>("ObservableCounter", () => 1);
+                ObservableGauge<int> observableGauge = meter.CreateObservableGauge<int>("ObservableGauge", () => 1);
+
+                MeterListener listener = new MeterListener();
+                listener.InstrumentPublished = (theInstrument, theListener) => theListener.EnableMeasurementEvents(theInstrument, theInstrument);
+
+                int totalCount = 0;
+                listener.SetMeasurementEventCallback<int>((inst, measurement, tags, state) => Interlocked.Add(ref totalCount, measurement));
+                listener.Start();
+
+                Task[] taskList = new Task[6];
+
+                int loopLength = 10_000;
+
+                taskList[0] = Task.Factory.StartNew(() => { for (int i = 0; i < loopLength; i++) { counter.Add(1); } });
+                taskList[1] = Task.Factory.StartNew(() => { for (int i = 0; i < loopLength; i++) { counter.Add(1); } });
+                taskList[2] = Task.Factory.StartNew(() => { for (int i = 0; i < loopLength; i++) { histogram.Record(1); } });
+                taskList[3] = Task.Factory.StartNew(() => { for (int i = 0; i < loopLength; i++) { histogram.Record(1); } });
+                taskList[4] = Task.Factory.StartNew(() => { for (int i = 0; i < loopLength; i++) { listener.RecordObservableInstruments(); } });
+                taskList[5] = Task.Factory.StartNew(() => { for (int i = 0; i < loopLength; i++) { listener.RecordObservableInstruments(); } });
+
+                Task.WaitAll(taskList);
+
+                Assert.Equal(loopLength * 8, totalCount);
+            }).Dispose();
+        }
+        private void PublishCounterMeasurement<T>(Counter<T> counter, T value, KeyValuePair<string, object?>[] tags) where T : struct
+        {
+            switch (tags.Length)
+            {
+                case 0: counter.Add(value); break;
+                case 1: counter.Add(value, tags[0]); break;
+                case 2: counter.Add(value, tags[0], tags[1]); break;
+                case 3: counter.Add(value, tags[0], tags[1], tags[2]); break;
+                case 4: counter.Add(value, tags[0], tags[1], tags[2], tags[3]); break;
+                default: counter.Add(value, tags); break;
+            }
+        }
+
+        private void PublishHistogramMeasurement<T>(Histogram<T> histogram, T value, KeyValuePair<string, object?>[] tags) where T : struct
+        {
+            switch (tags.Length)
+            {
+                case 0: histogram.Record(value); break;
+                case 1: histogram.Record(value, tags[0]); break;
+                case 2: histogram.Record(value, tags[0], tags[1]); break;
+                case 3: histogram.Record(value, tags[0], tags[1], tags[2]); break;
+                case 4: histogram.Record(value, tags[0], tags[1], tags[2], tags[3]); break;
+                default: histogram.Record(value, tags); break;
+            }
+        }
+
+        private void ValidateInstrumentInfo(Instrument instrument, string name, string unit, string description, bool isEnabled, bool isObservable)
+        {
+            Assert.Equal(name, instrument.Name);
+            Assert.Equal(unit, instrument.Unit);
+            Assert.Equal(description, instrument.Description);
+            Assert.Equal(isEnabled, instrument.Enabled);
+            Assert.Equal(isObservable, instrument.IsObservable);
+        }
+
+        private void InstruementMeasurementAggregationValidation<T>(Instrument<T> instrument, Action<T, KeyValuePair<string, object?>[]> record) where T : struct
+        {
+            using MeterListener listener = new MeterListener();
+            listener.InstrumentPublished = (theInstrument, theListener) =>
+            {
+                if (object.ReferenceEquals(instrument, theInstrument))
+                {
+                    Assert.Same(listener, theListener);
+                    listener.EnableMeasurementEvents(theInstrument, theInstrument);
+                }
+            };
+
+            List<KeyValuePair<string, object?>> expectedTags = new List<KeyValuePair<string, object?>>();
+            T expectedValue = default;
+            int counter = 0;
+
+            listener.SetMeasurementEventCallback<T>((inst, measurement, tags, state) =>
+            {
+                Assert.True(instrument.Enabled);
+                Assert.Same(instrument, inst);
+                Assert.Same(instrument, state);
+                Assert.Equal(expectedValue, measurement);
+                Assert.Equal(expectedTags.ToArray(), tags.ToArray());
+                counter++;
+            });
+
+            listener.Start();
+
+            for (byte i = 0; i < 100; i++)
+            {
+                expectedTags.Add(new KeyValuePair<string, object?>(i.ToString(), i.ToString()));
+                expectedValue = ConvertValue<T>(i);
+                record(expectedValue, expectedTags.ToArray());
+            }
+
+            Assert.Equal(100, counter);
+        }
+
+        private void ObservableInstruementMeasurementAggregationValidation<T>(ObservableInstrument<T> instrument, Measurement<T>[] expectedResult) where T : struct
+        {
+            using MeterListener listener = new MeterListener();
+            listener.InstrumentPublished = (theInstrument, theListener) =>
+            {
+                if (object.ReferenceEquals(instrument, theInstrument))
+                {
+                    Assert.Same(listener, theListener);
+                    listener.EnableMeasurementEvents(theInstrument, theInstrument);
+                }
+            };
+
+            int index = 0;
+
+            listener.SetMeasurementEventCallback<T>((inst, measurement, tags, state) =>
+            {
+                Assert.True(instrument.Enabled);
+                Assert.Same(instrument, inst);
+                Assert.Same(instrument, state);
+                Assert.True(index < expectedResult.Length, "We are getting more unexpected results");
+
+                Assert.Equal(expectedResult[index].Value, measurement);
+                Assert.Equal(expectedResult[index].Tags.ToArray(), tags.ToArray());
+                index++;
+            });
+
+            listener.Start();
+
+            listener.RecordObservableInstruments();
+
+            Assert.Equal(expectedResult.Length, index);
+        }
+
+        private void InstrumentPassingVariableTagsParametersTest<T>(Instrument<T> instrument, Func<Instrument<T>, T, KeyValuePair<string, object?>[], T> record) where T : struct
+        {
+            using MeterListener listener = new MeterListener();
+            listener.InstrumentPublished = (theInstrument, theListener) =>
+            {
+                if (object.ReferenceEquals(instrument, theInstrument))
+                {
+                    Assert.Same(listener, theListener);
+                    listener.EnableMeasurementEvents(theInstrument, theInstrument);
+                }
+            };
+
+            KeyValuePair<string, object?>[] expectedTags = Array.Empty<KeyValuePair<string, object?>>();
+            T expectedValue = default;
+
+            listener.SetMeasurementEventCallback<T>((inst, measurement, tags, state) =>
+            {
+                Assert.True(instrument.Enabled);
+                Assert.Same(instrument, inst);
+                Assert.Same(instrument, state);
+                Assert.Equal(expectedValue, measurement);
+                Assert.Equal(expectedTags, tags.ToArray());
+            });
+
+            listener.Start();
+
+            expectedValue = record(instrument, expectedValue, expectedTags);
+            expectedTags = new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("K1", "V1") };
+            expectedValue = record(instrument, expectedValue, expectedTags);
+            expectedTags = new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("K1", "V1"), new KeyValuePair<string, object?>("K2", "V2") };
+            expectedValue = record(instrument, expectedValue, expectedTags);
+            expectedTags = new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("K1", "V1"), new KeyValuePair<string, object?>("K2", "V2"), new KeyValuePair<string, object?>("K3", "V3") };
+            expectedValue = record(instrument, expectedValue, expectedTags);
+            expectedTags = new KeyValuePair<string, object?>[] { new KeyValuePair<string, object?>("K1", "V1"), new KeyValuePair<string, object?>("K2", "V2"), new KeyValuePair<string, object?>("K3", "V3"), new KeyValuePair<string, object?>("K4", "V4") };
+            expectedValue = record(instrument, expectedValue, expectedTags);
+        }
+
+        private T ConvertValue<T>(byte value) where T : struct
+        {
+            if (typeof(T) == typeof(byte))  { return (T)(object)value;}
+            if (typeof(T) == typeof(short)) { return (T)(object)Convert.ToInt16(value); }
+            if (typeof(T) == typeof(int)) { return (T)(object)Convert.ToInt32(value); }
+            if (typeof(T) == typeof(long)) { return (T)(object)Convert.ToInt64(value); }
+            if (typeof(T) == typeof(float)) { return (T)(object)Convert.ToSingle(value); }
+            if (typeof(T) == typeof(double)) { return (T)(object)Convert.ToDouble(value); }
+            if (typeof(T) == typeof(decimal)) { return (T)(object)Convert.ToDecimal(value);}
+
+            Assert.True(false, "We encountered unsupported type");
+            return default;
+        }
+
+    }
+}
\ No newline at end of file
index 7132e13..a2f9daa 100644 (file)
@@ -11,6 +11,7 @@
     <Compile Include="ActivityTests.cs" />
     <Compile Include="ActivitySourceTests.cs" />
     <Compile Include="ActivityTagsCollectionTests.cs" />
+    <Compile Include="MetricsTests.cs" />
   </ItemGroup>
   <ItemGroup Condition="$(TargetFramework.StartsWith('net4'))">
     <Compile Include="HttpHandlerDiagnosticListenerTests.cs" />