-
Notifications
You must be signed in to change notification settings - Fork 355
implementation for span derived primary tags #11358
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
07df666
4597764
983ae32
4b981d7
68fea6a
b6a04dc
9bfd57f
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 |
|---|---|---|
|
|
@@ -80,6 +80,16 @@ public final class ConflatingMetricsAggregator implements MetricsAggregator, Eve | |
| Pair.of( | ||
| DDCaches.newFixedSizeCache(512), | ||
| value -> UTF8BytesString.create(key + ":" + value)); | ||
| private static final DDCache< | ||
| String, Pair<DDCache<String, UTF8BytesString>, Function<String, UTF8BytesString>>> | ||
| ADDITIONAL_TAG_VALUES_CACHE = DDCaches.newFixedSizeCache(64); | ||
| private static final Function< | ||
| String, Pair<DDCache<String, UTF8BytesString>, Function<String, UTF8BytesString>>> | ||
| ADDITIONAL_TAG_VALUES_CACHE_ADDER = | ||
| key -> | ||
| Pair.of( | ||
| DDCaches.newFixedSizeCache(512), | ||
| value -> UTF8BytesString.create(key + ":" + value)); | ||
|
Contributor
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 as is this degenerates into hot allocation when a tag has high cardinality. In high cardinality situation, we need to avoid the concatenation before it happens. Right now, we're still paying the allocation cost and undoing the benefit of the cache.
Contributor
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. Just to verify, I ran this through Claude as well...
Contributor
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. Also worth noting, that the cache is still unbounded in terms of bytes. That has bit us with SQL statements previously. And I guess part of what Claude is pointing out is that tags may also contain complicated objects, we might need to filter those out otherwise allocation could explode if misconfigured. Although, that was definitely a pre-existing issue.
Author
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. added basic cardinality control and will add a char limit to the string values, would that address all your concerns here?
Contributor
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'm experimenting with a revised approach. I think the simplest solution might be to combine the cardinality tracking and the caching into a single class. In hindsight, I'd argue that DDCache isn't a great fit for metrics. DDCache works well with low cardinality, but doesn't work well in situations where cardinality can be high. In a strict sense, that's okay because the GC will be able to reclaim the memory, so memory isn't unbounded. However, a solution that deals with high cardinality more gracefully would be preferable. |
||
| private static final CharSequence SYNTHETICS_ORIGIN = "synthetics"; | ||
|
|
||
| private static final Set<String> ELIGIBLE_SPAN_KINDS_FOR_METRICS = | ||
|
|
@@ -93,6 +103,7 @@ public final class ConflatingMetricsAggregator implements MetricsAggregator, Eve | |
| new HashSet<>(Arrays.asList(SPAN_KIND_CLIENT, SPAN_KIND_PRODUCER, SPAN_KIND_CONSUMER))); | ||
|
|
||
| private final Set<String> ignoredResources; | ||
| private final List<String> additionalTagKeys; | ||
| private final MessagePassingQueue<Batch> batchPool; | ||
| private final ConcurrentHashMap<MetricKey, Batch> pending; | ||
| private final ConcurrentHashMap<MetricKey, MetricKey> keys; | ||
|
|
@@ -115,6 +126,7 @@ public ConflatingMetricsAggregator( | |
| this( | ||
| config.getWellKnownTags(), | ||
| config.getMetricsIgnoredResources(), | ||
| config.getTraceStatsAdditionalTags(), | ||
| sharedCommunicationObjects.featuresDiscovery(config), | ||
| healthMetrics, | ||
| new OkHttpSink( | ||
|
|
@@ -132,6 +144,7 @@ public ConflatingMetricsAggregator( | |
| ConflatingMetricsAggregator( | ||
| WellKnownTags wellKnownTags, | ||
| Set<String> ignoredResources, | ||
| List<String> additionalTagKeys, | ||
| DDAgentFeaturesDiscovery features, | ||
| HealthMetrics healthMetric, | ||
| Sink sink, | ||
|
|
@@ -141,6 +154,7 @@ public ConflatingMetricsAggregator( | |
| this( | ||
| wellKnownTags, | ||
| ignoredResources, | ||
| additionalTagKeys, | ||
| features, | ||
| healthMetric, | ||
| sink, | ||
|
|
@@ -154,6 +168,7 @@ public ConflatingMetricsAggregator( | |
| ConflatingMetricsAggregator( | ||
| WellKnownTags wellKnownTags, | ||
| Set<String> ignoredResources, | ||
| List<String> additionalTagKeys, | ||
| DDAgentFeaturesDiscovery features, | ||
| HealthMetrics healthMetric, | ||
| Sink sink, | ||
|
|
@@ -164,6 +179,7 @@ public ConflatingMetricsAggregator( | |
| boolean includeEndpointInMetrics) { | ||
| this( | ||
| ignoredResources, | ||
| additionalTagKeys, | ||
| features, | ||
| healthMetric, | ||
| sink, | ||
|
|
@@ -177,6 +193,7 @@ public ConflatingMetricsAggregator( | |
|
|
||
| ConflatingMetricsAggregator( | ||
| Set<String> ignoredResources, | ||
| List<String> additionalTagKeys, | ||
| DDAgentFeaturesDiscovery features, | ||
| HealthMetrics healthMetric, | ||
| Sink sink, | ||
|
|
@@ -187,6 +204,8 @@ public ConflatingMetricsAggregator( | |
| TimeUnit timeUnit, | ||
| boolean includeEndpointInMetrics) { | ||
| this.ignoredResources = ignoredResources; | ||
| this.additionalTagKeys = | ||
| additionalTagKeys == null ? Collections.emptyList() : additionalTagKeys; | ||
|
Contributor
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. We could also add a
Author
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. We only allow 4 unique primary tag keys in the UI so if a user is setting more than that they are aggregating their stats on an additional dimension only for that value to get discarded in our stats pipeline. |
||
| this.includeEndpointInMetrics = includeEndpointInMetrics; | ||
| this.inbox = Queues.mpscArrayQueue(queueSize); | ||
| this.batchPool = Queues.spmcArrayQueue(maxAggregates); | ||
|
|
@@ -350,7 +369,8 @@ private boolean publish(CoreSpan<?> span, boolean isTopLevel, CharSequence spanK | |
| getPeerTags(span, spanKind.toString()), | ||
| httpMethod, | ||
| httpEndpoint, | ||
| grpcStatusCode); | ||
| grpcStatusCode, | ||
| getAdditionalTags(span)); | ||
| MetricKey key = keys.putIfAbsent(newKey, newKey); | ||
| if (null == key) { | ||
| key = newKey; | ||
|
|
@@ -413,6 +433,28 @@ private List<UTF8BytesString> getPeerTags(CoreSpan<?> span, String spanKind) { | |
| return Collections.emptyList(); | ||
| } | ||
|
|
||
| private List<UTF8BytesString> getAdditionalTags(CoreSpan<?> span) { | ||
|
Contributor
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. We already have high allocation from creating MetricKey-s just to perform Map look-ups.
Author
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 copied how we interact with peer tags, to keep behavior consistent. What do you propose I do differently? |
||
| if (additionalTagKeys.isEmpty()) { | ||
| return Collections.emptyList(); | ||
| } | ||
| List<UTF8BytesString> result = null; | ||
| for (String tagKey : additionalTagKeys) { | ||
| Object value = span.unsafeGetTag(tagKey); | ||
| if (value == null) { | ||
| continue; | ||
| } | ||
| Pair<DDCache<String, UTF8BytesString>, Function<String, UTF8BytesString>> cacheAndCreator = | ||
| ADDITIONAL_TAG_VALUES_CACHE.computeIfAbsent(tagKey, ADDITIONAL_TAG_VALUES_CACHE_ADDER); | ||
| UTF8BytesString formatted = | ||
| cacheAndCreator.getLeft().computeIfAbsent(value.toString(), cacheAndCreator.getRight()); | ||
| if (result == null) { | ||
| result = new ArrayList<>(additionalTagKeys.size()); | ||
| } | ||
| result.add(formatted); | ||
| } | ||
| return result == null ? Collections.emptyList() : result; | ||
| } | ||
|
|
||
| private static boolean isSynthetic(CoreSpan<?> span) { | ||
| return span.getOrigin() != null && SYNTHETICS_ORIGIN.equals(span.getOrigin().toString()); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,7 @@ public final class SerializingMetricWriter implements MetricWriter { | |
| private static final byte[] IS_TRACE_ROOT = "IsTraceRoot".getBytes(ISO_8859_1); | ||
| private static final byte[] SPAN_KIND = "SpanKind".getBytes(ISO_8859_1); | ||
| private static final byte[] PEER_TAGS = "PeerTags".getBytes(ISO_8859_1); | ||
| private static final byte[] ADDITIONAL_METRIC_TAGS = "AdditionalMetricTags".getBytes(ISO_8859_1); | ||
| private static final byte[] HTTP_METHOD = "HTTPMethod".getBytes(ISO_8859_1); | ||
| private static final byte[] HTTP_ENDPOINT = "HTTPEndpoint".getBytes(ISO_8859_1); | ||
| private static final byte[] GRPC_STATUS_CODE = "GRPCStatusCode".getBytes(ISO_8859_1); | ||
|
|
@@ -149,7 +150,7 @@ public void add(MetricKey key, AggregateMetric aggregate) { | |
| final boolean hasServiceSource = key.getServiceSource() != null; | ||
| final boolean hasGrpcStatusCode = key.getGrpcStatusCode() != null; | ||
| final int mapSize = | ||
| 15 | ||
| 16 | ||
|
Contributor
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. A future improvement can be to make this conditional such as |
||
| + (hasServiceSource ? 1 : 0) | ||
| + (hasHttpMethod ? 1 : 0) | ||
| + (hasHttpEndpoint ? 1 : 0) | ||
|
|
@@ -189,6 +190,13 @@ public void add(MetricKey key, AggregateMetric aggregate) { | |
| writer.writeUTF8(peerTag); | ||
| } | ||
|
|
||
| writer.writeUTF8(ADDITIONAL_METRIC_TAGS); | ||
| final List<UTF8BytesString> additionalTags = key.getAdditionalTags(); | ||
| writer.startArray(additionalTags.size()); | ||
| for (UTF8BytesString tag : additionalTags) { | ||
| writer.writeUTF8(tag); | ||
| } | ||
|
|
||
| if (hasServiceSource) { | ||
| writer.writeUTF8(SERVICE_SOURCE); | ||
| writer.writeUTF8(key.getServiceSource()); | ||
|
|
||
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.
In hindsight, I wish we hadn't chosen to concatenate key + ":" + value in the payload. For keeping memory down, separate fields would probably have been better.