11from __future__ import annotations
22
3- from typing import Any , Dict , List , Tuple
4- from unittest import TestCase
3+ from typing import Any , Dict , List
54from unittest .mock import patch
65
7- from opentelemetry .sdk .metrics import MeterProvider
8- from opentelemetry .sdk .metrics .export import InMemoryMetricReader
9- from opentelemetry .sdk .trace import TracerProvider
10- from opentelemetry .sdk .trace .export import SimpleSpanProcessor
11- from opentelemetry .sdk .trace .export .in_memory_span_exporter import (
12- InMemorySpanExporter ,
13- )
146from opentelemetry .semconv ._incubating .attributes import (
157 gen_ai_attributes as GenAI ,
168)
179from opentelemetry .semconv .schemas import Schemas
10+ from opentelemetry .test .test_base import TestBase
1811from opentelemetry .util .genai .handler import TelemetryHandler
1912from opentelemetry .util .genai .types import Error , LLMInvocation
2013
2114_DEFAULT_SCHEMA_URL = Schemas .V1_37_0 .value
2215
16+ SCOPE = "opentelemetry.util.genai.handler"
2317
24- class TelemetryHandlerMetricsTest (TestCase ):
25- def setUp (self ) -> None :
26- self .metric_reader = InMemoryMetricReader ()
27- self .meter_provider = MeterProvider (
28- metric_readers = [self .metric_reader ]
29- )
30- self .span_exporter = InMemorySpanExporter ()
31- self .tracer_provider = TracerProvider ()
32- self .tracer_provider .add_span_processor (
33- SimpleSpanProcessor (self .span_exporter )
34- )
3518
19+ class TelemetryHandlerMetricsTest (TestBase ):
3620 def test_stop_llm_records_duration_and_tokens (self ) -> None :
3721 handler = TelemetryHandler (
3822 tracer_provider = self .tracer_provider ,
@@ -52,10 +36,8 @@ def test_stop_llm_records_duration_and_tokens(self) -> None:
5236 ):
5337 handler .stop_llm (invocation )
5438
55- metrics , resource_metrics = self ._harvest_metrics ()
56- self ._assert_metric_scope_schema_urls (
57- resource_metrics , _DEFAULT_SCHEMA_URL
58- )
39+ self ._assert_metric_scope_schema_urls (_DEFAULT_SCHEMA_URL )
40+ metrics = self ._harvest_metrics ()
5941 self .assertIn ("gen_ai.client.operation.duration" , metrics )
6042 duration_points = metrics ["gen_ai.client.operation.duration" ]
6143 self .assertEqual (len (duration_points ), 1 )
@@ -110,10 +92,8 @@ def test_stop_llm_records_duration_and_tokens_with_additional_attributes(
11092 invocation .attributes = {"should not be on metrics" : "value" }
11193 handler .stop_llm (invocation )
11294
113- metrics , resource_metrics = self ._harvest_metrics ()
114- self ._assert_metric_scope_schema_urls (
115- resource_metrics , _DEFAULT_SCHEMA_URL
116- )
95+ self ._assert_metric_scope_schema_urls (_DEFAULT_SCHEMA_URL )
96+ metrics = self ._harvest_metrics ()
11797 self .assertIn ("gen_ai.client.operation.duration" , metrics )
11898 duration_points = metrics ["gen_ai.client.operation.duration" ]
11999 self .assertIn ("gen_ai.client.token.usage" , metrics )
@@ -148,10 +128,8 @@ def test_fail_llm_records_error_and_available_tokens(self) -> None:
148128 ):
149129 handler .fail_llm (invocation , error )
150130
151- metrics , resource_metrics = self ._harvest_metrics ()
152- self ._assert_metric_scope_schema_urls (
153- resource_metrics , _DEFAULT_SCHEMA_URL
154- )
131+ self ._assert_metric_scope_schema_urls (_DEFAULT_SCHEMA_URL )
132+ metrics = self ._harvest_metrics ()
155133 self .assertIn ("gen_ai.client.operation.duration" , metrics )
156134 duration_points = metrics ["gen_ai.client.operation.duration" ]
157135 self .assertEqual (len (duration_points ), 1 )
@@ -177,35 +155,29 @@ def test_fail_llm_records_error_and_available_tokens(self) -> None:
177155
178156 def _harvest_metrics (
179157 self ,
180- ) -> Tuple [ Dict [str , List [ Any ]] , List [Any ]]:
158+ ) -> Dict [str , List [Any ]]:
181159 """Returns (metrics_by_name, resource_metrics).
182160
183161 metrics_by_name maps metric name to list of data points.
184162 resource_metrics is the raw ResourceMetrics list for scope-level
185163 assertions (e.g. schema_url).
186164 """
187- try :
188- self .meter_provider .force_flush ()
189- except Exception : # pylint: disable=broad-except
190- assert False , "force_flush raised an exception"
191- self .metric_reader .collect ()
165+ metrics = self .get_sorted_metrics (SCOPE )
192166 metrics_by_name : Dict [str , List [Any ]] = {}
193- metrics_data = self .metric_reader .get_metrics_data ()
194- resource_metrics = (
195- metrics_data and metrics_data .resource_metrics
196- ) or []
197- for resource_metric in resource_metrics :
198- for scope_metric in resource_metric .scope_metrics or []:
199- for metric in scope_metric .metrics or []:
200- points = metric .data .data_points or []
201- metrics_by_name .setdefault (metric .name , []).extend (points )
202- return metrics_by_name , resource_metrics
167+ for metric in metrics or []:
168+ points = metric .data .data_points or []
169+ metrics_by_name .setdefault (metric .name , []).extend (points )
170+ return metrics_by_name
203171
204172 def _assert_metric_scope_schema_urls (
205- self , resource_metrics : List [ Any ], expected_schema_url : str
173+ self , expected_schema_url : str
206174 ) -> None :
207- for resource_metric in resource_metrics :
175+ for (
176+ resource_metric
177+ ) in self .memory_metrics_reader .get_metrics_data ().resource_metrics :
208178 for scope_metric in resource_metric .scope_metrics :
179+ if scope_metric .scope .name != SCOPE :
180+ continue
209181 self .assertEqual (
210182 scope_metric .scope .schema_url , expected_schema_url
211183 )
0 commit comments