diff --git a/.devenv/docker/clickhouse/compose.yaml b/.devenv/docker/clickhouse/compose.yaml
index 5c2fbb3660..e6cef658c8 100644
--- a/.devenv/docker/clickhouse/compose.yaml
+++ b/.devenv/docker/clickhouse/compose.yaml
@@ -1,4 +1,22 @@
services:
+ init-clickhouse:
+ image: clickhouse/clickhouse-server:25.5.6
+ container_name: init-clickhouse
+ command:
+ - bash
+ - -c
+ - |
+ version="v0.0.1"
+ node_os=$$(uname -s | tr '[:upper:]' '[:lower:]')
+ node_arch=$$(uname -m | sed s/aarch64/arm64/ | sed s/x86_64/amd64/)
+ echo "Fetching histogram-binary for $${node_os}/$${node_arch}"
+ cd /tmp
+ wget -O histogram-quantile.tar.gz "https://github.com/SigNoz/signoz/releases/download/histogram-quantile%2F$${version}/histogram-quantile_$${node_os}_$${node_arch}.tar.gz"
+ tar -xvzf histogram-quantile.tar.gz
+ mv histogram-quantile /var/lib/clickhouse/user_scripts/histogramQuantile
+ restart: on-failure
+ volumes:
+ - ${PWD}/fs/tmp/var/lib/clickhouse/user_scripts/:/var/lib/clickhouse/user_scripts/
clickhouse:
image: clickhouse/clickhouse-server:25.5.6
container_name: clickhouse
@@ -7,6 +25,7 @@ services:
- ${PWD}/fs/etc/clickhouse-server/users.d/users.xml:/etc/clickhouse-server/users.d/users.xml
- ${PWD}/fs/tmp/var/lib/clickhouse/:/var/lib/clickhouse/
- ${PWD}/fs/tmp/var/lib/clickhouse/user_scripts/:/var/lib/clickhouse/user_scripts/
+ - ${PWD}/../../../deploy/common/clickhouse/custom-function.xml:/etc/clickhouse-server/custom-function.xml
ports:
- '127.0.0.1:8123:8123'
- '127.0.0.1:9000:9000'
@@ -22,7 +41,10 @@ services:
timeout: 5s
retries: 3
depends_on:
- - zookeeper
+ init-clickhouse:
+ condition: service_completed_successfully
+ zookeeper:
+ condition: service_healthy
environment:
- CLICKHOUSE_SKIP_USER_SETUP=1
zookeeper:
diff --git a/.devenv/docker/clickhouse/fs/etc/clickhouse-server/config.d/config.xml b/.devenv/docker/clickhouse/fs/etc/clickhouse-server/config.d/config.xml
index d942827b85..4c319d1f17 100644
--- a/.devenv/docker/clickhouse/fs/etc/clickhouse-server/config.d/config.xml
+++ b/.devenv/docker/clickhouse/fs/etc/clickhouse-server/config.d/config.xml
@@ -44,4 +44,6 @@
01
01
+ *function.xml
+ /var/lib/clickhouse/user_scripts/
\ No newline at end of file
diff --git a/tests/integration/fixtures/clickhouse.py b/tests/integration/fixtures/clickhouse.py
index 7dff90fcbd..4f8f0b6cb5 100644
--- a/tests/integration/fixtures/clickhouse.py
+++ b/tests/integration/fixtures/clickhouse.py
@@ -75,6 +75,9 @@ def clickhouse(
+ *function.xml
+ /var/lib/clickhouse/user_scripts/
+
/clickhouse/task_queue/ddl
default
@@ -117,17 +120,73 @@ def clickhouse(
"""
+ custom_function_config = """
+
+
+ executable
+ histogramQuantile
+ Float64
+
+ Array(Float64)
+ buckets
+
+
+ Array(Float64)
+ counts
+
+
+ Float64
+ quantile
+
+ CSV
+ ./histogramQuantile
+
+
+ """
+
tmp_dir = tmpfs("clickhouse")
cluster_config_file_path = os.path.join(tmp_dir, "cluster.xml")
with open(cluster_config_file_path, "w", encoding="utf-8") as f:
f.write(cluster_config)
+ custom_function_file_path = os.path.join(tmp_dir, "custom-function.xml")
+ with open(custom_function_file_path, "w", encoding="utf-8") as f:
+ f.write(custom_function_config)
+
container.with_volume_mapping(
cluster_config_file_path, "/etc/clickhouse-server/config.d/cluster.xml"
)
+ container.with_volume_mapping(
+ custom_function_file_path,
+ "/etc/clickhouse-server/custom-function.xml",
+ )
container.with_network(network)
container.start()
+ # Download and install the histogramQuantile binary
+ wrapped = container.get_wrapped_container()
+ exit_code, output = wrapped.exec_run(
+ [
+ "bash",
+ "-c",
+ (
+ 'version="v0.0.1" && '
+ 'node_os=$(uname -s | tr "[:upper:]" "[:lower:]") && '
+ 'node_arch=$(uname -m | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) && '
+ "cd /tmp && "
+ 'wget -O histogram-quantile.tar.gz "https://github.com/SigNoz/signoz/releases/download/histogram-quantile%2F${version}/histogram-quantile_${node_os}_${node_arch}.tar.gz" && '
+ "tar -xzf histogram-quantile.tar.gz && "
+ "mkdir -p /var/lib/clickhouse/user_scripts && "
+ "mv histogram-quantile /var/lib/clickhouse/user_scripts/histogramQuantile && "
+ "chmod +x /var/lib/clickhouse/user_scripts/histogramQuantile"
+ ),
+ ],
+ )
+ if exit_code != 0:
+ raise RuntimeError(
+ f"Failed to install histogramQuantile binary: {output.decode()}"
+ )
+
connection = clickhouse_connect.get_client(
user=container.username,
password=container.password,
diff --git a/tests/integration/src/querier/08_metrics_histogram.py b/tests/integration/src/querier/08_metrics_histogram.py
index 3ab73ea049..36da313374 100644
--- a/tests/integration/src/querier/08_metrics_histogram.py
+++ b/tests/integration/src/querier/08_metrics_histogram.py
@@ -372,3 +372,153 @@ def test_histogram_count_no_param(
values[1]["value"] == first_values[le]
) ## to keep parallel to the cumulative test cases, first_value refers to the value at 10:02
assert values[-1]["value"] == last_values[le]
+
+@pytest.mark.parametrize(
+ "space_agg, zeroth_value, first_value, last_value",
+ [
+ ("p50", 500, 818.182, 550.725),
+ ("p75", 750, 3000, 826.087),
+ ("p90", 900, 6400, 991.304),
+ ("p95", 950, 8000, 4200),
+ ("p99", 990, 8000, 8000),
+ ],
+)
+def test_histogram_percentile_for_all_services(
+ signoz: types.SigNoz,
+ create_user_admin: None, # pylint: disable=unused-argument
+ get_token: Callable[[str, str], str],
+ insert_metrics: Callable[[List[Metrics]], None],
+ space_agg: str,
+ zeroth_value: float,
+ first_value: float,
+ last_value: float,
+) -> None:
+ now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
+ start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
+ end_ms = int(now.timestamp() * 1000)
+ metric_name = f"test_{space_agg}_bucket"
+
+ metrics = Metrics.load_from_file(
+ FILE,
+ base_time=now - timedelta(minutes=60),
+ metric_name_override=metric_name,
+ )
+ insert_metrics(metrics)
+
+ token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
+ query = build_builder_query(
+ "A",
+ metric_name,
+ "doesnotreallymatter",
+ space_agg,
+ )
+
+ response = make_query_request(signoz, token, start_ms, end_ms, [query])
+ assert response.status_code == HTTPStatus.OK
+
+ data = response.json()
+ result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
+ assert len(result_values) == 60
+ assert result_values[0]["value"] == zeroth_value
+ assert result_values[1]["value"] == first_value
+ assert result_values[-1]["value"] == last_value
+
+@pytest.mark.parametrize(
+ "space_agg, first_value, last_value",
+ [
+ ("p50", 818.182, 550.725),
+ ("p75", 3000, 826.087),
+ ("p90", 6400, 991.304),
+ ("p95", 8000, 4200),
+ ("p99", 8000, 8000),
+ ],
+)
+def test_histogram_percentile_for_cumulative_service(
+ signoz: types.SigNoz,
+ create_user_admin: None, # pylint: disable=unused-argument
+ get_token: Callable[[str, str], str],
+ insert_metrics: Callable[[List[Metrics]], None],
+ space_agg: str,
+ first_value: float,
+ last_value: float,
+) -> None:
+ now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
+ start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
+ end_ms = int(now.timestamp() * 1000)
+ metric_name = f"test_{space_agg}_cumulative_bucket"
+
+ metrics = Metrics.load_from_file(
+ FILE,
+ base_time=now - timedelta(minutes=60),
+ metric_name_override=metric_name,
+ )
+ insert_metrics(metrics)
+
+ token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
+ query = build_builder_query(
+ "A",
+ metric_name,
+ "doesnotreallymatter",
+ space_agg,
+ filter_expression='service = "api"',
+ )
+
+ response = make_query_request(signoz, token, start_ms, end_ms, [query])
+ assert response.status_code == HTTPStatus.OK
+
+ data = response.json()
+ result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
+ assert len(result_values) == 59
+ assert result_values[0]["value"] == first_value
+ assert result_values[-1]["value"] == last_value
+
+@pytest.mark.parametrize(
+ "space_agg, zeroth_value, first_value, last_value",
+ [
+ ("p50", 500, 818.182, 550.725),
+ ("p75", 750, 3000, 826.087),
+ ("p90", 900, 6400, 991.304),
+ ("p95", 950, 8000, 4200),
+ ("p99", 990, 8000, 8000),
+ ],
+)
+def test_histogram_percentile_for_delta_service(
+ signoz: types.SigNoz,
+ create_user_admin: None, # pylint: disable=unused-argument
+ get_token: Callable[[str, str], str],
+ insert_metrics: Callable[[List[Metrics]], None],
+ space_agg: str,
+ zeroth_value: float,
+ first_value: float,
+ last_value: float,
+) -> None:
+ now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
+ start_ms = int((now - timedelta(minutes=65)).timestamp() * 1000)
+ end_ms = int(now.timestamp() * 1000)
+ metric_name = f"test_{space_agg}_bucket"
+
+ metrics = Metrics.load_from_file(
+ FILE,
+ base_time=now - timedelta(minutes=60),
+ metric_name_override=metric_name,
+ )
+ insert_metrics(metrics)
+
+ token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
+ query = build_builder_query(
+ "A",
+ metric_name,
+ "doesnotreallymatter",
+ space_agg,
+ filter_expression='service = "web"',
+ )
+
+ response = make_query_request(signoz, token, start_ms, end_ms, [query])
+ assert response.status_code == HTTPStatus.OK
+
+ data = response.json()
+ result_values = sorted(get_series_values(data, "A"), key=lambda x: x["timestamp"])
+ assert len(result_values) == 60
+ assert result_values[0]["value"] == zeroth_value
+ assert result_values[1]["value"] == first_value
+ assert result_values[-1]["value"] == last_value
\ No newline at end of file