-
Notifications
You must be signed in to change notification settings - Fork 358
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
Update metrics to support OpenTelemetry specification #5120
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,9 +37,9 @@ protected CounterPayload(DateTime timestamp, | |
|
||
public string Unit { get; } | ||
|
||
public double Value { get; } | ||
public double Value { get; protected set; } | ||
|
||
public DateTime Timestamp { get; } | ||
public DateTime Timestamp { get; protected set; } | ||
|
||
public float Interval { get; } | ||
|
||
|
@@ -86,7 +86,38 @@ protected MeterPayload(DateTime timestamp, | |
{ | ||
} | ||
|
||
public override bool IsMeter => true; | ||
public sealed override bool IsMeter => true; | ||
} | ||
|
||
internal abstract class MeterInstrumentDeltaMeasurementPayload : MeterPayload | ||
{ | ||
protected MeterInstrumentDeltaMeasurementPayload( | ||
DateTime timestamp, | ||
CounterMetadata counterMetadata, | ||
string displayName, | ||
string unit, | ||
double value, | ||
CounterType counterType, | ||
string valueTags, | ||
EventType eventType) | ||
: base(timestamp, counterMetadata, displayName, unit, value, counterType, valueTags, eventType) | ||
{ | ||
} | ||
|
||
public abstract bool SupportsDelta { get; } | ||
|
||
public virtual void Combine(MeterInstrumentDeltaMeasurementPayload other) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now all these payload objects are simple immutable objects that represent data which came across the wire and it would be nice to preserve that immutability. Rather than modify the objects in place perhaps you could track the mutating state in a couple variables you define together with the for loop iterating over them? That seems simpler and clearer to me than trying to reuse the payload object also as an accumulator for different subsets of the properties. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dropped the combine concept. I do like the immutability of these objects too. They can mutate DisplayName it seems but still 😄 Hard to know exactly how the aggregation will work. I took a look at my PoC. At the moment it passes a payload type. So it will have to allocate a new one with the final value. But that may not be the end of the world, or something else could be possible, or we could do more here. But figured OK to get the good pieces in so it can keep moving/evolving. |
||
{ | ||
if (other.Timestamp > Timestamp) | ||
{ | ||
Timestamp = other.Timestamp; | ||
} | ||
} | ||
} | ||
|
||
internal interface IRatePayload | ||
{ | ||
double Rate { get; } | ||
} | ||
|
||
internal sealed class GaugePayload : MeterPayload | ||
|
@@ -100,14 +131,29 @@ public GaugePayload(CounterMetadata counterMetadata, string displayName, string | |
} | ||
} | ||
|
||
internal class UpDownCounterPayload : MeterPayload | ||
internal class UpDownCounterPayload : MeterInstrumentDeltaMeasurementPayload, IRatePayload | ||
{ | ||
public UpDownCounterPayload(CounterMetadata counterMetadata, string displayName, string displayUnits, string valueTags, double value, DateTime timestamp) : | ||
public UpDownCounterPayload(CounterMetadata counterMetadata, string displayName, string displayUnits, string valueTags, double rate, double value, DateTime timestamp) : | ||
base(timestamp, counterMetadata, displayName, displayUnits, value, CounterType.Metric, valueTags, EventType.UpDownCounter) | ||
{ | ||
// In case these properties are not provided, set them to appropriate values. | ||
string counterName = string.IsNullOrEmpty(displayName) ? counterMetadata.CounterName : displayName; | ||
DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName; | ||
Rate = rate; | ||
} | ||
|
||
public double Rate { get; private set; } | ||
|
||
public override bool SupportsDelta => false; | ||
|
||
public override void Combine(MeterInstrumentDeltaMeasurementPayload other) | ||
{ | ||
if (other is UpDownCounterPayload upDownCounterPayload) | ||
{ | ||
Rate += upDownCounterPayload.Rate; | ||
} | ||
|
||
base.Combine(other); | ||
} | ||
} | ||
|
||
|
@@ -131,9 +177,8 @@ public CounterEndedPayload(CounterMetadata counterMetadata, DateTime timestamp) | |
/// This gets generated for Counter instruments from Meters. This is used for pre-.NET 8 versions of MetricsEventSource that only reported rate and not absolute value, | ||
/// or for any tools that haven't opted into using RateAndValuePayload in the CounterConfiguration settings. | ||
/// </summary> | ||
internal sealed class RatePayload : MeterPayload | ||
internal sealed class RatePayload : MeterInstrumentDeltaMeasurementPayload, IRatePayload | ||
{ | ||
|
||
public RatePayload(CounterMetadata counterMetadata, string displayName, string displayUnits, string valueTags, double rate, double intervalSecs, DateTime timestamp) : | ||
base(timestamp, counterMetadata, displayName, displayUnits, rate, CounterType.Rate, valueTags, EventType.Rate) | ||
{ | ||
|
@@ -143,13 +188,24 @@ public RatePayload(CounterMetadata counterMetadata, string displayName, string d | |
string intervalName = intervalSecs.ToString() + " sec"; | ||
DisplayName = $"{counterName} ({unitsName} / {intervalName})"; | ||
} | ||
|
||
public double Rate => Value; | ||
|
||
public override bool SupportsDelta => true; | ||
|
||
public override void Combine(MeterInstrumentDeltaMeasurementPayload other) | ||
{ | ||
Value += other.Value; | ||
|
||
base.Combine(other); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Starting in .NET 8, MetricsEventSource reports counters with both absolute value and rate. If enabled in the CounterConfiguration and the new value field is present | ||
/// then this payload will be created rather than the older RatePayload. Unlike RatePayload, this one treats the absolute value as the primary statistic. | ||
/// </summary> | ||
internal sealed class CounterRateAndValuePayload : MeterPayload | ||
internal sealed class CounterRateAndValuePayload : MeterInstrumentDeltaMeasurementPayload, IRatePayload | ||
{ | ||
public CounterRateAndValuePayload(CounterMetadata counterMetadata, string displayName, string displayUnits, string valueTags, double rate, double value, DateTime timestamp) : | ||
base(timestamp, counterMetadata, displayName, displayUnits, value, CounterType.Metric, valueTags, EventType.Rate) | ||
|
@@ -162,6 +218,18 @@ public CounterRateAndValuePayload(CounterMetadata counterMetadata, string displa | |
} | ||
|
||
public double Rate { get; private set; } | ||
|
||
public override bool SupportsDelta => true; | ||
|
||
public override void Combine(MeterInstrumentDeltaMeasurementPayload other) | ||
{ | ||
if (other is CounterRateAndValuePayload counterRateAndValuePayload) | ||
{ | ||
Rate += counterRateAndValuePayload.Rate; | ||
} | ||
|
||
base.Combine(other); | ||
} | ||
} | ||
|
||
internal record struct Quantile(double Percentage, double Value); | ||
|
@@ -182,17 +250,36 @@ public PercentilePayload(CounterMetadata counterMetadata, string displayName, st | |
// dotnet-counters created a separate payload for each quantile (multiple payloads per TraceEvent). | ||
// AggregatePercentilePayload allows dotnet-monitor to construct a PercentilePayload for individual quantiles | ||
// like dotnet-counters, while still keeping the quantiles together as a unit. | ||
internal sealed class AggregatePercentilePayload : MeterPayload | ||
internal sealed class AggregatePercentilePayload : MeterInstrumentDeltaMeasurementPayload | ||
{ | ||
public AggregatePercentilePayload(CounterMetadata counterMetadata, string displayName, string displayUnits, string valueTags, IEnumerable<Quantile> quantiles, DateTime timestamp) : | ||
public AggregatePercentilePayload(CounterMetadata counterMetadata, string displayName, string displayUnits, string valueTags, int count, double sum, IEnumerable<Quantile> quantiles, DateTime timestamp) : | ||
base(timestamp, counterMetadata, displayName, displayUnits, 0.0, CounterType.Metric, valueTags, EventType.Histogram) | ||
{ | ||
Count = count; | ||
Sum = sum; | ||
//string counterName = string.IsNullOrEmpty(displayName) ? name : displayName; | ||
//DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName; | ||
Quantiles = quantiles.ToArray(); | ||
} | ||
|
||
public Quantile[] Quantiles { get; } | ||
public int Count { get; private set; } | ||
|
||
public double Sum { get; private set; } | ||
|
||
public Quantile[] Quantiles { get; private set; } | ||
|
||
public override bool SupportsDelta => true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does doing delta aggregation make sense given that the quantiles can't be combined? I see that you are only doing it for Count and Sum and those do work functionally, so this is more of a broader scenario question if it makes sense to offer this partial information for Histograms vs leaving them out for now and telling people they have to update to a newer version of .NET to make that part of the scenario work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The whole combine concept is gone for now. I left count/sum on here though. Talking with @alanwest backends like NR can do nice things with just sum/count so I felt it was still worth exposing the data to interested listeners 🤷 |
||
|
||
public override void Combine(MeterInstrumentDeltaMeasurementPayload other) | ||
{ | ||
if (other is AggregatePercentilePayload aggregatePercentilePayload) | ||
{ | ||
Count += aggregatePercentilePayload.Count; | ||
Sum += aggregatePercentilePayload.Sum; | ||
} | ||
|
||
base.Combine(other); | ||
} | ||
} | ||
|
||
internal sealed class ErrorPayload : MeterPayload | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,7 +40,16 @@ internal static class TraceEventExtensions | |
private static Dictionary<int, CounterMetadata> counterMetadataById = new(); | ||
private static HashSet<string> inactiveSharedSessions = new(StringComparer.OrdinalIgnoreCase); | ||
|
||
private static CounterMetadata AddCounterMetadata(string providerName, string counterName, int? id, string meterTags, string instrumentTags, string scopeHash) | ||
private static CounterMetadata AddCounterMetadata( | ||
string providerName, | ||
string counterName, | ||
int? id, | ||
string meterTags, | ||
string instrumentTags, | ||
string scopeHash, | ||
string providerVersion = null, | ||
string counterUnit = null, | ||
string counterDescription = null) | ||
{ | ||
CounterMetadata metadata; | ||
if (id.HasValue && counterMetadataById.TryGetValue(id.Value, out metadata)) | ||
|
@@ -67,7 +76,7 @@ private static CounterMetadata AddCounterMetadata(string providerName, string co | |
} | ||
|
||
// no pre-existing counter metadata was found, create a new one | ||
metadata = new CounterMetadata(providerName, counterName, meterTags, instrumentTags, scopeHash); | ||
metadata = new CounterMetadata(providerName, providerVersion, counterName, counterUnit, counterDescription, meterTags, instrumentTags, scopeHash); | ||
if (id.HasValue) | ||
{ | ||
counterMetadataById.TryAdd(id.Value, metadata); | ||
|
@@ -266,21 +275,26 @@ private static void HandleBeginInstrumentReporting(TraceEvent traceEvent, Counte | |
} | ||
|
||
string meterName = (string)traceEvent.PayloadValue(1); | ||
//string meterVersion = (string)obj.PayloadValue(2); | ||
string meterVersion = (string)traceEvent.PayloadValue(2); | ||
string instrumentName = (string)traceEvent.PayloadValue(3); | ||
|
||
if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName)) | ||
{ | ||
return; | ||
} | ||
|
||
string instrumentUnit = null; | ||
string instrumentDescription = null; | ||
string instrumentTags = null; | ||
string meterTags = null; | ||
string meterScopeHash = null; | ||
int? instrumentID = null; | ||
|
||
if (traceEvent.Version >= 1) | ||
{ | ||
// string instrumentType = (string)traceEvent.PayloadValue(4); | ||
instrumentUnit = (string)traceEvent.PayloadValue(5); | ||
instrumentDescription = (string)traceEvent.PayloadValue(6); | ||
instrumentTags = (string)traceEvent.PayloadValue(7); | ||
meterTags = (string)traceEvent.PayloadValue(8); | ||
meterScopeHash = (string)traceEvent.PayloadValue(9); | ||
|
@@ -292,7 +306,18 @@ private static void HandleBeginInstrumentReporting(TraceEvent traceEvent, Counte | |
// Many different instruments may all share ID zero we don't want to index them by that ID. | ||
instrumentID = (id != 0) ? id : null; | ||
} | ||
payload = new BeginInstrumentReportingPayload(AddCounterMetadata(meterName, instrumentName, instrumentID, meterTags, instrumentTags, meterScopeHash), traceEvent.TimeStamp); | ||
payload = new BeginInstrumentReportingPayload( | ||
AddCounterMetadata( | ||
meterName, | ||
instrumentName, | ||
instrumentID, | ||
meterTags, | ||
instrumentTags, | ||
meterScopeHash, | ||
providerVersion: meterVersion, | ||
counterUnit: instrumentUnit, | ||
counterDescription: instrumentDescription), | ||
traceEvent.TimeStamp); | ||
} | ||
|
||
private static void HandleCounterRate(TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload) | ||
|
@@ -367,7 +392,7 @@ private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterConfi | |
string instrumentName = (string)traceEvent.PayloadValue(3); | ||
string unit = (string)traceEvent.PayloadValue(4); | ||
string tags = (string)traceEvent.PayloadValue(5); | ||
//string rateText = (string)traceEvent.PayloadValue(6); // Not currently using rate for UpDownCounters. | ||
string rateText = (string)traceEvent.PayloadValue(6); | ||
string valueText = (string)traceEvent.PayloadValue(7); | ||
int? id = null; | ||
if (traceEvent.Version >= 2) | ||
|
@@ -381,11 +406,11 @@ private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterConfi | |
} | ||
|
||
CounterMetadata metadata = GetCounterMetadata(meterName, instrumentName, id); | ||
if (double.TryParse(valueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) | ||
if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double rate) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the rate value not being present also tied to counter ending events? |
||
&& double.TryParse(valueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) | ||
{ | ||
// UpDownCounter reports the value, not the rate - this is different than how Counter behaves. | ||
payload = new UpDownCounterPayload(metadata, null, unit, tags, value, traceEvent.TimeStamp); | ||
|
||
payload = new UpDownCounterPayload(metadata, null, unit, tags, rate, value, traceEvent.TimeStamp); | ||
} | ||
else | ||
{ | ||
|
@@ -413,8 +438,8 @@ private static void HandleHistogram(TraceEvent obj, CounterConfiguration configu | |
string unit = (string)obj.PayloadValue(4); | ||
string tags = (string)obj.PayloadValue(5); | ||
string quantilesText = (string)obj.PayloadValue(6); | ||
//int count - unused arg 7 | ||
//double sum - unused arg 8 | ||
int count = (int)obj.PayloadValue(7); | ||
double sum = (double)obj.PayloadValue(8); | ||
int? id = null; | ||
if (obj.Version >= 2) | ||
{ | ||
|
@@ -429,7 +454,7 @@ private static void HandleHistogram(TraceEvent obj, CounterConfiguration configu | |
//Note quantiles can be empty. | ||
IList<Quantile> quantiles = ParseQuantiles(quantilesText); | ||
CounterMetadata metadata = GetCounterMetadata(meterName, instrumentName, id); | ||
payload = new AggregatePercentilePayload(metadata, null, unit, tags, quantiles, obj.TimeStamp); | ||
payload = new AggregatePercentilePayload(metadata, null, unit, tags, count, sum, quantiles, obj.TimeStamp); | ||
} | ||
|
||
private static void HandleHistogramLimitReached(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels confusing that the type is named "MeterInstrumentDeltaMeasurementPayload" and yet it might have SupportsDelta return false. Does the "Delta" in the type name have a different meaning than the Delta in the property name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved it on to
MeterPayload
with a default offalse
. For instruments where delta is supported they now returntrue
.