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

Feature/metrics instrumentation urllib3 #1198

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def response_hook(span, request, response):
import contextlib
import typing
from typing import Collection
from timeit import default_timer

import urllib3.connectionpool
import wrapt
Expand All @@ -83,9 +84,10 @@ def response_hook(span, request, response):
http_status_to_status_code,
unwrap,
)
from opentelemetry.metrics import Histogram, get_meter
from opentelemetry.propagate import inject
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import Span, SpanKind, get_tracer
from opentelemetry.trace import Span, Tracer, SpanKind, get_tracer
from opentelemetry.trace.status import Status
from opentelemetry.util.http.httplib import set_ip_on_next_http_connection

Expand Down Expand Up @@ -135,8 +137,31 @@ def _instrument(self, **kwargs):
"""
tracer_provider = kwargs.get("tracer_provider")
tracer = get_tracer(__name__, __version__, tracer_provider)

meter_provider = kwargs.get("meter_provider")
meter = get_meter(__name__, __version__, meter_provider)

duration_histogram = meter.create_histogram(
name="http.client.duration",
unit="ms",
description="measures the duration outbound HTTP requests",
)
request_size_histogram = meter.create_histogram(
name="http.client.request.size",
unit="By",
description="measures the size of HTTP request messages (compressed)",
)
response_size_histogram = meter.create_histogram(
name="http.client.response.size",
unit="By",
description="measures the size of HTTP response messages (compressed)",
)

_instrument(
tracer,
duration_histogram,
request_size_histogram,
response_size_histogram,
request_hook=kwargs.get("request_hook"),
response_hook=kwargs.get("response_hook"),
url_filter=kwargs.get("url_filter"),
Expand All @@ -147,7 +172,10 @@ def _uninstrument(self, **kwargs):


def _instrument(
tracer,
tracer: Tracer,
duration_histogram: Histogram,
request_size_histogram: Histogram,
response_size_histogram: Histogram,
request_hook: _RequestHookT = None,
response_hook: _ResponseHookT = None,
url_filter: _UrlFilterT = None,
Expand Down Expand Up @@ -175,11 +203,35 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
inject(headers)

with _suppress_further_instrumentation():
start_time = default_timer()
response = wrapped(*args, **kwargs)
elapsed_time = (default_timer() - start_time) * 1000
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved

_apply_response(span, response)
if callable(response_hook):
response_hook(span, instance, response)

parsed_url = urlparse(url)

metric_labels = {
SpanAttributes.HTTP_METHOD: method,
SpanAttributes.HTTP_HOST: parsed_url.hostname,
SpanAttributes.HTTP_SCHEME: parsed_url.scheme,
SpanAttributes.HTTP_STATUS_CODE: response.status,
SpanAttributes.NET_PEER_NAME: parsed_url.hostname,
SpanAttributes.NET_PEER_PORT: parsed_url.port
}

version = getattr(response, "version")
if version:
metric_labels[SpanAttributes.HTTP_FLAVOR] = \
"1.1" if version == 11 else "1.0"

request_size = 0 if body is None else len(body)
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
duration_histogram.record(elapsed_time, attributes=metric_labels)
request_size_histogram.record(request_size, attributes=metric_labels)
response_size_histogram.record(len(response.data), attributes=metric_labels)

return response

wrapt.wrap_function_wrapper(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = "0.32b0"
__version__ = "0.33b0"
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,50 @@ def assert_success_span(
"net.peer.ip": self.assert_ip,
}
self.assertGreaterEqual(span.attributes.items(), attributes.items())

class TestURLLib3InstrumentorMetric(HttpTestBase, TestBase):

def setUp(self):
super().setUp()
self.assert_ip = self.server.server_address[0]
self.assert_port = self.server.server_address[1]
self.http_host = ":".join(map(str, self.server.server_address[:2]))
self.http_url_base = "http://" + self.http_host
self.http_url = self.http_url_base + "/status/200"
URLLib3Instrumentor().instrument(meter_provider=self.meter_provider)

def tearDown(self):
super().tearDown()
URLLib3Instrumentor().uninstrument()

@staticmethod
def perform_request(url: str) -> urllib3.response.HTTPResponse:
with urllib3.PoolManager() as pool:
resp = pool.request("GET", url)
resp.close()
return resp

def test_basic_metric_success(self):
with urllib3.HTTPConnectionPool(self.http_host, timeout=3) as pool:
response = pool.request("GET", "/status/200",fields={"data":"test"})

expected_attributes = {
"http.status_code": 200,
"http.host": self.assert_ip,
"http.method": "GET",
"http.flavor": "1.1",
"http.scheme": "http",
'net.peer.name': self.assert_ip,
'net.peer.port': self.assert_port
}

resource_metrics = self.memory_metrics_reader.get_metrics_data().resource_metrics
for metrics in resource_metrics:
for scope_metrics in metrics.scope_metrics:
self.assertEqual(len(scope_metrics.metrics), 3)
for metric in scope_metrics.metrics:
for data_point in metric.data.data_points:
self.assertDictEqual(
expected_attributes, dict(data_point.attributes)
)
self.assertEqual(data_point.count, 1)
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved