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

Developers can trace their code using Open Telemetry Metrics #44445

Closed
Tracked by #5929
tarekgh opened this issue Nov 10, 2020 · 9 comments · Fixed by #52685
Closed
Tracked by #5929

Developers can trace their code using Open Telemetry Metrics #44445

tarekgh opened this issue Nov 10, 2020 · 9 comments · Fixed by #52685
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Diagnostics.Tracing blocking Marks issues that we want to fast track in order to unblock other important work Cost:XL Work that requires one engineer more than 4 weeks feature-request Priority:0 Work that we can't release without Team:Libraries tracking This issue is tracking the completion of other related issues. User Story A single user-facing feature. Can be grouped under an epic.
Milestone

Comments

@tarekgh
Copy link
Member

tarekgh commented Nov 10, 2020

The API proposal is captured in the design doc https://github.com/dotnet/designs/pull/211/files

AB#1244406
AB#1244379
Providing a set of metrics APIs from the .NET runtime that are compliant to or compatible with the OpenTelemetry Metrics API Specification.

@Dotnet-GitSync-Bot
Copy link
Collaborator

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.

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Nov 10, 2020
@tarekgh tarekgh self-assigned this Nov 10, 2020
@tarekgh tarekgh added area-System.Diagnostics.Tracing and removed untriaged New issue has not been triaged by the area owner labels Nov 10, 2020
@ghost
Copy link

ghost commented Nov 10, 2020

Tagging subscribers to this area: @tarekgh, @tommcdon, @pjanotti
See info in area-owners.md if you want to be subscribed.


Issue meta data
Issue content: Providing a set of metrics APIs from the .NET runtime that are compliant to or compatible with the [OpenTelemetry Metrics API Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/metrics/api.md).
Issue author: tarekgh
Assignees: tarekgh
Milestone: -

@tarekgh tarekgh added this to the 6.0.0 milestone Nov 10, 2020
@tarekgh tarekgh added the Cost:XL Work that requires one engineer more than 4 weeks label Nov 10, 2020
@tarekgh
Copy link
Member Author

tarekgh commented Nov 10, 2020

@danmosemsft @ericstj I created this issue to track the metrics work.

@tarekgh tarekgh added Priority:0 Work that we can't release without and removed Priority:0 Work that we can't release without labels Nov 10, 2020
@iSazonov

This comment has been minimized.

@danmoseley danmoseley added the User Story A single user-facing feature. Can be grouped under an epic. label Nov 10, 2020
@danmoseley
Copy link
Member

@glennc should this go under some epic under the cloud native theme?

@StephenBonikowsky StephenBonikowsky added this to New Issues in .NET Core impacting internal partners via automation Nov 20, 2020
@StephenBonikowsky StephenBonikowsky moved this from New Issues to .NET 6 Committed in .NET Core impacting internal partners Nov 20, 2020
@samsp-msft samsp-msft changed the title Metrics Support Metrics Support for tracing and Open Telemetry Nov 20, 2020
@danmoseley danmoseley added the Priority:0 Work that we can't release without label Nov 24, 2020
@danmoseley
Copy link
Member

@glennc @shirhatti I assume this is critical to release = P0?

@shirhatti
Copy link
Contributor

P0, only if the OpenTelemetry metrics specification has been ratified in time.

We won't block the release if the specification isn't ready.

@terrajobst terrajobst added this to Proposed in .NET 6.0 Nov 26, 2020
@marek-safar marek-safar added tracking This issue is tracking the completion of other related issues. and removed User Story A single user-facing feature. Can be grouped under an epic. labels Nov 27, 2020
@danmoseley danmoseley added the User Story A single user-facing feature. Can be grouped under an epic. label Jan 5, 2021
@danmoseley danmoseley changed the title Metrics Support for tracing and Open Telemetry Developers can trace their code using Open Telemetry Metrics Jan 5, 2021
@danmoseley danmoseley moved this from Proposed to Committed in .NET 6.0 Jan 13, 2021
@tarekgh tarekgh added api-ready-for-review API is ready for review, it is NOT ready for implementation blocking Marks issues that we want to fast track in order to unblock other important work labels Apr 30, 2021
@bartonjs
Copy link
Member

bartonjs commented May 7, 2021

Video

  • In the Metric.Create* methods, consider swapping the order of unit and description, if (name+unit) seems more likely than (name+description).
  • "Observable" in this context has a meaning opposite of what it means in "ObservableCollection"
    • We don't have a solid replacement name for it, though, since it seems that every appropriate word already has a conflicting meaning in OpenTelemetry.
  • Consider renaming Instrument<T> to StreamingInstrument<T>, or some other prefix, to give better separation between ObservableInstrument<T> and "the other kind".
  • Add a public Measurement(T value) constructor so the params one is not called with the empty array.
  • For Counter<T>.Add, rename measurement to delta to make it more clear that the caller should provide only the amount to increase since the last call, vs that "Add" means appending to a list.
  • Rename Histogram<T>.Record's measurement parameter to value (to avoid confusion with Measurement<T> and associated semantics)
  • Measurement<T> should be declared readonly
  • MeasurementCallback should constraint T to match the rest of the proposal (T : unmanaged)
  • Replace all of the T : unmanaged constraints with T : struct

We had to stop before really looking at MetricListener.

@bartonjs bartonjs added api-needs-work API needs work before it is approved, it is NOT ready for implementation api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-ready-for-review API is ready for review, it is NOT ready for implementation api-needs-work API needs work before it is approved, it is NOT ready for implementation labels May 7, 2021
@bartonjs
Copy link
Member

In addition to the previous feedback:

  • Do MeterListener.InstrumentPublished and MeasurementsCompleted need to be get/set? Should they be ctor parameters instead?
    • It seems there are theoretical scenarios for replacing them later, so get/set is fine
  • What is the experience when a SetMeasurementCallback wasn't called for the T that is being reported?
    • Consider something like public Action<Instrument>? UnregisteredMeasurementCallback { get; set; } to get called in that case instead of just data loss.
namespace System.Diagnostics.Metrics
{
    public class Meter : IDisposable
    {

        /// <summary>
        /// The constructor allows creating the Meter class with the name and optionally the version.
        /// The name should be validated as described by the OpenTelemetry specs.
        /// </summary>
        public Meter(string name)  { throw null;  }
        public Meter(string name, string? version) { throw null; }

        /// <summary>
        /// Getter properties to retrieve the Meter name and version
        /// </summary>
        public string Name { get; }
        public string? Version { get; }

        /// <summary>
        /// Factory methods to create Counter and Histogram instruments.
        /// </summary>
        public Counter<T> CreateCounter<T>(
                            string name,
                            string? unit = null,
                            string? description = null) where T : struct { throw null; }

        public Histogram<T> CreateHistogram<T>(
                            string name,
                            string? unit = null,
                            string? description = null) where T : struct { throw null; }

        /// <summary>
        /// Factory methods to create an ObservableCounter instrument.
        /// </summary>

        public ObservableCounter<T> CreateObservableCounter<T>(
                            string name,
                            Func<T> observeValue,
                            string? unit = null,
                            string? description = null) where T : struct { throw null; }

        public ObservableCounter<T> CreateObservableCounter<T>(
                            string name,
                            Func<Measurement<T>> observeValue,
                            string? unit = null,
                            string? description = null,) where T : struct { throw null; }

        public ObservableCounter<T> CreateObservableCounter<T>(
                            string name,
                            Func<IEnumerable<Measurement<T>>> observeValues,
                            string? unit = null,
                            string? description = null) where T : struct { throw null; }

        /// <summary>
        /// Factory methods to create ObservableGauge instrument.
        /// </summary>
        public ObservableGauge<T> CreateObservableGauge<T>(
                            string name,
                            Func<T> observeValue,
                            string? unit = null,
                            string? description = null,) where T : struct { throw null; }

        public ObservableGauge<T> CreateObservableGauge<T>(
                            string name,
                            Func<Measurement<T>> observeValue,
                            string? unit = null,
                            string? description = null) where T : struct { throw null; }

        public ObservableGauge<T> CreateObservableGauge<T>(
                            string name,
                            Func<IEnumerable<Measurement<T>>> observeValues,
                            string? unit = null,
                            string? description = null) where T : struct { throw null; }

        /// <summary>
        /// Factory methods to create ObservableUpDownCounter instrument.
        /// </summary>
        public ObservableUpDownCounter<T> CreateObservableUpDownCounter<T>(
                            string name,
                            Func<T> observeValue,
                            string? unit = null,
                            string? description = null,) where T : struct { throw null; }

        public ObservableUpDownCounter<T> CreateObservableUpDownCounter<T>(
                            string name,
                            Func<Measurement<T>> observeValue,
                            string? unit = null,
                            string? description = null,) where T : struct { throw null; }

        public ObservableUpDownCounter<T> CreateObservableUpDownCounter<T>(
                            string name,
                            Func<IEnumerable<Measurement<T>>> observeValues,
                            string? unit = null,
                            string? description = null) where T : struct { throw null; }

        public void Dispose() { throw null; }
    }

/// <summary>
    /// Is the base class which contains all common properties between different types of instruments.
    /// It contains the protected constructor and the Publish method allows activating the instrument
    /// to start recording measurements.
    /// </summary>

    public abstract class Instrument
    {
        /// <summary>
        /// Protected constructor to initialize the common instrument properties.
        /// </summary>
        protected Instrument(Meter meter, string name, string? unit, string? description) { throw null; }

        /// <summary>
        /// Publish is to allow activating the instrument to start recording measurements and to allow
        /// listeners to start listening to such measurements.
        /// </summary>
        protected void Publish() { throw null; }

        /// <summary>
        /// Getters to retrieve the properties that the instrument is created with.
        /// </summary>
        public Meter Meter { get; }
        public string Name { get; }
        public string? Unit { get; }
        public string? Description { get; }

        /// <summary>
        /// A property tells if a listener is listening to this instrument measurement recording.
        /// </summary>
        public bool Enabled => throw null;

        /// <summary>
        /// A property tells if the instrument is a regular instrument or an observable instrument.
        /// </summary>
        public virtual bool IsObservable => throw null;
    }


    /// <summary>
    /// Instrument<T> is the base class from which all instruments that report measurements in the context of the request will inherit from.
    /// Mainly it will support the CLS compliant numerical types.
    /// </summary>
    public abstract class Instrument<T> : Instrument where T : struct
    {
        /// <summary>
        /// Protected constructor to create the instrument with the common properties.
        /// </summary>
        protected Instrument(Meter meter, string name, string? unit, string? description) :
                        base(meter, name, unit, description) { throw null; }

        /// <summary>
        /// Record measurement overloads allowing passing different numbers of tags.
        /// </summary>

        protected void RecordMeasurement(T measurement) { throw null; }

        protected void RecordMeasurement(
                            T measurement,
                            KeyValuePair<string, object?> tag) { throw null; }

        protected void RecordMeasurement(
                            T measurement,
                            KeyValuePair<string, object?> tag1,
                            KeyValuePair<string, object?> tag2) { throw null; }

        protected void RecordMeasurement(
                            T measurement,
                            KeyValuePair<string, object?> tag1,
                            KeyValuePair<string, object?> tag2,
                            KeyValuePair<string, object?> tag3) { throw null; }

        protected void RecordMeasurement(
                            T measurement,
                            ReadOnlySpan<KeyValuePair<string, object?>> tags) { throw null; }
    }


    /// <summary>
    /// ObservableInstrument<T> is the base class from which all observable instruments will inherit from.
    /// It will only support the CLS compliant numerical types.
    /// </summary>
    public abstract class ObservableInstrument<T> : Instrument where T : struct
    {
        /// <summary>
        /// Protected constructor to create the instrument with the common properties.
        /// </summary>
        protected ObservableInstrument(
                    Meter meter,
                    string name,
                    string? unit,
                    string? description) : base(meter, name, unit, description) { throw null; }

        /// <summary>
        /// Observe() fetches the current measurements being tracked by this instrument.
        /// </summary>
        protected abstract IEnumerable<Measurement<T>> Observe();

        public override bool IsObservable => true;
    }


    /// <summary>
    /// A measurement stores one observed value and its associated tags. This type is used by Observable instruments' Observe() method when reporting current measurements with associated tags.
    /// with the associated tags.
    /// </summary>
    public readonly struct Measurement<T> where T : struct
    {
        /// <summary>
        /// Construct the Measurement using the value and the list of tags.
        /// We'll always copy the input list as this is not perf hot path.
        /// </summary>
        public Measurement(T value) { throw null; }
        public Measurement(T value, IEnumerable<KeyValuePair<string, object?>> tags) { throw null; }
        public Measurement(T value, params KeyValuePair<string, object?>[] tags) { throw null; }
        public Measurement(T value, ReadOnlySpan<KeyValuePair<string, object?>> tags) { throw null; }

        public ReadOnlySpan<KeyValuePair<string, object?>> Tags { get { throw null;  } }
        public T Value { get; }
    }


    /// <summary>
    /// The counter is an Instrument that supports non-negative increments.
    /// e.g. Number of completed requests.
    /// </summary>
    public sealed class Counter<T> : Instrument<T> where T : struct
    {
        public void Add(T delta) { throw null; }
        public void Add(T delta,
                        KeyValuePair<string, object?> tag) { throw null; }
        public void Add(T delta,
                        KeyValuePair<string, object?> tag1,
                        KeyValuePair<string, object?> tag2) { throw null; }
        public void Add(T delta,
                        KeyValuePair<string, object?> tag1,
                        KeyValuePair<string, object?> tag2,
                        KeyValuePair<string, object?> tag3) { throw null; }
        public void Add(T delta,
                        ReadOnlySpan<KeyValuePair<string, object?>> tags) { throw null; }
        public void Add(T delta,
                        params KeyValuePair<string, object?>[] tags) { throw null; }
    }

    /// <summary>
    /// The histogram is an Instrument that can be used to report arbitrary values
    /// that are likely to be statistically meaningful. It is intended for statistics such as the request duration.
    /// e.g. the request duration.
    /// </summary>
    public sealed class Histogram<T> : Instrument<T> where T : struct
    {
        public void Record(T value) { throw null; }
        public void Record(
                        T value,
                        KeyValuePair<string, object?> tag) { throw null; }
        public void Record(
                        T value,
                        KeyValuePair<string, object?> tag1,
                        KeyValuePair<string, object?> tag2) { throw null; }
        public void Record(
                        T value,
                        KeyValuePair<string, object?> tag1,
                        KeyValuePair<string, object?> tag2,
                        KeyValuePair<string, object?> tag3) { throw null; }
        public void Record(T value,
                            ReadOnlySpan<KeyValuePair<string, object?>> tags) { throw null; }
        public void Record(
                        T value,
                        params KeyValuePair<string, object?>[] tags) { throw null; }
    }


    /// <summary>
    /// ObservableCounter is an observable Instrument that reports monotonically increasing value(s)
    /// when the instrument is being observed.
    /// e.g. CPU time (for different processes, threads, user mode or kernel mode).
    /// </summary>
    public sealed class ObservableCounter<T> : ObservableInstrument<T> where T : struct
    {
        protected override IEnumerable<Measurement<T>> Observe() { throw null; }
    }

    /// <summary>
    /// ObservableUpDownCounter is an observable Instrument that reports additive value(s)
    /// when the instrument is being observed.
    /// e.g. the process heap size
    /// </summary>
    public sealed class ObservableUpDownCounter<T> : ObservableInstrument<T> where T : struct
    {
        protected override IEnumerable<Measurement<T>> Observe() { throw null; }
    }

    /// <summary>
    /// ObservableGauge is an observable Instrument that reports non-additive value(s)
    /// when the instrument is being observed.
    /// e.g. the current room temperature
    /// </summary>
    public sealed class ObservableGauge<T> : ObservableInstrument<T> where T : struct
    {
        protected override IEnumerable<Measurement<T>> Observe() { throw null; }
    }


    /// <summary>
    /// A delegate to represent the callbacks signatures used in the listener.
    /// </summary>
    public delegate void MeasurementCallback<T>(
                            Instrument instrument,
                            T measurement,
                            ReadOnlySpan<KeyValuePair<string, object?>> tags,
                            object? state) where T : struct;


    /// <summary>
    /// The listener class can be used to listen to kinds of instruments.
    /// recorded measurements.
    /// </summary>
    public sealed class MeterListener : IDisposable
    {
        /// <summary>
        /// Simple constructor
        /// </summary>
        public MeterListener() { throw null;  }

        /// <summary>
        /// Callbacks to get notification when an instrument is published
        /// </summary>
        public Action<Instrument, MeterListener>? InstrumentPublished { get; set; }

        /// <summary>
        /// Callbacks to get notification when stopping the measurement on some instrument
        /// this can happen when the Meter or the Listener is disposed of. Or calling Stop()
        /// on the listener.
        /// </summary>
        public Action<Instrument, object?>? MeasurementsCompleted { get; set; }

        /// <summary>
        /// Start listening to a specific instrument measurement recording.
        /// </summary>
        public void EnableMeasurementEvents(Instrument instrument, object? state = null) { throw null; }

        /// <summary>
        /// Stop listening to a specific instrument measurement recording.
        /// returns the associated state.
        /// </summary>
        public object? DisableMeasurementEvents(Instrument instrument) { throw null; }

        /// <summary>
        /// Set a callback for a specific numeric type to get the measurement recording notification
        /// from all instruments which enabled listened to and was created with the same specified
        /// numeric type. If a measurement of type T is recorded and a callback of type T is registered, that callback is used. If there is no callback for type T but there is a callback for type object, the measured value is boxed and reported via the object typed callback. If there is neither type T callback nor object callback then the measurement will not be reported.
        /// </summary>
        public void SetMeasurementEventCallback<T>(MeasurementCallback<T>? measurementCallback) where T : struct { throw null; }

        public void Start() { throw null; }

        /// <summary>
        /// Call all Observable instruments to get the recorded measurements reported to the
        /// callbacks enabled by SetMeasurementEventCallback<T>
        /// </summary>
        public void RecordObservableInstruments() { throw null; }

        public void Dispose() { throw null; }
    }
}

@bartonjs bartonjs 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 11, 2021
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label May 13, 2021
.NET Core impacting internal partners automation moved this from .NET 6 Committed to Done May 15, 2021
.NET 6.0 automation moved this from Committed to Completed May 15, 2021
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label May 15, 2021
@dotnet dotnet locked as resolved and limited conversation to collaborators Jun 14, 2021
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.Tracing blocking Marks issues that we want to fast track in order to unblock other important work Cost:XL Work that requires one engineer more than 4 weeks feature-request Priority:0 Work that we can't release without Team:Libraries tracking This issue is tracking the completion of other related issues. User Story A single user-facing feature. Can be grouped under an epic.
Development

Successfully merging a pull request may close this issue.

8 participants
@marek-safar @shirhatti @danmoseley @bartonjs @tarekgh @iSazonov @Dotnet-GitSync-Bot and others