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