Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 69219e5

Browse files
committed
added export metrics tests
2 parents a6f16ec + e5958db commit 69219e5

4 files changed

Lines changed: 62 additions & 55 deletions

File tree

google/cloud/bigtable/data/_metrics/handlers/opentelemetry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def on_attempt_complete(
210210
is_streaming = str(op.is_streaming)
211211

212212
self.otel.attempt_latencies.record(
213-
attempt.duration_ns, {"streaming": is_streaming, "status": status, **labels}
213+
attempt.duration_ns / 1e6, {"streaming": is_streaming, "status": status, **labels}
214214
)
215215
combined_throttling = attempt.grpc_throttling_time_ns / 1e6
216216
if not op.completed_attempts:

noxfile.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
]
6767
SYSTEM_TEST_EXTERNAL_DEPENDENCIES: List[str] = [
6868
"pytest-asyncio==0.21.2",
69+
"pytest-order==1.3.0",
6970
BLACK_VERSION,
7071
"pyyaml==6.0.2",
7172
]

tests/system/data/setup_fixtures.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import pytest
2020
import os
2121
import uuid
22+
import datetime
2223

2324
from . import TEST_FAMILY, TEST_FAMILY_2, TEST_AGGREGATE_FAMILY
2425

@@ -86,6 +87,12 @@ def column_split_config():
8687
"""
8788
return [(num * 1000).to_bytes(8, "big") for num in range(1, 10)]
8889

90+
@pytest.fixture(scope="session")
91+
def start_timestamp():
92+
"""
93+
A timestamp taken before any tests are run. Used to fetch back metrics relevant to the tests
94+
"""
95+
return datetime.datetime.now(datetime.timezone.utc)
8996

9097
@pytest.fixture(scope="session")
9198
def table_id(
@@ -95,6 +102,7 @@ def table_id(
95102
column_family_config,
96103
init_table_id,
97104
column_split_config,
105+
start_timestamp,
98106
):
99107
"""
100108
Returns BIGTABLE_TEST_TABLE if set, otherwise creates a new temporary table for the test session
@@ -108,6 +116,7 @@ def table_id(
108116
- init_table_id: The table ID to give to the test table, if pre-initialized table is not given with BIGTABLE_TEST_TABLE.
109117
Supplied by the init_table_id fixture.
110118
- column_split_config: A list of row keys to use as initial splits when creating the test table.
119+
- start_timestamp: accessed when building table to ensure timestamp data is loaded before tests are run
111120
"""
112121
from google.api_core import exceptions
113122
from google.api_core import retry

tests/system/data/test_metrics_async.py

Lines changed: 51 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import os
1616
import pytest
1717
import uuid
18+
import datetime
1819

1920
from grpc import StatusCode
2021

@@ -25,9 +26,11 @@
2526
from google.cloud.bigtable.data._metrics.data_model import (
2627
CompletedOperationMetric,
2728
CompletedAttemptMetric,
29+
OperationType,
2830
)
2931
from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery
3032
from google.cloud.bigtable_v2.types import ResponseParams
33+
from google.cloud.bigtable import __version__ as CLIENT_VERSION
3134

3235
from google.cloud.bigtable.data._cross_sync import CrossSync
3336

@@ -218,7 +221,8 @@ async def temp_rows(self, table):
218221
@CrossSync.pytest_fixture(scope="session")
219222
async def table(self, client, table_id, instance_id, handler):
220223
async with client.get_table(instance_id, table_id) as table:
221-
table._metrics.add_handler(handler)
224+
# override handlers with custom test object
225+
table._metrics.handlers = [handler]
222226
yield table
223227

224228
@CrossSync.convert
@@ -2189,71 +2193,64 @@ async def test_check_and_mutate_row_failure_unauthorized(
21892193
)
21902194

21912195

2196+
@pytest.mark.order('last')
21922197
@CrossSync.convert_class(sync_name="TestExportedMetrics")
21932198
class TestExportedMetricsAsync(SystemTestRunner):
2199+
"""
2200+
Checks to make sure metrics were exported by tests
21942201
2195-
@CrossSync.drop
2196-
@pytest.fixture(scope="session")
2197-
def event_loop(self):
2198-
loop = asyncio.get_event_loop()
2199-
yield loop
2200-
loop.stop()
2201-
loop.close()
2202+
Runs at the end of test suite, to allow other tests to write metrics
2203+
"""
22022204

2203-
def _make_client(self):
2204-
project = os.getenv("GOOGLE_CLOUD_PROJECT") or None
2205-
return CrossSync.DataClient(project=project)
22062205

2207-
@CrossSync.convert
2208-
@CrossSync.pytest_fixture(scope="session")
2209-
async def client(self):
2210-
async with self._make_client() as client:
2206+
@pytest.fixture(scope="session")
2207+
def client(self):
2208+
from google.cloud.bigtable.data import BigtableDataClient
2209+
project = os.getenv("GOOGLE_CLOUD_PROJECT") or None
2210+
with BigtableDataClient(project=project) as client:
22112211
yield client
22122212

22132213
@pytest.fixture(scope="session")
22142214
def metrics_client(self, client):
22152215
yield client._gcp_metrics_exporter.client
22162216

2217+
@pytest.fixture(scope="session")
2218+
def time_interval(self, start_timestamp):
2219+
"""
2220+
Build a time interval between when system tests started, and the exported metric tests
22172221
2218-
@CrossSync.convert
2219-
@CrossSync.pytest_fixture(scope="function")
2220-
async def temp_rows(self, table):
2221-
builder = CrossSync.TempRowBuilder(table)
2222-
yield builder
2223-
await builder.delete_rows()
2222+
Optionally adds LOOKBACK_MINUTES value for testing
2223+
"""
2224+
end_time = datetime.datetime.now(datetime.timezone.utc)
2225+
LOOKBACK_MINUTES = os.getenv("LOOKBACK_MINUTES")
2226+
if LOOKBACK_MINUTES is not None:
2227+
print(f"running with LOOKBACK_MINUTES={LOOKBACK_MINUTES}")
2228+
start_timestamp = start_timestamp - datetime.timedelta(minutes=int(LOOKBACK_MINUTES))
2229+
return {"start_time": start_timestamp, "end_time": end_time}
22242230

2225-
@CrossSync.convert
2226-
@CrossSync.pytest_fixture(scope="session")
2227-
async def table(self, client, table_id, instance_id):
2228-
async with client.get_table(instance_id, table_id) as table:
2229-
yield table
22302231

2232+
@pytest.mark.parametrize("metric,methods", [
2233+
("attempt_latencies", [m.value for m in OperationType]),
2234+
("operation_latencies", [m.value for m in OperationType]),
2235+
("retry_count", [m.value for m in OperationType]),
2236+
("first_response_latencies", [OperationType.READ_ROWS]),
2237+
("server_latencies", [m.value for m in OperationType]),
2238+
("connectivity_error_count", [m.value for m in OperationType]),
2239+
("application_blocking_latencies", [OperationType.READ_ROWS]),
2240+
])
22312241
@CrossSync.pytest
2232-
async def test_read_rows(self, table, temp_rows, metrics_client):
2233-
from datetime import datetime, timedelta, timezone
2234-
from google.cloud import monitoring_v3
2235-
import google.cloud.bigtable
2236-
2237-
await temp_rows.add_row(b"row_key_1")
2238-
await temp_rows.add_row(b"row_key_2")
2239-
row_list = await table.read_rows(ReadRowsQuery())
2240-
# read back metrics
2241-
2242-
# 1. Define the Time Interval
2243-
now = datetime.now(timezone.utc)
2244-
# The end time is inclusive
2245-
end_time = now
2246-
# The start time is exclusive, for an interval (startTime, endTime]
2247-
start_time = now - timedelta(minutes=5)
2248-
2249-
interval = {"start_time": start_time, "end_time": end_time}
2250-
metric_filter = (
2251-
f'metric.type = "bigtable.googleapis.com/client/attempt_latencies" AND metric.labels.client_name = "python-bigtable/{google.cloud.bigtable.__version__}"'
2252-
)
2253-
results = metrics_client.list_time_series(
2254-
name=f"projects/{table.client.project}",
2255-
filter=metric_filter,
2256-
interval=interval,
2257-
view=monitoring_v3.ListTimeSeriesRequest.TimeSeriesView.FULL,
2258-
)
2259-
print(results)
2242+
async def test_metric_existence(self, table_id, client, metrics_client, time_interval, metric, methods):
2243+
print(f"using table: {table_id}")
2244+
for m in methods:
2245+
metric_filter = (
2246+
f'metric.type = "bigtable.googleapis.com/client/{metric}" ' +
2247+
f'AND metric.labels.client_name = "python-bigtable/{CLIENT_VERSION}" ' +
2248+
f'AND resource.labels.table = "{table_id}" '
2249+
)
2250+
results = list(metrics_client.list_time_series(
2251+
name=f"projects/{client.project}",
2252+
filter=metric_filter,
2253+
interval=time_interval,
2254+
view=0,
2255+
))
2256+
assert len(results) > 0, f"No data found for {metric} {m}"

0 commit comments

Comments
 (0)