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

Commit de53154

Browse files
committed
added some unit tests
2 parents 65cd359 + facfb25 commit de53154

5 files changed

Lines changed: 449 additions & 14 deletions

File tree

google/cloud/bigtable/data/_async/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,8 @@ def __init__(
984984
exporter=client._gcp_metrics_exporter,
985985
instance_id=instance_id,
986986
table_id=table_id,
987-
app_profile_id=app_profile_id
987+
app_profile_id=app_profile_id,
988+
client_version=client._client_version(),
988989
)
989990
]
990991
)

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
from google.cloud.bigtable.data._metrics.data_model import CompletedAttemptMetric
2727
from google.cloud.bigtable.data._metrics.data_model import CompletedOperationMetric
2828

29+
# conversion factor for converting from nanoseconds to milliseconds
30+
NS_TO_MS= 1e6
2931

3032
class _OpenTelemetryInstruments:
3133
"""
@@ -124,14 +126,15 @@ def __init__(
124126
table_id: str,
125127
app_profile_id: str | None = None,
126128
client_uid: str | None = None,
129+
client_version: str | None = None,
127130
instruments: _OpenTelemetryInstruments = _OpenTelemetryInstruments(),
128-
**kwargs,
129131
):
130132
super().__init__()
131133
self.otel = instruments
134+
client_version = client_version or bigtable_version
132135
# fixed labels sent with each metric update
133136
self.shared_labels = {
134-
"client_name": f"python-bigtable/{bigtable_version}",
137+
"client_name": f"python-bigtable/{client_version}",
135138
"client_uid": client_uid or self._generate_client_uid(),
136139
"resource_instance": instance_id,
137140
"resource_table": table_id,
@@ -162,6 +165,7 @@ def on_operation_complete(self, op: CompletedOperationMetric) -> None:
162165
Update the metrics associated with a completed operation:
163166
- operation_latencies
164167
- retry_count
168+
- first_response_latencies
165169
"""
166170
labels = {
167171
"method": op.op_type.value,
@@ -173,14 +177,14 @@ def on_operation_complete(self, op: CompletedOperationMetric) -> None:
173177
is_streaming = str(op.is_streaming)
174178

175179
self.otel.operation_latencies.record(
176-
op.duration_ns / 1e6, {"streaming": is_streaming, **labels}
180+
op.duration_ns / NS_TO_MS, {"streaming": is_streaming, **labels}
177181
)
178182
if (
179183
op.op_type == OperationType.READ_ROWS
180184
and op.first_response_latency_ns is not None
181185
):
182186
self.otel.first_response_latencies.record(
183-
op.first_response_latency_ns / 1e6, labels
187+
op.first_response_latency_ns / NS_TO_MS, labels
184188
)
185189
# only record completed attempts if there were retries
186190
if op.completed_attempts:
@@ -192,7 +196,6 @@ def on_attempt_complete(
192196
"""
193197
Update the metrics associated with a completed attempt:
194198
- attempt_latencies
195-
- first_response_latencies
196199
- server_latencies
197200
- connectivity_error_count
198201
- application_latencies
@@ -208,19 +211,19 @@ def on_attempt_complete(
208211
is_streaming = str(op.is_streaming)
209212

210213
self.otel.attempt_latencies.record(
211-
attempt.duration_ns / 1e6, {"streaming": is_streaming, "status": status, **labels}
214+
attempt.duration_ns / NS_TO_MS, {"streaming": is_streaming, "status": status, **labels}
212215
)
213-
combined_throttling = attempt.grpc_throttling_time_ns / 1e6
216+
combined_throttling = attempt.grpc_throttling_time_ns / NS_TO_MS
214217
if not op.completed_attempts:
215218
# add flow control latency to first attempt's throttling latency
216-
combined_throttling += (op.flow_throttling_time_ns / 1e6 if op.flow_throttling_time_ns else 0)
219+
combined_throttling += (op.flow_throttling_time_ns / NS_TO_MS if op.flow_throttling_time_ns else 0)
217220
self.otel.throttling_latencies.record(combined_throttling, labels)
218221
self.otel.application_latencies.record(
219-
(attempt.application_blocking_time_ns + attempt.backoff_before_attempt_ns) / 1e6, labels
222+
(attempt.application_blocking_time_ns + attempt.backoff_before_attempt_ns) / NS_TO_MS, labels
220223
)
221224
if attempt.gfe_latency_ns is not None:
222225
self.otel.server_latencies.record(
223-
attempt.gfe_latency_ns / 1e6,
226+
attempt.gfe_latency_ns / NS_TO_MS,
224227
{"streaming": is_streaming, "status": status, **labels},
225228
)
226229
else:

tests/system/data/test_system_async.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,11 +1381,10 @@ def test_metric_existence(self, client, table_id, metrics_client, time_interval,
13811381
"""
13821382
Checks existence of each metric in Cloud Monitoring
13831383
"""
1384-
from google.cloud.bigtable import __version__ as CLIENT_VERSION
13851384
for m in methods:
13861385
metric_filter = (
13871386
f'metric.type = "bigtable.googleapis.com/client/{metric}" ' +
1388-
f'AND metric.labels.client_name = "python-bigtable/{CLIENT_VERSION}" '
1387+
f'AND metric.labels.client_name = "python-bigtable/{client._client_version()}" ' +
13891388
f'AND resource.labels.table = "{table_id}" '
13901389
)
13911390
results = list(metrics_client.list_time_series(

tests/unit/data/_async/test_client.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ def _make_client(cls, *args, use_emulator=True, **kwargs):
110110

111111
@CrossSync.pytest
112112
async def test_ctor(self):
113+
from google.cloud.bigtable.data._metrics.handlers.gcp_exporter import (
114+
BigtableMetricsExporter,
115+
)
116+
113117
expected_project = "project-id"
114118
expected_credentials = AnonymousCredentials()
115119
client = self._make_client(
@@ -123,6 +127,8 @@ async def test_ctor(self):
123127
assert client._channel_refresh_task is not None
124128
assert client.transport._credentials == expected_credentials
125129
assert isinstance(client._metrics_interceptor, CrossSync.MetricsInterceptor)
130+
assert client._gcp_metrics_exporter is not None
131+
assert isinstance(client._gcp_metrics_exporter, BigtableMetricsExporter)
126132
await client.close()
127133

128134
@CrossSync.pytest
@@ -189,6 +195,22 @@ async def test_ctor_dict_options(self):
189195
start_background_refresh.assert_called_once()
190196
await client.close()
191197

198+
@CrossSync.pytest
199+
async def test_metrics_exporter_init_shares_arguments(self):
200+
expected_credentials = AnonymousCredentials()
201+
expected_project = "custom_project"
202+
expected_options = client_options.ClientOptions()
203+
expected_options.credentials_file = None
204+
expected_options.quota_project_id = None
205+
with mock.patch("google.cloud.bigtable.data._metrics.handlers.gcp_exporter.BigtableMetricsExporter.__init__", return_value=None) as exporter_mock:
206+
async with self._make_client(project=expected_project, credentials=expected_credentials, client_options=expected_options):
207+
exporter_mock.assert_called_once_with(project_id=expected_project, credentials=expected_credentials, client_options=expected_options)
208+
209+
@CrossSync.pytest
210+
async def test_metrics_exporter_init_implicit_project(self):
211+
async with self._make_client() as client:
212+
assert client._gcp_metrics_exporter.project_id == client.project
213+
192214
@CrossSync.pytest
193215
async def test_veneer_grpc_headers(self):
194216
client_component = "data-async" if CrossSync.is_async else "data"
@@ -1163,6 +1185,9 @@ async def test_ctor(self):
11631185
from google.cloud.bigtable.data._metrics import (
11641186
BigtableClientSideMetricsController,
11651187
)
1188+
from google.cloud.bigtable.data._metrics import (
1189+
GoogleCloudMetricsHandler
1190+
)
11661191

11671192
expected_table_id = "table-id"
11681193
expected_instance_id = "instance-id"
@@ -1205,6 +1230,8 @@ async def test_ctor(self):
12051230
assert instance_key in client._active_instances
12061231
assert client._instance_owners[instance_key] == {id(table)}
12071232
assert isinstance(table._metrics, BigtableClientSideMetricsController)
1233+
assert len(table._metrics.handlers) == 1
1234+
assert isinstance(table._metrics.handlers[0], GoogleCloudMetricsHandler)
12081235
assert table.default_operation_timeout == expected_operation_timeout
12091236
assert table.default_attempt_timeout == expected_attempt_timeout
12101237
assert (
@@ -1495,7 +1522,7 @@ def _make_one(
14951522
async def test_ctor(self):
14961523
from google.cloud.bigtable.data._helpers import _WarmedInstanceKey
14971524
from google.cloud.bigtable.data._metrics import (
1498-
BigtableClientSideMetricsController,
1525+
BigtableClientSideMetricsController, GoogleCloudMetricsHandler
14991526
)
15001527

15011528
expected_table_id = "table-id"
@@ -1546,6 +1573,8 @@ async def test_ctor(self):
15461573
assert instance_key in client._active_instances
15471574
assert client._instance_owners[instance_key] == {id(view)}
15481575
assert isinstance(view._metrics, BigtableClientSideMetricsController)
1576+
assert len(view._metrics.handlers) == 1
1577+
assert isinstance(view._metrics.handlers[0], GoogleCloudMetricsHandler)
15491578
assert view.default_operation_timeout == expected_operation_timeout
15501579
assert view.default_attempt_timeout == expected_attempt_timeout
15511580
assert (

0 commit comments

Comments
 (0)