Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metric instrumentation falcon #1230

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6a43838
add metric instrumentation in falcon
TheAnshul756 Aug 16, 2022
cf29480
Merge branch 'main' of https://github.com/TheAnshul756/opentelemetry-…
TheAnshul756 Aug 16, 2022
53221ec
fix lint
TheAnshul756 Aug 16, 2022
78147e9
add changelog
TheAnshul756 Aug 16, 2022
beae966
fix lint error
TheAnshul756 Aug 16, 2022
aa92185
Merge branch 'main' into metric-instrumentation-falcon
ocelotl Aug 17, 2022
2d9eb1e
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Aug 23, 2022
515ea44
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Aug 24, 2022
a76731e
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Aug 26, 2022
fd33567
fix generate
TheAnshul756 Aug 26, 2022
330df27
fix changelog
TheAnshul756 Aug 26, 2022
ff0e4cf
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Aug 29, 2022
78940cd
Merge branch 'main' into metric-instrumentation-falcon
lzchen Aug 30, 2022
556c3fc
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Sep 2, 2022
a12566b
fix time position
TheAnshul756 Sep 6, 2022
8d78aaf
Merge branch 'metric-instrumentation-falcon' of https://github.com/Th…
TheAnshul756 Sep 6, 2022
d14cd02
add readme
TheAnshul756 Sep 6, 2022
6526d9c
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Sep 7, 2022
9c16269
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Sep 8, 2022
d4db803
Merge branch 'main' of https://github.com/TheAnshul756/opentelemetry-…
TheAnshul756 Sep 9, 2022
bd5a5da
Merge branch 'main' into metric-instrumentation-falcon
srikanthccv Sep 15, 2022
527a6c3
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Sep 16, 2022
496e1d2
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Sep 19, 2022
eae609a
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Sep 19, 2022
90ee628
Merge branch 'main' into metric-instrumentation-falcon
TheAnshul756 Sep 20, 2022
efb2b32
Merge branch 'main' into metric-instrumentation-falcon
lzchen Sep 20, 2022
c88e25d
Merge branch 'main' into metric-instrumentation-falcon
srikanthccv Sep 20, 2022
d5f4a7a
Merge branch 'main' of https://github.com/TheAnshul756/opentelemetry-…
TheAnshul756 Sep 21, 2022
f6e3174
fix uninstruent test
TheAnshul756 Sep 21, 2022
3df3780
Merge branch 'main' into metric-instrumentation-falcon
srikanthccv Sep 21, 2022
d415b66
remove time initi twice
TheAnshul756 Sep 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `opentelemetry-instrumentation-grpc` add supports to filter requests to instrument. ([#1241](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1241))
- Flask sqlalchemy psycopg2 integration
([#1224](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1224))
- Add metric instrumentation in Falcon
([#1230](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1230))
- Add metric instrumentation in fastapi
([#1199](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1199))
- Add metric instrumentation in Pyramid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def response_hook(span, req, resp):
from logging import getLogger
from sys import exc_info
from time import time_ns
from timeit import default_timer
from typing import Collection

import falcon
Expand All @@ -163,6 +164,7 @@ def response_hook(span, req, resp):
extract_attributes_from_object,
http_status_to_status_code,
)
from opentelemetry.metrics import get_meter
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace.status import Status
from opentelemetry.util.http import get_excluded_urls, get_traced_request_attrs
Expand Down Expand Up @@ -202,12 +204,24 @@ def __init__(self, *args, **kwargs):
# inject trace middleware
self._middlewares_list = kwargs.pop("middleware", [])
tracer_provider = otel_opts.pop("tracer_provider", None)
meter_provider = otel_opts.pop("meter_provider", None)
if not isinstance(self._middlewares_list, (list, tuple)):
self._middlewares_list = [self._middlewares_list]

self._otel_tracer = trace.get_tracer(
__name__, __version__, tracer_provider
)
self._otel_meter = get_meter(__name__, __version__, meter_provider)
self.duration_histogram = self._otel_meter.create_histogram(
name="http.server.duration",
unit="ms",
description="measures the duration of the inbound HTTP request",
)
self.active_requests_counter = self._otel_meter.create_up_down_counter(
name="http.server.active_requests",
unit="requests",
description="measures the number of concurrent HTTP requests that are currently in-flight",
)

trace_middleware = _TraceMiddleware(
self._otel_tracer,
Expand Down Expand Up @@ -261,6 +275,7 @@ def _handle_exception(

def __call__(self, env, start_response):
# pylint: disable=E1101
# pylint: disable=too-many-locals
if self._otel_excluded_urls.url_disabled(env.get("PATH_INFO", "/")):
return super().__call__(env, start_response)

Expand All @@ -276,9 +291,14 @@ def __call__(self, env, start_response):
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)
)
duration_attrs = otel_wsgi._parse_duration_attrs(attributes)
self.active_requests_counter.add(1, active_requests_count_attrs)

if span.is_recording():
attributes = otel_wsgi.collect_request_attributes(env)
for key, value in attributes.items():
span.set_attribute(key, value)
if span.is_recording() and span.kind == trace.SpanKind.SERVER:
Expand All @@ -302,6 +322,7 @@ def _start_response(status, response_headers, *args, **kwargs):
context.detach(token)
return response

start = default_timer()
try:
return super().__call__(env, _start_response)
except Exception as exc:
Expand All @@ -313,6 +334,13 @@ def _start_response(status, response_headers, *args, **kwargs):
if token is not None:
context.detach(token)
raise
finally:
duration_attrs[
SpanAttributes.HTTP_STATUS_CODE
] = span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
duration = max(round((default_timer() - start) * 1000), 0)
self.duration_histogram.record(duration, duration_attrs)
self.active_requests_counter.add(-1, active_requests_count_attrs)


class _TraceMiddleware:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
from timeit import default_timer
from unittest.mock import Mock, patch

import pytest
Expand All @@ -26,6 +27,14 @@
get_global_response_propagator,
set_global_response_propagator,
)
from opentelemetry.instrumentation.wsgi import (
_active_requests_count_attrs,
_duration_attrs,
)
from opentelemetry.sdk.metrics.export import (
HistogramDataPoint,
NumberDataPoint,
)
from opentelemetry.sdk.resources import Resource
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.test.test_base import TestBase
Expand All @@ -38,6 +47,15 @@

from .app import make_app

_expected_metric_names = [
"http.server.active_requests",
"http.server.duration",
]
_recommended_attrs = {
"http.server.active_requests": _active_requests_count_attrs,
"http.server.duration": _duration_attrs,
}


class TestFalconBase(TestBase):
def setUp(self):
Expand Down Expand Up @@ -254,6 +272,87 @@ def test_uninstrument_after_instrument(self):
spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 0)

def test_falcon_metrics(self):
self.client().simulate_get("/hello/756")
self.client().simulate_get("/hello/756")
self.client().simulate_get("/hello/756")
metrics_list = self.memory_metrics_reader.get_metrics_data()
number_data_point_seen = False
histogram_data_point_seen = False
self.assertTrue(len(metrics_list.resource_metrics) != 0)
for resource_metric in metrics_list.resource_metrics:
self.assertTrue(len(resource_metric.scope_metrics) != 0)
for scope_metric in resource_metric.scope_metrics:
self.assertTrue(len(scope_metric.metrics) != 0)
for metric in scope_metric.metrics:
self.assertIn(metric.name, _expected_metric_names)
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, 3)
histogram_data_point_seen = True
if isinstance(point, NumberDataPoint):
number_data_point_seen = True
for attr in point.attributes:
self.assertIn(
attr, _recommended_attrs[metric.name]
)
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,
"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",
}
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:
for point in list(metric.data.data_points):
if isinstance(point, HistogramDataPoint):
self.assertDictEqual(
expected_duration_attributes,
dict(point.attributes),
)
self.assertEqual(point.count, 1)
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)

def test_metric_uninstrument(self):
self.client().simulate_request(method="POST", path="/hello/756")
FalconInstrumentor().uninstrument()
self.client().simulate_request(method="POST", path="/hello/756")
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):
if isinstance(point, HistogramDataPoint):
self.assertEqual(point.count, 1)


class TestFalconInstrumentationWithTracerProvider(TestBase):
def setUp(self):
Expand Down