|
13 | 13 | # limitations under the License.
|
14 | 14 |
|
15 | 15 | import unittest
|
| 16 | +from timeit import default_timer |
16 | 17 | from unittest.mock import patch
|
17 | 18 |
|
18 | 19 | from starlette import applications
|
|
22 | 23 | from starlette.websockets import WebSocket
|
23 | 24 |
|
24 | 25 | import opentelemetry.instrumentation.starlette as otel_starlette
|
| 26 | +from opentelemetry.sdk.metrics.export import ( |
| 27 | + HistogramDataPoint, |
| 28 | + NumberDataPoint, |
| 29 | +) |
25 | 30 | from opentelemetry.sdk.resources import Resource
|
26 | 31 | from opentelemetry.semconv.trace import SpanAttributes
|
27 | 32 | from opentelemetry.test.globals_test import reset_trace_globals
|
|
35 | 40 | from opentelemetry.util.http import (
|
36 | 41 | OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST,
|
37 | 42 | OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE,
|
| 43 | + _active_requests_count_attrs, |
| 44 | + _duration_attrs, |
38 | 45 | get_excluded_urls,
|
39 | 46 | )
|
40 | 47 |
|
| 48 | +_expected_metric_names = [ |
| 49 | + "http.server.active_requests", |
| 50 | + "http.server.duration", |
| 51 | +] |
| 52 | +_recommended_attrs = { |
| 53 | + "http.server.active_requests": _active_requests_count_attrs, |
| 54 | + "http.server.duration": _duration_attrs, |
| 55 | +} |
| 56 | + |
41 | 57 |
|
42 | 58 | class TestStarletteManualInstrumentation(TestBase):
|
43 | 59 | def _create_app(self):
|
@@ -100,6 +116,109 @@ def test_starlette_excluded_urls(self):
|
100 | 116 | spans = self.memory_exporter.get_finished_spans()
|
101 | 117 | self.assertEqual(len(spans), 0)
|
102 | 118 |
|
| 119 | + def test_starlette_metrics(self): |
| 120 | + self._client.get("/foobar") |
| 121 | + self._client.get("/foobar") |
| 122 | + self._client.get("/foobar") |
| 123 | + metrics_list = self.memory_metrics_reader.get_metrics_data() |
| 124 | + number_data_point_seen = False |
| 125 | + histogram_data_point_seen = False |
| 126 | + self.assertTrue(len(metrics_list.resource_metrics) == 1) |
| 127 | + for resource_metric in metrics_list.resource_metrics: |
| 128 | + self.assertTrue(len(resource_metric.scope_metrics) == 1) |
| 129 | + for scope_metric in resource_metric.scope_metrics: |
| 130 | + self.assertTrue(len(scope_metric.metrics) == 2) |
| 131 | + for metric in scope_metric.metrics: |
| 132 | + self.assertIn(metric.name, _expected_metric_names) |
| 133 | + data_points = list(metric.data.data_points) |
| 134 | + self.assertEqual(len(data_points), 1) |
| 135 | + for point in data_points: |
| 136 | + if isinstance(point, HistogramDataPoint): |
| 137 | + self.assertEqual(point.count, 3) |
| 138 | + histogram_data_point_seen = True |
| 139 | + if isinstance(point, NumberDataPoint): |
| 140 | + number_data_point_seen = True |
| 141 | + for attr in point.attributes: |
| 142 | + self.assertIn( |
| 143 | + attr, _recommended_attrs[metric.name] |
| 144 | + ) |
| 145 | + self.assertTrue(number_data_point_seen and histogram_data_point_seen) |
| 146 | + |
| 147 | + def test_basic_post_request_metric_success(self): |
| 148 | + start = default_timer() |
| 149 | + expected_duration_attributes = { |
| 150 | + "http.flavor": "1.1", |
| 151 | + "http.host": "testserver", |
| 152 | + "http.method": "POST", |
| 153 | + "http.scheme": "http", |
| 154 | + "http.server_name": "testserver", |
| 155 | + "http.status_code": 405, |
| 156 | + "net.host.port": 80, |
| 157 | + } |
| 158 | + expected_requests_count_attributes = { |
| 159 | + "http.flavor": "1.1", |
| 160 | + "http.host": "testserver", |
| 161 | + "http.method": "POST", |
| 162 | + "http.scheme": "http", |
| 163 | + "http.server_name": "testserver", |
| 164 | + } |
| 165 | + self._client.post("/foobar") |
| 166 | + duration = max(round((default_timer() - start) * 1000), 0) |
| 167 | + metrics_list = self.memory_metrics_reader.get_metrics_data() |
| 168 | + for metric in ( |
| 169 | + metrics_list.resource_metrics[0].scope_metrics[0].metrics |
| 170 | + ): |
| 171 | + for point in list(metric.data.data_points): |
| 172 | + if isinstance(point, HistogramDataPoint): |
| 173 | + self.assertEqual(point.count, 1) |
| 174 | + self.assertAlmostEqual(duration, point.sum, delta=30) |
| 175 | + self.assertDictEqual( |
| 176 | + dict(point.attributes), expected_duration_attributes |
| 177 | + ) |
| 178 | + if isinstance(point, NumberDataPoint): |
| 179 | + self.assertDictEqual( |
| 180 | + expected_requests_count_attributes, |
| 181 | + dict(point.attributes), |
| 182 | + ) |
| 183 | + self.assertEqual(point.value, 0) |
| 184 | + |
| 185 | + def test_metric_for_uninstrment_app_method(self): |
| 186 | + self._client.get("/foobar") |
| 187 | + # uninstrumenting the existing client app |
| 188 | + self._instrumentor.uninstrument_app(self._app) |
| 189 | + self._client.get("/foobar") |
| 190 | + self._client.get("/foobar") |
| 191 | + metrics_list = self.memory_metrics_reader.get_metrics_data() |
| 192 | + for metric in ( |
| 193 | + metrics_list.resource_metrics[0].scope_metrics[0].metrics |
| 194 | + ): |
| 195 | + for point in list(metric.data.data_points): |
| 196 | + if isinstance(point, HistogramDataPoint): |
| 197 | + self.assertEqual(point.count, 1) |
| 198 | + if isinstance(point, NumberDataPoint): |
| 199 | + self.assertEqual(point.value, 0) |
| 200 | + |
| 201 | + def test_metric_uninstrument_inherited_by_base(self): |
| 202 | + # instrumenting class and creating app to send request |
| 203 | + self._instrumentor.instrument() |
| 204 | + app = self._create_starlette_app() |
| 205 | + client = TestClient(app) |
| 206 | + client.get("/foobar") |
| 207 | + # calling uninstrument and checking for telemetry data |
| 208 | + self._instrumentor.uninstrument() |
| 209 | + client.get("/foobar") |
| 210 | + client.get("/foobar") |
| 211 | + client.get("/foobar") |
| 212 | + metrics_list = self.memory_metrics_reader.get_metrics_data() |
| 213 | + for metric in ( |
| 214 | + metrics_list.resource_metrics[0].scope_metrics[0].metrics |
| 215 | + ): |
| 216 | + for point in list(metric.data.data_points): |
| 217 | + if isinstance(point, HistogramDataPoint): |
| 218 | + self.assertEqual(point.count, 1) |
| 219 | + if isinstance(point, NumberDataPoint): |
| 220 | + self.assertEqual(point.value, 0) |
| 221 | + |
103 | 222 | @staticmethod
|
104 | 223 | def _create_starlette_app():
|
105 | 224 | def home(_):
|
|
0 commit comments