Skip to content
Open
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
24 changes: 24 additions & 0 deletions .github/workflows/ci-tap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,31 @@ jobs:
run: |
docker exec clickhouse-test clickhouse-client --multiquery < docker/init/00-schema.sql

- name: Start OTel Collector
run: |
export OTEL_DATA_DIR=/tmp/psch-otel-data
mkdir -p "$OTEL_DATA_DIR"
chmod 777 "$OTEL_DATA_DIR"
docker compose -f docker/docker-compose.otel.yml up -d
for i in {1..30}; do
if curl -sf http://localhost:13133/ >/dev/null 2>&1; then
echo "OTel Collector ready"
exit 0
fi
echo "Waiting for OTel Collector... ($i/30)"
sleep 1
done
echo "OTel Collector not ready after 30s"
docker logs psch-otel-collector
exit 1

- name: Run TAP tests
run: |
export PATH="${{ env.PG_TAP_DIR }}/bin:$PATH"
export PG_REGRESS="${{ env.PG_TAP_DIR }}/lib/postgresql/pgxs/src/test/regress/pg_regress"
export PERL_LIB="${{ env.PG_TAP_DIR }}/lib/postgresql/pgxs/src/test/perl"
export OTEL_DATA_DIR=/tmp/psch-otel-data
export PROJECT_DIR="${{ github.workspace }}"
prove -v --timer -I "$PERL_LIB" -I t t/*.pl

- name: Upload logs on failure
Expand All @@ -113,3 +133,7 @@ jobs:
- name: Cleanup ClickHouse
if: always()
run: docker rm -f clickhouse-test 2>/dev/null || true

- name: Cleanup OTel Collector
if: always()
run: docker compose -f docker/docker-compose.otel.yml down -v 2>/dev/null || true
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*.a
.deps/
build/
CMakeCache.txt
CMakeFiles/

# Compile database
compile_commands.json
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "third_party/clickhouse-cpp"]
path = third_party/clickhouse-cpp
url = https://github.com/ClickHouse/clickhouse-cpp.git
[submodule "third_party/opentelemetry-cpp"]
path = third_party/opentelemetry-cpp
url = https://github.com/open-telemetry/opentelemetry-cpp.git
43 changes: 39 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,38 @@ option(WITH_OPENSSL "Enable OpenSSL for TLS connections" ON)
include(PgStatChOpenSSL)
pg_stat_ch_setup_openssl()

# Add clickhouse-cpp library (statically linked, compiled with PIC for shared library linking)
# Build all third-party libraries as static with PIC
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build clickhouse-cpp as static library" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build third-party libs as static" FORCE)

# ---------------------------------------------------------------------------
# Add opentelemetry-cpp FIRST (fetches gRPC + abseil via FetchContent).
# This must come before clickhouse-cpp to avoid duplicate abseil targets.
# ---------------------------------------------------------------------------
include(FetchContent)
set(WITH_OTLP_GRPC ON CACHE BOOL "Enable OTel OTLP gRPC exporter" FORCE)
set(WITH_OTLP_HTTP OFF CACHE BOOL "" FORCE)
set(BUILD_TESTING OFF CACHE BOOL "Disable tests" FORCE)
set(WITH_BENCHMARK OFF CACHE BOOL "Disable benchmarks" FORCE)
set(WITH_EXAMPLES OFF CACHE BOOL "Disable OTel examples" FORCE)
set(WITH_FUNC_TESTS OFF CACHE BOOL "Disable OTel functional tests" FORCE)
set(OPENTELEMETRY_INSTALL OFF CACHE BOOL "" FORCE)
set(WITH_ABSEIL ON CACHE BOOL "Use Abseil for OTel" FORCE)
# Always use vendored gRPC + abseil (never pick up system packages).
# This prevents find_package(gRPC) from short-circuiting FetchContent
# and leaving absl:: targets undefined.
set(CMAKE_DISABLE_FIND_PACKAGE_gRPC TRUE)
add_subdirectory(third_party/opentelemetry-cpp EXCLUDE_FROM_ALL)

# ---------------------------------------------------------------------------
# Add clickhouse-cpp AFTER opentelemetry-cpp.
# Use WITH_SYSTEM_ABSEIL so it finds the abseil already built by gRPC above
# (via our cmake/Findabsl.cmake shim).
# ---------------------------------------------------------------------------
set(WITH_SYSTEM_ABSEIL ON CACHE BOOL "Use abseil from gRPC build" FORCE)
set(WITH_OPENSSL ${WITH_OPENSSL} CACHE BOOL "Pass OpenSSL setting to clickhouse-cpp" FORCE)
set(BUILD_TESTS OFF CACHE BOOL "Disable clickhouse-cpp tests")
set(BUILD_BENCHMARK OFF CACHE BOOL "Disable clickhouse-cpp benchmarks")
set(BUILD_TESTS OFF CACHE BOOL "Disable clickhouse-cpp tests" FORCE)
set(BUILD_BENCHMARK OFF CACHE BOOL "Disable clickhouse-cpp benchmarks" FORCE)
add_subdirectory(third_party/clickhouse-cpp EXCLUDE_FROM_ALL)

# Collect source files
Expand All @@ -50,10 +76,19 @@ target_include_directories(pg_stat_ch SYSTEM PRIVATE
${CMAKE_SOURCE_DIR}/third_party/clickhouse-cpp
${CMAKE_SOURCE_DIR}/third_party/clickhouse-cpp/contrib/absl
${CMAKE_SOURCE_DIR}/third_party/clickhouse-cpp/contrib/zstd
${CMAKE_SOURCE_DIR}/third_party/opentelemetry-cpp/api/include
${CMAKE_SOURCE_DIR}/third_party/opentelemetry-cpp/sdk/include
${CMAKE_SOURCE_DIR}/third_party/opentelemetry-cpp/exporters/otlp/include
)
target_link_libraries(pg_stat_ch PRIVATE
PostgreSQLServer::PostgreSQLServer
clickhouse-cpp-lib
opentelemetry_api
opentelemetry_sdk
opentelemetry_exporter_otlp_grpc_metrics
opentelemetry_exporter_otlp_grpc_log
opentelemetry_metrics
opentelemetry_logs
)
if(WITH_OPENSSL)
target_link_libraries(pg_stat_ch PRIVATE OpenSSL::SSL OpenSSL::Crypto)
Expand Down
16 changes: 16 additions & 0 deletions cmake/Findabsl.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Findabsl.cmake - Bridge find_package(absl) to vendored abseil targets.
#
# clickhouse-cpp calls find_package(absl REQUIRED) when WITH_SYSTEM_ABSEIL=ON.
# The vendored gRPC (fetched by opentelemetry-cpp) builds abseil as a
# subdirectory, so there's no abslConfig.cmake on disk. This Module-mode
# find script checks that the required target exists and sets absl_FOUND.

if(TARGET absl::int128)
set(absl_FOUND TRUE)
else()
set(absl_FOUND FALSE)
if(absl_FIND_REQUIRED)
message(FATAL_ERROR "absl::int128 target not found. "
"Ensure opentelemetry-cpp (which fetches gRPC + abseil) is added before clickhouse-cpp.")
endif()
endif()
10 changes: 10 additions & 0 deletions docker/docker-compose.otel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:0.120.0
container_name: psch-otel-collector
ports:
- "4317:4317"
- "13133:13133"
volumes:
- ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro
- ${OTEL_DATA_DIR:-/tmp/psch-otel-data}:/otel-data
29 changes: 29 additions & 0 deletions docker/otel-collector-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317

exporters:
file/logs:
path: /otel-data/logs.jsonl
format: json
flush_interval: 500ms
file/metrics:
path: /otel-data/metrics.jsonl
format: json
flush_interval: 500ms

extensions:
health_check:
endpoint: 0.0.0.0:13133

service:
extensions: [health_check]
pipelines:
logs:
receivers: [otlp]
exporters: [file/logs]
metrics:
receivers: [otlp]
exporters: [file/metrics]
3 changes: 3 additions & 0 deletions include/config/guc.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
#define PG_STAT_CH_GUC_H

extern bool psch_enabled;
extern bool psch_use_otel;
extern char* psch_clickhouse_host;
extern int psch_clickhouse_port;
extern char* psch_clickhouse_user;
extern char* psch_clickhouse_password;
extern char* psch_clickhouse_database;
extern bool psch_clickhouse_use_tls;
extern bool psch_clickhouse_skip_tls_verify;
extern char* psch_otel_endpoint;
extern char* psch_hostname;
extern int psch_queue_capacity;
extern int psch_flush_interval_ms;
extern int psch_batch_max;
Expand Down
68 changes: 67 additions & 1 deletion scripts/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ usage() {
echo "Usage: $0 <PG_VERSION|PG_PATH> [test_type] [test_filter]"
echo " PG_VERSION: PostgreSQL version (16, 17, 18) - uses mise"
echo " PG_PATH: Path to local PostgreSQL installation"
echo " test_type: regress, tap, isolation, stress, clickhouse, or all (default: all)"
echo " test_type: regress, tap, isolation, stress, clickhouse, otel, or all (default: all)"
echo " test_filter: (tap only) pattern to match test files, e.g., '021' for t/*021*.pl"
echo ""
echo "Examples:"
Expand All @@ -27,6 +27,7 @@ usage() {
echo " $0 ../postgres/install_tap tap 021 # Run only t/*021*.pl test"
echo " $0 18 stress # Run only stress test against PG 18"
echo " $0 18 clickhouse # Run ClickHouse integration tests (requires Docker)"
echo " $0 18 otel # Run OTel integration tests (requires Docker)"
exit 1
}

Expand Down Expand Up @@ -250,6 +251,68 @@ run_clickhouse() {
t/010_clickhouse_export.pl t/011_clickhouse_reconnect.pl
}

# Run OTel integration tests
run_otel() {
log_info "Running OTel integration tests..."

# Check if Docker is available
if ! command -v docker &> /dev/null; then
log_error "Docker is required for OTel tests"
return 1
fi

# Check if OTel Collector is running, start if not
if ! curl -sf http://localhost:13133/ >/dev/null 2>&1; then
log_info "Starting OTel Collector container..."

export OTEL_DATA_DIR="${OTEL_DATA_DIR:-/tmp/psch-otel-data}"
mkdir -p "${OTEL_DATA_DIR}"
chmod 777 "${OTEL_DATA_DIR}"

docker compose -f docker/docker-compose.otel.yml up -d
# Wait for health check
for i in $(seq 1 30); do
if curl -sf http://localhost:13133/ >/dev/null 2>&1; then
break
fi
sleep 1
done

if ! curl -sf http://localhost:13133/ >/dev/null 2>&1; then
log_error "OTel Collector failed to start"
return 1
fi
fi

local perl_lib="${PG_LIB}/pgxs/src/test/perl"
if [[ ! -d "${perl_lib}" ]]; then
log_warn "PostgreSQL TAP test modules not found at ${perl_lib}"
log_warn "OTel tests require PostgreSQL built with --enable-tap-tests"
log_warn "Skipping OTel tests."
return 0
fi

local pg_regress="${PG_LIB}/pgxs/src/test/regress/pg_regress"
if [[ ! -x "${pg_regress}" ]]; then
log_warn "pg_regress not found at ${pg_regress}"
log_warn "Skipping OTel tests."
return 0
fi

# Clean up stale test data directories
rm -rf tmp_check

# Set env vars for the test helper module
export PROJECT_DIR="${PROJECT_DIR}"
export OTEL_DATA_DIR="${OTEL_DATA_DIR:-/tmp/psch-otel-data}"

# Run only OTel-related tests
PG_REGRESS="${pg_regress}" prove -v --timer \
-I "${perl_lib}" \
-I t \
t/024_otel_export.pl t/025_otel_reconnect.pl
}

# Main execution
case "${TEST_TYPE}" in
regress)
Expand All @@ -267,6 +330,9 @@ case "${TEST_TYPE}" in
clickhouse)
run_clickhouse
;;
otel)
run_otel
;;
all)
run_regress
run_tap
Expand Down
33 changes: 33 additions & 0 deletions src/config/guc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ extern "C" {

// GUC variable storage
bool psch_enabled = true;
bool psch_use_otel = false;
char* psch_clickhouse_host = nullptr;
int psch_clickhouse_port = 9000;
char* psch_clickhouse_user = nullptr;
char* psch_clickhouse_password = nullptr;
char* psch_clickhouse_database = nullptr;
bool psch_clickhouse_use_tls = false;
bool psch_clickhouse_skip_tls_verify = false;
char* psch_otel_endpoint = nullptr;
char* psch_hostname = nullptr;
int psch_queue_capacity = 131072;
int psch_flush_interval_ms = 200;
int psch_batch_max = 200000;
Expand Down Expand Up @@ -79,6 +82,16 @@ void PschInitGuc(void) {
0, // flags
nullptr, nullptr, nullptr); // hooks

DefineCustomBoolVariable(
"pg_stat_ch.use_otel",
"Send metrics through OpenTelemetry instead of ClickHouse.",
"When enabled, stats will be sent to an OTel endpoint instead of ClickHouse.",
&psch_use_otel,
false,
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

Inconsistent default value. The variable psch_use_otel is initialized to true at line 15, but the GUC definition at line 90 sets the boot value to false. This inconsistency could cause confusion. The initialization and the boot value should match.

Copilot uses AI. Check for mistakes.
PGC_POSTMASTER,
0,
nullptr, nullptr, nullptr);

DefineCustomStringVariable(
"pg_stat_ch.clickhouse_host",
"ClickHouse server hostname.",
Expand Down Expand Up @@ -150,6 +163,26 @@ void PschInitGuc(void) {
0,
nullptr, nullptr, nullptr);

DefineCustomStringVariable(
"pg_stat_ch.otel_endpoint",
"OpenTelemetry gRPC endpoint (host:port).",
nullptr,
&psch_otel_endpoint,
"localhost:4317",
PGC_POSTMASTER,
0,
nullptr, nullptr, nullptr);

DefineCustomStringVariable(
"pg_stat_ch.hostname",
"The hostname of the current machine.",
nullptr,
&psch_hostname,
"",
PGC_POSTMASTER,
0,
nullptr, nullptr, nullptr);

DefineCustomIntVariable(
"pg_stat_ch.queue_capacity",
"Maximum number of events in the shared memory queue (must be a power of 2).",
Expand Down
3 changes: 3 additions & 0 deletions src/config/guc.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ extern "C" {

// GUC variables (defined in guc.cc)
extern bool psch_enabled;
extern bool psch_use_otel;
extern char* psch_clickhouse_host;
extern int psch_clickhouse_port;
extern char* psch_clickhouse_user;
extern char* psch_clickhouse_password;
extern char* psch_clickhouse_database;
extern char* psch_otel_endpoint;
extern char* psch_hostname;
extern int psch_queue_capacity;
extern int psch_flush_interval_ms;
extern int psch_batch_max;
Expand Down
Loading
Loading