mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-29 21:23:55 +08:00
Add http.server.request.size for ASGI metric implementation (#1867)
* Update changelog file. * Update changelog file. * Add new request.size metric for ASGI middleware. * Clean-up. * Refactor try except section. --------- Co-authored-by: Shalev Roda <65566801+shalevr@users.noreply.github.com>
This commit is contained in:
@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
- `opentelemetry-instrumentation-asgi` Add `http.server.request.size` metric
|
||||||
|
([#1867](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1867))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -511,6 +511,11 @@ class OpenTelemetryMiddleware:
|
|||||||
unit="By",
|
unit="By",
|
||||||
description="measures the size of HTTP response messages (compressed).",
|
description="measures the size of HTTP response messages (compressed).",
|
||||||
)
|
)
|
||||||
|
self.server_request_size_histogram = self.meter.create_histogram(
|
||||||
|
name=MetricInstruments.HTTP_SERVER_REQUEST_SIZE,
|
||||||
|
unit="By",
|
||||||
|
description="Measures the size of HTTP request messages (compressed).",
|
||||||
|
)
|
||||||
self.active_requests_counter = self.meter.create_up_down_counter(
|
self.active_requests_counter = self.meter.create_up_down_counter(
|
||||||
name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS,
|
name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS,
|
||||||
unit="requests",
|
unit="requests",
|
||||||
@ -603,6 +608,16 @@ class OpenTelemetryMiddleware:
|
|||||||
self.server_response_size_histogram.record(
|
self.server_response_size_histogram.record(
|
||||||
self.content_length_header, duration_attrs
|
self.content_length_header, duration_attrs
|
||||||
)
|
)
|
||||||
|
request_size = asgi_getter.get(scope, "content-length")
|
||||||
|
if request_size:
|
||||||
|
try:
|
||||||
|
request_size_amount = int(request_size[0])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.server_request_size_histogram.record(
|
||||||
|
request_size_amount, duration_attrs
|
||||||
|
)
|
||||||
if token:
|
if token:
|
||||||
context.detach(token)
|
context.detach(token)
|
||||||
|
|
||||||
|
@ -47,16 +47,19 @@ _expected_metric_names = [
|
|||||||
"http.server.active_requests",
|
"http.server.active_requests",
|
||||||
"http.server.duration",
|
"http.server.duration",
|
||||||
"http.server.response.size",
|
"http.server.response.size",
|
||||||
|
"http.server.request.size",
|
||||||
]
|
]
|
||||||
_recommended_attrs = {
|
_recommended_attrs = {
|
||||||
"http.server.active_requests": _active_requests_count_attrs,
|
"http.server.active_requests": _active_requests_count_attrs,
|
||||||
"http.server.duration": _duration_attrs,
|
"http.server.duration": _duration_attrs,
|
||||||
"http.server.response.size": _duration_attrs,
|
"http.server.response.size": _duration_attrs,
|
||||||
|
"http.server.request.size": _duration_attrs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def http_app(scope, receive, send):
|
async def http_app(scope, receive, send):
|
||||||
message = await receive()
|
message = await receive()
|
||||||
|
scope["headers"] = [(b"content-length", b"128")]
|
||||||
assert scope["type"] == "http"
|
assert scope["type"] == "http"
|
||||||
if message.get("type") == "http.request":
|
if message.get("type") == "http.request":
|
||||||
await send(
|
await send(
|
||||||
@ -99,6 +102,7 @@ async def error_asgi(scope, receive, send):
|
|||||||
assert isinstance(scope, dict)
|
assert isinstance(scope, dict)
|
||||||
assert scope["type"] == "http"
|
assert scope["type"] == "http"
|
||||||
message = await receive()
|
message = await receive()
|
||||||
|
scope["headers"] = [(b"content-length", b"128")]
|
||||||
if message.get("type") == "http.request":
|
if message.get("type") == "http.request":
|
||||||
try:
|
try:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
@ -592,6 +596,8 @@ class TestAsgiApplication(AsgiTestBase):
|
|||||||
)
|
)
|
||||||
elif metric.name == "http.server.response.size":
|
elif metric.name == "http.server.response.size":
|
||||||
self.assertEqual(1024, point.sum)
|
self.assertEqual(1024, point.sum)
|
||||||
|
elif metric.name == "http.server.request.size":
|
||||||
|
self.assertEqual(128, point.sum)
|
||||||
elif isinstance(point, NumberDataPoint):
|
elif isinstance(point, NumberDataPoint):
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
expected_requests_count_attributes,
|
expected_requests_count_attributes,
|
||||||
@ -630,7 +636,7 @@ class TestAsgiApplication(AsgiTestBase):
|
|||||||
expected_target,
|
expected_target,
|
||||||
)
|
)
|
||||||
assertions += 1
|
assertions += 1
|
||||||
self.assertEqual(assertions, 2)
|
self.assertEqual(assertions, 3)
|
||||||
|
|
||||||
def test_no_metric_for_websockets(self):
|
def test_no_metric_for_websockets(self):
|
||||||
self.scope = {
|
self.scope = {
|
||||||
|
@ -45,6 +45,7 @@ _expected_metric_names = [
|
|||||||
"http.server.active_requests",
|
"http.server.active_requests",
|
||||||
"http.server.duration",
|
"http.server.duration",
|
||||||
"http.server.response.size",
|
"http.server.response.size",
|
||||||
|
"http.server.request.size",
|
||||||
]
|
]
|
||||||
_recommended_attrs = {
|
_recommended_attrs = {
|
||||||
"http.server.active_requests": _active_requests_count_attrs,
|
"http.server.active_requests": _active_requests_count_attrs,
|
||||||
@ -53,6 +54,10 @@ _recommended_attrs = {
|
|||||||
*_duration_attrs,
|
*_duration_attrs,
|
||||||
SpanAttributes.HTTP_TARGET,
|
SpanAttributes.HTTP_TARGET,
|
||||||
},
|
},
|
||||||
|
"http.server.request.size": {
|
||||||
|
*_duration_attrs,
|
||||||
|
SpanAttributes.HTTP_TARGET,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -251,8 +256,13 @@ class TestFastAPIManualInstrumentation(TestBase):
|
|||||||
|
|
||||||
def test_basic_post_request_metric_success(self):
|
def test_basic_post_request_metric_success(self):
|
||||||
start = default_timer()
|
start = default_timer()
|
||||||
self._client.post("/foobar")
|
response = self._client.post(
|
||||||
|
"/foobar",
|
||||||
|
json={"foo": "bar"},
|
||||||
|
)
|
||||||
duration = max(round((default_timer() - start) * 1000), 0)
|
duration = max(round((default_timer() - start) * 1000), 0)
|
||||||
|
response_size = int(response.headers.get("content-length"))
|
||||||
|
request_size = int(response.request.headers.get("content-length"))
|
||||||
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
||||||
for metric in (
|
for metric in (
|
||||||
metrics_list.resource_metrics[0].scope_metrics[0].metrics
|
metrics_list.resource_metrics[0].scope_metrics[0].metrics
|
||||||
@ -260,7 +270,12 @@ class TestFastAPIManualInstrumentation(TestBase):
|
|||||||
for point in list(metric.data.data_points):
|
for point in list(metric.data.data_points):
|
||||||
if isinstance(point, HistogramDataPoint):
|
if isinstance(point, HistogramDataPoint):
|
||||||
self.assertEqual(point.count, 1)
|
self.assertEqual(point.count, 1)
|
||||||
|
if metric.name == "http.server.duration":
|
||||||
self.assertAlmostEqual(duration, point.sum, delta=30)
|
self.assertAlmostEqual(duration, point.sum, delta=30)
|
||||||
|
elif metric.name == "http.server.response.size":
|
||||||
|
self.assertEqual(response_size, point.sum)
|
||||||
|
elif metric.name == "http.server.request.size":
|
||||||
|
self.assertEqual(request_size, point.sum)
|
||||||
if isinstance(point, NumberDataPoint):
|
if isinstance(point, NumberDataPoint):
|
||||||
self.assertEqual(point.value, 0)
|
self.assertEqual(point.value, 0)
|
||||||
|
|
||||||
|
@ -50,11 +50,13 @@ _expected_metric_names = [
|
|||||||
"http.server.active_requests",
|
"http.server.active_requests",
|
||||||
"http.server.duration",
|
"http.server.duration",
|
||||||
"http.server.response.size",
|
"http.server.response.size",
|
||||||
|
"http.server.request.size",
|
||||||
]
|
]
|
||||||
_recommended_attrs = {
|
_recommended_attrs = {
|
||||||
"http.server.active_requests": _active_requests_count_attrs,
|
"http.server.active_requests": _active_requests_count_attrs,
|
||||||
"http.server.duration": _duration_attrs,
|
"http.server.duration": _duration_attrs,
|
||||||
"http.server.response.size": _duration_attrs,
|
"http.server.response.size": _duration_attrs,
|
||||||
|
"http.server.request.size": _duration_attrs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -165,8 +167,13 @@ class TestStarletteManualInstrumentation(TestBase):
|
|||||||
"http.scheme": "http",
|
"http.scheme": "http",
|
||||||
"http.server_name": "testserver",
|
"http.server_name": "testserver",
|
||||||
}
|
}
|
||||||
self._client.post("/foobar")
|
response = self._client.post(
|
||||||
|
"/foobar",
|
||||||
|
json={"foo": "bar"},
|
||||||
|
)
|
||||||
duration = max(round((default_timer() - start) * 1000), 0)
|
duration = max(round((default_timer() - start) * 1000), 0)
|
||||||
|
response_size = int(response.headers.get("content-length"))
|
||||||
|
request_size = int(response.request.headers.get("content-length"))
|
||||||
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
||||||
for metric in (
|
for metric in (
|
||||||
metrics_list.resource_metrics[0].scope_metrics[0].metrics
|
metrics_list.resource_metrics[0].scope_metrics[0].metrics
|
||||||
@ -174,10 +181,15 @@ class TestStarletteManualInstrumentation(TestBase):
|
|||||||
for point in list(metric.data.data_points):
|
for point in list(metric.data.data_points):
|
||||||
if isinstance(point, HistogramDataPoint):
|
if isinstance(point, HistogramDataPoint):
|
||||||
self.assertEqual(point.count, 1)
|
self.assertEqual(point.count, 1)
|
||||||
self.assertAlmostEqual(duration, point.sum, delta=30)
|
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
dict(point.attributes), expected_duration_attributes
|
dict(point.attributes), expected_duration_attributes
|
||||||
)
|
)
|
||||||
|
if metric.name == "http.server.duration":
|
||||||
|
self.assertAlmostEqual(duration, point.sum, delta=30)
|
||||||
|
elif metric.name == "http.server.response.size":
|
||||||
|
self.assertEqual(response_size, point.sum)
|
||||||
|
elif metric.name == "http.server.request.size":
|
||||||
|
self.assertEqual(request_size, point.sum)
|
||||||
if isinstance(point, NumberDataPoint):
|
if isinstance(point, NumberDataPoint):
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
expected_requests_count_attributes,
|
expected_requests_count_attributes,
|
||||||
|
Reference in New Issue
Block a user