1515import os
1616import pytest
1717import uuid
18+ import datetime
1819
1920from grpc import StatusCode
2021
2526from google .cloud .bigtable .data ._metrics .data_model import (
2627 CompletedOperationMetric ,
2728 CompletedAttemptMetric ,
29+ OperationType ,
2830)
2931from google .cloud .bigtable .data .read_rows_query import ReadRowsQuery
3032from google .cloud .bigtable_v2 .types import ResponseParams
33+ from google .cloud .bigtable import __version__ as CLIENT_VERSION
3134
3235from 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" )
21932198class 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