diff --git a/src/prometheus_fastapi_instrumentator/metrics.py b/src/prometheus_fastapi_instrumentator/metrics.py index abd5ec8..6fdd5df 100644 --- a/src/prometheus_fastapi_instrumentator/metrics.py +++ b/src/prometheus_fastapi_instrumentator/metrics.py @@ -27,6 +27,7 @@ def __init__( modified_handler: str, modified_status: str, modified_duration: float, + modified_duration_without_streaming: float, ): """Creates Info object that is used for instrumentation functions. @@ -42,6 +43,8 @@ def __init__( by instrumentator. For example grouping into `2xx`, `3xx` and so on. modified_duration (float): Latency representation after processing by instrumentator. For example rounding of decimals. Seconds. + modified_duration_without_streaming (float): Latency between request arrival and response starts (i.e. first chunk duration). + Excluding the streaming duration. """ self.request = request @@ -50,6 +53,7 @@ def __init__( self.modified_handler = modified_handler self.modified_status = modified_status self.modified_duration = modified_duration + self.modified_duration_without_streaming = modified_duration_without_streaming def _build_label_attribute_names( @@ -114,6 +118,7 @@ def latency( should_include_handler: bool = True, should_include_method: bool = True, should_include_status: bool = True, + should_exclude_streaming_duration: bool = False, buckets: Sequence[Union[float, str]] = Histogram.DEFAULT_BUCKETS, registry: CollectorRegistry = REGISTRY, ) -> Optional[Callable[[Info], None]]: @@ -184,15 +189,19 @@ def latency( ) def instrumentation(info: Info) -> None: + duration = info.modified_duration + if should_exclude_streaming_duration == True: + duration = info.modified_duration_without_streaming + if label_names: label_values = [ getattr(info, attribute_name) for attribute_name in info_attribute_names ] - METRIC.labels(*label_values).observe(info.modified_duration) + METRIC.labels(*label_values).observe(duration) else: - METRIC.observe(info.modified_duration) + METRIC.observe(duration) return instrumentation except ValueError as e: diff --git a/src/prometheus_fastapi_instrumentator/middleware.py b/src/prometheus_fastapi_instrumentator/middleware.py index e0d9a6b..643d5fb 100644 --- a/src/prometheus_fastapi_instrumentator/middleware.py +++ b/src/prometheus_fastapi_instrumentator/middleware.py @@ -140,6 +140,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: status_code = 500 headers = [] body = b"" + response_start_time = None # Message body collected for handlers matching body_handlers patterns. if any(pattern.search(handler) for pattern in self.body_handlers): @@ -158,9 +159,10 @@ async def send_wrapper(message: Message) -> None: async def send_wrapper(message: Message) -> None: if message["type"] == "http.response.start": - nonlocal status_code, headers + nonlocal status_code, headers, response_start_time headers = message["headers"] status_code = message["status"] + response_start_time = default_timer() await send(message) try: @@ -176,12 +178,17 @@ async def send_wrapper(message: Message) -> None: if not is_excluded: duration = max(default_timer() - start_time, 0) + duration_without_streaming = 0 + + if response_start_time: + duration_without_streaming = max(response_start_time - start_time, 0) if self.should_instrument_requests_inprogress: inprogress.dec() if self.should_round_latency_decimals: duration = round(duration, self.round_latency_decimals) + duration_without_streaming = round(duration_without_streaming, self.round_latency_decimals) if self.should_group_status_codes: status = status[0] + "xx" @@ -197,6 +204,7 @@ async def send_wrapper(message: Message) -> None: modified_handler=handler, modified_status=status, modified_duration=duration, + modified_duration_without_streaming=duration_without_streaming, ) for instrumentation in self.instrumentations: