-
Notifications
You must be signed in to change notification settings - Fork 773
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
Metric AggregatorStore optimization for sorting Tag keys #2777
Changes from 6 commits
6d39035
9839605
18f6607
a4dcc68
1d72e8e
1a7d115
4cd8314
d24c265
956d836
4660264
3374769
2b1c532
e66874e
b62c0f9
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 |
---|---|---|
|
@@ -382,6 +382,78 @@ public void ObservableCounterAggregationTest(bool exportDelta) | |
} | ||
} | ||
|
||
[Theory] | ||
[InlineData(false, false)] | ||
[InlineData(false, true)] | ||
[InlineData(true, false)] | ||
[InlineData(true, true)] | ||
public void DimensionsAreOrderInsensitive(bool exportDelta, bool hasView) | ||
{ | ||
var exportedItems = new List<Metric>(); | ||
|
||
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{exportDelta}.{hasView}"); | ||
var counterLong = meter.CreateCounter<long>("Counter"); | ||
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder() | ||
.AddMeter(meter.Name) | ||
.AddReader(new BaseExportingMetricReader(new InMemoryExporter<Metric>(exportedItems)) | ||
{ | ||
Temporality = exportDelta ? AggregationTemporality.Delta : AggregationTemporality.Cumulative, | ||
}); | ||
|
||
if (hasView) | ||
{ | ||
meterProviderBuilder.AddView(instrumentName: "Counter", new MetricStreamConfiguration() { TagKeys = new string[] { "Key1", "Key2" } }); | ||
} | ||
|
||
using var meterProvider = meterProviderBuilder.Build(); | ||
|
||
counterLong.Add(10, new("Key1", "Value1"), new("Key2", "Value2"), new("Key3", "Value3")); | ||
counterLong.Add(10, new("Key1", "Value1"), new("Key3", "Value3"), new("Key2", "Value2")); | ||
meterProvider.ForceFlush(MaxTimeToAllowForFlush); | ||
long sumReceived = GetLongSum(exportedItems); | ||
Assert.Equal(20, sumReceived); | ||
|
||
exportedItems.Clear(); | ||
counterLong.Add(10, new("Key2", "Value2"), new("Key1", "Value1"), new("Key3", "Value3")); | ||
counterLong.Add(10, new("Key2", "Value2"), new("Key3", "Value3"), new("Key1", "Value1")); | ||
meterProvider.ForceFlush(MaxTimeToAllowForFlush); | ||
sumReceived = GetLongSum(exportedItems); | ||
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 that going to really tell if we exported one MetricPoint or more than one? IIRC, this method simply sums up all metric points, so won't really validate what you are after.. 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 think we should validate that only one Metric is received, and that metric has a single MetricPoint, with the tags (key1,key2,key3). 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. Ahh sorry my bad. I thought this I'll update this. |
||
if (exportDelta) | ||
{ | ||
Assert.Equal(20, sumReceived); | ||
} | ||
else | ||
{ | ||
Assert.Equal(40, sumReceived); | ||
} | ||
|
||
exportedItems.Clear(); | ||
meterProvider.ForceFlush(MaxTimeToAllowForFlush); | ||
sumReceived = GetLongSum(exportedItems); | ||
if (exportDelta) | ||
{ | ||
Assert.Equal(0, sumReceived); | ||
} | ||
else | ||
{ | ||
Assert.Equal(40, sumReceived); | ||
} | ||
|
||
exportedItems.Clear(); | ||
counterLong.Add(40, new("Key3", "Value3"), new("Key1", "Value1"), new("Key2", "Value2")); | ||
counterLong.Add(20, new("Key3", "Value3"), new("Key2", "Value2"), new("Key1", "Value1")); | ||
meterProvider.ForceFlush(MaxTimeToAllowForFlush); | ||
sumReceived = GetLongSum(exportedItems); | ||
if (exportDelta) | ||
{ | ||
Assert.Equal(60, sumReceived); | ||
} | ||
else | ||
{ | ||
Assert.Equal(100, sumReceived); | ||
} | ||
} | ||
|
||
[Theory] | ||
[InlineData(AggregationTemporality.Cumulative)] | ||
[InlineData(AggregationTemporality.Delta)] | ||
|
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.
another alternate option which doesn't have the risk of too many entries (when user keeps providing keys in different order)
Have the dictionary as before
If tagKeys lookup fail, sort and lookup again.
If fails, insert both original tagKeys and its sorted one to the dictionary.
So that we only store atmost 2 entries per key set. And we only do a single lookup in hotpath, as opposed to 2 look ups.
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.
That's a good suggestion. The only issue with this would be if the user provides the sorted combination as the very first combination and uses some random combination later on. In this case, we would always be sorting the keys.
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.
yes, if the same order is re-used, then you get max performance. else lower perf.
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 agree this is good optimization to try out. Reusing the same order is probably the most common scenario. I suppose it's possible for library to use a single instrument in multiple code paths which add dimensions in a different order, but probably an edge case.
A different order may be likely in the event I have two libraries emitting the same metric name, but since they're different libraries they'd be separate Metric instances, 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.
Based on offline sync, this may not be feasible.
We can come back to this and keep optimizing. For now, this PR avoids sorting in hot path, and makes a very significant perf boost.
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.
They cannot emit with same metric name (unless different Meter). So it'll be different instances, yes.
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 was wondering about this myself. Was the issue that synchronizing the two inserts (sorted and original order) would be tough?
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 problem here is in keeping the two entries for a given combination in sync.
For example:
counter.Add(10, new("A",1), new("C",3), new("B",2));
Here, since, ("A","C","B") is not present in the dictionary, we add these two entries:
("A","C","B") -> (1,3,2) -> MetricPointIndex1
("A","B","C") -> (1,2,3) -> MetricPointIndex1
Now, if we encounter a
counter.Add(10, new("A",10), new("B",20), new("C",30));
we will add another entry to the inner dictionary:
This is fine but we also need to add this entry to ("A","C","B"):
When we get a new set of tag values we have to find if there is another tag key combination present in the dictionary, and if it's present we have to add the same
MetricPointIndex
for the tag values sorted according to the tag keys.In this case, we get the new tag values (10,20,30) which are sorted by ("A","B","C"). Now we have to find if there is some other combination of ("A","B","C") present in the dictionary. If it's present we then have to sort the tag values according to the combination that is present, in this case, ("A","C","B") which would mean we have to sort the tag values like this: (10,30,20). We then have to ensure that we assign it the same
MetricPoint
index.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.
Ugh... right gotta deal with the values as well.