Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add "hints" in Metric API to provide things like histogram bounds #63650

Closed
Tracked by #97522
tarekgh opened this issue Jan 11, 2022 · 39 comments · Fixed by #102524
Closed
Tracked by #97522

Add "hints" in Metric API to provide things like histogram bounds #63650

tarekgh opened this issue Jan 11, 2022 · 39 comments · Fixed by #102524
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Diagnostics.Metric blocking Marks issues that we want to fast track in order to unblock other important work feature-request in-pr There is an active PR which will close this issue when it is merged
Milestone

Comments

@tarekgh
Copy link
Member

tarekgh commented Jan 11, 2022

Metrics currently supports the Histogram instrument, which is used to publish measurement data. The data aggregator (such as the OpenTelemetry SDK) divides the full interval of possible values into consecutive, non-overlapping ranges called buckets. The OpenTelemetry SDK currently uses some defaults to define the bucket boundaries, but it also provides a way to customize or configure these boundaries. OpenTelemetry has introduced a new specification that allows creators of the Histogram instrument to specify advised bucket boundary values, which will be the recommended values for Histogram data aggregation.

Explicit bucket boundaries OpenTelemetry specs.

Proposal

namespace System.Diagnostics.Metrics;

+public sealed class HistogramAdvice<T> where T : struct
+{
+    public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries) { ... }
+    public IEnumerable<T> ExplicitBucketBoundaries { get; }
+}

public class Meter
{
    public System.Diagnostics.Metrics.Histogram<T> CreateHistogram<T> (
        string name,
        string? unit = default,
        string? description = default) where T : struct;

    public Histogram<T> CreateHistogram<T>(
        string name,
        string? unit,
        string? description,
        IEnumerable<KeyValuePair<string, object?>>? tags) where T : struct;

+   public System.Diagnostics.Metrics.Histogram<T> CreateHistogram<T> (
+       string name,
+       string? unit,
+       string? description,
+       IEnumerable<KeyValuePair<string, object?>>? tags,
+       HistogramAdvice<T> advice) where T : struct;
}

+public sealed class Histogram<T> : System.Diagnostics.Metrics.Instrument<T> where T : struct
+{
+    public HistogramAdvice<T>? Advice { get; }
+}

Usage Example

private static readonly Meter MyMeter = new("MyApplicationMeter");​

...

Histogram<long> requestLatencyHistogram = MyMeter.CreateHistogram<long>(
                                           name: "RequestLatency", 
                                           unit: "ms", 
                                           description: "Request Latency", 
                                           tags: null, 
                                           new HistogramAdvice<long>(new long[] { 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 }));requestLatencyHistogram.Record(150)
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Jan 11, 2022
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@tarekgh tarekgh added area-System.Diagnostics.Metric and removed untriaged New issue has not been triaged by the area owner labels Jan 11, 2022
@tarekgh tarekgh added this to the 7.0.0 milestone Jan 11, 2022
@noahfalk
Copy link
Member

No design has materialized yet, moving this out to Future.

@noahfalk noahfalk modified the milestones: 7.0.0, Future Jul 26, 2022
@JamesNK
Copy link
Member

JamesNK commented Jun 1, 2023

I have a scenario for using this feature. OpenTelemetry has some default bucket bounds for histograms. However, some histograms have recommended bounds.

For example, http.server.duration recommends some values: https://github.com/open-telemetry/opentelemetry-specification/blob/ce2c5946faa9c961ea01d89c7aed83621865b67b/specification/metrics/semantic_conventions/http-metrics.md#metric-httpserverduration

This metric SHOULD be specified with ExplicitBucketBoundaries of [ 0, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 ].

ASP.NET Core has a very similar histogram, http-server-request-duration. It should be used with the same bounds.

Currently, someone configuring OpenTelemetry to export meter measurements must use the default bucket bounds. To override, you must add a view for that counter to get the desired bucket sizes.

services
    .AddOpenTelemetry()
        .WithMetrics(metrics =>
        {
            metrics
                .AddView("request-duration", new ExplicitBucketHistogramConfiguration
                {
                    Boundaries = new double[] { 0, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 }
                })
                .AddMeter("Microsoft.AspNetCore.Hosting", "Microsoft.AspNetCore.Server.Kestrel")
                .AddPrometheusExporter();
        })

https://github.com/bradygaster/dotnet-cloud-native-build-2023/blob/c14269055669823659e7962ef9da4d700e597133/ContosoOnline/Diagnostics/DiagnosticServiceCollectionExtensions.cs#L33-L36

Histograms should support the advice feature to configure recommended explicit bounds: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-advice

@JamesNK
Copy link
Member

JamesNK commented Jun 5, 2023

API proposal:

namespace System.Diagnostics.Metrics;

+public sealed class HistogramAdvice
+{
+    public IReadOnlyList<double>? ExplicitBucketBoundaries { get; init; }
+}

public class Meter
{
    public System.Diagnostics.Metrics.Histogram<T> CreateHistogram<T> (
        string name,
        string? unit = default,
        string? description = default) where T : struct;
+   public System.Diagnostics.Metrics.Histogram<T> CreateHistogram<T> (
+       string name,
+       string? unit = default,
+       string? description = default,
+       HistogramAdvice? advice = default) where T : struct;
}

+public sealed class Histogram<T> : System.Diagnostics.Metrics.Instrument<T> where T : struct
+{
+    public HistogramAdvice Advice { get; }
+}

Open Questions

  • Histogram<T> is based around a generic argument as the numerical type of the measurement. ExplicitBucketBoundaries is currently double. Does it need to be T? What does that mean for APIs that would use this, e.g. opentelemetry-dotnet SDK? The Java prototype of advice uses double boundaries for long and double histogram implementations: Prototype histogram advice API (i.e. Hints) open-telemetry/opentelemetry-java#5217

@cijothomas
Copy link
Contributor

This is renamed now to "advisory parameters", and there are 2 now - one for histogram custom bounds, one for attributes.

https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument-advisory-parameters

@CodeBlanch
Copy link
Contributor

@noahfalk @tarekgh

It looks like the explicit histogram advisory bit of the spec is stable so we should be good to move forward with @JamesNK's proposal for .NET 9?

Thinking ahead a bit there is also an attributes bit which is experimental. We could add that in the future (if needed) via a base class right?

// .NET 10 or whatever

+public class InstrumentAdvice
+{
+        public IEnumerable<KeyValuePair<string, object?>>? Tags { get; set; }
+}

+public sealed class HistogramAdvice : InstrumentAdvice
{
}

@JamesNK
Copy link
Member

JamesNK commented Jan 24, 2024

What does it mean? The doc says they're recommended.

Are they tags that are automatically added to all measurements for the instrument?

@CodeBlanch
Copy link
Contributor

@JamesNK

Are they tags that are automatically added to all measurements for the instrument?

Not exactly. We already have a mechanism for that.

The SDK part of the spec has a View API. The View API allows users to manually configure the buckets for explicit bucket histograms as well as select the tags they want from the instrument (among other things).

What we are talking about here is the "Advisory API" which is part of the API spec. It sort of mirrors the SDK View API and allows instrumentation to (more or less) pre-configure the default View users will have. They can always use the SDK View API to change the defaults.

Let's say we have an Instrument emitting 3 tags:

[
   { "importantTag", "value" },
   { "nonimportantButInterestingToSomeUsersTag1", "value" },
   { "nonimportantButInterestingToSomeUsersTag2", "value" },
]

The instrumentation knows one of its tags ("importantTag") most users will care about. It also knows two of its tags ("nonimportantButInterestingToSomeUsersTag1" & "nonimportantButInterestingToSomeUsersTag2") most users won't care about (but some will).

Today it emits all three and by default users will get all three. They can use the SDK View API to drop what they don't care about, but that is kind of heavy lifting.

Using these attributes on the Advisory API allows the instrumentation to basically pre-configure a default View which will drop the non-important stuff. If users want it, they have to opt-into them using the SDK View API.

/cc @alanwest

@KalleOlaviNiemitalo
Copy link

 public class InstrumentAdvice
 {
-        public IEnumerable<KeyValuePair<string, object?>>? Tags { get; set; }
+        public IEnumerable<string>? RecommendedTags { get; set; }
 }

@CodeBlanch
Copy link
Contributor

On the question of whether it should be...

public sealed class HistogramAdvice
{
    public IReadOnlyList<double>? ExplicitBucketBoundaries { get; init; }
}

Or

public sealed class HistogramAdvice<T> where T : struct
{
    public IReadOnlyList<T>? ExplicitBucketBoundaries { get; init; }
}

I did a prototype of this API:

namespace System.Diagnostics.Metrics;

+public sealed class HistogramAdvice<T> where T : struct
+{
+    public IReadOnlyList<T>? ExplicitBucketBoundaries { get; init; }
+}

public class Meter
{
    public System.Diagnostics.Metrics.Histogram<T> CreateHistogram<T> (
        string name,
        string? unit = default,
        string? description = default) where T : struct;
    public Histogram<T> CreateHistogram<T>(
        string name,
        string? unit,
        string? description,
        IEnumerable<KeyValuePair<string, object?>>? tags) where T : struct;
+   public System.Diagnostics.Metrics.Histogram<T> CreateHistogram<T> (
+       string name,
+       string? unit,
+       string? description,
+       IEnumerable<KeyValuePair<string, object?>>? tags,
+       HistogramAdvice<T>? advice) where T : struct;
}

+public sealed class Histogram<T> : System.Diagnostics.Metrics.Instrument<T> where T : struct
+{
+    public HistogramAdvice<T>? Advice { get; }
+}

Runtime: https://github.com/dotnet/runtime/compare/main...CodeBlanch:metrics-histogramadvice?expand=1
OTel: https://github.com/open-telemetry/opentelemetry-dotnet/compare/main...CodeBlanch:poc/histogramadvice?expand=1

OTel has to do some work to reason out the type of the histogram in order to get at the advice. It isn't much more effort to also convert non-double boundaries to double. So I think we are good to use HistogramAdvice<T>. It makes the .NET API more consistent IMO and allows other consumers to support histograms of types other than double (if they want) even though OTel only supports double.

In OTel if you have a Histogram<long> and you record some super huge value (>= 54 significant bits?) there is going to be some rounding error converting it to double. Same thing will be true for super huge bucket boundaries specified as long. But both recorded measurement and bucket boundary will be subject to the same rounding so things should end up in the correct buckets and no one will be the wiser?

@tarekgh
Copy link
Member Author

tarekgh commented Mar 28, 2024

@CodeBlanch I like your proposal having HistogramAdvice<T>. I assume there is no requirements to have tags there at least for now . right?

@CodeBlanch
Copy link
Contributor

@tarekgh The tags part of the spec isn't stable so we don't need it yet. Can you confirm though that if we do something like this in the future (say .NET 10) it would NOT be a breaking change:

+public class Advice
+{
+    public IReadOnlyList<string>? RecommendedTags { get; init; }
+}

+public sealed class HistogramAdvice<T> : Advice where T : struct
{
    public IReadOnlyList<T>? ExplicitBucketBoundaries { get; init; }
}

As long as we can do that in the future I think we're good.

@tarekgh tarekgh added the api-ready-for-review API is ready for review, it is NOT ready for implementation label May 6, 2024
@tarekgh
Copy link
Member Author

tarekgh commented May 6, 2024

I have updated the proposal on the top and marked the issue as ready for design review. Please let me know if there is anything missing before we proceed. Thanks all for the feedback!

@terrajobst
Copy link
Member

terrajobst commented May 21, 2024

Video

  • We should expose the ExplicitBucketBoundaries as an IReadOnlyList<T>
  • Let's use get/set rather than a constructor + property
  • We should make it simpler
    • Have one method with just the required parameter (name)
    • Have one method that takes all the optional parameters
    • We can't remove methods, but we can hide them and remove all optionality to avoid ambiguity
  • We discussed whether to use get/set for the buckets but concluded that we should use the constructor
namespace System.Diagnostics.Metrics;

public sealed class HistogramAdvice<T> where T : struct
{
    public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries);
    public IReadOnlyList<T> ExplicitBucketBoundaries { get; }
}

public partial class Meter
{
    // NEW: Simple one
    public Histogram<T> CreateHistogram<T> (
        string name) where T : struct;

    // Existing, mark parameters as required and EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    public Histogram<T> CreateHistogram<T> (
        string name,
        string? unit,
        string? description) where T : struct;

    // Also existing, mark EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    public Histogram<T> CreateHistogram<T>(
        string name,
        string? unit,
        string? description,
        IEnumerable<KeyValuePair<string, object?>>? tags) 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;
}

public sealed class Histogram<T> : Instrument<T> where T : struct
{
    public HistogramAdvice<T>? Advice { get; }
}

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels May 21, 2024
@CodeBlanch
Copy link
Contributor

@terrajobst @tarekgh

I think we should make one tweak to the API as proposed:

public sealed class HistogramAdvice<T> where T : struct
{
    public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries);
-   public IReadOnlyList<T> ExplicitBucketBoundaries { get; }
+   public IReadOnlyList<T>? ExplicitBucketBoundaries { get; }
}

The reason is, advice may be expanded in the future. There is already stuff in the OTel spec like RecommendedTags discussed above. What I'm thinking is we should plan for a future where there may be advice needed for histogram but NOT explicit bucket configuration. Imagine this happening in .NET 10:

+public class InstrumentAdvice
+{
+    public Advice(IEnumerable<string>? recommendedTags = default) {}
+    public IReadOnlyList<string>? RecommendedTags { get; }
+}

public sealed class HistogramAdvice<T>
+ : InstrumentAdvice
    where T : struct
{
    public HistogramAdvice(IEnumerable<T> explicitBucketBoundaries) : this(explicitBucketBoundaries: explicitBucketBoundaries) {}
+   public HistogramAdvice(IEnumerable<string>? recommendedTags = default, IEnumerable<T>? explicitBucketBoundaries = default) : base(recommendedTags) {}
    public IReadOnlyList<T>? ExplicitBucketBoundaries { get; }
}

@dotnet-policy-service dotnet-policy-service bot added the in-pr There is an active PR which will close this issue when it is merged label May 21, 2024
@tarekgh
Copy link
Member Author

tarekgh commented May 21, 2024

@CodeBlanch that is fine to have it as public IReadOnlyList<T>? ExplicitBucketBoundaries { get; }. We have already discussed that, and I recall no one objected to having a nullable return type.

@CodeBlanch
Copy link
Contributor

CodeBlanch commented Jun 4, 2024

After discussions with @noahfalk, @reyang, @vishweshbankwar, and @tarekgh we decided to make some changes and propose a new API for advice/hints.

  • HistogramAdvice<T> has been renamed InstrumentAdvice<T> and moved onto the base Instrument<T>. We decided that advice is likely to change (there is already another property in OTel spec) in ways that will impact all instruments. It should also be possible to change instruments at consumption time (for example Counter -> Histogram) in which case advice would need to be specified generically. The idea here is to have a single advice class with all possible settings. We will NOT have instrument-specific advice classes.

  • Factory methods are NOT being added here for all instruments. It is currently only possible to specify InstrumentAdvice<T> when creating histograms. There will likely be a follow-up proposal to add those methods and also a way to attach unstructured advice to InstrumentAdvice<T> similar to the Activity.SetCustomProperty API.

  • We stuck with the "Advice" name and did NOT switch to "AdvisoryParameters" (from OTel spec) as it felt awkward for a .NET API.

Proposal

namespace System.Diagnostics.Metrics;

// NEW
public sealed class InstrumentAdvice<T> where T : struct
{
    public IReadOnlyList<T>? HistogramBucketBoundaries { get; init; }
}

public partial class Meter
{
    // NEW: Simple one
    public Histogram<T> CreateHistogram<T> (
        string name) where T : struct;

    // Existing, mark parameters as required and EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    public Histogram<T> CreateHistogram<T> (
        string name,
        string? unit,
        string? description) where T : struct;

    // Also existing, mark EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    public Histogram<T> CreateHistogram<T>(
        string name,
        string? unit,
        string? description,
        IEnumerable<KeyValuePair<string, object?>>? tags) where T : struct;

    // NEW
    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;
}

public abstract class Instrument<T> : Instrument where T : struct
{
    // NEW: Simple one
    protected Instrument(Meter meter, string name) {}

    // Existing, mark EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    protected Instrument(Meter meter, string name, string? unit, string? description) {}

    // Also existing, mark EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    protected Instrument(Meter meter, string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags) {}

    // NEW
    protected Instrument(
        Meter meter,
        string name,
        string? unit = default,
        string? description = default,
        IEnumerable<KeyValuePair<string, object?>>? tags = default,
        InstrumentAdvice<T>? advice = default) {}

    // NEW
    public InstrumentAdvice<T>? Advice { get; }
}

public abstract class Instrument
{
    // NEW: Simple one
    protected Instrument(Meter meter, string name) {}

    // Existing, mark EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    protected Instrument(Meter meter, string name, string? unit, string? description) {}

    // Existing, decorate optional parameters with defaults
    protected Instrument(Meter meter, string name, string? unit = default, string? description = default, IEnumerable<KeyValuePair<string, object?>>? tags = default) {}
}

@noahfalk
Copy link
Member

noahfalk commented Jun 4, 2024

Is it important to have new simple Instrument constructors? I am guessing the point of these things is to improve the intellisence experience but since I expect user created derivations would be rare it may have low value. No objections to it though if we need it.

For HistogramExplicitBucketBoundaries do you think we could shorten the name by removing the word 'Explicit'? It just seemed a little verbose.

For InstrumentAdvice if we assume more properties are coming and that folks might want to set somewhat arbitrary subsets of those properties its unlikely we can give each set of properties its own dedicated constructor overload. It also might be odd if someone types new Counter<int>(..., new InstrumentAdvice( and the intellisence list immediately starts suggesting a constructor overload that has histogram buckets in it. Perhaps we add setters to the properties and when initializing an instrument we copy the advice and set the copy read-only? Calling a property setter on a readonly copy could throw an InvalidOperationException()? Just spitballing a bit here.

@tarekgh
Copy link
Member Author

tarekgh commented Jun 4, 2024

when initializing an instrument we copy the advice and set the copy read-only?

You don't care about the allocation I guess as instrument creation is not happening much. right?
maybe using Init will be better here? I know I'll get some pushback on that from the design review (because of the reflection ability), but we can discuss it with them as init used in other places anyway. In general, I like the idea of having a way to set the Advice properties independent from the constructors.

@CodeBlanch
Copy link
Contributor

I just made this change to the proposal in the comment above:

public sealed class InstrumentAdvice<T> where T : struct
{
-    public InstrumentAdvice(IEnumerable<T> histogramExplicitBucketBoundaries);
-    public IReadOnlyList<T>? HistogramExplicitBucketBoundaries { get; }
+    public IReadOnlyList<T>? HistogramBucketBoundaries { get; init; }
}
  • I renamed HistogramExplicitBucketBoundaries -> HistogramBucketBoundaries
  • Dropped the ctor and switched to init. I think using init here feels very natural if the design reviewers allow it 😄

@noahfalk As far as the ctor changes, I just kind of applied what the reviewers asked for in the first go around. None of it is necessary, just clean up really. I always lean towards consistency personally so I like it but not opposed to dropping it.

@noahfalk
Copy link
Member

noahfalk commented Jun 5, 2024

As far as the ctor changes, I just kind of applied what the reviewers asked for in the first go around.

For constructors my suggestion would be leave how it is but just give a heads up to the reviewers that we expect its very rare for 3rd party code to derive their own instrument. As long as that doesn't cause them to change their opinion then consider it resolved :)

You don't care about the allocation I guess as instrument creation is not happening much. right?

Exactly.

maybe using Init will be better here?

Init is one way of doing this, but not the only one. I was trying to avoid init because of the issues that had been raised in the past around it. I was suggesting something like:

class Advice
{
    IEnumerable[] _buckets;
    internal bool _isReadOnly;
    
    public IEnumerable[] HistogramBuckets
    {
        get { return _buckets; }
        set { if(_isReadOnly) { ThrowException(); } else { _buckets = value; }
    }
    
    internal Advice MakeReadOnlyCopy(Advice a)
    {
        Advice copy = new Advice();
        copy .HistogramBuckets = CopyBuckets(a.Buckets);
        copy._isReadOnly = true;
    }
}

The copy not only stops people trying to modify the top level property, we could also stop people trying to mutate the nested data structures:

(instrument.Advice.HistogramBuckets as Bucket[])[2] = 1000;

It also lets people do this if they want:
Advice a = new Advice();
a.Buckets = ComputeSomeBuckets(); // this is being called after the constructor and still works fine
Histogram h = meter.CreateHistogram(..., a);

@tarekgh
Copy link
Member Author

tarekgh commented Jun 5, 2024

Init is one way of doing this, but not the only one. I was trying to avoid init because of the issues that had been raised in the past around it. I was suggesting something like:

I want to clarify that in the design reviews, it was mentioned before it is ok to use init. The issue in the design review was not about the compiler version at all. If the docs are concerned, we can raise this issue to update it if needed. I don't think we'll continue to be restricted forever with the 7.3 features. There is not a real problem for users to use recent compiler versions with such features as it is a pure IL emission and no runtime dependency. I understand your way to avoid using init here but as you see it is more code, more allocations (even if we don't care much), in addition in the future we must maintain updating the copy code as we add more.

@tarekgh tarekgh added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-approved API was approved in API review, it can be implemented labels Jun 6, 2024
@noahfalk
Copy link
Member

noahfalk commented Jun 7, 2024

There is not a real problem for users to use recent compiler versions with such features as it is a pure IL emission and no runtime dependency.

When you say "with such features", is there going to be guidance in the docs that says what these features are and how users can make use of it in a way that is supported and practical without accidentally using something unsupported? Right now the official doc discourages anyone from trying that strategy and strongly implies that anyone who does this is unsupported and should expect trouble:

"Choosing a language version newer than the default can cause hard to diagnose compile-time and runtime errors."

@tarekgh
Copy link
Member Author

tarekgh commented Jun 7, 2024

In my last reply, I mentioned If the docs are concerned, we can raise this issue to update it if needed. . I am trying to follow up regarding that offline.

@terrajobst
Copy link
Member

terrajobst commented Jun 11, 2024

Video

  • Looks good as proposed.
namespace System.Diagnostics.Metrics;

// NEW
public sealed class InstrumentAdvice<T> where T : struct
{
    public IReadOnlyList<T>? HistogramBucketBoundaries { get; init; }
}

public partial class Meter
{
    // NEW: Simple one
    public Histogram<T> CreateHistogram<T> (
        string name) where T : struct;

    // Existing, mark parameters as required and EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    public Histogram<T> CreateHistogram<T> (
        string name,
        string? unit,
        string? description) where T : struct;

    // Also existing, mark EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    public Histogram<T> CreateHistogram<T>(
        string name,
        string? unit,
        string? description,
        IEnumerable<KeyValuePair<string, object?>>? tags) where T : struct;

    // NEW
    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;
}

public abstract class Instrument<T> : Instrument where T : struct
{
    // NEW: Simple one
    protected Instrument(Meter meter, string name);

    // Existing, mark EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    protected Instrument(Meter meter, string name, string? unit, string? description);

    // Also existing, mark EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    protected Instrument(Meter meter, string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags);

    // NEW
    protected Instrument(
        Meter meter,
        string name,
        string? unit = default,
        string? description = default,
        IEnumerable<KeyValuePair<string, object?>>? tags = default,
        InstrumentAdvice<T>? advice = default);

    // NEW
    public InstrumentAdvice<T>? Advice { get; }
}

public abstract class Instrument
{
    // NEW: Simple one
    protected Instrument(Meter meter, string name);

    // Existing, mark EB never
    [EditorBrowsable(EditorBrowsableState.Never)]
    protected Instrument(Meter meter, string name, string? unit, string? description);

    // Existing, decorate optional parameters with defaults
    protected Instrument(Meter meter, string name, string? unit = default, string? description = default, IEnumerable<KeyValuePair<string, object?>>? tags = default);
}

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Jun 11, 2024
@teo-tsirpanis
Copy link
Contributor

Should InstrumentAdvice<T> support collection expressions?

@tarekgh
Copy link
Member Author

tarekgh commented Jun 12, 2024

Should InstrumentAdvice support collection expressions?

What do you mean by that? you already can do something like the following:

InstrumentAdvice<int> ia = new InstrumentAdvice<int> { HistogramBucketBoundaries = [ 1, 2, 3, 4 ] };

Note, in the future, InstrumentAdvice<T> can add more properties of any types.

antonfirsov added a commit that referenced this issue Jul 10, 2024
Fixes #104137.

HistogramBucketBoundaries is a new feature added in #63650. As described in #104137, adding support helps removing a workaround in the otel SDK, and also might help users who do not use the SDK.
@github-actions github-actions bot locked and limited conversation to collaborators Jul 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Diagnostics.Metric blocking Marks issues that we want to fast track in order to unblock other important work feature-request in-pr There is an active PR which will close this issue when it is merged
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants