Skip to content

[SDTEST-3774] Add TelemetryMetricExporter bridging OTel metrics to TelemetryExporter#268

Merged
ypopovych merged 10 commits into
mainfrom
yehor.popovych/sdtest-3774-telemetry-metric-exporter
Jun 2, 2026
Merged

[SDTEST-3774] Add TelemetryMetricExporter bridging OTel metrics to TelemetryExporter#268
ypopovych merged 10 commits into
mainfrom
yehor.popovych/sdtest-3774-telemetry-metric-exporter

Conversation

@ypopovych

@ypopovych ypopovych commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

What & why

Implements SDTEST-3774: a MetricExporter bridge that converts OTel metrics into our telemetry format and forwards them to TelemetryExporter for batching/upload — mirroring TelemetryLogExporter.

Changes

  • TelemetryMetricExporter — implements OTel's MetricExporter. Converts [MetricData] and forwards to TelemetryExporter.
    • Gauge → gauge; monotonic Sum → count; non-monotonic Sum → gauge; Summary → {name}.count + {name}.sum.
    • Histograms → distributions: explicit-boundary histograms reconstruct approximate samples from bucket midpoints (edge buckets use min/max, falling back to the boundary edge); exponential histograms use the geometric bucket midpoint.
    • Temporality mirrors OTel's deltaPreferred(): counters/histograms request .delta (DD count/distributions are per-interval); up/down counters stay .cumulative (mapped to gauge).
    • Attribute sets are grouped per series; attributes become sorted tags.
  • TelemetryDistribution — new payload type for the distributions request type (raw sample points, its own namespace set), wired into TelemetryRequestType, TelemetryMessageBatch, and TelemetryApi.sendDistributions.
  • Per-metric namespace via Resource — typed accessors Resource.telemetryMetricNamespace / telemetryDistributionNamespace (separate keys, enum-typed get/set) set the per-series namespace override.
  • FixDataUploadWorker.flush() returned false on an empty queue; an empty flush is a success.

Tests

  • TelemetryMetricExporterTests — gauge/sum/summary/histogram/exponential conversions, sample reconstruction, namespace-from-resource, temporality.
  • TelemetryLogExporterTests — message extraction, severity mapping, stack trace, tags, timestamps, flush.
  • TelemetryExporterTests — extended for the distributions round-trip and mixed-type batches.

🤖 Generated with Claude Code

ypopovych and others added 10 commits June 1, 2026 15:57
…lemetryExporter

Implement MetricExporter that converts OTel MetricData to TelemetryMetric
and forwards to TelemetryExporter for batching and upload, following the
same pattern as TelemetryLogExporter.

- Gauge/Sum types map to gauge or count series
- Histogram/ExponentialHistogram/Summary emit .count and .sum sub-series
- Attributes are converted to sorted tag arrays
- getAggregationTemporality returns cumulative for all instrument types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers message extraction (body, message attribute, eventName, default),
severity→level mapping (error/fatal→ERROR, warn→WARN, others→DEBUG),
stack trace extraction, attribute→tag conversion, timestamp fallback,
multiple records, and forceFlush.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
An empty flush (no pending batches) is a success — there was nothing to
upload, so the operation trivially succeeded. Initialising result=true
instead of false makes flush() consistent with that expectation and
allows forceFlush()/flush() callers to distinguish "nothing to do" from
a real upload failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the distributions payload type per the telemetry spec:
- TelemetryDistribution with its own Namespace (tracers/profilers/rum/appsec)
  and Series carrying raw sample values ([Double]) rather than [timestamp,value] pairs
- Wired into TelemetryRequestType enum, TelemetryMessageBatch decoder, and TelemetryApi
- sendDistributions() added to TelemetryApi protocol and TelemetryApiService
- TelemetryExporterTests extended to cover distributions round-trip and mixed-type batches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Histogram types carry sampled data, so they map naturally to the
distributions request type rather than the generate-metrics count/sum pair.

Conversion strategy:
- Histogram (explicit boundaries): reconstruct approximate sample values
  using bucket midpoints; edge buckets use min/max when available.
- ExponentialHistogram: geometric midpoint of each exponential bucket
  (base^(offset+i+0.5) where base = 2^(2^-scale)).
- Summary: kept as generate-metrics count+sum (already pre-aggregated).

TelemetryMetricExporter.export(metrics:) now emits a TelemetryMetric
payload for scalar types and a separate TelemetryDistribution payload for
histogram types when both are present in the same batch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a histogram's underflow (first) bucket has no recorded min, use the
first explicit boundary as the representative value instead of 0. A value
in the underflow bucket is <= boundaries[0], so the boundary itself is a
closer approximation than 0 — especially for histograms whose boundaries
don't start near zero.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The exporter previously declared cumulative temporality for all instruments.
Under cumulative, OTel emits running totals since process start on every
collection, which is wrong for the DD targets:
- DD `count` sums submitted values per interval, so feeding it cumulative
  snapshots inflates the count.
- `distributions` expect the interval's raw samples, so reconstructing from
  cumulative bucket counts re-emits the entire sample set each cycle.

Mirror OTel's deltaPreferred() selector: up/down counters (mapped to DD
gauge) stay cumulative so the current running total is reported; counters,
histograms, and gauges request delta.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Read the telemetry namespace from each metric's Resource (attribute key
"dd.telemetry.namespace") and set it as the per-series namespace override.
This lets a meter provider route its metrics to a specific namespace without
a dedicated exporter instance. When the attribute is absent or its value
isn't valid for the target payload (e.g. a generate-metrics-only namespace
on a distribution), the override is omitted and the exporter's configured
payload-level namespace applies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the ad-hoc TelemetryMetricResourceKeys constant and MetricConversion
namespace helpers with a typed `Resource.telemetryNamespace` getter/setter in
OpenTelemetry+Extensions, matching the existing applicationName/environment/etc.
accessors. The converter now reads metric.resource.telemetryNamespace and maps
it to the payload-specific Namespace enum via init(rawValue:).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split the single string-based telemetryNamespace into two typed accessors,
one per payload type with its own resource key:
- telemetryMetricNamespace: TelemetryMetric.Namespace?
  (key "dd.telemetry.metric.namespace")
- telemetryDistributionNamespace: TelemetryDistribution.Namespace?
  (key "dd.telemetry.distribution.namespace")

Getters/setters work directly with the enum types (no String at the call
site), and the separate keys let metrics and distributions carry independent
namespaces — necessary since the two enums accept different value sets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ypopovych
ypopovych requested review from a team as code owners June 1, 2026 15:45
@ypopovych
ypopovych requested a review from anmarchenko June 1, 2026 15:56
@ypopovych
ypopovych merged commit ee8887b into main Jun 2, 2026
12 checks passed
@ypopovych
ypopovych deleted the yehor.popovych/sdtest-3774-telemetry-metric-exporter branch June 2, 2026 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants