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

@dougqh dougqh 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 Variable System Property Default Description
DD_TRACE_EXPERIMENTAL_FEATURES_ENABLED dd.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_TAGS dd.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_LIMIT dd.trace.stats.additional.tags.cardinality.limit 100 Per-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)):

limitsEnabled throughput
false 22.3M ± 0.7M ops/s
true 6.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

@dougqh dougqh 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
dougqh force-pushed the dougqh/metrics-memory-efficiency branch from 46c04bd to 823a5d4 Compare May 18, 2026 19:28
@dougqh
dougqh force-pushed the dougqh/metrics-arbitrary-tags branch from c552e73 to 42947dd Compare May 18, 2026 19:30
@datadog-official

datadog-official Bot 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!

dougqh and 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 queue Jul 25, 2026
@dd-octo-sts

dd-octo-sts Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot 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-queue Bot 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 queue Jul 27, 2026
@dd-octo-sts

dd-octo-sts Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot 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-queue Bot removed this pull request from the merge queue due to failed status checks Jul 27, 2026
dougqh and 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

dougqh commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

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

Finding Resolution
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 interval and 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 queue Jul 27, 2026
@dd-octo-sts

dd-octo-sts Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot 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-queue Bot 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 queue Jul 27, 2026
@dd-octo-sts

dd-octo-sts Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot 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-queue Bot 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-cf854d Bot merged commit f5472d9 into master Jul 27, 2026
782 of 784 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the dougqh/metrics-arbitrary-tags branch July 27, 2026 21:06
@github-actions github-actions Bot added this to the 1.65.0 milestone Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: metrics Metrics tag: ai generated Largely based on code generated by an AI or LLM type: feature Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants