Skip to content

Commit 92156ac

Browse files
committed
use passed in project for all exporter tasks
1 parent 0950159 commit 92156ac

6 files changed

Lines changed: 50 additions & 57 deletions

File tree

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,6 @@ def __init__(
281281
try:
282282
# create a metrics exporter using the same client configuration
283283
exporter = BigtableMetricsExporter(
284-
project_id=self.project,
285284
credentials=credentials,
286285
client_options=client_options,
287286
)

packages/google-cloud-bigtable/google/cloud/bigtable/data/_metrics/handlers/gcp_exporter.py

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from __future__ import annotations
1616

17+
from collections import defaultdict
1718
import logging
1819
import time
1920

@@ -136,18 +137,13 @@ class BigtableMetricsExporter(MetricExporter):
136137
We must use a custom exporter because the public one doesn't support writing to internal
137138
metrics like `bigtable.googleapis.com/internal/client/`
138139
139-
Each GoogleCloudMetricsHandler will maintain its own exporter instance associated with the
140-
project_id it is configured with.
141-
142-
Args:
143-
project_id: GCP project id to associate metrics with
140+
Each GoogleCloudMetricsHandler will maintain its own exporter instance.
144141
"""
145142

146-
def __init__(self, project_id: str, *client_args, **client_kwargs):
143+
def __init__(self, *client_args, **client_kwargs):
147144
super().__init__()
148145
self.client = MetricServiceClient(*client_args, **client_kwargs)
149146
self.prefix = "bigtable.googleapis.com/internal/client"
150-
self.project_id = project_id
151147

152148
def export(
153149
self, metrics_data: MetricsData, timeout_millis: float = 10_000, **kwargs
@@ -158,19 +154,26 @@ def export(
158154
"""
159155
deadline = time.monotonic() + (timeout_millis / 1000)
160156
metric_kind = MetricDescriptor.MetricKind.CUMULATIVE
161-
all_series: list[TimeSeries] = []
157+
series_by_project: dict[str, list[TimeSeries]] = defaultdict(list)
162158
# process each metric from OTel format into Cloud Monitoring format
163159
for resource_metric in metrics_data.resource_metrics:
164160
for scope_metric in resource_metric.scope_metrics:
165161
for metric in scope_metric.metrics:
166162
for data_point in metric.data.data_points:
167163
if data_point.attributes:
164+
project_id = data_point.attributes.get(
165+
"resource_project", ""
166+
)
167+
if not project_id:
168+
_LOGGER.warning(
169+
"Missing resource_project attribute for metric %s",
170+
metric.name,
171+
)
172+
continue
168173
monitored_resource = MonitoredResource(
169174
type="bigtable_client_raw",
170175
labels={
171-
"project_id": data_point.attributes.get(
172-
"resource_project", ""
173-
),
176+
"project_id": project_id,
174177
"instance": data_point.attributes.get(
175178
"resource_instance", ""
176179
),
@@ -209,15 +212,16 @@ def export(
209212
),
210213
unit=metric.unit,
211214
)
212-
all_series.append(series)
215+
series_by_project[project_id].append(series)
213216
# send all metrics to Cloud Monitoring
214217
try:
215-
_LOGGER.debug(
216-
"Exporting %d time series to Cloud Monitoring for project %s",
217-
len(all_series),
218-
self.project_id,
219-
)
220-
self._batch_write(all_series, deadline)
218+
for project_id, series_list in series_by_project.items():
219+
_LOGGER.debug(
220+
"Exporting %d time series to Cloud Monitoring for project %s",
221+
len(series_list),
222+
project_id,
223+
)
224+
self._batch_write(project_id, series_list, deadline)
221225
return MetricExportResult.SUCCESS
222226
except Exception as e:
223227
_LOGGER.warning(
@@ -226,13 +230,18 @@ def export(
226230
return MetricExportResult.FAILURE
227231

228232
def _batch_write(
229-
self, series: list[TimeSeries], deadline=None, max_batch_size=200
233+
self,
234+
project_id: str,
235+
series: list[TimeSeries],
236+
deadline=None,
237+
max_batch_size=200,
230238
) -> None:
231239
"""
232240
Adapted from CloudMonitoringMetricsExporter
233241
https://github.com/GoogleCloudPlatform/opentelemetry-operations-python/blob/3668dfe7ce3b80dd01f42af72428de957b58b316/opentelemetry-exporter-gcp-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py#L82
234242
235243
Args:
244+
project_id: GCP project ID to write metrics to
236245
series: list of TimeSeries to write. Will be split into batches if necessary
237246
deadline: designates the time.time() at which to stop writing. If None, uses API default
238247
max_batch_size: maximum number of time series to write at once.
@@ -250,15 +259,15 @@ def _batch_write(
250259
batch = series[write_ind : write_ind + max_batch_size]
251260
self.client.create_service_time_series(
252261
CreateTimeSeriesRequest(
253-
name=f"projects/{self.project_id}",
262+
name=f"projects/{project_id}",
254263
time_series=batch,
255264
),
256265
timeout=timeout,
257266
)
258267
_LOGGER.debug(
259268
"Successfully wrote batch of %d time series to projects/%s",
260269
len(batch),
261-
self.project_id,
270+
project_id,
262271
)
263272
write_ind += max_batch_size
264273

packages/google-cloud-bigtable/google/cloud/bigtable/data/_sync_autogen/client.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ def __init__(
211211
else:
212212
try:
213213
exporter = BigtableMetricsExporter(
214-
project_id=self.project,
215214
credentials=credentials,
216215
client_options=client_options,
217216
)

packages/google-cloud-bigtable/tests/unit/data/_async/test_client.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,16 +245,10 @@ async def test_metrics_exporter_init_shares_arguments(
245245
use_emulator=False,
246246
):
247247
mock_called.assert_called_once_with(
248-
project_id=expected_project,
249248
credentials=expected_credentials,
250249
client_options=expected_options,
251250
)
252251

253-
@CrossSync.pytest
254-
async def test_metrics_exporter_init_implicit_project(self):
255-
async with self._make_client(use_emulator=False) as client:
256-
assert client._metrics.handlers[0]._exporter.project_id == client.project
257-
258252
@CrossSync.pytest
259253
@mock.patch(
260254
"google.cloud.bigtable.data._async.client.BigtableMetricsExporter",

packages/google-cloud-bigtable/tests/unit/data/_metrics/test_gcp_exporter_handler.py

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def _make_one(self, *args, **kwargs):
4848
def test_ctor_defaults(self, mock_auth):
4949
from google.cloud.bigtable import __version__ as CLIENT_VERSION
5050

51-
expected_exporter = BigtableMetricsExporter("project")
51+
expected_exporter = BigtableMetricsExporter()
5252
with mock.patch.object(
5353
GoogleCloudMetricsHandler, "_generate_client_uid"
5454
) as uid_mock:
@@ -64,7 +64,7 @@ def test_ctor_defaults(self, mock_auth):
6464
def test_ctor_explicit(self, mock_auth):
6565
expected_version = "my_version"
6666
expected_uid = "my_uid"
67-
expected_exporter = BigtableMetricsExporter("project")
67+
expected_exporter = BigtableMetricsExporter()
6868
handler = self._make_one(
6969
expected_exporter,
7070
client_uid=expected_uid,
@@ -133,22 +133,18 @@ def _make_one(self, *args, **kwargs):
133133
def test_ctor_defaults(self):
134134
from google.cloud.monitoring_v3 import MetricServiceClient
135135

136-
expected_project = "custom"
137-
instance = self._make_one(expected_project)
138-
assert instance.project_id == expected_project
136+
instance = self._make_one()
139137
assert instance.prefix == "bigtable.googleapis.com/internal/client"
140138
assert isinstance(instance.client, MetricServiceClient)
141139

142140
def test_ctor_mocks(self):
143-
expected_project = "custom"
144141
with mock.patch(
145142
"google.cloud.monitoring_v3.MetricServiceClient.__init__",
146143
return_value=None,
147144
) as mock_client:
148145
args = [mock.Mock(), object()]
149146
kwargs = {"a": "b"}
150-
instance = self._make_one(expected_project, *args, **kwargs)
151-
assert instance.project_id == expected_project
147+
instance = self._make_one(*args, **kwargs)
152148
assert instance.prefix == "bigtable.googleapis.com/internal/client"
153149
mock_client.assert_called_once_with(*args, **kwargs)
154150

@@ -161,7 +157,7 @@ def test_ctor_mocks(self):
161157
)
162158
def test__to_point_w_number(self, value, expected_field):
163159
"""Test that NumberDataPoint is converted to a Point correctly."""
164-
instance = self._make_one("project")
160+
instance = self._make_one()
165161
expected_start_time_nanos = 100
166162
expected_end_time_nanos = 200
167163
dp = NumberDataPoint(
@@ -182,7 +178,7 @@ def test__to_point_w_number(self, value, expected_field):
182178

183179
def test__to_point_w_histogram(self):
184180
"""Test that HistogramDataPoint is converted to a Point correctly."""
185-
instance = self._make_one("project")
181+
instance = self._make_one()
186182
expected_start_time_nanos = 100
187183
expected_end_time_nanos = 200
188184
expected_count = 10
@@ -220,7 +216,7 @@ def test__to_point_w_histogram(self):
220216

221217
def test__to_point_w_histogram_zero_count(self):
222218
"""Test that HistogramDataPoint with zero count is converted to a Point correctly."""
223-
instance = self._make_one("project")
219+
instance = self._make_one()
224220
dp = HistogramDataPoint(
225221
attributes={},
226222
start_time_unix_nano=100,
@@ -252,15 +248,16 @@ def test__batch_write(
252248
self, num_series, batch_size, expected_calls, expected_batch_sizes
253249
):
254250
"""Test that _batch_write splits series into batches correctly."""
255-
instance = self._make_one("project")
251+
instance = self._make_one()
256252
instance.client = mock.Mock()
257253
series = [TimeSeries() for _ in range(num_series)]
258-
instance._batch_write(series, max_batch_size=batch_size)
254+
instance._batch_write("project", series, max_batch_size=batch_size)
259255
assert instance.client.create_service_time_series.call_count == expected_calls
260256
for i, call in enumerate(
261257
instance.client.create_service_time_series.call_args_list
262258
):
263259
call_args, _ = call
260+
assert call_args[0].name == "projects/project"
264261
assert len(call_args[0].time_series) == expected_batch_sizes[i]
265262

266263
def test__batch_write_with_deadline(self):
@@ -269,12 +266,12 @@ def test__batch_write_with_deadline(self):
269266

270267
from google.api_core import gapic_v1
271268

272-
instance = self._make_one("project")
269+
instance = self._make_one()
273270
instance.client = mock.Mock()
274271
series = [TimeSeries() for _ in range(10)]
275272
# test with deadline
276273
deadline = time.monotonic() + 10
277-
instance._batch_write(series, deadline=deadline)
274+
instance._batch_write("project", series, deadline=deadline)
278275
(
279276
call_args,
280277
call_kwargs,
@@ -283,7 +280,7 @@ def test__batch_write_with_deadline(self):
283280
assert 9 < call_kwargs["timeout"] < 10
284281
# test without deadline
285282
instance.client.create_service_time_series.reset_mock()
286-
instance._batch_write(series, deadline=None)
283+
instance._batch_write("project", series, deadline=None)
287284
(
288285
call_args,
289286
call_kwargs,
@@ -294,7 +291,7 @@ def test__batch_write_with_deadline(self):
294291
def test_export(self):
295292
"""Test that export correctly converts metrics and calls _batch_write."""
296293
project_id = "project"
297-
instance = self._make_one(project_id)
294+
instance = self._make_one()
298295
instance._batch_write = mock.Mock()
299296
# create mock metrics data
300297
expected_value = 123
@@ -334,7 +331,8 @@ def test_export(self):
334331
instance._batch_write.assert_called_once()
335332
# check the TimeSeries passed to _batch_write
336333
call_args, call_kwargs = instance._batch_write.call_args_list[0]
337-
series_list = call_args[0]
334+
assert call_args[0] == project_id
335+
series_list = call_args[1]
338336
assert len(series_list) == 1
339337
series = series_list[0]
340338
assert series.metric.type == f"{instance.prefix}/operation_latencies"
@@ -352,7 +350,7 @@ def test_export(self):
352350

353351
def test_export_no_attributes(self):
354352
"""Test that export skips data points with no attributes."""
355-
instance = self._make_one("project")
353+
instance = self._make_one()
356354
instance._batch_write = mock.Mock()
357355
data_point = NumberDataPoint(
358356
attributes={}, start_time_unix_nano=100, time_unix_nano=200, value=123
@@ -376,18 +374,17 @@ def test_export_no_attributes(self):
376374
metrics_data = MetricsData(resource_metrics=[resource_metric])
377375
result = instance.export(metrics_data)
378376
assert result == MetricExportResult.SUCCESS
379-
instance._batch_write.assert_called_once()
380-
series_list = instance._batch_write.call_args[0][0]
381-
assert len(series_list) == 0
377+
instance._batch_write.assert_not_called()
382378

383379
def test_exception_in_export(self):
384380
"""
385381
make sure exceptions don't raise
386382
"""
387-
instance = self._make_one("project")
383+
instance = self._make_one()
388384
instance._batch_write = mock.Mock(side_effect=Exception("test"))
389385
# create mock metrics data with one valid data point
390386
attributes = {
387+
"resource_project": "project",
391388
"resource_instance": "instance1",
392389
"resource_cluster": "cluster1",
393390
"resource_table": "table1",

packages/google-cloud-bigtable/tests/unit/data/_sync_autogen/test_client.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,10 @@ def test_metrics_exporter_init_shares_arguments(
216216
use_emulator=False,
217217
):
218218
mock_called.assert_called_once_with(
219-
project_id=expected_project,
220219
credentials=expected_credentials,
221220
client_options=expected_options,
222221
)
223222

224-
def test_metrics_exporter_init_implicit_project(self):
225-
with self._make_client(use_emulator=False) as client:
226-
assert client._metrics.handlers[0]._exporter.project_id == client.project
227-
228223
@mock.patch(
229224
"google.cloud.bigtable.data._async.client.BigtableMetricsExporter",
230225
side_effect=Exception("Auth error"),

0 commit comments

Comments
 (0)