From bed3ff7d9d5d9e2c55f3c298706a39ea7fc4500c Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Thu, 28 Mar 2024 13:10:47 -0700
Subject: [PATCH 01/11] Prototype HistogramAdvice API.

---
 ...em.Diagnostics.DiagnosticSourceActivity.cs |  8 +++++++-
 ...System.Diagnostics.DiagnosticSource.csproj |  1 +
 .../System/Diagnostics/Metrics/Histogram.cs   |  8 ++++++--
 .../Diagnostics/Metrics/HistogramAdvice.cs    | 12 ++++++++++++
 .../src/System/Diagnostics/Metrics/Meter.cs   | 19 +++++++++++++++++--
 5 files changed, 43 insertions(+), 5 deletions(-)
 create mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
index 7cb90d245dcf93..906cfdb596bcdf 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
@@ -356,6 +356,7 @@ internal UpDownCounter(Meter meter, string name, string? unit, string? descripti
     }
     public sealed class Histogram<T> : Instrument<T> where T : struct
     {
+        public HistogramAdvice<T>? Advice { get { throw null; } }
         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; }
@@ -410,7 +411,8 @@ public class Meter : IDisposable
         public UpDownCounter<T> CreateUpDownCounter<T>(string name, string? unit = null, string? description = null) where T : struct  { throw null; }
         public UpDownCounter<T> CreateUpDownCounter<T>(string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>> tags) where T : struct  { throw null; }
         public Histogram<T> CreateHistogram<T>(string name, string? unit = null, string? description = null) where T : struct { throw null; }
-        public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>> tags) where T : struct { throw null; }
+        public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags) where T : struct { throw null; }
+        public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags, HistogramAdvice<T>? advice) where T : struct { throw null; }
         public ObservableCounter<T> CreateObservableCounter<T>(
                             string name,
                             Func<T> observeValue,
@@ -567,4 +569,8 @@ public abstract class ObservableInstrument<T> : Instrument where T : struct
         protected ObservableInstrument(Meter meter, string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>> tags) : base(meter, name, unit, description) { throw null; }
         protected abstract System.Collections.Generic.IEnumerable<Measurement<T>> Observe();
     }
+    public sealed class HistogramAdvice<T> where T : struct
+    {
+        public System.Collections.Generic.IReadOnlyList<T>? ExplicitBucketBoundaries { get { throw null; } init { throw null; } }
+    }
 }
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
index fb55fdbebd23b4..76a6685c3e5b09 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
@@ -54,6 +54,7 @@ System.Diagnostics.DiagnosticSource</PackageDescription>
     <Compile Include="System\Diagnostics\Metrics\Counter.cs" />
     <Compile Include="System\Diagnostics\Metrics\ExponentialHistogramAggregator.cs" />
     <Compile Include="System\Diagnostics\Metrics\Histogram.cs" />
+    <Compile Include="System\Diagnostics\Metrics\HistogramAdvice.cs" />
     <Compile Include="System\Diagnostics\Metrics\IMeterFactory.cs" />
     <Compile Include="System\Diagnostics\Metrics\Instrument.cs" />
     <Compile Include="System\Diagnostics\Metrics\Instrument.common.cs" />
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
index ed3431edfce8be..656fd55b948456 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Histogram.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Histogram.cs
@@ -15,12 +15,16 @@ namespace System.Diagnostics.Metrics
     /// </remarks>
     public sealed class Histogram<T> : Instrument<T> where T : struct
     {
-        internal Histogram(Meter meter, string name, string? unit, string? description) : this(meter, name, unit, description, tags: null)
+        public HistogramAdvice<T>? Advice { get; }
+
+        internal Histogram(Meter meter, string name, string? unit, string? description) : this(meter, name, unit, description, tags: null, advice: null)
         {
         }
 
-        internal Histogram(Meter meter, string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags) : base(meter, name, unit, description, tags)
+        internal Histogram(Meter meter, string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags, HistogramAdvice<T>? advice) : base(meter, name, unit, description, tags)
         {
+            Advice = advice;
+
             Publish();
         }
 
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
new file mode 100644
index 00000000000000..ab122ceb2b2c38
--- /dev/null
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
@@ -0,0 +1,12 @@
+﻿// 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
+{
+    public sealed class HistogramAdvice<T> where T : struct
+    {
+        public IReadOnlyList<T>? ExplicitBucketBoundaries { get; init; }
+    }
+}
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
index 856725680857c0..fbac05a3f0cd44 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
@@ -152,7 +152,8 @@ public Counter<T> CreateCounter<T>(string name, string? unit, string? descriptio
         /// <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 => CreateHistogram<T>(name, unit, description, tags: null);
+        public Histogram<T> CreateHistogram<T>(string name, string? unit = null, string? description = null) where T : struct
+            => CreateHistogram<T>(name, unit, description, tags: null, advice: null);
 
         /// <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.
@@ -165,7 +166,21 @@ public Counter<T> CreateCounter<T>(string name, string? unit, string? descriptio
         /// Example uses for Histogram: the request duration and the size of the response payload.
         /// </remarks>
         public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags) where T : struct
-                => (Histogram<T>)GetOrCreateInstrument<T>(typeof(Histogram<T>), name, unit, description, tags, () => new Histogram<T>(this, name, unit, description, tags));
+                => CreateHistogram<T>(name, unit, description, tags, advice: null);
+
+        /// <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>
+        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="advice"><see cref="HistogramAdvice{T}"/> to attach to the counter.</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, string? description, IEnumerable<KeyValuePair<string, object?>>? tags, HistogramAdvice<T>? advice) where T : struct
+                => (Histogram<T>)GetOrCreateInstrument<T>(typeof(Histogram<T>), name, unit, description, tags, () => new Histogram<T>(this, name, unit, description, tags, advice));
 
         /// <summary>
         /// Create a metrics UpDownCounter object.

From 0aa4c7221d14d54da930d0e7a8301f2c4332b283 Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Tue, 21 May 2024 13:39:48 -0700
Subject: [PATCH 02/11] Updates.

---
 ...em.Diagnostics.DiagnosticSourceActivity.cs | 10 ++-
 .../src/Resources/Strings.resx                | 59 +++++++-------
 .../System/Diagnostics/Metrics/Histogram.cs   |  3 +
 .../Diagnostics/Metrics/HistogramAdvice.cs    | 53 ++++++++++++-
 .../src/System/Diagnostics/Metrics/Meter.cs   | 79 +++++++++++--------
 5 files changed, 139 insertions(+), 65 deletions(-)

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
index 635bceb910cd74..b801b5751b3202 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
@@ -411,9 +411,12 @@ public class Meter : IDisposable
         public Counter<T> CreateCounter<T>(string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>> tags) where T : struct  { throw null; }
         public UpDownCounter<T> CreateUpDownCounter<T>(string name, string? unit = null, string? description = null) where T : struct  { throw null; }
         public UpDownCounter<T> CreateUpDownCounter<T>(string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>> tags) where T : struct  { throw null; }
-        public Histogram<T> CreateHistogram<T>(string name, string? unit = null, string? description = null) where T : struct { throw null; }
+        public Histogram<T> CreateHistogram<T>(string name) where T : struct { throw null; }
+        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+        public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description) where T : struct { throw null; }
+        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
         public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags) where T : struct { throw null; }
-        public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags, HistogramAdvice<T>? advice) where T : struct { throw null; }
+        public Histogram<T> CreateHistogram<T>(string name, string? unit = default, string? description = default, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags = default, HistogramAdvice<T>? advice = default) where T : struct { throw null; }
         public ObservableCounter<T> CreateObservableCounter<T>(
                             string name,
                             Func<T> observeValue,
@@ -572,6 +575,7 @@ public abstract class ObservableInstrument<T> : Instrument where T : struct
     }
     public sealed class HistogramAdvice<T> where T : struct
     {
-        public System.Collections.Generic.IReadOnlyList<T>? ExplicitBucketBoundaries { get { throw null; } init { throw null; } }
+        public HistogramAdvice(System.Collections.Generic.IEnumerable<T> explicitBucketBoundaries) { throw null; }
+        public System.Collections.Generic.IReadOnlyList<T>? ExplicitBucketBoundaries { get { throw null; } }
     }
 }
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx b/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx
index 6ad3bfd0b2a118..f1493d25fae37f 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx
@@ -1,17 +1,17 @@
 ﻿<?xml version="1.0" encoding="utf-8"?>
 <root>
-  <!--
-    Microsoft ResX Schema
-
+  <!-- 
+    Microsoft ResX Schema 
+    
     Version 2.0
-
-    The primary goals of this format is to allow a simple XML format
-    that is mostly human readable. The generation and parsing of the
-    various data types are done through the TypeConverter classes
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
     associated with the data types.
-
+    
     Example:
-
+    
     ... ado.net/XML headers & schema ...
     <resheader name="resmimetype">text/microsoft-resx</resheader>
     <resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
         <comment>This is a comment</comment>
     </data>
-
-    There are any number of "resheader" rows that contain simple
+                
+    There are any number of "resheader" rows that contain simple 
     name/value pairs.
-
-    Each data row contains a name, and value. The row also contains a
-    type or mimetype. Type corresponds to a .NET class that support
-    text/value conversion through the TypeConverter architecture.
-    Classes that don't support this are serialized and stored with the
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
     mimetype set.
-
-    The mimetype is used for serialized objects, and tells the
-    ResXResourceReader how to depersist the object. This is currently not
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
     extensible. For a given mimetype the value must be set accordingly:
-
-    Note - application/x-microsoft.net.object.binary.base64 is the format
-    that the ResXResourceWriter will generate, however the reader can
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
     read any of the formats listed below.
-
+    
     mimetype: application/x-microsoft.net.object.binary.base64
-    value   : The object must be serialized with
+    value   : The object must be serialized with 
             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
             : and then encoded with base64 encoding.
-
+    
     mimetype: application/x-microsoft.net.object.soap.base64
-    value   : The object must be serialized with
+    value   : The object must be serialized with 
             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
             : and then encoded with base64 encoding.
 
     mimetype: application/x-microsoft.net.object.bytearray.base64
-    value   : The object must be serialized into a byte array
+    value   : The object must be serialized into a byte array 
             : using a System.ComponentModel.TypeConverter
             : and then encoded with base64 encoding.
     -->
@@ -174,4 +174,7 @@
   <data name="InvalidInstrumentType" xml:space="preserve">
     <value>The instrument is of different generic type.</value>
   </data>
-</root>
\ No newline at end of file
+  <data name="InvalidHistogramExplicitBucketBoundaries" xml:space="preserve">
+    <value>Histogram explicit bucket boundaries MUST be specified in ascending order and CANNOT contain duplicate values.</value>
+  </data>
+</root>
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
index 87e3eef0414c64..7e3999695b0bad 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Histogram.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Histogram.cs
@@ -15,6 +15,9 @@ namespace System.Diagnostics.Metrics
     /// </remarks>
     public sealed class Histogram<T> : Instrument<T> where T : struct
     {
+        /// <summary>
+        /// Gets the <see cref="HistogramAdvice{T}"/> associated with the instrument.
+        /// </summary>
         public HistogramAdvice<T>? Advice { get; }
 
         internal Histogram(Meter meter, string name, string? unit, string? description) : this(meter, name, unit, description, tags: null, advice: null)
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
index ab122ceb2b2c38..f40d2fb5cd0a0d 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
@@ -5,8 +5,59 @@
 
 namespace System.Diagnostics.Metrics
 {
+    /// <summary>
+    /// Contains settings used to advise metrics consumers how to construct storage for <see cref="Histogram{T}"/> instruments.
+    /// </summary>
+    /// <typeparam name="T">Histogram value type.</typeparam>
     public sealed class HistogramAdvice<T> where T : struct
     {
-        public IReadOnlyList<T>? ExplicitBucketBoundaries { get; init; }
+        /// <summary>
+        /// Constructs a new instance of <see cref="HistogramAdvice{T}"/>.
+        /// </summary>
+        /// <param name="explicitBucketBoundaries">
+        /// <para>Explicit bucket boundaries advised to be used with the histogram.</para>
+        /// <para>Notes:
+        /// <list type="bullet">
+        /// <item>Bucket boundaries MUST be specified in ascending order and CANNOT contain duplicate values.</item>
+        /// <item>An empty set of bucket boundaries hints that the histogram should NOT contain buckets and should only track count and sum values.</item>
+        /// </list>
+        /// </para>
+        /// </param>
+        public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries)
+        {
+            if (explicitBucketBoundaries is null)
+            {
+                throw new ArgumentNullException(nameof(explicitBucketBoundaries));
+            }
+
+            List<T> explicitBucketBoundariesCopy = [.. explicitBucketBoundaries];
+
+            if (!IsSortedAndDistinct(explicitBucketBoundariesCopy))
+            {
+                throw new ArgumentException(SR.InvalidHistogramExplicitBucketBoundaries, nameof(explicitBucketBoundaries));
+            }
+
+            ExplicitBucketBoundaries = explicitBucketBoundariesCopy;
+        }
+
+        /// <summary>
+        /// Gets the explicit bucket boundaries advised to be used with the histogram.
+        /// </summary>
+        public IReadOnlyList<T>? ExplicitBucketBoundaries { get; }
+
+        private static bool IsSortedAndDistinct(List<T> values)
+        {
+            Comparer<T> comparer = Comparer<T>.Default;
+
+            for (int i = 1; i < values.Count; i++)
+            {
+                if (comparer.Compare(values[i - 1], values[i]) >= 0)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
     }
 }
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
index fbac05a3f0cd44..4ca5f0fe8daa54 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Diagnostics;
 using System.Threading;
 
@@ -120,7 +121,7 @@ private void Initialize(string name, string? version, IEnumerable<KeyValuePair<s
         /// <summary>
         /// Create a metrics Counter object.
         /// </summary>
-        /// <param name="name">The instrument name. cannot be null.</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>
         /// <remarks>
@@ -132,10 +133,10 @@ private void Initialize(string name, string? version, IEnumerable<KeyValuePair<s
         /// <summary>
         /// Create a metrics Counter object.
         /// </summary>
-        /// <param name="name">The instrument name. cannot be null.</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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the counter.</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.
@@ -146,40 +147,52 @@ public Counter<T> CreateCounter<T>(string name, string? unit, string? descriptio
         /// <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="name">The instrument name. Cannot be null.</param>
+        /// <remarks>
+        /// Example uses for Histogram: the request duration and the size of the response payload.
+        /// </remarks>
+        public Histogram<T> CreateHistogram<T>(string name) where T : struct
+            => CreateHistogram<T>(name, unit: null, description: null, tags: null, advice: null);
+
+        /// <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
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description) where T : struct
             => CreateHistogram<T>(name, unit, description, tags: null, advice: null);
 
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="unit">Optional instrument unit of measurements.</param>
         /// <param name="description">Optional instrument description.</param>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the histogram.</param>
         /// <remarks>
         /// Example uses for Histogram: the request duration and the size of the response payload.
         /// </remarks>
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags) where T : struct
                 => CreateHistogram<T>(name, unit, description, tags, advice: null);
 
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="unit">Optional instrument unit of measurements.</param>
         /// <param name="description">Optional instrument description.</param>
-        /// <param name="tags">tags to attach to the counter.</param>
-        /// <param name="advice"><see cref="HistogramAdvice{T}"/> to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the histogram.</param>
+        /// <param name="advice">Optional <see cref="HistogramAdvice{T}"/> to attach to the histogram.</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, string? description, IEnumerable<KeyValuePair<string, object?>>? tags, HistogramAdvice<T>? advice) where T : struct
+        public Histogram<T> CreateHistogram<T>(string name, string? unit = default, string? description = default, IEnumerable<KeyValuePair<string, object?>>? tags = default, HistogramAdvice<T>? advice = default) where T : struct
                 => (Histogram<T>)GetOrCreateInstrument<T>(typeof(Histogram<T>), name, unit, description, tags, () => new Histogram<T>(this, name, unit, description, tags, advice));
 
         /// <summary>
@@ -201,7 +214,7 @@ public UpDownCounter<T> CreateUpDownCounter<T>(string name, string? unit = null,
         /// <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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the counter.</param>
         /// <remarks>
         /// UpDownCounter is an Instrument which supports reporting positive or negative metric values.
         /// Example uses for UpDownCounter: reporting the change in active requests or queue size.
@@ -229,7 +242,7 @@ public ObservableUpDownCounter<T> CreateObservableUpDownCounter<T>(string name,
         /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the counter.</param>
         /// <remarks>
         /// Example uses for ObservableUpDownCounter: the process heap size or the approximate number of items in a lock-free circular buffer.
         /// </remarks>
@@ -257,7 +270,7 @@ public ObservableUpDownCounter<T> CreateObservableUpDownCounter<T>(string name,
         /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the counter.</param>
         /// <remarks>
         /// Example uses for ObservableUpDownCounter: the process heap size or the approximate number of items in a lock-free circular buffer.
         /// </remarks>
@@ -284,7 +297,7 @@ public ObservableUpDownCounter<T> CreateObservableUpDownCounter<T>(string name,
         /// <param name="observeValues">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the counter.</param>
         /// <remarks>
         /// Example uses for ObservableUpDownCounter: the process heap size or the approximate number of items in a lock-free circular buffer.
         /// </remarks>
@@ -294,7 +307,7 @@ public ObservableUpDownCounter<T> CreateObservableUpDownCounter<T>(string name,
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
@@ -307,11 +320,11 @@ public ObservableCounter<T> CreateObservableCounter<T>(string name, Func<T> obse
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the counter.</param>
         /// <remarks>
         /// Example uses for ObservableCounter: The number of page faults for each process.
         /// </remarks>
@@ -321,7 +334,7 @@ public ObservableCounter<T> CreateObservableCounter<T>(string name, Func<T> obse
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
@@ -334,11 +347,11 @@ public ObservableCounter<T> CreateObservableCounter<T>(string name, Func<Measure
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the counter.</param>
         /// <remarks>
         /// Example uses for ObservableCounter: The number of page faults for each process.
         /// </remarks>
@@ -349,7 +362,7 @@ public ObservableCounter<T> CreateObservableCounter<T>(string name, Func<Measure
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValues">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
@@ -362,11 +375,11 @@ public ObservableCounter<T> CreateObservableCounter<T>(string name, Func<IEnumer
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValues">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the counter.</param>
         /// <remarks>
         /// Example uses for ObservableCounter: The number of page faults for each process.
         /// </remarks>
@@ -376,7 +389,7 @@ public ObservableCounter<T> CreateObservableCounter<T>(string name, Func<IEnumer
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
@@ -386,18 +399,18 @@ public ObservableGauge<T> CreateObservableGauge<T>(string name, Func<T> observeV
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the gauge.</param>
         public ObservableGauge<T> CreateObservableGauge<T>(string name, Func<T> observeValue, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags) where T : struct =>
                                         new ObservableGauge<T>(this, name, observeValue, unit, description, tags);
 
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
@@ -407,18 +420,18 @@ public ObservableGauge<T> CreateObservableGauge<T>(string name, Func<Measurement
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValue">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the gauge.</param>
         public ObservableGauge<T> CreateObservableGauge<T>(string name, Func<Measurement<T>> observeValue, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags) where T : struct =>
                                         new ObservableGauge<T>(this, name, observeValue, unit, description, tags);
 
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValues">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
@@ -428,11 +441,11 @@ public ObservableGauge<T> CreateObservableGauge<T>(string name, Func<IEnumerable
         /// <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="name">The instrument name. Cannot be null.</param>
         /// <param name="observeValues">The callback to call to get the measurements when the <see cref="ObservableInstrument{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>
-        /// <param name="tags">tags to attach to the counter.</param>
+        /// <param name="tags">Optional tags to attach to the gauge.</param>
         public ObservableGauge<T> CreateObservableGauge<T>(string name, Func<IEnumerable<Measurement<T>>> observeValues, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags) where T : struct =>
                                         new ObservableGauge<T>(this, name, observeValues, unit, description, tags);
 

From 94958bc2f68b0da7125df07394a6c78a32fba717 Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Tue, 21 May 2024 14:11:31 -0700
Subject: [PATCH 03/11] Add tests.

---
 .../tests/MetricsAdviceTests.cs               | 38 +++++++++++++++++++
 .../tests/MetricsTests.cs                     | 23 +++++++++--
 ....Diagnostics.DiagnosticSource.Tests.csproj |  1 +
 3 files changed, 59 insertions(+), 3 deletions(-)
 create mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
new file mode 100644
index 00000000000000..9c794812cee339
--- /dev/null
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
@@ -0,0 +1,38 @@
+﻿// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace System.Diagnostics.Metrics.Tests
+{
+    public class MetricsAdviceTests
+    {
+        [Fact]
+        public void HistogramAdviceConstructionTest()
+        {
+            Assert.Throws<ArgumentNullException>(() => new HistogramAdvice<int>(explicitBucketBoundaries: null));
+
+            HistogramAdvice<int> emptyExplicitBucketBoundariesAdvice = new HistogramAdvice<int>(explicitBucketBoundaries: Array.Empty<int>());
+
+            Assert.Equal(Array.Empty<int>(), emptyExplicitBucketBoundariesAdvice.ExplicitBucketBoundaries);
+
+            int[] singleExplicitBucketBoundary = new int[] { 0 };
+
+            HistogramAdvice<int> singleExplicitBucketBoundaryAdvice = new HistogramAdvice<int>(singleExplicitBucketBoundary);
+
+            Assert.Equal(singleExplicitBucketBoundary, singleExplicitBucketBoundaryAdvice.ExplicitBucketBoundaries);
+
+            // Verify data cannot be mutated after construction
+            singleExplicitBucketBoundary[0] = int.MaxValue;
+            Assert.NotEqual(singleExplicitBucketBoundary, singleExplicitBucketBoundaryAdvice.ExplicitBucketBoundaries);
+
+            int[] invalidBucketBoundariesNondistinctValues = new int[] { 0, 1, 2, 3, 4, 4 };
+
+            Assert.Throws<ArgumentException>(() => new HistogramAdvice<int>(invalidBucketBoundariesNondistinctValues));
+
+            int[] invalidBucketBoundariesOrdering = new int[] { 0, 1, 2, 3, 5, 4 };
+
+            Assert.Throws<ArgumentException>(() => new HistogramAdvice<int>(invalidBucketBoundariesOrdering));
+        }
+    }
+}
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
index b292e55f1bcf20..2068ed056c6839 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
@@ -3,7 +3,6 @@
 
 using Microsoft.DotNet.RemoteExecutor;
 using System.Collections.Generic;
-using System.Diagnostics.Metrics;
 using System.Diagnostics.Tests;
 using System.Linq;
 using System.Threading;
@@ -26,7 +25,7 @@ public void MeasurementConstructionTest()
                 var measurement = new Measurement<int>(i, tags);
                 Assert.Equal(i, measurement.Value);
                 TagListTests.ValidateTags(new TagList(measurement.Tags), tagsArray);
-                
+
                 measurement = new Measurement<int>(i, tagsArray);
                 Assert.Equal(i, measurement.Value);
                 TagListTests.ValidateTags(new TagList(measurement.Tags), tagsArray);
@@ -335,7 +334,7 @@ public void InstrumentMeasurementTest(bool useSpan)
                 InstrumentMeasurementAggregationValidation(histogram6, (value, tags) => { Record(histogram6, value, tags, useSpan); } );
 
             }, useSpan.ToString()).Dispose();
-            
+
             void AddToCounter<T>(Counter<T> counter, T delta, KeyValuePair<string, object?>[] tags, bool useSpan) where T : struct
             {
                 if (useSpan)
@@ -1461,6 +1460,24 @@ public void TestInstrumentCreationWithTags()
             }).Dispose();
         }
 
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void TestHistogramCreationWithAdvice()
+        {
+           RemoteExecutor.Invoke(() => {
+                using Meter meter = new Meter(nameof(TestHistogramCreationWithAdvice));
+
+                int[] explicitBucketBoundaries = new int[] { 0, 100, 1000, 10000 };
+
+                Histogram<int> histogramWithoutAdvice = meter.CreateHistogram<int>(name: nameof(histogramWithoutAdvice));
+
+                Assert.Null(histogramWithoutAdvice.Advice);
+
+                Histogram<int> histogramWithAdvice = meter.CreateHistogram<int>(name: nameof(histogramWithAdvice), advice: new HistogramAdvice<int>(explicitBucketBoundaries));
+
+                Assert.NotNull(histogramWithAdvice.Advice?.ExplicitBucketBoundaries);
+                Assert.Equal(explicitBucketBoundaries, histogramWithAdvice.Advice.ExplicitBucketBoundaries);
+            }).Dispose();
+        }
 
         private void PublishCounterMeasurement<T>(Counter<T> counter, T value, KeyValuePair<string, object?>[] tags) where T : struct
         {
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj
index 29a8e55dc2fc02..446c67800c4a07 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj
@@ -32,6 +32,7 @@
     <Compile Include="ActivitySourceTests.cs" />
     <Compile Include="ActivityTagsCollectionTests.cs" />
     <Compile Include="MetricEventSourceTests.cs" />
+    <Compile Include="MetricsAdviceTests.cs" />
     <Compile Include="MetricsTests.cs" />
     <Compile Include="PropagatorTests.cs" />
     <Compile Include="TagListTests.cs" />

From 8d65122e662af0cba3c86de980797a4fd9261777 Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Tue, 21 May 2024 14:15:32 -0700
Subject: [PATCH 04/11] Code review.

---
 .../ref/System.Diagnostics.DiagnosticSourceActivity.cs | 10 +++++-----
 .../src/Resources/Strings.resx                         |  4 ++--
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
index b801b5751b3202..8bfaeb309ea551 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
@@ -367,6 +367,11 @@ public sealed class Histogram<T> : Instrument<T> where T : struct
         public void Record(T value, params System.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 sealed class HistogramAdvice<T> where T : struct
+    {
+        public HistogramAdvice(System.Collections.Generic.IEnumerable<T> explicitBucketBoundaries) { throw null; }
+        public System.Collections.Generic.IReadOnlyList<T>? ExplicitBucketBoundaries { get { throw null; } }
+    }
     public interface IMeterFactory : System.IDisposable
     {
         System.Diagnostics.Metrics.Meter Create(System.Diagnostics.Metrics.MeterOptions options);
@@ -573,9 +578,4 @@ public abstract class ObservableInstrument<T> : Instrument where T : struct
         protected ObservableInstrument(Meter meter, string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>> tags) : base(meter, name, unit, description) { throw null; }
         protected abstract System.Collections.Generic.IEnumerable<Measurement<T>> Observe();
     }
-    public sealed class HistogramAdvice<T> where T : struct
-    {
-        public HistogramAdvice(System.Collections.Generic.IEnumerable<T> explicitBucketBoundaries) { throw null; }
-        public System.Collections.Generic.IReadOnlyList<T>? ExplicitBucketBoundaries { get { throw null; } }
-    }
 }
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx b/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx
index f1493d25fae37f..b228dca0765cc3 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx
@@ -175,6 +175,6 @@
     <value>The instrument is of different generic type.</value>
   </data>
   <data name="InvalidHistogramExplicitBucketBoundaries" xml:space="preserve">
-    <value>Histogram explicit bucket boundaries MUST be specified in ascending order and CANNOT contain duplicate values.</value>
+    <value>Histogram explicit bucket boundaries must be specified in ascending order and cannot contain duplicate values.</value>
   </data>
-</root>
+</root>
\ No newline at end of file

From 922abfca3360ad44c82134e80bf649dd3dbeb3d9 Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Tue, 21 May 2024 15:09:58 -0700
Subject: [PATCH 05/11] Code review.

---
 .../Diagnostics/Metrics/HistogramAdvice.cs    | 13 +++++++++++--
 .../tests/MetricsAdviceTests.cs               |  3 ++-
 .../tests/MetricsTests.cs                     | 19 ++++++++++---------
 3 files changed, 23 insertions(+), 12 deletions(-)

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
index f40d2fb5cd0a0d..065dca591a4b2f 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 
 namespace System.Diagnostics.Metrics
 {
@@ -30,7 +31,7 @@ public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries)
                 throw new ArgumentNullException(nameof(explicitBucketBoundaries));
             }
 
-            List<T> explicitBucketBoundariesCopy = [.. explicitBucketBoundaries];
+            IReadOnlyList<T> explicitBucketBoundariesCopy = new ReadOnlyCollection<T>(new List<T>(explicitBucketBoundaries));
 
             if (!IsSortedAndDistinct(explicitBucketBoundariesCopy))
             {
@@ -43,9 +44,17 @@ public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries)
         /// <summary>
         /// Gets the explicit bucket boundaries advised to be used with the histogram.
         /// </summary>
+        /// <remarks>
+        /// Notes:
+        /// <list type="bullet">
+        /// <item>A <see langword="null"/> value means no bucket boundaries have been configured and default values should be used for bucket configuration.</item>
+        /// <item>An empty set of bucket boundaries hints that the histogram by default should NOT contain buckets and should only track count and sum values.</item>
+        /// <item>A set of non-distinct increasing values for bucket boundaries hints that the histogram should use those for its default bucket configuration.</item>
+        /// </list>
+        /// </remarks>
         public IReadOnlyList<T>? ExplicitBucketBoundaries { get; }
 
-        private static bool IsSortedAndDistinct(List<T> values)
+        private static bool IsSortedAndDistinct(IReadOnlyList<T> values)
         {
             Comparer<T> comparer = Comparer<T>.Default;
 
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
index 9c794812cee339..91549509c60d14 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
@@ -14,7 +14,8 @@ public void HistogramAdviceConstructionTest()
 
             HistogramAdvice<int> emptyExplicitBucketBoundariesAdvice = new HistogramAdvice<int>(explicitBucketBoundaries: Array.Empty<int>());
 
-            Assert.Equal(Array.Empty<int>(), emptyExplicitBucketBoundariesAdvice.ExplicitBucketBoundaries);
+            Assert.NotNull(emptyExplicitBucketBoundariesAdvice.ExplicitBucketBoundaries);
+            Assert.Empty(emptyExplicitBucketBoundariesAdvice.ExplicitBucketBoundaries);
 
             int[] singleExplicitBucketBoundary = new int[] { 0 };
 
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
index 2068ed056c6839..467531a5fd2045 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
@@ -1463,20 +1463,21 @@ public void TestInstrumentCreationWithTags()
         [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
         public void TestHistogramCreationWithAdvice()
         {
-           RemoteExecutor.Invoke(() => {
-                using Meter meter = new Meter(nameof(TestHistogramCreationWithAdvice));
+           RemoteExecutor.Invoke(() =>
+           {
+               using Meter meter = new Meter(nameof(TestHistogramCreationWithAdvice));
 
-                int[] explicitBucketBoundaries = new int[] { 0, 100, 1000, 10000 };
+               Histogram<int> histogramWithoutAdvice = meter.CreateHistogram<int>(name: nameof(histogramWithoutAdvice));
 
-                Histogram<int> histogramWithoutAdvice = meter.CreateHistogram<int>(name: nameof(histogramWithoutAdvice));
+               Assert.Null(histogramWithoutAdvice.Advice);
 
-                Assert.Null(histogramWithoutAdvice.Advice);
+               int[] explicitBucketBoundaries = new int[] { 0, 100, 1000, 10000 };
 
-                Histogram<int> histogramWithAdvice = meter.CreateHistogram<int>(name: nameof(histogramWithAdvice), advice: new HistogramAdvice<int>(explicitBucketBoundaries));
+               Histogram<int> histogramWithAdvice = meter.CreateHistogram<int>(name: nameof(histogramWithAdvice), advice: new HistogramAdvice<int>(explicitBucketBoundaries));
 
-                Assert.NotNull(histogramWithAdvice.Advice?.ExplicitBucketBoundaries);
-                Assert.Equal(explicitBucketBoundaries, histogramWithAdvice.Advice.ExplicitBucketBoundaries);
-            }).Dispose();
+               Assert.NotNull(histogramWithAdvice.Advice?.ExplicitBucketBoundaries);
+               Assert.Equal(explicitBucketBoundaries, histogramWithAdvice.Advice.ExplicitBucketBoundaries);
+           }).Dispose();
         }
 
         private void PublishCounterMeasurement<T>(Counter<T> counter, T value, KeyValuePair<string, object?>[] tags) where T : struct

From 3f0fbccfeba2de466a2065d6db161e21784d66e7 Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Tue, 21 May 2024 15:13:03 -0700
Subject: [PATCH 06/11] Tweaks.

---
 .../src/System/Diagnostics/Metrics/HistogramAdvice.cs       | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
index 065dca591a4b2f..5b000493cbb256 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
@@ -19,8 +19,8 @@ public sealed class HistogramAdvice<T> where T : struct
         /// <para>Explicit bucket boundaries advised to be used with the histogram.</para>
         /// <para>Notes:
         /// <list type="bullet">
-        /// <item>Bucket boundaries MUST be specified in ascending order and CANNOT contain duplicate values.</item>
-        /// <item>An empty set of bucket boundaries hints that the histogram should NOT contain buckets and should only track count and sum values.</item>
+        /// <item>An empty set of bucket boundaries hints that the histogram by default should NOT contain buckets and should only track count and sum values.</item>
+        /// <item>A set of distinct increasing values for bucket boundaries hints that the histogram should use those for its default bucket configuration.</item>
         /// </list>
         /// </para>
         /// </param>
@@ -49,7 +49,7 @@ public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries)
         /// <list type="bullet">
         /// <item>A <see langword="null"/> value means no bucket boundaries have been configured and default values should be used for bucket configuration.</item>
         /// <item>An empty set of bucket boundaries hints that the histogram by default should NOT contain buckets and should only track count and sum values.</item>
-        /// <item>A set of non-distinct increasing values for bucket boundaries hints that the histogram should use those for its default bucket configuration.</item>
+        /// <item>A set of distinct increasing values for bucket boundaries hints that the histogram should use those for its default bucket configuration.</item>
         /// </list>
         /// </remarks>
         public IReadOnlyList<T>? ExplicitBucketBoundaries { get; }

From ea17f1734048230553a1593bb87451073f20e5c0 Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Tue, 21 May 2024 22:52:16 -0700
Subject: [PATCH 07/11] Code review.

---
 .../System/Diagnostics/Metrics/HistogramAdvice.cs | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
index 5b000493cbb256..5bd2c197efeb2c 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
@@ -15,15 +15,14 @@ public sealed class HistogramAdvice<T> where T : struct
         /// <summary>
         /// Constructs a new instance of <see cref="HistogramAdvice{T}"/>.
         /// </summary>
-        /// <param name="explicitBucketBoundaries">
-        /// <para>Explicit bucket boundaries advised to be used with the histogram.</para>
-        /// <para>Notes:
+        /// <remarks>
+        /// Notes:
         /// <list type="bullet">
         /// <item>An empty set of bucket boundaries hints that the histogram by default should NOT contain buckets and should only track count and sum values.</item>
         /// <item>A set of distinct increasing values for bucket boundaries hints that the histogram should use those for its default bucket configuration.</item>
         /// </list>
-        /// </para>
-        /// </param>
+        /// </remarks>
+        /// <param name="explicitBucketBoundaries">Explicit bucket boundaries advised to be used with the histogram.</param>
         public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries)
         {
             if (explicitBucketBoundaries is null)
@@ -31,14 +30,14 @@ public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries)
                 throw new ArgumentNullException(nameof(explicitBucketBoundaries));
             }
 
-            IReadOnlyList<T> explicitBucketBoundariesCopy = new ReadOnlyCollection<T>(new List<T>(explicitBucketBoundaries));
+            List<T> explicitBucketBoundariesCopy = new List<T>(explicitBucketBoundaries);
 
             if (!IsSortedAndDistinct(explicitBucketBoundariesCopy))
             {
                 throw new ArgumentException(SR.InvalidHistogramExplicitBucketBoundaries, nameof(explicitBucketBoundaries));
             }
 
-            ExplicitBucketBoundaries = explicitBucketBoundariesCopy;
+            ExplicitBucketBoundaries = new ReadOnlyCollection<T>(explicitBucketBoundariesCopy);
         }
 
         /// <summary>
@@ -54,7 +53,7 @@ public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries)
         /// </remarks>
         public IReadOnlyList<T>? ExplicitBucketBoundaries { get; }
 
-        private static bool IsSortedAndDistinct(IReadOnlyList<T> values)
+        private static bool IsSortedAndDistinct(List<T> values)
         {
             Comparer<T> comparer = Comparer<T>.Default;
 

From afc3facc8209a9dabb22916d3ee1755693931769 Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Tue, 4 Jun 2024 11:07:10 -0700
Subject: [PATCH 08/11] Revisions.

---
 ...em.Diagnostics.DiagnosticSourceActivity.cs | 34 ++++++----
 ...System.Diagnostics.DiagnosticSource.csproj | 11 ++--
 .../System/Diagnostics/Metrics/Histogram.cs   | 13 ++--
 .../Diagnostics/Metrics/Instrument.common.cs  | 62 +++++++++++++++----
 .../System/Diagnostics/Metrics/Instrument.cs  | 38 +++++++++---
 ...HistogramAdvice.cs => InstrumentAdvice.cs} | 30 ++++-----
 .../src/System/Diagnostics/Metrics/Meter.cs   |  4 +-
 .../tests/MetricsAdviceTests.cs               | 18 +++---
 .../tests/MetricsTests.cs                     |  6 +-
 9 files changed, 139 insertions(+), 77 deletions(-)
 rename src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/{HistogramAdvice.cs => InstrumentAdvice.cs} (54%)

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
index 8bfaeb309ea551..c5f1a73e575157 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
@@ -357,7 +357,6 @@ internal UpDownCounter(Meter meter, string name, string? unit, string? descripti
     }
     public sealed class Histogram<T> : Instrument<T> where T : struct
     {
-        public HistogramAdvice<T>? Advice { get { throw null; } }
         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; }
@@ -367,11 +366,6 @@ public sealed class Histogram<T> : Instrument<T> where T : struct
         public void Record(T value, params System.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 sealed class HistogramAdvice<T> where T : struct
-    {
-        public HistogramAdvice(System.Collections.Generic.IEnumerable<T> explicitBucketBoundaries) { throw null; }
-        public System.Collections.Generic.IReadOnlyList<T>? ExplicitBucketBoundaries { get { throw null; } }
-    }
     public interface IMeterFactory : System.IDisposable
     {
         System.Diagnostics.Metrics.Meter Create(System.Diagnostics.Metrics.MeterOptions options);
@@ -380,8 +374,10 @@ 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;}
-        protected Instrument(Meter meter, string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags) {throw null;}
+        protected Instrument(Meter meter, string name) : this(meter, name, unit: null, description: null, tags: null) { throw null; }
+        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+        protected Instrument(Meter meter, string name, string? unit, string? description) : this(meter, name, unit, description, tags: null) { throw null; }
+        protected Instrument(Meter meter, string name, string? unit = default, string? description = default, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags = default) { throw null; }
         public virtual bool IsObservable { get  {throw null; } }
         public Meter Meter { get {throw null;} }
         public string Name { get {throw null;} }
@@ -391,8 +387,19 @@ public abstract class Instrument
     }
     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 Instrument(Meter meter, string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags) : base(meter, name, unit, description, tags)  {throw null;}
+        public InstrumentAdvice<T>? Advice { get { throw null; } }
+        protected Instrument(Meter meter, string name) : this(meter, name, unit: null, description: null, tags: null, advice: null) { throw null; }
+        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+        protected Instrument(Meter meter, string name, string? unit, string? description) : this(meter, name, unit, description, tags: null, advice: null) { throw null; }
+        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+        protected Instrument(Meter meter, string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags) : this(meter, name, unit, description, tags, advice: null)  { throw null; }
+        protected Instrument(
+            Meter meter,
+            string name,
+            string? unit = default,
+            string? description = default,
+            System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags = default,
+            InstrumentAdvice<T>? advice = default) : base(meter, name, unit, description, tags) { 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; }
@@ -400,6 +407,11 @@ public abstract class Instrument<T> : Instrument where T : struct
         protected void RecordMeasurement(T measurement, in TagList tagList) { throw null; }
         protected void RecordMeasurement(T measurement, ReadOnlySpan<System.Collections.Generic.KeyValuePair<string, object?>> tags) { throw null; }
     }
+    public sealed class InstrumentAdvice<T> where T : struct
+    {
+        public InstrumentAdvice(System.Collections.Generic.IEnumerable<T> histogramExplicitBucketBoundaries) { throw null; }
+        public System.Collections.Generic.IReadOnlyList<T>? HistogramExplicitBucketBoundaries { get { throw null; } }
+    }
     public readonly struct Measurement<T> where T : struct
     {
         public Measurement(T value) { throw null; }
@@ -421,7 +433,7 @@ public class Meter : IDisposable
         public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description) where T : struct { throw null; }
         [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
         public Histogram<T> CreateHistogram<T>(string name, string? unit, string? description, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags) where T : struct { throw null; }
-        public Histogram<T> CreateHistogram<T>(string name, string? unit = default, string? description = default, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags = default, HistogramAdvice<T>? advice = default) where T : struct { throw null; }
+        public Histogram<T> CreateHistogram<T>(string name, string? unit = default, string? description = default, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags = default, InstrumentAdvice<T>? advice = default) where T : struct { throw null; }
         public ObservableCounter<T> CreateObservableCounter<T>(
                             string name,
                             Func<T> observeValue,
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
index 76a6685c3e5b09..d8775312d8bcb5 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
@@ -54,7 +54,7 @@ System.Diagnostics.DiagnosticSource</PackageDescription>
     <Compile Include="System\Diagnostics\Metrics\Counter.cs" />
     <Compile Include="System\Diagnostics\Metrics\ExponentialHistogramAggregator.cs" />
     <Compile Include="System\Diagnostics\Metrics\Histogram.cs" />
-    <Compile Include="System\Diagnostics\Metrics\HistogramAdvice.cs" />
+    <Compile Include="System\Diagnostics\Metrics\InstrumentAdvice.cs" />
     <Compile Include="System\Diagnostics\Metrics\IMeterFactory.cs" />
     <Compile Include="System\Diagnostics\Metrics\Instrument.cs" />
     <Compile Include="System\Diagnostics\Metrics\Instrument.common.cs" />
@@ -93,10 +93,8 @@ System.Diagnostics.DiagnosticSource</PackageDescription>
     <Compile Include="System\Diagnostics\Metrics\ObjectSequence.netcore.cs" />
     <Compile Include="System\Diagnostics\Metrics\StringSequence.netcore.cs" />
     <Compile Include="System\Diagnostics\System.Diagnostics.DiagnosticSource.Typeforwards.netcoreapp.cs" />
-    <Compile Include="$(CommonPath)System\LocalAppContextSwitches.Common.cs"
-             Link="Common\System\LocalAppContextSwitches.Common.cs" />
-    <Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs"
-             Link="Common\System\Text\ValueStringBuilder.cs" />
+    <Compile Include="$(CommonPath)System\LocalAppContextSwitches.Common.cs" Link="Common\System\LocalAppContextSwitches.Common.cs" />
+    <Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs" Link="Common\System\Text\ValueStringBuilder.cs" />
   </ItemGroup>
 
   <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
@@ -116,8 +114,7 @@ System.Diagnostics.DiagnosticSource</PackageDescription>
     <Compile Include="$(CoreLibSharedDir)System\Runtime\CompilerServices\IsExternalInit.cs" />
   </ItemGroup>
 
-  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' or
-                        '$(TargetFramework)' == 'netstandard2.0'">
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' or&#xD;&#xA;                        '$(TargetFramework)' == 'netstandard2.0'">
     <Compile Include="System\Diagnostics\Activity.DateTime.corefx.cs" />
   </ItemGroup>
 
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
index 7e3999695b0bad..88c99481670c06 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Histogram.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Histogram.cs
@@ -15,19 +15,14 @@ namespace System.Diagnostics.Metrics
     /// </remarks>
     public sealed class Histogram<T> : Instrument<T> where T : struct
     {
-        /// <summary>
-        /// Gets the <see cref="HistogramAdvice{T}"/> associated with the instrument.
-        /// </summary>
-        public HistogramAdvice<T>? Advice { get; }
-
-        internal Histogram(Meter meter, string name, string? unit, string? description) : this(meter, name, unit, description, tags: null, advice: null)
+        internal Histogram(Meter meter, string name, string? unit, string? description)
+            : this(meter, name, unit, description, tags: null, advice: null)
         {
         }
 
-        internal Histogram(Meter meter, string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags, HistogramAdvice<T>? advice) : base(meter, name, unit, description, tags)
+        internal Histogram(Meter meter, string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags, InstrumentAdvice<T>? advice)
+            : base(meter, name, unit, description, tags, advice)
         {
-            Advice = advice;
-
             Publish();
         }
 
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
index bc813c0d4cfe47..ca1c7dd63d236a 100644
--- 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
@@ -2,11 +2,12 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.ComponentModel;
 
 namespace System.Diagnostics.Metrics
 {
     /// <summary>
-    /// Instrument{T} is the base class for all non-observable instruments.
+    /// <see cref="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" />
@@ -15,28 +16,67 @@ namespace System.Diagnostics.Metrics
     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.
+        /// Gets the <see cref="InstrumentAdvice{T}"/> associated with the instrument.
         /// </summary>
-        /// <param name="meter">The meter that created the instrument.</param>
-        /// <param name="name">The instrument name. cannot be null.</param>
+        public InstrumentAdvice<T>? Advice { get; }
+
+        /// <summary>
+        /// Constructs a new instance of <see cref="Instrument{T}"/>.
+        /// </summary>
+        /// <param name="meter">The meter that created the instrument. Cannot be null.</param>
+        /// <param name="name">The instrument name. Cannot be null.</param>
+        protected Instrument(Meter meter, string name)
+            : this(meter, name, unit: null, description: null, tags: null, advice: null)
+        {
+        }
+
+        /// <summary>
+        /// Constructs a new instance of <see cref="Instrument{T}"/>.
+        /// </summary>
+        /// <param name="meter">The meter that created the instrument. Cannot be null.</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) : this(meter, name, unit, description, tags: null)
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected Instrument(Meter meter, string name, string? unit, string? description)
+            : this(meter, name, unit, description, tags: null, advice: null)
         {
         }
 
         /// <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.
+        /// Constructs a new instance of <see cref="Instrument{T}"/>.
         /// </summary>
-        /// <param name="meter">The meter that created the instrument.</param>
-        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="meter">The meter that created the instrument. Cannot be null.</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>
         /// <param name="tags">Optional instrument tags.</param>
-        protected Instrument(Meter meter, string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags) : base(meter, name, unit, description, tags)
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected Instrument(Meter meter, string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags)
+            : this(meter, name, unit, description, tags, advice: null)
         {
+        }
+
+        /// <summary>
+        /// Constructs a new instance of <see cref="Instrument{T}"/>.
+        /// </summary>
+        /// <param name="meter">The meter that created the instrument. Cannot be null.</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>
+        /// <param name="tags">Optional instrument tags.</param>
+        /// <param name="advice">Optional <see cref="InstrumentAdvice{T}"/>.</param>
+        protected Instrument(
+            Meter meter,
+            string name,
+            string? unit = default,
+            string? description = default,
+            IEnumerable<KeyValuePair<string, object?>>? tags = default,
+            InstrumentAdvice<T>? advice = default)
+            : base(meter, name, unit, description, tags)
+        {
+            Advice = advice;
+
             ValidateTypeParameter<T>();
         }
 
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs
index 17941aa0d8b830..0a3362e015d1a7 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.cs
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.ComponentModel;
 
 namespace System.Diagnostics.Metrics
 {
@@ -27,25 +28,42 @@ public abstract class Instrument
         internal readonly DiagLinkedList<ListenerSubscription> _subscriptions = new DiagLinkedList<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.
+        /// Constructs a new instance of <see cref="Instrument"/>.
         /// </summary>
-        /// <param name="meter">The meter that created the instrument.</param>
-        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="meter">The meter that created the instrument. Cannot be null.</param>
+        /// <param name="name">The instrument name. Cannot be null.</param>
+        protected Instrument(Meter meter, string name)
+            : this(meter, name, unit: null, description: null, tags: null)
+        {
+        }
+
+        /// <summary>
+        /// Constructs a new instance of <see cref="Instrument"/>.
+        /// </summary>
+        /// <param name="meter">The meter that created the instrument. Cannot be null.</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) : this(meter, name, unit, description, null) { }
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected Instrument(Meter meter, string name, string? unit, string? description)
+            : this(meter, name, unit, description, tags: null)
+        {
+        }
 
         /// <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.
+        /// Constructs a new instance of <see cref="Instrument"/>.
         /// </summary>
-        /// <param name="meter">The meter that created the instrument.</param>
-        /// <param name="name">The instrument name. cannot be null.</param>
+        /// <param name="meter">The meter that created the instrument. Cannot be null.</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>
         /// <param name="tags">Optional instrument tags.</param>
-        protected Instrument(Meter meter, string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags)
+        protected Instrument(
+            Meter meter,
+            string name,
+            string? unit = default,
+            string? description = default,
+            IEnumerable<KeyValuePair<string, object?>>? tags = default)
         {
             Meter = meter ?? throw new ArgumentNullException(nameof(meter));
             Name = name ?? throw new ArgumentNullException(nameof(name));
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs
similarity index 54%
rename from src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
rename to src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs
index 5bd2c197efeb2c..d651d83f350512 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/HistogramAdvice.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs
@@ -7,41 +7,41 @@
 namespace System.Diagnostics.Metrics
 {
     /// <summary>
-    /// Contains settings used to advise metrics consumers how to construct storage for <see cref="Histogram{T}"/> instruments.
+    /// Contains configuration settings advised to be used by metrics consumers when recording measurements for a given <see cref="Instrument{T}"/>.
     /// </summary>
-    /// <typeparam name="T">Histogram value type.</typeparam>
-    public sealed class HistogramAdvice<T> where T : struct
+    /// <typeparam name="T">Instrument value type.</typeparam>
+    public sealed class InstrumentAdvice<T> where T : struct
     {
         /// <summary>
-        /// Constructs a new instance of <see cref="HistogramAdvice{T}"/>.
+        /// Constructs a new instance of <see cref="InstrumentAdvice{T}"/>.
         /// </summary>
         /// <remarks>
         /// Notes:
         /// <list type="bullet">
-        /// <item>An empty set of bucket boundaries hints that the histogram by default should NOT contain buckets and should only track count and sum values.</item>
-        /// <item>A set of distinct increasing values for bucket boundaries hints that the histogram should use those for its default bucket configuration.</item>
+        /// <item>An empty set of bucket boundaries hints that histogram instruments by default should NOT contain buckets and should only track count and sum values.</item>
+        /// <item>A set of distinct increasing values for histogram bucket boundaries hints that histogram instruments should use those for its default bucket configuration.</item>
         /// </list>
         /// </remarks>
-        /// <param name="explicitBucketBoundaries">Explicit bucket boundaries advised to be used with the histogram.</param>
-        public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries)
+        /// <param name="histogramExplicitBucketBoundaries">Explicit bucket boundaries advised to be used with histogram instruments.</param>
+        public InstrumentAdvice(IEnumerable<T> histogramExplicitBucketBoundaries)
         {
-            if (explicitBucketBoundaries is null)
+            if (histogramExplicitBucketBoundaries is null)
             {
-                throw new ArgumentNullException(nameof(explicitBucketBoundaries));
+                throw new ArgumentNullException(nameof(histogramExplicitBucketBoundaries));
             }
 
-            List<T> explicitBucketBoundariesCopy = new List<T>(explicitBucketBoundaries);
+            List<T> explicitBucketBoundariesCopy = new List<T>(histogramExplicitBucketBoundaries);
 
             if (!IsSortedAndDistinct(explicitBucketBoundariesCopy))
             {
-                throw new ArgumentException(SR.InvalidHistogramExplicitBucketBoundaries, nameof(explicitBucketBoundaries));
+                throw new ArgumentException(SR.InvalidHistogramExplicitBucketBoundaries, nameof(histogramExplicitBucketBoundaries));
             }
 
-            ExplicitBucketBoundaries = new ReadOnlyCollection<T>(explicitBucketBoundariesCopy);
+            HistogramExplicitBucketBoundaries = new ReadOnlyCollection<T>(explicitBucketBoundariesCopy);
         }
 
         /// <summary>
-        /// Gets the explicit bucket boundaries advised to be used with the histogram.
+        /// Gets the explicit bucket boundaries advised to be used with histogram instruments.
         /// </summary>
         /// <remarks>
         /// Notes:
@@ -51,7 +51,7 @@ public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries)
         /// <item>A set of distinct increasing values for bucket boundaries hints that the histogram should use those for its default bucket configuration.</item>
         /// </list>
         /// </remarks>
-        public IReadOnlyList<T>? ExplicitBucketBoundaries { get; }
+        public IReadOnlyList<T>? HistogramExplicitBucketBoundaries { get; }
 
         private static bool IsSortedAndDistinct(List<T> values)
         {
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
index 4ca5f0fe8daa54..8578f0a51fd640 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Meter.cs
@@ -188,11 +188,11 @@ public Histogram<T> CreateHistogram<T>(string name, string? unit, string? descri
         /// <param name="unit">Optional instrument unit of measurements.</param>
         /// <param name="description">Optional instrument description.</param>
         /// <param name="tags">Optional tags to attach to the histogram.</param>
-        /// <param name="advice">Optional <see cref="HistogramAdvice{T}"/> to attach to the histogram.</param>
+        /// <param name="advice">Optional <see cref="InstrumentAdvice{T}"/> to attach to the histogram.</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 = default, string? description = default, IEnumerable<KeyValuePair<string, object?>>? tags = default, HistogramAdvice<T>? advice = default) where T : struct
+        public Histogram<T> CreateHistogram<T>(string name, string? unit = default, string? description = default, IEnumerable<KeyValuePair<string, object?>>? tags = default, InstrumentAdvice<T>? advice = default) where T : struct
                 => (Histogram<T>)GetOrCreateInstrument<T>(typeof(Histogram<T>), name, unit, description, tags, () => new Histogram<T>(this, name, unit, description, tags, advice));
 
         /// <summary>
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
index 91549509c60d14..26d05183c50b00 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
@@ -10,30 +10,30 @@ public class MetricsAdviceTests
         [Fact]
         public void HistogramAdviceConstructionTest()
         {
-            Assert.Throws<ArgumentNullException>(() => new HistogramAdvice<int>(explicitBucketBoundaries: null));
+            Assert.Throws<ArgumentNullException>(() => new InstrumentAdvice<int>(histogramExplicitBucketBoundaries: null));
 
-            HistogramAdvice<int> emptyExplicitBucketBoundariesAdvice = new HistogramAdvice<int>(explicitBucketBoundaries: Array.Empty<int>());
+            InstrumentAdvice<int> emptyExplicitBucketBoundariesAdvice = new InstrumentAdvice<int>(histogramExplicitBucketBoundaries: Array.Empty<int>());
 
-            Assert.NotNull(emptyExplicitBucketBoundariesAdvice.ExplicitBucketBoundaries);
-            Assert.Empty(emptyExplicitBucketBoundariesAdvice.ExplicitBucketBoundaries);
+            Assert.NotNull(emptyExplicitBucketBoundariesAdvice.HistogramExplicitBucketBoundaries);
+            Assert.Empty(emptyExplicitBucketBoundariesAdvice.HistogramExplicitBucketBoundaries);
 
             int[] singleExplicitBucketBoundary = new int[] { 0 };
 
-            HistogramAdvice<int> singleExplicitBucketBoundaryAdvice = new HistogramAdvice<int>(singleExplicitBucketBoundary);
+            InstrumentAdvice<int> singleExplicitBucketBoundaryAdvice = new InstrumentAdvice<int>(singleExplicitBucketBoundary);
 
-            Assert.Equal(singleExplicitBucketBoundary, singleExplicitBucketBoundaryAdvice.ExplicitBucketBoundaries);
+            Assert.Equal(singleExplicitBucketBoundary, singleExplicitBucketBoundaryAdvice.HistogramExplicitBucketBoundaries);
 
             // Verify data cannot be mutated after construction
             singleExplicitBucketBoundary[0] = int.MaxValue;
-            Assert.NotEqual(singleExplicitBucketBoundary, singleExplicitBucketBoundaryAdvice.ExplicitBucketBoundaries);
+            Assert.NotEqual(singleExplicitBucketBoundary, singleExplicitBucketBoundaryAdvice.HistogramExplicitBucketBoundaries);
 
             int[] invalidBucketBoundariesNondistinctValues = new int[] { 0, 1, 2, 3, 4, 4 };
 
-            Assert.Throws<ArgumentException>(() => new HistogramAdvice<int>(invalidBucketBoundariesNondistinctValues));
+            Assert.Throws<ArgumentException>(() => new InstrumentAdvice<int>(invalidBucketBoundariesNondistinctValues));
 
             int[] invalidBucketBoundariesOrdering = new int[] { 0, 1, 2, 3, 5, 4 };
 
-            Assert.Throws<ArgumentException>(() => new HistogramAdvice<int>(invalidBucketBoundariesOrdering));
+            Assert.Throws<ArgumentException>(() => new InstrumentAdvice<int>(invalidBucketBoundariesOrdering));
         }
     }
 }
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
index 467531a5fd2045..49a66f9e30def6 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
@@ -1473,10 +1473,10 @@ public void TestHistogramCreationWithAdvice()
 
                int[] explicitBucketBoundaries = new int[] { 0, 100, 1000, 10000 };
 
-               Histogram<int> histogramWithAdvice = meter.CreateHistogram<int>(name: nameof(histogramWithAdvice), advice: new HistogramAdvice<int>(explicitBucketBoundaries));
+               Histogram<int> histogramWithAdvice = meter.CreateHistogram<int>(name: nameof(histogramWithAdvice), advice: new InstrumentAdvice<int>(explicitBucketBoundaries));
 
-               Assert.NotNull(histogramWithAdvice.Advice?.ExplicitBucketBoundaries);
-               Assert.Equal(explicitBucketBoundaries, histogramWithAdvice.Advice.ExplicitBucketBoundaries);
+               Assert.NotNull(histogramWithAdvice.Advice?.HistogramExplicitBucketBoundaries);
+               Assert.Equal(explicitBucketBoundaries, histogramWithAdvice.Advice.HistogramExplicitBucketBoundaries);
            }).Dispose();
         }
 

From 6f5607225aa2c78ad70213bfc575b2de769006ee Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Tue, 4 Jun 2024 15:49:55 -0700
Subject: [PATCH 09/11] Tweaks.

---
 ...em.Diagnostics.DiagnosticSourceActivity.cs |  3 +-
 .../Diagnostics/Metrics/InstrumentAdvice.cs   | 47 ++++++++-----------
 .../tests/MetricsAdviceTests.cs               | 18 +++----
 .../tests/MetricsTests.cs                     |  6 +--
 4 files changed, 33 insertions(+), 41 deletions(-)

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
index c5f1a73e575157..b6d6a352520e7f 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
@@ -409,8 +409,7 @@ protected Instrument(
     }
     public sealed class InstrumentAdvice<T> where T : struct
     {
-        public InstrumentAdvice(System.Collections.Generic.IEnumerable<T> histogramExplicitBucketBoundaries) { throw null; }
-        public System.Collections.Generic.IReadOnlyList<T>? HistogramExplicitBucketBoundaries { get { throw null; } }
+        public System.Collections.Generic.IReadOnlyList<T>? HistogramBucketBoundaries { get { throw null; } init { throw null; } }
     }
     public readonly struct Measurement<T> where T : struct
     {
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs
index d651d83f350512..1a78325ca53e9b 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs
@@ -12,47 +12,40 @@ namespace System.Diagnostics.Metrics
     /// <typeparam name="T">Instrument value type.</typeparam>
     public sealed class InstrumentAdvice<T> where T : struct
     {
+        private readonly ReadOnlyCollection<T>? _HistogramBucketBoundaries;
+
         /// <summary>
-        /// Constructs a new instance of <see cref="InstrumentAdvice{T}"/>.
+        /// Gets the explicit bucket boundaries advised to be used with histogram instruments.
         /// </summary>
         /// <remarks>
         /// Notes:
         /// <list type="bullet">
-        /// <item>An empty set of bucket boundaries hints that histogram instruments by default should NOT contain buckets and should only track count and sum values.</item>
-        /// <item>A set of distinct increasing values for histogram bucket boundaries hints that histogram instruments should use those for its default bucket configuration.</item>
+        /// <item>A <see langword="null"/> value means no bucket boundaries have been configured and default values should be used for bucket configuration.</item>
+        /// <item>An empty set of bucket boundaries hints that the histogram by default should NOT contain buckets and should only track count and sum values.</item>
+        /// <item>A set of distinct increasing values for bucket boundaries hints that the histogram should use those for its default bucket configuration.</item>
         /// </list>
         /// </remarks>
-        /// <param name="histogramExplicitBucketBoundaries">Explicit bucket boundaries advised to be used with histogram instruments.</param>
-        public InstrumentAdvice(IEnumerable<T> histogramExplicitBucketBoundaries)
+        public IReadOnlyList<T>? HistogramBucketBoundaries
         {
-            if (histogramExplicitBucketBoundaries is null)
+            get => _HistogramBucketBoundaries;
+            init
             {
-                throw new ArgumentNullException(nameof(histogramExplicitBucketBoundaries));
-            }
+                if (value is null)
+                {
+                    throw new ArgumentNullException(nameof(value));
+                }
 
-            List<T> explicitBucketBoundariesCopy = new List<T>(histogramExplicitBucketBoundaries);
+                List<T> bucketBoundariesCopy = new List<T>(value);
 
-            if (!IsSortedAndDistinct(explicitBucketBoundariesCopy))
-            {
-                throw new ArgumentException(SR.InvalidHistogramExplicitBucketBoundaries, nameof(histogramExplicitBucketBoundaries));
-            }
+                if (!IsSortedAndDistinct(bucketBoundariesCopy))
+                {
+                    throw new ArgumentException(SR.InvalidHistogramExplicitBucketBoundaries, nameof(value));
+                }
 
-            HistogramExplicitBucketBoundaries = new ReadOnlyCollection<T>(explicitBucketBoundariesCopy);
+                _HistogramBucketBoundaries = new ReadOnlyCollection<T>(bucketBoundariesCopy);
+            }
         }
 
-        /// <summary>
-        /// Gets the explicit bucket boundaries advised to be used with histogram instruments.
-        /// </summary>
-        /// <remarks>
-        /// Notes:
-        /// <list type="bullet">
-        /// <item>A <see langword="null"/> value means no bucket boundaries have been configured and default values should be used for bucket configuration.</item>
-        /// <item>An empty set of bucket boundaries hints that the histogram by default should NOT contain buckets and should only track count and sum values.</item>
-        /// <item>A set of distinct increasing values for bucket boundaries hints that the histogram should use those for its default bucket configuration.</item>
-        /// </list>
-        /// </remarks>
-        public IReadOnlyList<T>? HistogramExplicitBucketBoundaries { get; }
-
         private static bool IsSortedAndDistinct(List<T> values)
         {
             Comparer<T> comparer = Comparer<T>.Default;
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
index 26d05183c50b00..e825fed29f8d40 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsAdviceTests.cs
@@ -10,30 +10,30 @@ public class MetricsAdviceTests
         [Fact]
         public void HistogramAdviceConstructionTest()
         {
-            Assert.Throws<ArgumentNullException>(() => new InstrumentAdvice<int>(histogramExplicitBucketBoundaries: null));
+            Assert.Throws<ArgumentNullException>(() => new InstrumentAdvice<int> { HistogramBucketBoundaries = null });
 
-            InstrumentAdvice<int> emptyExplicitBucketBoundariesAdvice = new InstrumentAdvice<int>(histogramExplicitBucketBoundaries: Array.Empty<int>());
+            InstrumentAdvice<int> emptyExplicitBucketBoundariesAdvice = new InstrumentAdvice<int> { HistogramBucketBoundaries = Array.Empty<int>() };
 
-            Assert.NotNull(emptyExplicitBucketBoundariesAdvice.HistogramExplicitBucketBoundaries);
-            Assert.Empty(emptyExplicitBucketBoundariesAdvice.HistogramExplicitBucketBoundaries);
+            Assert.NotNull(emptyExplicitBucketBoundariesAdvice.HistogramBucketBoundaries);
+            Assert.Empty(emptyExplicitBucketBoundariesAdvice.HistogramBucketBoundaries);
 
             int[] singleExplicitBucketBoundary = new int[] { 0 };
 
-            InstrumentAdvice<int> singleExplicitBucketBoundaryAdvice = new InstrumentAdvice<int>(singleExplicitBucketBoundary);
+            InstrumentAdvice<int> singleExplicitBucketBoundaryAdvice = new InstrumentAdvice<int> { HistogramBucketBoundaries = singleExplicitBucketBoundary };
 
-            Assert.Equal(singleExplicitBucketBoundary, singleExplicitBucketBoundaryAdvice.HistogramExplicitBucketBoundaries);
+            Assert.Equal(singleExplicitBucketBoundary, singleExplicitBucketBoundaryAdvice.HistogramBucketBoundaries);
 
             // Verify data cannot be mutated after construction
             singleExplicitBucketBoundary[0] = int.MaxValue;
-            Assert.NotEqual(singleExplicitBucketBoundary, singleExplicitBucketBoundaryAdvice.HistogramExplicitBucketBoundaries);
+            Assert.NotEqual(singleExplicitBucketBoundary, singleExplicitBucketBoundaryAdvice.HistogramBucketBoundaries);
 
             int[] invalidBucketBoundariesNondistinctValues = new int[] { 0, 1, 2, 3, 4, 4 };
 
-            Assert.Throws<ArgumentException>(() => new InstrumentAdvice<int>(invalidBucketBoundariesNondistinctValues));
+            Assert.Throws<ArgumentException>(() => new InstrumentAdvice<int> { HistogramBucketBoundaries = invalidBucketBoundariesNondistinctValues });
 
             int[] invalidBucketBoundariesOrdering = new int[] { 0, 1, 2, 3, 5, 4 };
 
-            Assert.Throws<ArgumentException>(() => new InstrumentAdvice<int>(invalidBucketBoundariesOrdering));
+            Assert.Throws<ArgumentException>(() => new InstrumentAdvice<int> { HistogramBucketBoundaries = invalidBucketBoundariesOrdering });
         }
     }
 }
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
index 49a66f9e30def6..035b45ea3d2fe0 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricsTests.cs
@@ -1473,10 +1473,10 @@ public void TestHistogramCreationWithAdvice()
 
                int[] explicitBucketBoundaries = new int[] { 0, 100, 1000, 10000 };
 
-               Histogram<int> histogramWithAdvice = meter.CreateHistogram<int>(name: nameof(histogramWithAdvice), advice: new InstrumentAdvice<int>(explicitBucketBoundaries));
+               Histogram<int> histogramWithAdvice = meter.CreateHistogram<int>(name: nameof(histogramWithAdvice), advice: new InstrumentAdvice<int> { HistogramBucketBoundaries = explicitBucketBoundaries });
 
-               Assert.NotNull(histogramWithAdvice.Advice?.HistogramExplicitBucketBoundaries);
-               Assert.Equal(explicitBucketBoundaries, histogramWithAdvice.Advice.HistogramExplicitBucketBoundaries);
+               Assert.NotNull(histogramWithAdvice.Advice?.HistogramBucketBoundaries);
+               Assert.Equal(explicitBucketBoundaries, histogramWithAdvice.Advice.HistogramBucketBoundaries);
            }).Dispose();
         }
 

From 8e76e819c2a4bbd0dad1fd57e252519af5fa45db Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Tue, 11 Jun 2024 13:29:27 -0700
Subject: [PATCH 10/11] Code review.

---
 .../src/System.Diagnostics.DiagnosticSource.csproj        | 3 ++-
 .../src/System/Diagnostics/Metrics/InstrumentAdvice.cs    | 8 ++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
index d8775312d8bcb5..b2654e92ee003b 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
@@ -114,7 +114,8 @@ System.Diagnostics.DiagnosticSource</PackageDescription>
     <Compile Include="$(CoreLibSharedDir)System\Runtime\CompilerServices\IsExternalInit.cs" />
   </ItemGroup>
 
-  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' or&#xD;&#xA;                        '$(TargetFramework)' == 'netstandard2.0'">
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' or
+                        '$(TargetFramework)' == 'netstandard2.0'">
     <Compile Include="System\Diagnostics\Activity.DateTime.corefx.cs" />
   </ItemGroup>
 
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs
index 1a78325ca53e9b..b06a5e4012a5bd 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentAdvice.cs
@@ -14,6 +14,14 @@ public sealed class InstrumentAdvice<T> where T : struct
     {
         private readonly ReadOnlyCollection<T>? _HistogramBucketBoundaries;
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="InstrumentAdvice{T}"/> class.
+        /// </summary>
+        public InstrumentAdvice()
+        {
+            Instrument.ValidateTypeParameter<T>();
+        }
+
         /// <summary>
         /// Gets the explicit bucket boundaries advised to be used with histogram instruments.
         /// </summary>

From 75868494f2b20261c8330518398af6ca7b59bb6e Mon Sep 17 00:00:00 2001
From: Mikel Blanchard <mblanchard@macrosssoftware.com>
Date: Tue, 11 Jun 2024 16:45:20 -0700
Subject: [PATCH 11/11] Add InstrumentAdvice ctor to ref.

---
 .../ref/System.Diagnostics.DiagnosticSourceActivity.cs           | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
index b6d6a352520e7f..e8ddcd2b140ae4 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
@@ -409,6 +409,7 @@ protected Instrument(
     }
     public sealed class InstrumentAdvice<T> where T : struct
     {
+        public InstrumentAdvice() { throw null; }
         public System.Collections.Generic.IReadOnlyList<T>? HistogramBucketBoundaries { get { throw null; } init { throw null; } }
     }
     public readonly struct Measurement<T> where T : struct
