Skip to content

Add span-derived primary tags (CSS v1.3.0) - #11402

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 342 commits into
masterfrom
dougqh/metrics-arbitrary-tags
Jul 27, 2026
Merged

Add span-derived primary tags (CSS v1.3.0)#11402
gh-worker-dd-mergequeue-cf854d[bot] merged 342 commits into
masterfrom
dougqh/metrics-arbitrary-tags

Conversation

@dougqh

@dougqhdougqh commented May 18, 2026

Copy link
Copy Markdown
Contributor

What Does This Do

Implements span-derived primary tags (Client-Side Stats v1.3.0): users configure DD_TRACE_STATS_ADDITIONAL_TAGS to extract matching span tag values as additional aggregation dimensions on ClientGroupedStats.AdditionalMetricTags.

Motivation

From the RFC...

Users may want to aggregate APM statistics by custom dimensions beyond the standard primary tag (typically env). For example, a user might want stats grouped by region, tenant_id, or other business-relevant span tags. This enables more granular visibility into service performance across different segments.

Configuration

This feature is experimental and must be explicitly opted in.

Environment VariableSystem PropertyDefaultDescription
DD_TRACE_EXPERIMENTAL_FEATURES_ENABLEDdd.trace.experimental.features.enabled(none)Required to enable this feature. Set to DD_TRACE_STATS_ADDITIONAL_TAGS (comma-separate to enable multiple experimental features).
DD_TRACE_STATS_ADDITIONAL_TAGSdd.trace.stats.additional.tags(none)Comma-separated span tag keys to use as additional aggregation dimensions. Max 4 keys; excess dropped at startup with a warn log.
DD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMITdd.trace.stats.additional.tags.cardinality.limit100Per-key distinct-value cap per reporting cycle. Values beyond the cap are reported as tracer_blocked_value.

Minimal configuration to enable:

DD_TRACE_EXPERIMENTAL_FEATURES_ENABLED=DD_TRACE_STATS_ADDITIONAL_TAGS
DD_TRACE_STATS_ADDITIONAL_TAGS=region,tenant_id

Additional Notes

Design

Wire format: new AdditionalMetricTags field on ClientGroupedStats, emitted as repeated string of "<key>:<value>" entries (mirrors PeerTags). Schema-ordered (alphabetical by key); null slots skipped; field omitted when empty, so unconfigured deployments pay zero payload overhead.

Cardinality protection — three caps, matching the .NET implementation:

  • MAX_ADDITIONAL_TAG_KEYS = 4 — configured-key count cap; excess keys dropped at startup with a warn log.
  • Per-value length cap (200 chars) — values exceeding this are replaced with tracer_blocked_value.
  • DD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMIT (default 100) — per-key distinct-value cap per reporting cycle; further new values collapse to the "<key>:tracer_blocked_value" sentinel.

Threading: all canonicalization (UTF8BytesString interning, cardinality limiting) runs on the aggregator thread. The producer path captures raw String values into a String[] parallel to the schema — no sync on the hot path.

Tests

Migrated SerializingMetricWriterTest from Spock/Groovy to JUnit 5 Java and folded the additional-tags coverage into its shared ValidatingSink — one msgpack-decode harness instead of two.

Benchmark

AdditionalTagsMetricsBenchmark.publish (JMH, Java 17, 8 threads, @Fork(1)):

limitsEnabledthroughput
false22.3M ± 0.7M ops/s
true6.9M ± 1.4M ops/s

The true arm is slower because it records more — over-cardinality values collapse into the tracer_blocked_value sentinel — not because limiting itself is expensive.

🤖 Generated with Claude Code

@dougqhdougqh added type: feature Enhancements and improvements comp: metrics Metrics tag: ai generated Largely based on code generated by an AI or LLM labels May 18, 2026
@dougqh
dougqhforce-pushed the dougqh/metrics-memory-efficiency branch from 46c04bd to 823a5d4CompareMay 18, 2026 19:28
@dougqh
dougqhforce-pushed the dougqh/metrics-arbitrary-tags branch from c552e73 to 42947ddCompareMay 18, 2026 19:30
@datadog-official

datadog-officialBot commented May 20, 2026

Copy link
Copy Markdown
Contributor

🎯 Code Coverage (details)
Patch Coverage: 88.07%
Overall Coverage: 57.64% (+0.04%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 27bc29c | Docs | Datadog PR Page | Give us feedback!

dougqhand others added 24 commits May 20, 2026 14:03
… create()
Replace Support.MAX_RATIO_NUMERATOR / _DENOMINATOR with a single float
MAX_RATIO constant, and add a Support.create(int, float) overload that
takes a scale factor. Callers now write Support.create(n, MAX_RATIO)
instead of stitching together the int arithmetic at the call site.
The scaled size is truncated (not ceiled) before going through sizeFor.
sizeFor already rounds up to the next power of two, so truncation just
absorbs float fuzz that would otherwise push a result like 12 * 4/3 =
16.0000005f past 16 and double the bucket array size for no reason.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five small cleanups from a design re-review pass:
1. Support javadoc: drop the stale "methods are package-private" sentence;
most of them were made public in earlier commits for higher-arity
callers. Also drop the "nested BucketIterator" framing (iterators are
peers of Support inside Hashtable, not nested inside Support).
2. MAX_RATIO javadoc: drop the Math.ceil recommendation; create(int, float)
deliberately truncates and is the canonical pathway.
3. Document the null-hash treatment on D1.Entry.hash and D2.Entry.hash so
the behavior difference is explicit: D1 uses Long.MIN_VALUE as a
sentinel that's collision-free against any int-valued hashCode(); D2
has no such sentinel and relies on matches() to resolve null/null vs
hash-0 collisions.
4. Rename Support.MAX_CAPACITY -> MAX_BUCKETS and sizeFor's parameter to
requestedSize. The cap is on the bucket-array length, not entry count;
the new name reflects that. Error messages updated to match.
5. Drop the `abstract` modifier on Hashtable in favor of `final` with a
private constructor. Nothing actually subclasses Hashtable -- the
abstract was a namespace device that read as "intended for extension."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add Support.insertHeadEntry(buckets, long keyHash, entry) overload that
derives the bucket index itself. Callers that already have a hash but
not the index (the common case) now avoid the redundant bucketIndex(...)
hop.
- D1.insert, D1.insertOrReplace, D2.insert, D2.insertOrReplace: use the
new overload, drop the (thisBuckets local, bucketIndex compute,
setNext, store) sequence at each call site.
- D2.buckets: drop the `private` modifier to match D1.buckets. Both are
package-private so iterator tests in the same package can drive
Support.bucketIterator against the table's bucket array. Added a short
comment on both fields documenting the rationale.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three follow-ups from the design review:
- Make Hashtable.Entry.next private. All same-package readers
(BucketIterator) already had a next() accessor; the leftover direct
field reads now route through it. Closes the "mixed encapsulation"
gap where some readers used the accessor and same-package ones
reached for the field.
- BucketIterator and MutatingBucketIterator now document that chain-walk
work happens in next() (and the constructor for the first match);
hasNext() is an O(1) field read.
- Add D1.getOrCreate(K, Function) and D2.getOrCreate(K1, K2, BiFunction).
Both reuse the lookup hash for the insert on miss, avoiding the
double-hash that "get; if null then insert" callers would otherwise
pay.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses PR #11409 review comments:
- #3267164119 / #3267165525: wrap every single-line if/break body in
braces (7 sites across BucketIterator, MutatingBucketIterator, and the
full-table Iterator).
- #3275947761 / #3275948108 (sarahchen6): null out the removed/replaced
entry's next pointer after splicing it out of the chain in
MutatingBucketIterator.remove / .replace. Applied the same fix to the
full-table Iterator.remove for consistency.
Rationale: detaching prevents accidental traversal through a removed
entry via a stale reference and lets the GC reclaim a chain tail that
the removed entry was the last referrer to.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sistency
Addresses PR #11409 review comment #3276167001. The method parallels the
primitive hash(boolean) / hash(int) / hash(long) / ... family, so naming
it hash(Object) -- with null collapsing to Long.MIN_VALUE as a sentinel
distinct from any real hashCode -- matches the rest of the public surface.
Test call sites that pass a literal null now disambiguate against
hash(int[]) / hash(Object[]) / hash(Iterable) via an (Object) cast.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses sarahchen6's review comment on ConflatingMetricsAggregator
extractPeerTagPairs: replaces the worst-case-allocation + trim-and-copy
flat-pairs layout with a parallel-array carrier.
- New PeerTagSchema: minimal carrier of String[] names. Two flavors -- a
static INTERNAL singleton (one entry: base.service) for internal-kind
spans, and per-discovery built schemas for client/producer/consumer
spans. Deliberately no cardinality limiters or per-cycle state; that
layers on top in a later PR.
- ConflatingMetricsAggregator: caches the peer-aggregation schema keyed
on reference equality of features.peerTags() -- a single volatile read
+ a long compare on the steady-state producer hot path, no allocation.
The producer now captures only a String[] of values parallel to the
schema's names; the schema reference is carried on SpanSnapshot. The
prior "build worst-case pairs then trim" code is gone.
- SpanSnapshot: replaces String[] peerTagPairs with PeerTagSchema +
String[] peerTagValues. Producer drops the schema reference if no
values fired so the consumer short-circuits on null.
- Aggregator.materializePeerTags: now reads name/value pairs at the same
index from (schema.names, snapshot.peerTagValues). Counts hits once
for exact-size allocation; preserves the singletonList fast path for
the common one-entry case (e.g. internal-kind base.service).
Producer-side cost goes from "allocate String[2n] + walk + maybe trim"
to "single volatile read + walk + lazy String[n] only on first hit".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Aggregator.materializePeerTags: fold the firstHit-discovery nested if
into a single guarded post-increment (amarziali, #3279243138). One
body line: `if (values[i] != null && hitCount++ == 0) firstHit = i;`.
- Drop redundant isKind(SpanKindFilter) overrides in both
TraceGenerator.groovy files (amarziali, #3279264553 / #3279382648).
CoreSpan.java:84 already supplies a default implementation that reads
the same span.kind tag.
- Bump TRACER_METRICS_MAX_PENDING default from 2048 -> 131072 to address
the capacity regression amarziali flagged (#3279378375). Without
producer-side conflation, the inbox now holds 1 SpanSnapshot per
metrics-eligible span instead of 1 conflated Batch per ~64 spans;
restoring effective capacity parity (~2048 * ~64 = 131072) prevents a
~64x rise in inbox-full drops at the same span rate. ~100 B per
SpanSnapshot puts the worst-case heap floor at ~13 MB -- bounded.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses PR #11381 review (amarziali, #3279325340 -- "Are the existing
tests covering this case?").
New ConflatingMetricsAggregatorInboxFullTest constructs the aggregator
with a small inbox (queueSize=8), deliberately does NOT call start() so
the consumer thread never drains, then publishes enough spans to
overflow the inbox. Verifies that healthMetrics.onStatsInboxFull() is
called at least once -- the fast-path's `inbox.size() >= inbox.capacity()`
short-circuit triggers when the producer-side queue is at capacity.
Test is Java + JUnit 5 + Mockito per the project convention for new
tests; uses a CoreSpan Mockito mock rather than the SimpleSpan Groovy
fixture so we don't depend on Groovy-then-Java compile order from the
test source set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…read
Addresses amarziali's review comment #3279340181 ("It would be more
efficient to trigger from the other side"). The producer-side reference
compare on every publish goes away; the aggregator thread reconciles
the cached schema against feature discovery once per reporting cycle.
- DDAgentFeaturesDiscovery: expose getLastTimeDiscovered() so callers
can detect a discovery refresh without copying the peerTags Set.
- PeerTagSchema: add `long lastTimeDiscovered` (plain, aggregator-only)
and `hasSameTagsAs(Set)`. of(Set, long) takes the timestamp; INTERNAL
uses a -1L sentinel since it's never reconciled.
- ConflatingMetricsAggregator:
* Drop the cachedPeerTagsSource volatile and the per-publish reference
compare.
* Producer fast path is now `cachedPeerTagSchema` volatile read +
null-check; first publish takes the one-time synchronized bootstrap.
* Add reconcilePeerTagSchema() that runs once per cycle on the
aggregator thread: fast-path timestamp compare, slow-path set
compare, bump-in-place when the set is unchanged.
- Aggregator: new `Runnable onReportCycle` constructor parameter, run at
the start of report() (before the flush, so any test awaiting
writer.finishBucket() observes the schema in its post-reconcile state
and so the next publish sees the new schema without a handoff).
- Update "should create bucket for each set of peer tags" to drive two
reporting cycles separated by a report() that triggers reconcile. The
old test relied on per-publish reference detection, which the new
design intentionally doesn't preserve -- the schema is now stable
within a cycle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses round-3 review nice-to-haves on PR #11381.
- PeerTagSchemaTest: unit coverage for hasSameTagsAs() (the predicate
that drives the reconcile fast/slow path split), the of(Set, long)
factory, and the INTERNAL singleton. The hasSameTagsAs cases include
same-content-different-Set-reference (the case the reconcile fast path
relies on after a discovery refresh) and content-mismatch in either
direction.
- ConflatingMetricsAggregatorBootstrapTest: integration coverage for
the producer-side bootstrap + aggregator-thread reconcile flow.
* bootstrapHappensOnceOnFirstPublish -- three publishes against an
un-started aggregator (no consumer thread, no reconciles); verifies
features.peerTags() and features.getLastTimeDiscovered() are each
called exactly once.
* reconcileSkipsDeepCompareWhenTimestampMatches -- two cycles with
constant features.getLastTimeDiscovered(); each post-report
reconcile short-circuits on the timestamp fast path, so peerTags()
is called only by bootstrap (1 total).
* reconcileSurvivesTimestampBumpWhenTagsUnchanged -- timestamps bump
every reconcile, forcing the slow set-compare path; the tag set
stays identical, so the schema is preserved and continues to flush
buckets correctly across cycles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bility
The verify(writer).add(MetricKey, AggregateMetric) signature is unique
to #11381; downstream branches use AggregateEntry. Switching to
verify(writer, times(2)).finishBucket() keeps the same behavioral
guarantee (both cycles flushed) across the stack.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bility
The verify(writer).add(MetricKey, AggregateMetric) signature is unique
to #11381; downstream branches use AggregateEntry. Switching to
verify(writer, times(2)).finishBucket() keeps the same behavioral
guarantee (both cycles flushed) across the stack.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#11387's ClientStatsAggregator renames ConflatingMetricsAggregator; the
test file's name and class refs need to match. PeerTagSchemaTest's
PeerTagSchema.of() calls need the (Set, long, HealthMetrics) signature
this branch introduced.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dougqh
dougqh added this pull request to the merge queueJul 25, 2026
@dd-octo-sts

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351Bot commented Jul 25, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-07-25 01:53:55 UTC ℹ️ Start processing command /merge


2026-07-25 01:53:59 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 1h (p90).


2026-07-25 02:01:58 UTCMergeQueue: The build pipeline contains failing jobs for this merge request

Build pipeline has failing jobs for 5e7bcf5:

⚠️ Do NOT retry failed jobs directly (why?).

What to do next?

  • Investigate the failures and when ready, re-add your pull request to the queue!
  • If your PR checks are green, try to rebase/merge. It might be because the CI run is a bit old.
  • Any question, go check the FAQ.
Details

Since those jobs are not marked as being allowed to fail, the pipeline will most likely fail.
Therefore, and to allow other builds to be processed, this merge request has been rejected and the pipeline got canceled.

@github-merge-queue
github-merge-queueBot removed this pull request from the merge queue due to failed status checks Jul 25, 2026
@dougqh
dougqh enabled auto-merge July 25, 2026 02:25
@dougqh
dougqh added this pull request to the merge queueJul 27, 2026
@dd-octo-sts

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351Bot commented Jul 27, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-07-27 13:53:44 UTC ℹ️ Start processing command /merge


2026-07-27 13:53:50 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 1h (p90).


2026-07-27 14:10:27 UTCMergeQueue: This merge request was updated

This PR is rejected because it was updated

@github-merge-queue
github-merge-queueBot removed this pull request from the merge queue due to failed status checks Jul 27, 2026
dougqhand others added 4 commits July 27, 2026 10:10
A mis-set trace.stats.<tag>.cardinality.limit flowed straight into
TagCardinalityHandler, which eagerly allocates four reference arrays of
nextPow2(limit * 2) slots. A large value could exhaust the heap while the
aggregator is constructed, before the tracer finished starting.
Cap the accepted value at 1 << 16 (~2 MB per handler worst case, far above
the largest built-in default of 1024) and fall back to the default with a
warning when it is exceeded, mirroring the existing <= 0 fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
recordDurations had no callers in any source tree; drop it and its now-orphaned
AtomicLongArray import.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
getTraceStatsAdditionalTags re-queried ConfigProvider on every call, so a late
system-property mutation could change an existing Config's value, and the
setting never appeared in Config.toString() (nor was it resolved at all when no
metrics aggregator was constructed). Resolve it once into a final field during
construction, return that field, and include it in toString(), per the
add_new_configurations convention.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh

Copy link
Copy Markdown
ContributorAuthor

Codex review resolution (base-branch findings)

Codex surfaced these while reviewing the stacked PRs (#12069/#12070); they concern this base branch, so tracking their resolution here.

Fixed

FindingResolution
Bound the configurable additional-tag / stats cardinality limit (P1)Config.getTraceStatsCardinalityLimit now caps the value at 1 << 16 and falls back to the default with a warning above that, mirroring the existing <= 0 fallback, so a mis-set limit can no longer throw or exhaust the heap during aggregator construction. — 4188b43471
Snapshot the additional-tags configuration in Config (P1/P2)Resolved once into a final field at construction, returned by the getter, and included in toString(), per the add-new-configuration convention. No more per-call ConfigProvider re-query or invisibility when no aggregator is built. — f5b083bd80 (+ tests 86896d7ce3)
Remove the unused AggregateEntry.recordDurations helper (P3)Removed along with its now-orphaned AtomicLongArray import; no callers in any source tree. — 976d335d7a

Working as intended / acceptable by design

Cap the combined additional-tag cardinality (P1) — The per-key budgets intentionally don't bound the product; the aggregate table does. AggregateTable caps rows (2048 normal / 256 tight-heap); once full, Aggregator.findOrInsert returns null and the span is dropped via healthMetrics.onStatsAggregateDropped(), reported under datadog.tracer.stats.collapsed_spans tagged collapsed:whole_key. So aggregate cardinality is bounded by the table cap regardless of the per-key product, and overflow is observable rather than silent. A shared tuple budget would only move the same cap earlier and lose the per-key collapsed:/oversized: attribution.

Retain retired peer-tag schemas until producers release them (P2) — The one-cycle previousPeerTagSchema retention covers the normal straggler (a producer that read the old schema just before a discovery-driven change). The residual window described requires a producer stalled beyond a full reporting intervaland a coinciding rare discovery tag change; even then only the collapse counter for that one straggler span is lost, never its aggregate contribution — and there was always fundamentally a race with the config update itself. Deemed negligible. Full mechanical write-up in #11402 (comment).

Deferred

Move the new unit test to JUnit 5 (P1) — Agreed on the standing convention. The added coverage extends the existing ClientStatsAggregatorTest.groovy Spock suite rather than introducing a standalone test; a full JUnit 5 migration of that large suite is a mechanical change tracked separately, out of scope for this PR.

Removing recordDurations took away the only AtomicLongArray access touching the
recording counters, so SpotBugs no longer raises the AT_* atomicity warnings on
recordOneDuration and clearAggregate. Their @SuppressFBWarnings annotations are
now flagged as US_USELESS_SUPPRESSION; remove them (and the orphaned import).
The single-writer invariant they documented remains described in the class
javadoc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh
dougqh enabled auto-merge July 27, 2026 14:58
@dougqh
dougqh added this pull request to the merge queueJul 27, 2026
@dd-octo-sts

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351Bot commented Jul 27, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-07-27 16:25:27 UTC ℹ️ Start processing command /merge


2026-07-27 16:25:32 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 1h (p90).


2026-07-27 18:20:38 UTCMergeQueue: The build pipeline contains failing jobs for this merge request

Build pipeline has failing jobs for 8eb59e4:

⚠️ Do NOT retry failed jobs directly (why?).

What to do next?

  • Investigate the failures and when ready, re-add your pull request to the queue!
  • If your PR checks are green, try to rebase/merge. It might be because the CI run is a bit old.
  • Any question, go check the FAQ.
Details

Since those jobs are not marked as being allowed to fail, the pipeline will most likely fail.
Therefore, and to allow other builds to be processed, this merge request has been rejected and the pipeline got canceled.

@github-merge-queue
github-merge-queueBot removed this pull request from the merge queue due to failed status checks Jul 27, 2026
@dougqh
dougqh added this pull request to the merge queueJul 27, 2026
@dd-octo-sts

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351Bot commented Jul 27, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-07-27 20:10:07 UTC ℹ️ Start processing command /merge


2026-07-27 20:10:11 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 1h (p90).


2026-07-27 21:06:41 UTC ℹ️ MergeQueue: This merge request was merged

@github-merge-queue
github-merge-queueBot removed this pull request from the merge queue due to failed status checks Jul 27, 2026
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854dBot merged commit f5472d9 into masterJul 27, 2026
782 of 784 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854dBot deleted the dougqh/metrics-arbitrary-tags branch July 27, 2026 21:06
@github-actionsgithub-actionsBot added this to the 1.65.0 milestone Jul 27, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: metricsMetricstag: ai generatedLargely based on code generated by an AI or LLMtype: featureEnhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@dougqh@mhdatie@mhlidd@bric3@amarziali@sarahchen6@AlexeyKuznetsov-DD