Implement new HTTP semantic convention opt-in for Falcon (#2790)

This commit is contained in:
Filip Nikolovski
2025-01-13 20:49:22 +01:00
committed by GitHub
parent 406707b2bd
commit b7e7d0cbe5
5 changed files with 410 additions and 104 deletions

View File

@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3133](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3133))
- `opentelemetry-instrumentation-falcon` add support version to v4
([#3086](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3086))
- `opentelemetry-instrumentation-falcon` Implement new HTTP semantic convention opt-in for Falcon
([#2790](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2790))
- `opentelemetry-instrumentation-wsgi` always record span status code to have it available in metrics
([#3148](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3148))
- add support to Python 3.13

View File

@ -20,7 +20,7 @@
| [opentelemetry-instrumentation-dbapi](./opentelemetry-instrumentation-dbapi) | dbapi | No | experimental
| [opentelemetry-instrumentation-django](./opentelemetry-instrumentation-django) | django >= 1.10 | Yes | experimental
| [opentelemetry-instrumentation-elasticsearch](./opentelemetry-instrumentation-elasticsearch) | elasticsearch >= 6.0 | No | experimental
| [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 5.0.0 | Yes | experimental
| [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 5.0.0 | Yes | migration
| [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | Yes | migration
| [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0 | Yes | migration
| [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio >= 1.42.0 | No | experimental

View File

@ -193,6 +193,14 @@ from packaging import version as package_version
import opentelemetry.instrumentation.wsgi as otel_wsgi
from opentelemetry import context, trace
from opentelemetry.instrumentation._semconv import (
_get_schema_url,
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilitySignalType,
_report_new,
_report_old,
_StabilityMode,
)
from opentelemetry.instrumentation.falcon.package import _instruments
from opentelemetry.instrumentation.falcon.version import __version__
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
@ -203,18 +211,22 @@ from opentelemetry.instrumentation.propagators import (
from opentelemetry.instrumentation.utils import (
_start_internal_or_server_span,
extract_attributes_from_object,
http_status_to_status_code,
)
from opentelemetry.metrics import get_meter
from opentelemetry.semconv.attributes.http_attributes import (
HTTP_ROUTE,
)
from opentelemetry.semconv.metrics import MetricInstruments
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.semconv.metrics.http_metrics import (
HTTP_SERVER_REQUEST_DURATION,
)
from opentelemetry.util.http import get_excluded_urls, get_traced_request_attrs
_logger = getLogger(__name__)
_ENVIRON_STARTTIME_KEY = "opentelemetry-falcon.starttime_key"
_ENVIRON_SPAN_KEY = "opentelemetry-falcon.span_key"
_ENVIRON_REQ_ATTRS = "opentelemetry-falcon.req_attrs"
_ENVIRON_ACTIVATION_KEY = "opentelemetry-falcon.activation_key"
_ENVIRON_TOKEN = "opentelemetry-falcon.token"
_ENVIRON_EXC = "opentelemetry-falcon.exc"
@ -243,6 +255,10 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
def __init__(self, *args, **kwargs):
otel_opts = kwargs.pop("_otel_opts", {})
self._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
_OpenTelemetryStabilitySignalType.HTTP,
)
# inject trace middleware
self._middlewares_list = kwargs.pop("middleware", [])
if self._middlewares_list is None:
@ -257,19 +273,30 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
__name__,
__version__,
tracer_provider,
schema_url="https://opentelemetry.io/schemas/1.11.0",
schema_url=_get_schema_url(self._sem_conv_opt_in_mode),
)
self._otel_meter = get_meter(
__name__,
__version__,
meter_provider,
schema_url="https://opentelemetry.io/schemas/1.11.0",
)
self.duration_histogram = self._otel_meter.create_histogram(
name=MetricInstruments.HTTP_SERVER_DURATION,
unit="ms",
description="Measures the duration of inbound HTTP requests.",
schema_url=_get_schema_url(self._sem_conv_opt_in_mode),
)
self.duration_histogram_old = None
if _report_old(self._sem_conv_opt_in_mode):
self.duration_histogram_old = self._otel_meter.create_histogram(
name=MetricInstruments.HTTP_SERVER_DURATION,
unit="ms",
description="Measures the duration of inbound HTTP requests.",
)
self.duration_histogram_new = None
if _report_new(self._sem_conv_opt_in_mode):
self.duration_histogram_new = self._otel_meter.create_histogram(
name=HTTP_SERVER_REQUEST_DURATION,
description="Duration of HTTP server requests.",
unit="s",
)
self.active_requests_counter = self._otel_meter.create_up_down_counter(
name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS,
unit="requests",
@ -283,6 +310,7 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
),
otel_opts.pop("request_hook", None),
otel_opts.pop("response_hook", None),
self._sem_conv_opt_in_mode,
)
self._middlewares_list.insert(0, trace_middleware)
kwargs["middleware"] = self._middlewares_list
@ -343,11 +371,14 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
context_carrier=env,
context_getter=otel_wsgi.wsgi_getter,
)
attributes = otel_wsgi.collect_request_attributes(env)
active_requests_count_attrs = (
otel_wsgi._parse_active_request_count_attrs(attributes)
attributes = otel_wsgi.collect_request_attributes(
env, self._sem_conv_opt_in_mode
)
active_requests_count_attrs = (
otel_wsgi._parse_active_request_count_attrs(
attributes, self._sem_conv_opt_in_mode
)
)
duration_attrs = otel_wsgi._parse_duration_attrs(attributes)
self.active_requests_counter.add(1, active_requests_count_attrs)
if span.is_recording():
@ -364,6 +395,7 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
activation.__enter__()
env[_ENVIRON_SPAN_KEY] = span
env[_ENVIRON_ACTIVATION_KEY] = activation
env[_ENVIRON_REQ_ATTRS] = attributes
exception = None
def _start_response(status, response_headers, *args, **kwargs):
@ -379,12 +411,22 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
exception = exc
raise
finally:
if span.is_recording():
duration_attrs[SpanAttributes.HTTP_STATUS_CODE] = (
span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
duration_s = default_timer() - start
if self.duration_histogram_old:
duration_attrs = otel_wsgi._parse_duration_attrs(
attributes, _StabilityMode.DEFAULT
)
duration = max(round((default_timer() - start) * 1000), 0)
self.duration_histogram.record(duration, duration_attrs)
self.duration_histogram_old.record(
max(round(duration_s * 1000), 0), duration_attrs
)
if self.duration_histogram_new:
duration_attrs = otel_wsgi._parse_duration_attrs(
attributes, _StabilityMode.HTTP
)
self.duration_histogram_new.record(
max(duration_s, 0), duration_attrs
)
self.active_requests_counter.add(-1, active_requests_count_attrs)
if exception is None:
activation.__exit__(None, None, None)
@ -407,11 +449,13 @@ class _TraceMiddleware:
traced_request_attrs=None,
request_hook=None,
response_hook=None,
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
):
self.tracer = tracer
self._traced_request_attrs = traced_request_attrs
self._request_hook = request_hook
self._response_hook = response_hook
self._sem_conv_opt_in_mode = sem_conv_opt_in_mode
def process_request(self, req, resp):
span = req.env.get(_ENVIRON_SPAN_KEY)
@ -437,58 +481,60 @@ class _TraceMiddleware:
def process_response(self, req, resp, resource, req_succeeded=None): # pylint:disable=R0201,R0912
span = req.env.get(_ENVIRON_SPAN_KEY)
req_attrs = req.env.get(_ENVIRON_REQ_ATTRS)
if not span or not span.is_recording():
if not span:
return
status = resp.status
reason = None
if resource is None:
status = "404"
reason = "NotFound"
status = falcon.HTTP_404
else:
exc_type, exc = None, None
if _ENVIRON_EXC in req.env:
exc = req.env[_ENVIRON_EXC]
exc_type = type(exc)
else:
exc_type, exc = None, None
if exc_type and not req_succeeded:
if "HTTPNotFound" in exc_type.__name__:
status = "404"
reason = "NotFound"
status = falcon.HTTP_404
elif isinstance(exc, (falcon.HTTPError, falcon.HTTPStatus)):
try:
if _falcon_version > 2:
status = falcon.code_to_http_status(exc.status)
else:
status = exc.status
except ValueError:
status = falcon.HTTP_500
else:
status = "500"
reason = f"{exc_type.__name__}: {exc}"
status = falcon.HTTP_500
status = status.split(" ")[0]
# Falcon 1 does not support response headers. So
# send an empty dict.
response_headers = {}
if _falcon_version > 1:
response_headers = resp.headers
otel_wsgi.add_response_attributes(
span,
status,
response_headers,
req_attrs,
self._sem_conv_opt_in_mode,
)
if (
_report_new(self._sem_conv_opt_in_mode)
and req.uri_template
and req_attrs is not None
):
req_attrs[HTTP_ROUTE] = req.uri_template
try:
status_code = int(status)
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
otel_status_code = http_status_to_status_code(
status_code, server_span=True
)
# set the description only when the status code is ERROR
if otel_status_code is not StatusCode.ERROR:
reason = None
span.set_status(
Status(
status_code=otel_status_code,
description=reason,
)
)
# Falcon 1 does not support response headers. So
# send an empty dict.
response_headers = {}
if _falcon_version > 1:
response_headers = resp.headers
if span.is_recording() and span.kind == trace.SpanKind.SERVER:
# Check if low-cardinality route is available as per semantic-conventions
if req.uri_template:
span.update_name(f"{req.method} {req.uri_template}")
span.set_attribute(HTTP_ROUTE, req.uri_template)
else:
span.update_name(f"{req.method}")

View File

@ -16,3 +16,5 @@
_instruments = ("falcon >= 1.4.1, < 5.0.0",)
_supports_metrics = True
_semconv_status = "migration"

View File

@ -22,7 +22,11 @@ from packaging import version as package_version
from opentelemetry import trace
from opentelemetry.instrumentation._semconv import (
OTEL_SEMCONV_STABILITY_OPT_IN,
_OpenTelemetrySemanticConventionStability,
_server_active_requests_count_attrs_new,
_server_active_requests_count_attrs_old,
_server_duration_attrs_new,
_server_duration_attrs_old,
)
from opentelemetry.instrumentation.falcon import FalconInstrumentor
@ -36,6 +40,24 @@ from opentelemetry.sdk.metrics.export import (
NumberDataPoint,
)
from opentelemetry.sdk.resources import Resource
from opentelemetry.semconv.attributes.client_attributes import (
CLIENT_PORT,
)
from opentelemetry.semconv.attributes.http_attributes import (
HTTP_REQUEST_METHOD,
HTTP_RESPONSE_STATUS_CODE,
)
from opentelemetry.semconv.attributes.network_attributes import (
NETWORK_PROTOCOL_VERSION,
)
from opentelemetry.semconv.attributes.server_attributes import (
SERVER_ADDRESS,
SERVER_PORT,
)
from opentelemetry.semconv.attributes.url_attributes import (
URL_PATH,
URL_SCHEME,
)
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.test.test_base import TestBase
from opentelemetry.test.wsgitestutil import WsgiTestBase
@ -52,10 +74,33 @@ _expected_metric_names = [
"http.server.active_requests",
"http.server.duration",
]
_recommended_attrs = {
"http.server.active_requests": _server_active_requests_count_attrs_new
+ _server_active_requests_count_attrs_old,
"http.server.duration": _server_duration_attrs_new
+ _server_duration_attrs_old,
}
_recommended_metrics_attrs_old = {
"http.server.active_requests": _server_active_requests_count_attrs_old,
"http.server.duration": _server_duration_attrs_old,
}
_recommended_metrics_attrs_new = {
"http.server.active_requests": _server_active_requests_count_attrs_new,
"http.server.request.duration": _server_duration_attrs_new,
}
_server_active_requests_count_attrs_both = (
_server_active_requests_count_attrs_old
)
_server_active_requests_count_attrs_both.extend(
_server_active_requests_count_attrs_new
)
_recommended_metrics_attrs_both = {
"http.server.active_requests": _server_active_requests_count_attrs_both,
"http.server.duration": _server_duration_attrs_old,
"http.server.request.duration": _server_duration_attrs_new,
}
_parsed_falcon_version = package_version.parse(_falcon_version)
@ -63,13 +108,26 @@ _parsed_falcon_version = package_version.parse(_falcon_version)
class TestFalconBase(TestBase):
def setUp(self):
super().setUp()
test_name = ""
if hasattr(self, "_testMethodName"):
test_name = self._testMethodName
sem_conv_mode = "default"
if "new_semconv" in test_name:
sem_conv_mode = "http"
elif "both_semconv" in test_name:
sem_conv_mode = "http/dup"
self.env_patch = patch.dict(
"os.environ",
{
"OTEL_PYTHON_FALCON_EXCLUDED_URLS": "ping",
"OTEL_PYTHON_FALCON_TRACED_REQUEST_ATTRS": "query_string",
OTEL_SEMCONV_STABILITY_OPT_IN: sem_conv_mode,
},
)
_OpenTelemetrySemanticConventionStability._initialized = False
self.env_patch.start()
FalconInstrumentor().instrument(
@ -95,26 +153,63 @@ class TestFalconBase(TestBase):
self.env_patch.stop()
# pylint: disable=too-many-public-methods
class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
def test_get(self):
self._test_method("GET")
def test_get_new_semconv(self):
self._test_method("GET", old_semconv=False, new_semconv=True)
def test_get_both_semconv(self):
self._test_method("GET", old_semconv=True, new_semconv=True)
def test_post(self):
self._test_method("POST")
def test_post_new_semconv(self):
self._test_method("POST", old_semconv=False, new_semconv=True)
def test_post_both_semconv(self):
self._test_method("POST", old_semconv=True, new_semconv=True)
def test_patch(self):
self._test_method("PATCH")
def test_patch_new_semconv(self):
self._test_method("PATCH", old_semconv=False, new_semconv=True)
def test_patch_both_semconv(self):
self._test_method("PATCH", old_semconv=True, new_semconv=True)
def test_put(self):
self._test_method("PUT")
def test_put_new_semconv(self):
self._test_method("PUT", old_semconv=False, new_semconv=True)
def test_put_both_semconv(self):
self._test_method("PUT", old_semconv=True, new_semconv=True)
def test_delete(self):
self._test_method("DELETE")
def test_delete_new_semconv(self):
self._test_method("DELETE", old_semconv=False, new_semconv=True)
def test_delete_both_semconv(self):
self._test_method("DELETE", old_semconv=True, new_semconv=True)
def test_head(self):
self._test_method("HEAD")
def _test_method(self, method):
def test_head_new_semconv(self):
self._test_method("HEAD", old_semconv=False, new_semconv=True)
def test_head_both_semconv(self):
self._test_method("HEAD", old_semconv=True, new_semconv=True)
def _test_method(self, method, old_semconv=True, new_semconv=False):
self.client().simulate_request(method=method, path="/hello")
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1)
@ -125,23 +220,42 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
span.status.description,
None,
)
self.assertSpanHasAttributes(
span,
{
SpanAttributes.HTTP_METHOD: method,
SpanAttributes.HTTP_SERVER_NAME: "falconframework.org",
SpanAttributes.HTTP_SCHEME: "http",
SpanAttributes.NET_HOST_PORT: 80,
SpanAttributes.HTTP_HOST: "falconframework.org",
SpanAttributes.HTTP_TARGET: "/"
if self._has_fixed_http_target
else "/hello",
SpanAttributes.NET_PEER_PORT: 65133,
SpanAttributes.HTTP_FLAVOR: "1.1",
"falcon.resource": "HelloWorldResource",
SpanAttributes.HTTP_STATUS_CODE: 201,
},
)
expected_attributes = {}
expected_attributes_old = {
SpanAttributes.HTTP_METHOD: method,
SpanAttributes.HTTP_SERVER_NAME: "falconframework.org",
SpanAttributes.HTTP_SCHEME: "http",
SpanAttributes.NET_HOST_PORT: 80,
SpanAttributes.HTTP_HOST: "falconframework.org",
SpanAttributes.HTTP_TARGET: "/"
if self._has_fixed_http_target
else "/hello",
SpanAttributes.NET_PEER_PORT: 65133,
SpanAttributes.HTTP_FLAVOR: "1.1",
"falcon.resource": "HelloWorldResource",
SpanAttributes.HTTP_STATUS_CODE: 201,
SpanAttributes.HTTP_ROUTE: "/hello",
}
expected_attributes_new = {
HTTP_REQUEST_METHOD: method,
SERVER_ADDRESS: "falconframework.org",
URL_SCHEME: "http",
SERVER_PORT: 80,
URL_PATH: "/" if self._has_fixed_http_target else "/hello",
CLIENT_PORT: 65133,
NETWORK_PROTOCOL_VERSION: "1.1",
"falcon.resource": "HelloWorldResource",
HTTP_RESPONSE_STATUS_CODE: 201,
SpanAttributes.HTTP_ROUTE: "/hello",
}
if old_semconv:
expected_attributes.update(expected_attributes_old)
if new_semconv:
expected_attributes.update(expected_attributes_new)
self.assertSpanHasAttributes(span, expected_attributes)
# In falcon<3, NET_PEER_IP is always set by default to 127.0.0.1
# In falcon>=3, NET_PEER_IP is not set to anything by default
# https://github.com/falconry/falcon/blob/5233d0abed977d9dab78ebadf305f5abe2eef07c/falcon/testing/helpers.py#L1168-L1172 # noqa
@ -193,10 +307,16 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
self.assertEqual(span.name, "GET /error")
self.assertFalse(span.status.is_ok)
self.assertEqual(span.status.status_code, StatusCode.ERROR)
self.assertEqual(
span.status.description,
"NameError: name 'non_existent_var' is not defined",
)
_parsed_falcon_version = package_version.parse(_falcon_version)
if _parsed_falcon_version < package_version.parse("3.0.0"):
self.assertEqual(
span.status.description,
"NameError: name 'non_existent_var' is not defined",
)
else:
self.assertEqual(span.status.description, None)
self.assertSpanHasAttributes(
span,
{
@ -211,6 +331,7 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
SpanAttributes.NET_PEER_PORT: 65133,
SpanAttributes.HTTP_FLAVOR: "1.1",
SpanAttributes.HTTP_STATUS_CODE: 500,
SpanAttributes.HTTP_ROUTE: "/error",
},
)
# In falcon<3, NET_PEER_IP is always set by default to 127.0.0.1
@ -221,6 +342,47 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
span.attributes[SpanAttributes.NET_PEER_IP], "127.0.0.1"
)
def test_url_template_new_semconv(self):
self.client().simulate_get("/user/123")
spans = self.memory_exporter.get_finished_spans()
metrics_list = self.memory_metrics_reader.get_metrics_data()
self.assertEqual(len(spans), 1)
self.assertTrue(len(metrics_list.resource_metrics) != 0)
span = spans[0]
self.assertEqual(span.name, "GET /user/{user_id}")
self.assertEqual(span.status.status_code, StatusCode.UNSET)
self.assertEqual(
span.status.description,
None,
)
self.assertSpanHasAttributes(
span,
{
HTTP_REQUEST_METHOD: "GET",
SERVER_ADDRESS: "falconframework.org",
URL_SCHEME: "http",
SERVER_PORT: 80,
URL_PATH: "/" if self._has_fixed_http_target else "/user/123",
CLIENT_PORT: 65133,
NETWORK_PROTOCOL_VERSION: "1.1",
"falcon.resource": "UserResource",
HTTP_RESPONSE_STATUS_CODE: 200,
SpanAttributes.HTTP_ROUTE: "/user/{user_id}",
},
)
for resource_metric in metrics_list.resource_metrics:
for scope_metric in resource_metric.scope_metrics:
for metric in scope_metric.metrics:
if metric.name == "http.server.request.duration":
data_points = list(metric.data.data_points)
for point in data_points:
self.assertIn(
"http.route",
point.attributes,
)
def test_url_template(self):
self.client().simulate_get("/user/123")
spans = self.memory_exporter.get_finished_spans()
@ -247,6 +409,7 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
SpanAttributes.HTTP_FLAVOR: "1.1",
"falcon.resource": "UserResource",
SpanAttributes.HTTP_STATUS_CODE: 200,
SpanAttributes.HTTP_ROUTE: "/user/{user_id}",
},
)
@ -305,6 +468,27 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
self.assertFalse(mock_span.set_attribute.called)
self.assertFalse(mock_span.set_status.called)
metrics_list = self.memory_metrics_reader.get_metrics_data()
self.assertTrue(len(metrics_list.resource_metrics) != 0)
for resource_metric in metrics_list.resource_metrics:
for scope_metric in resource_metric.scope_metrics:
for metric in scope_metric.metrics:
data_points = list(metric.data.data_points)
self.assertEqual(len(data_points), 1)
for point in list(metric.data.data_points):
if isinstance(point, HistogramDataPoint):
self.assertEqual(point.count, 1)
if isinstance(point, NumberDataPoint):
self.assertEqual(point.value, 0)
for attr in point.attributes:
self.assertIn(
attr,
_recommended_metrics_attrs_old[
metric.name
],
)
def test_uninstrument_after_instrument(self):
self.client().simulate_get(path="/hello")
spans = self.memory_exporter.get_finished_spans()
@ -345,47 +529,119 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
)
self.assertTrue(number_data_point_seen and histogram_data_point_seen)
def test_falcon_metric_values(self):
expected_duration_attributes = {
"http.method": "GET",
"http.host": "falconframework.org",
"http.scheme": "http",
"http.flavor": "1.1",
"http.server_name": "falconframework.org",
"net.host.port": 80,
"net.host.name": "falconframework.org",
"http.status_code": 404,
}
expected_requests_count_attributes = {
"http.method": "GET",
"http.host": "falconframework.org",
"http.scheme": "http",
"http.flavor": "1.1",
"http.server_name": "falconframework.org",
}
def test_falcon_metric_values_new_semconv(self):
number_data_point_seen = False
histogram_data_point_seen = False
start = default_timer()
self.client().simulate_get("/hello/756")
duration = max(round((default_timer() - start) * 1000), 0)
duration = max(default_timer() - start, 0)
metrics_list = self.memory_metrics_reader.get_metrics_data()
for resource_metric in metrics_list.resource_metrics:
for scope_metric in resource_metric.scope_metrics:
for metric in scope_metric.metrics:
for point in list(metric.data.data_points):
data_points = list(metric.data.data_points)
self.assertEqual(len(data_points), 1)
for point in data_points:
if isinstance(point, HistogramDataPoint):
self.assertDictEqual(
expected_duration_attributes,
dict(point.attributes),
)
self.assertEqual(point.count, 1)
histogram_data_point_seen = True
self.assertAlmostEqual(
duration, point.sum, delta=10
)
if isinstance(point, NumberDataPoint):
self.assertDictEqual(
expected_requests_count_attributes,
dict(point.attributes),
)
self.assertEqual(point.value, 0)
number_data_point_seen = True
for attr in point.attributes:
self.assertIn(
attr,
_recommended_metrics_attrs_new[metric.name],
)
self.assertTrue(number_data_point_seen and histogram_data_point_seen)
def test_falcon_metric_values_both_semconv(self):
number_data_point_seen = False
histogram_data_point_seen = False
start = default_timer()
self.client().simulate_get("/hello/756")
duration_s = default_timer() - start
metrics_list = self.memory_metrics_reader.get_metrics_data()
# pylint: disable=too-many-nested-blocks
for resource_metric in metrics_list.resource_metrics:
for scope_metric in resource_metric.scope_metrics:
for metric in scope_metric.metrics:
if metric.unit == "ms":
self.assertEqual(metric.name, "http.server.duration")
elif metric.unit == "s":
self.assertEqual(
metric.name, "http.server.request.duration"
)
else:
self.assertEqual(
metric.name, "http.server.active_requests"
)
data_points = list(metric.data.data_points)
self.assertEqual(len(data_points), 1)
for point in data_points:
if isinstance(point, HistogramDataPoint):
self.assertEqual(point.count, 1)
if metric.unit == "ms":
self.assertAlmostEqual(
max(round(duration_s * 1000), 0),
point.sum,
delta=10,
)
elif metric.unit == "s":
self.assertAlmostEqual(
max(duration_s, 0), point.sum, delta=10
)
histogram_data_point_seen = True
if isinstance(point, NumberDataPoint):
self.assertEqual(point.value, 0)
number_data_point_seen = True
for attr in point.attributes:
self.assertIn(
attr,
_recommended_metrics_attrs_both[metric.name],
)
self.assertTrue(number_data_point_seen and histogram_data_point_seen)
def test_falcon_metric_values(self):
number_data_point_seen = False
histogram_data_point_seen = False
start = default_timer()
self.client().simulate_get("/hello/756")
duration = max(round((default_timer() - start) * 1000), 0)
metrics_list = self.memory_metrics_reader.get_metrics_data()
for resource_metric in metrics_list.resource_metrics:
for scope_metric in resource_metric.scope_metrics:
for metric in scope_metric.metrics:
data_points = list(metric.data.data_points)
self.assertEqual(len(data_points), 1)
for point in list(metric.data.data_points):
if isinstance(point, HistogramDataPoint):
self.assertEqual(point.count, 1)
histogram_data_point_seen = True
self.assertAlmostEqual(
duration, point.sum, delta=10
)
if isinstance(point, NumberDataPoint):
self.assertEqual(point.value, 0)
number_data_point_seen = True
for attr in point.attributes:
self.assertIn(
attr,
_recommended_metrics_attrs_old[metric.name],
)
self.assertTrue(number_data_point_seen and histogram_data_point_seen)
def test_metric_uninstrument(self):
self.client().simulate_request(method="POST", path="/hello/756")