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

Commit d73f379

Browse files
committed
moved first_response_latency to operation
1 parent de5d07b commit d73f379

2 files changed

Lines changed: 15 additions & 22 deletions

File tree

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License
1414
from __future__ import annotations
1515

16+
import time
1617
from functools import wraps
1718
from google.cloud.bigtable.data._metrics.data_model import (
1819
OPERATION_INTERCEPTOR_METADATA_KEY,
@@ -126,10 +127,18 @@ async def intercept_unary_unary(
126127
async def intercept_unary_stream(
127128
self, operation, continuation, client_call_details, request
128129
):
130+
# TODO: benchmark
129131
async def response_wrapper(call):
132+
has_first_response = operation.first_response_latency is not None
130133
encountered_exc = None
131134
try:
132135
async for response in call:
136+
# record time to first response. Currently only used for READ_ROWs
137+
if not has_first_response:
138+
operation.first_response_latency_ns = (
139+
time.monotonic_ns() - operation.start_time_ns
140+
)
141+
has_first_response = True
133142
yield response
134143

135144
except Exception as e:

google/cloud/bigtable/data/_metrics/data_model.py

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ class CompletedAttemptMetric:
8383

8484
duration_ns: int
8585
end_status: StatusCode
86-
first_response_latency_ns: int | None = None
8786
gfe_latency_ns: int | None = None
8887
application_blocking_time_ns: int = 0
8988
backoff_before_attempt_ns: int = 0
@@ -108,6 +107,7 @@ class CompletedOperationMetric:
108107
cluster_id: str
109108
zone: str
110109
is_streaming: bool
110+
first_response_latency_ns: int | None = None
111111
flow_throttling_time_ns: int = 0
112112

113113

@@ -120,9 +120,6 @@ class ActiveAttemptMetric:
120120

121121
# keep monotonic timestamps for active attempts
122122
start_time_ns: int = field(default_factory=time.monotonic_ns)
123-
# the time it takes to recieve the first response from the server, in nanoseconds
124-
# currently only tracked for ReadRows
125-
first_response_latency_ns: int | None = None
126123
# the time taken by the backend, in nanoseconds. Taken from response header
127124
gfe_latency_ns: int | None = None
128125
# time waiting on user to process the response, in nanoseconds
@@ -159,6 +156,10 @@ class ActiveOperationMetric:
159156
is_streaming: bool = False # only True for read_rows operations
160157
was_completed: bool = False
161158
handlers: list[MetricsHandler] = field(default_factory=list)
159+
# the time it takes to recieve the first response from the server, in nanoseconds
160+
# attached by interceptor
161+
# currently only tracked for ReadRows
162+
first_response_latency_ns: int | None = None
162163
# time waiting on flow control, in nanoseconds
163164
flow_throttling_time_ns: int = 0
164165

@@ -274,23 +275,6 @@ def _parse_response_metadata_blob(blob: bytes) -> Tuple[str, str] | None:
274275
# failed to parse metadata
275276
return None
276277

277-
def attempt_first_response(self) -> None:
278-
"""
279-
Called to mark the timestamp of the first completed response for the
280-
active attempt.
281-
282-
Assumes operation is in ACTIVE_ATTEMPT state.
283-
"""
284-
if self.state != OperationState.ACTIVE_ATTEMPT or self.active_attempt is None:
285-
return self._handle_error(
286-
INVALID_STATE_ERROR.format("attempt_first_response", self.state)
287-
)
288-
if self.active_attempt.first_response_latency_ns is not None:
289-
return self._handle_error("Attempt already received first response")
290-
self.active_attempt.first_response_latency_ns = (
291-
time.monotonic_ns() - self.active_attempt.start_time_ns
292-
)
293-
294278
def end_attempt_with_status(self, status: StatusCode | Exception) -> None:
295279
"""
296280
Called to mark the end of an attempt for the operation.
@@ -311,7 +295,6 @@ def end_attempt_with_status(self, status: StatusCode | Exception) -> None:
311295
if isinstance(status, Exception):
312296
status = self._exc_to_status(status)
313297
complete_attempt = CompletedAttemptMetric(
314-
first_response_latency_ns=self.active_attempt.first_response_latency_ns,
315298
duration_ns=time.monotonic_ns() - self.active_attempt.start_time_ns,
316299
end_status=status,
317300
gfe_latency_ns=self.active_attempt.gfe_latency_ns,
@@ -355,6 +338,7 @@ def end_with_status(self, status: StatusCode | Exception) -> None:
355338
cluster_id=self.cluster_id or DEFAULT_CLUSTER_ID,
356339
zone=self.zone or DEFAULT_ZONE,
357340
is_streaming=self.is_streaming,
341+
first_response_latency_ns=self.first_response_latency_ns,
358342
flow_throttling_time_ns=self.flow_throttling_time_ns,
359343
)
360344
for handler in self.handlers:

0 commit comments

Comments
 (0)