Allow duplicate instrument registration#2916
Allow duplicate instrument registration#2916cijothomas merged 30 commits intoopen-telemetry:mainfrom
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
|
| } | ||
|
|
||
| if (this.metricStreamNames.Contains(metricStreamName)) | ||
| if (this.instrumentIdentityToMetric.TryGetValue(instrumentIdentity, out var existingMetric)) |
There was a problem hiding this comment.
we need to add existingMetric to the metrics.Add(existingMetric) as well.
There was a problem hiding this comment.
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.
You are correct. Views hurt my mind 🤕. Fixed 2dd0058
There was a problem hiding this comment.
And yet, everyone loves a room with a view! 😄
cijothomas
left a comment
There was a problem hiding this comment.
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.
| { | ||
| 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.
Should we update the message? The user doesn't really need to resolve any conflict here, right?
There was a problem hiding this comment.
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.
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.
| 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.
We need to clear an entry, upon instrument dispose as well.
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
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 Meters 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.
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.
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.
This is a good improvement. Don't know if we need to introduce new Struct for this.
There was a problem hiding this comment.
Need to add this to changelog as this is breaking any existing exporters.
cijothomas
left a comment
There was a problem hiding this comment.
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.
Minor: would it help if the hash is calculated and stored during ctor?
There was a problem hiding this comment.
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.