Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/statsd-receiver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@hyperdx/otel-collector": minor
---

feat(otel-collector): compile in statsdreceiver

Available for a user's own pipeline config (e.g. via
`CUSTOM_OTELCOL_CONFIG_FILE`) to ingest StatsD/DogStatsD metrics
directly, without a separate StatsD-to-OTLP bridge. Purely additive:
being compiled in changes no default pipeline or behavior on its own.
6 changes: 6 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ services:
- '${HDX_DEV_OTEL_GRPC_PORT:-4317}:4317' # OTLP gRPC receiver
- '${HDX_DEV_OTEL_HTTP_PORT:-4318}:4318' # OTLP http receiver
- '${HDX_DEV_OTEL_METRICS_PORT:-8888}:8888' # metrics extension
# Uncomment to enable statsd ingestion (also requires adding the
# `statsd` receiver to a pipeline in custom.config.yaml - see
# packages/otel-collector/README.md). Left commented out by default:
# a fixed host port mapping fails the whole service's startup if
# something else on the host already owns 8125/udp.
# - '${HDX_DEV_OTEL_STATSD_PORT:-8125}:8125/udp' # statsd receiver
Comment thread
arj22 marked this conversation as resolved.
Resolved
Comment thread
arj22 marked this conversation as resolved.
Resolved

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — HDX_DEV_OTEL_STATSD_PORT is never allocated per dev slot, so it hardcodes 8125 for every worktree

Every other HDX_DEV_*_PORT in this file is assigned a per-slot value in scripts/dev-env.sh (HDX_DEV_OTEL_JSON_HTTP_PORT=$((31100 + HDX_DEV_SLOT)), line 57) and documented in the port table in agent_docs/development.md:63-67. HDX_DEV_OTEL_STATSD_PORT appears in neither, so the :-8125 fallback always wins and two worktrees that both uncomment this line collide on the host — exactly the startup failure the comment says it is avoiding. Add HDX_DEV_OTEL_STATSD_PORT=$((31200 + HDX_DEV_SLOT)) plus its export in scripts/dev-env.sh and a row in the agent_docs/development.md table.

Comment thread
arj22 marked this conversation as resolved.
Resolved
restart: always
networks:
- internal
Expand Down
8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ services:
- '4317:4317' # OTLP gRPC receiver
- '4318:4318' # OTLP http receiver
- '8888:8888' # metrics extension
# Uncomment to enable statsd ingestion (also requires adding the
# `statsd` receiver to a pipeline via CUSTOM_OTELCOL_CONFIG_FILE - see
Comment thread
arj22 marked this conversation as resolved.
Resolved

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — docker-compose.yml's otel-collector has no CUSTOM_OTELCOL_CONFIG_FILE env var or config mount, so the instruction can't be followed

Unlike docker-compose.dev.yml (which sets CUSTOM_OTELCOL_CONFIG_FILE at line 39 and bind-mounts custom.config.yaml at line 52), this service declares no volumes: and no such env var, so a user who uncomments the port has nowhere to put the statsd receiver block. Add the matching commented-out environment:/volumes: lines here (or in the README's statsd section) so the opt-in is actually completable in the shipped production compose.

# packages/otel-collector/README.md). Left commented out by default:
# a fixed host port mapping fails the whole service's startup if
# something else on the host (e.g. a local StatsD daemon or Datadog
# Agent) already owns 8125/udp, which would otherwise break the
# default stack for anyone not using this feature.
# - '8125:8125/udp' # statsd receiver
restart: always
networks:
- internal
Expand Down
2 changes: 1 addition & 1 deletion docker/hyperdx/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ WORKDIR /app
# Add hosts entry in entrypoint script instead of here

# Expose ports
EXPOSE 8080 4317 4318 13133 8123 9000
EXPOSE 8080 4317 4318 13133 8123 9000 8125/udp

# all-in-one no auth ############################################################################################
FROM all-in-one-base AS all-in-one-noauth
Expand Down
4 changes: 2 additions & 2 deletions docker/otel-collector/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ COPY --chown=10001:10001 docker/otel-collector/config.standalone.promql.yaml /et
COPY --chown=10001:10001 docker/otel-collector/supervisor_docker.yaml.tmpl /etc/otel/supervisor.yaml.tmpl
COPY --chown=10001:10001 docker/otel-collector/schema /etc/otel/schema

EXPOSE 4317 4318 13133
EXPOSE 4317 4318 13133 8125/udp

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD wget -q --spider http://localhost:13133/ || exit 1
Expand All @@ -103,7 +103,7 @@ COPY --chown=10001:10001 docker/otel-collector/config.standalone.promql.yaml /et
COPY --chown=10001:10001 docker/otel-collector/supervisor_docker.yaml.tmpl /etc/otel/supervisor.yaml.tmpl
COPY --chown=10001:10001 docker/otel-collector/schema /etc/otel/schema

EXPOSE 4317 4318 13133
EXPOSE 4317 4318 13133 8125/udp

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD wget -q --spider http://localhost:13133/ || exit 1
Expand Down
81 changes: 81 additions & 0 deletions packages/otel-collector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ custom OTel configurations without rebuilding the collector.
| `k8scluster` | contrib | user configs |
| `kubeletstats` | contrib | user configs |
| `prometheus` | contrib | OpAMP controller, smoke tests |
| `statsd` | contrib | user configs |

### Processors

Expand Down Expand Up @@ -322,6 +323,86 @@ run (`otelcontribcol --config ...`, not just `validate`): it reaches
`span_metrics` connector, confirming `validate`'s static schema/graph
checks aren't hiding a startup-only failure here.

## Ingesting StatsD metrics

`statsdreceiver` is compiled in so StatsD-formatted metrics can be ingested
Comment thread
arj22 marked this conversation as resolved.
Resolved
directly, without a separate StatsD-to-OTLP bridge — this covers any StatsD
client, not just a particular vendor's dialect. It also understands the
DogStatsD tag extension: valued tags (`#tag:value`) work with no extra
config, but bare tags (`#mytag`, no value) need `enable_simple_tags: true`
Comment thread
arj22 marked this conversation as resolved.
Resolved
(set below) or they're silently dropped.

**This component does not support horizontally-scaled deployments** — not
a HyperDX-specific limitation, it's stated directly in the upstream
component's own docs. Aggregation state lives independently in each
collector instance's memory with no cross-replica coordination, so the
same metric landing on two different replicas (e.g. behind a
load-balanced Service) gets aggregated separately by each one instead of
together, silently splitting/undercounting it. HyperDX's default
deployments (`docker-compose.yml`, the all-in-one image) run the
OpAMP-managed collector as a single instance, so this doesn't bite out of
the box — it only matters once *some* collector instance ingesting
`statsd` is scaled to multiple replicas, at which point that traffic
needs to go to one dedicated single-replica instance instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — New section doesn't note that statsd has no authentication, unlike every other exposed receiver

packages/api/src/opamp/controllers/opampController.ts:361-371 and :397-406 attach bearertokenauth to otlp/hyperdx and datadog when collector auth is enforced, and the Datadog section here has a dedicated "Authentication" subsection. statsdreceiver supports no authenticator, so the recommended endpoint: 0.0.0.0:8125 plus a published 8125:8125/udp is an unauthenticated, spoofable metrics ingest path even in an auth-enforced deployment. Add a sentence saying so and recommending the port be restricted at the network layer (e.g. bind the host side to an internal address).


`docker-compose.yml`/`docker-compose.dev.yml` have an `8125/udp` port
Comment thread
arj22 marked this conversation as resolved.
Resolved
mapping for the `otel-collector` service, commented out by default —
uncomment it to enable statsd ingestion. It's opt-in rather than always
on because a fixed host port mapping fails that service's entire startup
if something else on the host (e.g. a local StatsD daemon or Datadog
Agent) already owns 8125/udp, which would otherwise break the default
stack for anyone not using this feature. The standalone/all-in-one images
also `EXPOSE 8125/udp` (metadata only, doesn't reserve the host port on
its own).

**In standalone mode**, `metrics` is a plain pipeline in your own config
file, so add `statsd` to it directly:

```yaml
receivers:
statsd:
# Defaults to localhost:8125, which only accepts local traffic -
Comment thread
arj22 marked this conversation as resolved.
Resolved
# 0.0.0.0 is required here since traffic arrives from outside the
# container (the published port above forwards to the container's

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — The parenthetical explaining why 0.0.0.0 is needed states the mechanism backwards

A published port is DNAT'd to the container's bridge interface, not to the container's loopback — which is precisely why a receiver bound to localhost:8125 is unreachable. As written ("the published port above forwards to the container's own loopback otherwise") it implies the default localhost binding would work, contradicting the advice it supports. Reword to "traffic arrives on the container's external interface, not its loopback".

# own loopback otherwise).
endpoint: 0.0.0.0:8125
Comment thread
arj22 marked this conversation as resolved.
Resolved
Comment thread
arj22 marked this conversation as resolved.
Resolved
Comment thread
arj22 marked this conversation as resolved.
Resolved
Comment thread
arj22 marked this conversation as resolved.
Resolved
enable_simple_tags: true
service:
pipelines:
metrics:
receivers: [otlp/hyperdx, statsd]
Comment thread
arj22 marked this conversation as resolved.
Resolved
Comment thread
arj22 marked this conversation as resolved.
Resolved
```

**In OpAMP supervisor mode** (HyperDX's default), editing the `metrics`
pipeline this way will not work: `receivers:`/`exporters:` on it are set
dynamically by `opampController.ts`, and the remote config overwrites
(not merges) those keys — see the [span metrics section
above](#computing-span-metrics-from-ingested-traces) for the same
behavior. Adding `statsd` to `metrics.receivers` from
`CUSTOM_OTELCOL_CONFIG_FILE` gets silently dropped: no error, the receiver
Comment thread
arj22 marked this conversation as resolved.
Resolved
Comment thread
arj22 marked this conversation as resolved.
Resolved
Comment thread
arj22 marked this conversation as resolved.
Resolved

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — README says an unwired statsd receiver "just starts"; an unreferenced receiver is never instantiated

When the OpAMP remote config overwrites metrics.receivers, the statsd receiver is left referenced by no pipeline, and the collector never builds or starts it — nothing binds 8125/udp at all. The equivalent span-metrics paragraph gets this right ("the connector is just compiled in and wired to nothing", line 282). Reword to match, since the current text sends a debugging user toward the port mapping instead of the pipeline.

just starts and is wired to nothing. Use a separate pipeline name instead,
which the remote config never touches:

```yaml
receivers:
statsd:
endpoint: 0.0.0.0:8125
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved
enable_simple_tags: true
service:
pipelines:
metrics/statsd:
Comment thread
arj22 marked this conversation as resolved.
Resolved
receivers: [statsd]
exporters: [clickhouse]
processors: [memory_limiter, batch]
```

Both shapes are verified against the real compiled binary via
`otelcontribcol validate --config`, and the standalone shape was also
actually run (`otelcontribcol --config ...`, not just `validate`),
reaching `"Everything is ready. Begin running and processing data."` —
the same rigor as the span metrics section above, using the same
multi-`--config` merge the container actually uses.

## Overriding base components via `CUSTOM_OTELCOL_CONFIG_FILE`

The collector ships with a default `memory_limiter` processor sized for a
Expand Down
3 changes: 3 additions & 0 deletions packages/otel-collector/builder-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ receivers:
- gomod:
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver
v__OTEL_COLLECTOR_VERSION__
- gomod:
Comment thread
arj22 marked this conversation as resolved.
Resolved
github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver
Comment thread
arj22 marked this conversation as resolved.
Resolved
v__OTEL_COLLECTOR_VERSION__
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved

processors:
# Core
Expand Down
Loading