From 5b21e7c0f9f5ada06aa758d67673aea51e3854a8 Mon Sep 17 00:00:00 2001 From: Lujeni Date: Tue, 7 Jan 2025 21:30:24 +0100 Subject: [PATCH 1/2] build(pre-commit): initial support --- .pre-commit-config.yaml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..2d88c93 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +default_language_version: + python: python3.12 + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace # Trims trailing whitespace. + - id: end-of-file-fixer # Makes sure files end in a newline and only a newline. + - id: check-added-large-files # Prevent giant files from being committed. + - id: requirements-txt-fixer + - id: check-merge-conflict + + - repo: https://github.com/commitizen-tools/commitizen + rev: v3.5.3 + hooks: + - id: commitizen + stages: + - commit-msg + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.1 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.13.0 + hooks: + - id: mypy + entry: mypy + files: send/ + language: system + args: [--install-types, --non-interactive] From 88f575b149f384535a19462b6df88a6b13328046 Mon Sep 17 00:00:00 2001 From: Lujeni Date: Tue, 7 Jan 2025 21:30:44 +0100 Subject: [PATCH 2/2] test(metrics): initial support --- Dockerfile | 1 - README.md | 1 - requirements-dev.txt | 3 +- tests/conftest.py | 89 +++++++++++++++++++++++++++ tests/test_typesense_exporter.py | 101 ++++++++++++++++++++++--------- 5 files changed, 163 insertions(+), 32 deletions(-) diff --git a/Dockerfile b/Dockerfile index 94e91b8..a0b6526 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,4 +18,3 @@ USER nobody:nogroup EXPOSE 8000 CMD ["python", "typesense_exporter.py", "--port=8000"] - diff --git a/README.md b/README.md index d456238..9eda6bb 100644 --- a/README.md +++ b/README.md @@ -82,4 +82,3 @@ This design guarantees that metrics are always up-to-date at scrape time (with n ## License This exporter is released under the MIT License. See LICENSE for details (or replace with your preferred license). - diff --git a/requirements-dev.txt b/requirements-dev.txt index a6b8462..babaa15 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,6 @@ mypy>=1.14 +pytest>=7.0 pytest-asyncio>=0.23.5 pytest-cov>=4.1 -pytest>=7.0 +requests-mock==1.12.1 types-requests>=2.32 diff --git a/tests/conftest.py b/tests/conftest.py index e69de29..ea13d84 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -0,0 +1,89 @@ +import pytest +from typesense_exporter import TypesenseCollector + + +@pytest.fixture +def response_metrics_json(): + yield { + "system_cpu1_active_percentage": "9.09", + "system_cpu2_active_percentage": "0.00", + "system_cpu3_active_percentage": "0.00", + "system_cpu4_active_percentage": "0.00", + "system_cpu_active_percentage": "0.00", + "system_disk_total_bytes": "102888095744", + "system_disk_used_bytes": "4177268736", + "system_memory_total_bytes": "16764186624", + "system_memory_total_swap_bytes": "0", + "system_memory_used_bytes": "3234148352", + "system_memory_used_swap_bytes": "0", + "system_network_received_bytes": "6534814741", + "system_network_sent_bytes": "4613106962", + "typesense_memory_active_bytes": "51126272", + "typesense_memory_allocated_bytes": "43065104", + "typesense_memory_fragmentation_ratio": "0.16", + "typesense_memory_mapped_bytes": "97370112", + "typesense_memory_metadata_bytes": "9009280", + "typesense_memory_resident_bytes": "51126272", + "typesense_memory_retained_bytes": "30556160", + } + + +@pytest.fixture +def response_stats_json(): + yield { + "delete_latency_ms": 0, + "delete_requests_per_second": 0, + "import_latency_ms": 0, + "import_requests_per_second": 0, + "latency_ms": {"GET /health": 0.0, "GET /status": 0.0}, + "overloaded_requests_per_second": 0, + "pending_write_batches": 0, + "requests_per_second": {"GET /health": 1.5, "GET /status": 0.6}, + "search_latency_ms": 0, + "search_requests_per_second": 0, + "total_requests_per_second": 2.1, + "write_latency_ms": 0, + "write_requests_per_second": 0, + } + + +@pytest.fixture +def response_debug_json(): + yield {"state": 1, "version": "0.24.0"} + + +@pytest.fixture +def response_collections_json(): + yield [ + {"name": "products", "num_documents": 100}, + {"name": "users", "num_documents": 50}, + ] + + +@pytest.fixture(autouse=True) +def mock_typesense_api( + requests_mock, + response_metrics_json, + response_stats_json, + response_debug_json, + response_collections_json, +): + base_url = "http://localhost:8108" + + requests_mock.get(f"{base_url}/metrics.json", json=response_metrics_json) + requests_mock.get(f"{base_url}/stats.json", json=response_stats_json) + requests_mock.get(f"{base_url}/debug", json=response_debug_json) + requests_mock.get(f"{base_url}/collections", json=response_collections_json) + + yield requests_mock + + +@pytest.fixture +def typesense_collector(): + return TypesenseCollector( + typesense_api_key="123", + metrics_url="http://localhost:8108/metrics.json", + stats_url="http://localhost:8108/stats.json", + debug_url="http://localhost:8108/debug", + nodes=[{"host": "localhost", "port": "8108", "protocol": "http"}], + ) diff --git a/tests/test_typesense_exporter.py b/tests/test_typesense_exporter.py index afd390c..f796f97 100644 --- a/tests/test_typesense_exporter.py +++ b/tests/test_typesense_exporter.py @@ -2,36 +2,79 @@ from typesense_exporter import parse_nodes_from_str -@pytest.mark.parametrize("test_input,expected,protocol,description", [ - ( - "localhost:8108", - [{"host": "localhost", "port": "8108", "protocol": "https"}], - "https", - "single node with port" - ), - ( - "host1:8108,host2:8108", - [ - {"host": "host1", "port": "8108", "protocol": "https"}, - {"host": "host2", "port": "8108", "protocol": "https"} - ], - "https", - "multiple nodes" - ), - ( - "localhost", - [{"host": "localhost", "port": "8108", "protocol": "https"}], - "https", - "default port" - ), - ( - "localhost:8108", - [{"host": "localhost", "port": "8108", "protocol": "http"}], - "http", - "custom protocol" - ), -]) +@pytest.mark.parametrize( + "test_input,expected,protocol,description", + [ + ( + "localhost:8108", + [{"host": "localhost", "port": "8108", "protocol": "https"}], + "https", + "single node with port", + ), + ( + "host1:8108,host2:8108", + [ + {"host": "host1", "port": "8108", "protocol": "https"}, + {"host": "host2", "port": "8108", "protocol": "https"}, + ], + "https", + "multiple nodes", + ), + ( + "localhost", + [{"host": "localhost", "port": "8108", "protocol": "https"}], + "https", + "default port", + ), + ( + "localhost:8108", + [{"host": "localhost", "port": "8108", "protocol": "http"}], + "http", + "custom protocol", + ), + ], +) def test_parse_nodes_from_str(test_input, expected, protocol, description): nodes = parse_nodes_from_str(test_input, default_protocol=protocol) assert len(nodes) == len(expected) assert nodes == expected + + +def test_typesense_collector_metrics(typesense_collector): + metrics = list(typesense_collector._collect_metrics_json()) + assert len(metrics) == 20 + + +def test_typesense_collector_stats(typesense_collector): + metrics = list(typesense_collector._collect_stats_json()) + assert len(metrics) == 13 + + +def test_typesense_collector_json(typesense_collector): + metrics = list(typesense_collector._collect_debug_json()) + assert len(metrics) == 1 + + +def test_typesense_collector_collections(typesense_collector): + metrics = list(typesense_collector._collect_collections()) + assert len(metrics) == 1 + + metric_family = metrics[0] + assert metric_family.name == "typesense_collection_documents" + assert ( + metric_family.documentation + == "Number of documents in each Typesense collection" + ) + + assert len(metric_family.samples) == 2 + + sample_1 = metric_family.samples[0] + sample_2 = metric_family.samples[1] + + assert sample_1.name == "typesense_collection_documents" + assert sample_1.labels == {"collection_name": "products"} + assert sample_1.value == 100.0 + + assert sample_2.name == "typesense_collection_documents" + assert sample_2.labels == {"collection_name": "users"} + assert sample_2.value == 50.0