Skip to content
Draft
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
13 changes: 12 additions & 1 deletion .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ jobs:
# rebuilds the entire App/prod image. To avoid duplicating that ~8-minute
# Node build on every app change, the pull-request path filter below is
# scoped ONLY to the incremental bundling inputs (ClickHouse / OTel / the
# shared Dockerfile) — NOT api/app/common-utils/deps. Those are covered by
# shared Dockerfile) and the smoke script — NOT api/app/common-utils/deps. Those are covered by
# the App Image job above, and they can't break the bundling layers, which
# only COPY the prod output. Pushes to main ignore the filter entirely.
all-in-one-image:
Expand All @@ -169,6 +169,7 @@ jobs:
docker/clickhouse/**
docker/otel-collector/**
.vex/**
scripts/ci/smoke-all-in-one.sh

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 PromQL/MCP smoke never runs on PRs that change the MCP/PromQL code it tests

This step tests API behaviour (/api/mcp, clickstack_promql, PromQL tiles). The job's PR path filter deliberately leaves out packages/api/**, on the grounds that those changes "can't break the bundling layers". That reasoning holds for the old canary smoke but not for this one. A PR that breaks PromQL over MCP passes PR CI and only fails on the push to main. Fix: run this smoke in a job that triggers on packages/api/**, or document in the workflow that it only gates main.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

- name: Setup Docker Buildx
if:
github.event_name != 'pull_request' ||
Expand All @@ -185,6 +186,8 @@ jobs:
target: all-in-one-auth
platforms: linux/amd64
push: false
load: true
tags: hyperdx-aio-auth-smoke:pr
build-contexts: |
clickhouse=./docker/clickhouse
otel-collector=./docker/otel-collector
Expand Down Expand Up @@ -232,3 +235,11 @@ jobs:
steps.changed-files.outputs.any_changed == 'true'
timeout-minutes: 10
run: scripts/ci/smoke-all-in-one.sh hyperdx-aio-smoke:pr
# Boot the auth image with PromQL on, remote-write one OTLP gauge into
# the TimeSeries table, and query it over MCP with a PromQL tile.
- name: Smoke test PromQL over MCP on the auth image
if:
github.event_name != 'pull_request' ||
steps.changed-files.outputs.any_changed == 'true'
timeout-minutes: 10
run: scripts/ci/smoke-all-in-one.sh hyperdx-aio-auth-smoke:pr --promql-mcp
10 changes: 6 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@ include .env
# parallel. scripts/slots.sh computes them; override the slot with
# `make dev-int HDX_CI_SLOT=5`.
# ---------------------------------------------------------------------------
# One shell for all six values: this runs on every make invocation, and each
# One shell for all seven values: this runs on every make invocation, and each
# shell costs ~20ms.
_hdx_ci := $(shell HDX_CI_SLOT='$(HDX_CI_SLOT)' sh -c '. ./scripts/slots.sh && hdx_ci_ports && echo "$$HDX_CI_SLOT $$HDX_CI_PROJECT $$HDX_CI_CH_PORT $$HDX_CI_MONGO_PORT $$HDX_CI_API_PORT $$HDX_CI_OPAMP_PORT"')
_hdx_ci := $(shell HDX_CI_SLOT='$(HDX_CI_SLOT)' sh -c '. ./scripts/slots.sh && hdx_ci_ports && echo "$$HDX_CI_SLOT $$HDX_CI_PROJECT $$HDX_CI_CH_PORT $$HDX_CI_MONGO_PORT $$HDX_CI_API_PORT $$HDX_CI_OPAMP_PORT $$HDX_CI_PROMETHEUS_PORT"')

HDX_CI_SLOT := $(word 1,$(_hdx_ci))
HDX_CI_PROJECT := $(word 2,$(_hdx_ci))
HDX_CI_CH_PORT := $(word 3,$(_hdx_ci))
HDX_CI_MONGO_PORT:= $(word 4,$(_hdx_ci))
HDX_CI_API_PORT := $(word 5,$(_hdx_ci))
HDX_CI_OPAMP_PORT:= $(word 6,$(_hdx_ci))
HDX_CI_PROMETHEUS_PORT:= $(word 7,$(_hdx_ci))

export HDX_CI_CH_PORT HDX_CI_MONGO_PORT HDX_CI_API_PORT HDX_CI_OPAMP_PORT
export HDX_CI_CH_PORT HDX_CI_MONGO_PORT HDX_CI_API_PORT HDX_CI_OPAMP_PORT \
HDX_CI_PROMETHEUS_PORT

# Log directory for dev-portal visibility (integration tests)
HDX_CI_LOGS_DIR := $(HOME)/.config/hyperdx/dev-slots/$(HDX_CI_SLOT)/logs-int
Expand Down Expand Up @@ -155,7 +157,7 @@ dev-int-build:

.PHONY: dev-int
dev-int:
@echo "Using CI slot $(HDX_CI_SLOT) (project=$(HDX_CI_PROJECT) ch=$(HDX_CI_CH_PORT) mongo=$(HDX_CI_MONGO_PORT) api=$(HDX_CI_API_PORT))"
@echo "Using CI slot $(HDX_CI_SLOT) (project=$(HDX_CI_PROJECT) ch=$(HDX_CI_CH_PORT) mongo=$(HDX_CI_MONGO_PORT) api=$(HDX_CI_API_PORT) prometheus=$(HDX_CI_PROMETHEUS_PORT))"
@mkdir -p $(HDX_CI_LOGS_DIR)
@bash scripts/ensure-dev-portal.sh
docker compose -p $(HDX_CI_PROJECT) -f ./docker-compose.ci.yml up -d
Expand Down
16 changes: 9 additions & 7 deletions agent_docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,18 +170,20 @@ without port conflicts.

Port mapping (base + slot):

| Service | Default port (slot 0) | Variable |
| --------------- | --------------------- | ----------------- |
| ClickHouse HTTP | 18123 | HDX_CI_CH_PORT |
| MongoDB | 39999 | HDX_CI_MONGO_PORT |
| API test server | 19000 | HDX_CI_API_PORT |
| OpAMP | 14320 | HDX_CI_OPAMP_PORT |
| Service | Default port (slot 0) | Variable |
| --------------- | --------------------- | ---------------------- |
| ClickHouse HTTP | 18123 | HDX_CI_CH_PORT |
| MongoDB | 39999 | HDX_CI_MONGO_PORT |
| API test server | 19000 | HDX_CI_API_PORT |
| OpAMP | 14320 | HDX_CI_OPAMP_PORT |
| Prometheus | 19100 | HDX_CI_PROMETHEUS_PORT |

**CI Testing Notes:**

- Uses separate Docker Compose configuration (`docker-compose.ci.yml`)
- Isolated test environment with unique `-p int-<slot>` project name
- Includes all necessary services (ClickHouse, MongoDB, OTel Collector)
- Includes all necessary services (ClickHouse, MongoDB, OTel Collector, and a
Prometheus that remote-reads `default.metrics_ts` for PromQL tests)
- Tests run against real database instances for accurate integration testing

### E2E Testing
Expand Down
10 changes: 10 additions & 0 deletions docker-compose.ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ services:
- '${HDX_CI_CH_PORT:-18123}:8123' # http api
networks:
- internal
prometheus:
image: prom/prometheus:v3.16.0
volumes:
- ./docker/prometheus/prometheus.ci.yml:/etc/prometheus/prometheus.yml
ports:
- '${HDX_CI_PROMETHEUS_PORT:-19100}:9090'
networks:
- internal
depends_on:
- ch-server
db:
image: mongo:5.0.32-focal
command: --port 29999
Expand Down
12 changes: 12 additions & 0 deletions docker/prometheus/prometheus.ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Integration tests: a Prometheus with no local data that reads every sample
# from ClickHouse's remote-read handler (default.metrics_ts, see
# docker/clickhouse/local/config.xml). PromQL tests can then compare Prometheus
# with ClickHouse's own PromQL engine on the same samples.
global:
scrape_interval: 1h

remote_read:
- url: http://ch-server:9363/read
# Without this, Prometheus skips the remote for the window its own
# (empty) TSDB would cover.
read_recent: true
5 changes: 4 additions & 1 deletion packages/api/.env.test
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Environment configuration for Integration tests, corresponding to ports defined in docker-compose.ci.yml
# For multi-agent worktree support, ports are configured via HDX_CI_* env vars.
# Set HDX_CI_CH_PORT, HDX_CI_MONGO_PORT, HDX_CI_API_PORT, HDX_CI_OPAMP_PORT
# Set HDX_CI_CH_PORT, HDX_CI_MONGO_PORT, HDX_CI_API_PORT, HDX_CI_OPAMP_PORT,
# HDX_CI_PROMETHEUS_PORT
# before running tests to use non-default ports (see Makefile dev-int target).
CLICKHOUSE_HOST=http://localhost:${HDX_CI_CH_PORT:-18123}
CLICKHOUSE_PASSWORD=api
Expand All @@ -11,6 +12,8 @@ MONGO_URI=mongodb://localhost:${HDX_CI_MONGO_PORT:-39999}/hyperdx-test
NODE_ENV=test
PORT=${HDX_CI_API_PORT:-19000}
OPAMP_PORT=${HDX_CI_OPAMP_PORT:-14320}
# The Prometheus in docker-compose.ci.yml, which remote-reads default.metrics_ts.
CI_PROMETHEUS_URL=http://localhost:${HDX_CI_PROMETHEUS_PORT:-19100}
# Default to only logging errors. Adjust if you need more verbosity.
# Note: the logger module is mocked in jest.setup.ts to suppress expected
# operational noise (validation errors, MCP tool errors, etc.) during tests.
Expand Down
24 changes: 23 additions & 1 deletion packages/api/src/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ export type TimeSeriesFixtureSeries = {
/** Series window in unix seconds; ignored when the table stores no bounds. */
startSec: number;
endSec: number;
/**
* `[unix seconds, value]` samples. With `withSamples`, written instead of
* the value 1 at `startSec` and `endSec`.
*/
samples?: [number, number][];
};

/**
Expand Down Expand Up @@ -392,7 +397,24 @@ export const seedTimeSeriesTagsTable = async ({
);
}
}
if (withSamples) {
const ownSamples = series.filter(s => s.samples?.length).length;
if (ownSamples > 0 && ownSamples < series.length) {
throw new Error('give every series its own samples, or none');
}
if (withSamples && ownSamples > 0) {
for (const s of series) {
const samples = (s.samples ?? [])
.map(([ts, value]) => `(${ts}, ${value})`)
.join(', ');
await executeTimeSeriesSqlCommand(
`INSERT INTO TABLE FUNCTION timeSeriesData('${database}', '${table}')
SELECT id, toDateTime64(sample.1, 3) AS timestamp, sample.2 AS value
FROM timeSeriesTags('${database}', '${table}')
ARRAY JOIN [${samples}] AS sample
WHERE metric_name = ${quoted(s.metricName)} AND tags = ${mapLiteral(s.tags)}`,
);
}
} else if (withSamples) {
const bounds = timeRanges
? `(SELECT id, min(min_time) AS min_time, max(max_time) AS max_time
FROM timeSeriesTimeRanges('${database}', '${table}') GROUP BY id)`
Expand Down
Loading
Loading