|
2 | 2 | # pylint: disable=unused-argument |
3 | 3 | # pylint: disable=unused-variable |
4 | 4 |
|
| 5 | +import asyncio |
5 | 6 | import importlib |
6 | 7 | from collections.abc import Callable |
7 | 8 | from functools import partial |
@@ -40,11 +41,17 @@ def set_and_clean_settings_env_vars( |
40 | 41 | monkeypatch.setenv( |
41 | 42 | "TRACING_OPENTELEMETRY_COLLECTOR_PORT", f"{tracing_settings_in[1]}" |
42 | 43 | ) |
| 44 | + sampling_probability_mocked = False |
| 45 | + if tracing_settings_in[2]: |
| 46 | + sampling_probability_mocked = True |
| 47 | + monkeypatch.setenv("TRACING_SAMPLING_PROBABILITY", tracing_settings_in[2]) |
43 | 48 | yield |
44 | 49 | if endpoint_mocked: |
45 | 50 | monkeypatch.delenv("TRACING_OPENTELEMETRY_COLLECTOR_ENDPOINT") |
46 | 51 | if port_mocked: |
47 | 52 | monkeypatch.delenv("TRACING_OPENTELEMETRY_COLLECTOR_PORT") |
| 53 | + if sampling_probability_mocked: |
| 54 | + monkeypatch.delenv("TRACING_SAMPLING_PROBABILITY") |
48 | 55 |
|
49 | 56 |
|
50 | 57 | @pytest.mark.parametrize( |
@@ -201,3 +208,61 @@ async def handler(handler_data: dict, request: web.Request) -> web.Response: |
201 | 208 | assert ( |
202 | 209 | trace_id == handler_data[_OSPARC_TRACE_ID_HEADER] |
203 | 210 | ) # Ensure trace IDs match |
| 211 | + |
| 212 | + |
| 213 | +@pytest.mark.parametrize( |
| 214 | + "tracing_settings_in", |
| 215 | + [ |
| 216 | + ("http://opentelemetry-collector", 4318, 0.05), |
| 217 | + ], |
| 218 | + indirect=True, |
| 219 | +) |
| 220 | +async def test_tracing_sampling_probability_effective( |
| 221 | + mock_otel_collector: InMemorySpanExporter, |
| 222 | + aiohttp_client: Callable, |
| 223 | + set_and_clean_settings_env_vars: Callable[[], None], |
| 224 | + tracing_settings_in, |
| 225 | +): |
| 226 | + """ |
| 227 | + This test checks that the TRACING_SAMPLING_PROBABILITY setting in TracingSettings |
| 228 | + is effective by sending 1000 requests and verifying that the number of collected traces |
| 229 | + is close to 0.05 * 1000 (with some tolerance). |
| 230 | + """ |
| 231 | + n_requests = 1000 |
| 232 | + tolerance_probability = 0.5 |
| 233 | + |
| 234 | + app = web.Application() |
| 235 | + service_name = "simcore_service_webserver" |
| 236 | + tracing_settings = TracingSettings() |
| 237 | + |
| 238 | + async def handler(request: web.Request) -> web.Response: |
| 239 | + return web.Response(text="ok") |
| 240 | + |
| 241 | + app.router.add_get("/", handler) |
| 242 | + |
| 243 | + async for _ in get_tracing_lifespan( |
| 244 | + app=app, |
| 245 | + service_name=service_name, |
| 246 | + tracing_settings=tracing_settings, |
| 247 | + )(app): |
| 248 | + client = await aiohttp_client(app) |
| 249 | + |
| 250 | + async def make_request(): |
| 251 | + await client.get("/") |
| 252 | + |
| 253 | + await asyncio.gather(*(make_request() for _ in range(n_requests))) |
| 254 | + spans = mock_otel_collector.get_finished_spans() |
| 255 | + trace_ids = set() |
| 256 | + for span in spans: |
| 257 | + if span.context is not None: |
| 258 | + trace_ids.add(span.context.trace_id) |
| 259 | + num_traces = len(trace_ids) |
| 260 | + expected_num_traces = int( |
| 261 | + tracing_settings.TRACING_SAMPLING_PROBABILITY * n_requests |
| 262 | + ) |
| 263 | + tolerance = int(tolerance_probability * expected_num_traces) |
| 264 | + assert ( |
| 265 | + expected_num_traces - tolerance |
| 266 | + <= num_traces |
| 267 | + <= expected_num_traces + tolerance |
| 268 | + ), f"Expected roughly {expected_num_traces} distinct trace ids, got {num_traces}" |
0 commit comments