-
Notifications
You must be signed in to change notification settings - Fork 782
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
Allow duplicate instrument registration #2916
Allow duplicate instrument registration #2916
Conversation
…lemetry-dotnet into alanwest/metric-identity
Codecov Report
@@ Coverage Diff @@
## main #2916 +/- ##
==========================================
+ Coverage 84.01% 84.03% +0.02%
==========================================
Files 254 255 +1
Lines 8950 8996 +46
==========================================
+ Hits 7519 7560 +41
- Misses 1431 1436 +5
|
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.
LGTM.
@@ -102,12 +111,16 @@ internal List<Metric> AddMetricsListWithViews(Instrument instrument, List<Metric | |||
continue; | |||
} | |||
|
|||
if (this.metricStreamNames.Contains(metricStreamName)) | |||
if (this.instrumentIdentityToMetric.TryGetValue(instrumentIdentity, out var existingMetric)) |
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.
we need to add existingMetric
to the metrics.Add(existingMetric) as well.
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.
consider example:
instr1 has view, which says produce 2 streams named inst1_a, inst1_b.
instr2 has view, which says produce 2 streams named inst1_a, inst1_c.
instr1 creation will return List{M1,M2}.
instr2 creation should return List {M1,M3} --> this PR currently only returns {M3}.
^ we should add unittest to cover this.
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.
You are correct. Views hurt my mind 🤕. Fixed 2dd0058
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.
And yet, everyone loves a room with a view! 😄
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.
See this comment : https://github.com/open-telemetry/opentelemetry-dotnet/pull/2916/files#r818252181
I think we are missing a case, when Views are involved.
if (this.metricStreamNames.Contains(metricStreamName)) | ||
{ | ||
OpenTelemetrySdkEventSource.Log.MetricInstrumentIgnored(metricName, instrument.Meter.Name, "Metric name conflicting with existing name.", "Either change the name of the instrument or change name using View."); | ||
return null; | ||
OpenTelemetrySdkEventSource.Log.DuplicateMetricInstrument(metricName, meterName, "Metric instrument has the same name as an existing one but differs by description, unit, or instrument type.", "Either change the name of the instrument or use MeterProviderBuilder.AddView to resolve the conflict."); |
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.
Should we update the message? The user doesn't really need to resolve any conflict here, right?
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.
The reason for this warning is to highlight a duplicate instrument registration by name that is not an identical match - i.e., differs by description, unit, or instrument type.
For example:
var instrument = meter.CreateCounter<double>("MyCounter", unit: "seconds");
var duplicateButNotIdenticalInstrument = meter.CreateCounter<int>("MyCounter", unit: "milliseconds");
Per the spec, SDKs should no longer prevent this, but in practice, the differences in this example imply semantic differences between the two instruments which are likely not intended. Furthermore, the spec indicates that consumers of these two metric streams may do what they see fit including simply dropping one of them. So this is why the spec still requires SDKs to emit diagnostics that can help folks identify potential problems.
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 think we can also mention in the log that "SDK will still exporter duplicates". Else it is not clear if SDK is going to drop it or pass it to exporters.
@@ -27,6 +27,7 @@ namespace OpenTelemetry.Metrics | |||
public abstract partial class MetricReader | |||
{ | |||
private readonly HashSet<string> metricStreamNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | |||
private readonly Dictionary<InstrumentIdentity, Metric> instrumentIdentityToMetric = new Dictionary<InstrumentIdentity, Metric>(); |
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.
We need to clear an entry, upon instrument dispose as well.
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.
Turns out there's a bit more to just freeing up an entry in this dictionary. I hadn't fully followed the dispose path, so I didn't notice that when an instrument is disposed the metric is freed up. Now that multiple instruments can modify a single metric we need to free up the metric only when no more instruments reference it.
Working on this...
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.
Ah ok, my initial understanding was incorrect. Instruments don't get disposed, meters do, so I think is less complicated than I had thought... 0ff1d22
this.InstrumentType = instrumentType; | ||
} | ||
|
||
public readonly string MeterName { get; } |
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 is not quite right. The entire meter - that is, name, version, and schema url (when we eventually support it) should be a component of the identity. Fixing this...
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.
Fixed 03a245e
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 is not quite right. The entire meter - that is, name, version, and schema url (when we eventually support it) should be a component of the identity. Fixing this...
Wouldn't it be better to just have Meter
as the only public
property in that case? We would only need to update the Equals()
check like below:
...
&& this.Meter.Name == other.Meter.Name
&& this.Meter.Version == other.Meter.Version
&& this.Meter.SchemaUrl == other.Meter.SchemaUrl
...
This way if schema url gets added to Meter
later on, we don't have to add a new public
property dedicated just for that.
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.
Since this is an internal
struct
, it wouldn't matter as much if we have to add more properties later on, but I think we could just use Meter
here as it provides the best encapsulation for everything Meter
related.
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.
Do you mean keep a handle on the Meter
itself? Same concern here #2916 (comment) would apply. Meter
might be disposed so safer to not keep a handle on it.
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.
Maybe it'd be less of a concern to keep a handle on the meter since InstrumentIdentity
is internal?
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.
Yeah, I was suggesting to have a handle to Meter
itself as all of this is happening in the InstrumentPublished
callback so the Meter
would not be disposed in this path. But I overlooked the fact that in case of dictionary lookup collisions, the Equals
check might fail as the dictionary might still have entries to instruments whose Meter
s are disposed. This would only work if we can ensure that the dictionary would never have any instrument whose Meter
is disposed.
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.
Okay to park this into an issue and come back to this ? (so that we can merge and do a release.)
OpenTelemetry.Metrics.Metric.Meter.get -> System.Diagnostics.Metrics.Meter | ||
OpenTelemetry.Metrics.Metric.MeterName.get -> string | ||
OpenTelemetry.Metrics.Metric.MeterVersion.get -> string |
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.
What do folks think about this change? In part I was thinking since Meter can be disposed, maybe it's safer to not keep a handle on it from the metric. Another option would be to create a new struct to encapsulate these fields.
namespace OpenTelemetry.Api;
public struct InstrumentationScope
{
string Name { get; }
string Version { get; }
// Eventually
string SchemaUrl { get; }
}
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 is a good improvement. Don't know if we need to introduce new Struct for this.
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.
Need to add this to changelog as this is breaking any existing exporters.
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.
LGTM.
Please modify changelog to warn export authors about breaking change from previous version.
Also a improved log message to indicate SDK is not dropping the metric.
{ | ||
unchecked | ||
{ | ||
int hash = 17; |
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.
Minor: would it help if the hash is calculated and stored during ctor?
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.
Interesting thought. Done.
Fixes #2925
See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument
Duplicate instrument registration
Identical instruments
Instantiating multiple metric instruments with the same name and also identical in all other respects - same type, description, and unit - result in a single metric stream aggregating measurements from all the identical instruments.
For example, measurements from the following instruments will be aggregated together. No warning is produced by the SDK.
Distinct instruments
Instantiating multiple metric instruments with the same name but differ in some respect - different type, description, or unit - will result in a separate metric stream for each distinct instrument.
For example, measurements from the following instruments will result in three distinct metric streams. The second one differs by unit/description and the third one differs by type. A warning is logged by the SDK in the event of distinct duplicate instrument registrations.