@@ -67,6 +67,7 @@ def response_hook(span, request, response):
6767import contextlib
6868import typing
6969from typing import Collection
70+ from timeit import default_timer
7071
7172import urllib3 .connectionpool
7273import wrapt
@@ -83,9 +84,10 @@ def response_hook(span, request, response):
8384 http_status_to_status_code ,
8485 unwrap ,
8586)
87+ from opentelemetry .metrics import Histogram , get_meter
8688from opentelemetry .propagate import inject
8789from opentelemetry .semconv .trace import SpanAttributes
88- from opentelemetry .trace import Span , SpanKind , get_tracer
90+ from opentelemetry .trace import Span , Tracer , SpanKind , get_tracer
8991from opentelemetry .trace .status import Status
9092from opentelemetry .util .http .httplib import set_ip_on_next_http_connection
9193
@@ -135,8 +137,31 @@ def _instrument(self, **kwargs):
135137 """
136138 tracer_provider = kwargs .get ("tracer_provider" )
137139 tracer = get_tracer (__name__ , __version__ , tracer_provider )
140+
141+ meter_provider = kwargs .get ("meter_provider" )
142+ meter = get_meter (__name__ , __version__ , meter_provider )
143+
144+ duration_histogram = meter .create_histogram (
145+ name = "http.client.duration" ,
146+ unit = "ms" ,
147+ description = "measures the duration outbound HTTP requests" ,
148+ )
149+ request_size_histogram = meter .create_histogram (
150+ name = "http.client.request.size" ,
151+ unit = "By" ,
152+ description = "measures the size of HTTP request messages (compressed)" ,
153+ )
154+ response_size_histogram = meter .create_histogram (
155+ name = "http.client.response.size" ,
156+ unit = "By" ,
157+ description = "measures the size of HTTP response messages (compressed)" ,
158+ )
159+
138160 _instrument (
139161 tracer ,
162+ duration_histogram ,
163+ request_size_histogram ,
164+ response_size_histogram ,
140165 request_hook = kwargs .get ("request_hook" ),
141166 response_hook = kwargs .get ("response_hook" ),
142167 url_filter = kwargs .get ("url_filter" ),
@@ -147,7 +172,10 @@ def _uninstrument(self, **kwargs):
147172
148173
149174def _instrument (
150- tracer ,
175+ tracer : Tracer ,
176+ duration_histogram : Histogram ,
177+ request_size_histogram : Histogram ,
178+ response_size_histogram : Histogram ,
151179 request_hook : _RequestHookT = None ,
152180 response_hook : _ResponseHookT = None ,
153181 url_filter : _UrlFilterT = None ,
@@ -175,11 +203,35 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
175203 inject (headers )
176204
177205 with _suppress_further_instrumentation ():
206+ start_time = default_timer ()
178207 response = wrapped (* args , ** kwargs )
208+ elapsed_time = (default_timer () - start_time ) * 1000
179209
180210 _apply_response (span , response )
181211 if callable (response_hook ):
182212 response_hook (span , instance , response )
213+
214+ parsed_url = urlparse (url )
215+
216+ metric_labels = {
217+ SpanAttributes .HTTP_METHOD : method ,
218+ SpanAttributes .HTTP_HOST : parsed_url .hostname ,
219+ SpanAttributes .HTTP_SCHEME : parsed_url .scheme ,
220+ SpanAttributes .HTTP_STATUS_CODE : response .status ,
221+ SpanAttributes .NET_PEER_NAME : parsed_url .hostname ,
222+ SpanAttributes .NET_PEER_PORT : parsed_url .port
223+ }
224+
225+ version = getattr (response , "version" )
226+ if version :
227+ metric_labels [SpanAttributes .HTTP_FLAVOR ] = \
228+ "1.1" if version == 11 else "1.0"
229+
230+ request_size = 0 if body is None else len (body )
231+ duration_histogram .record (elapsed_time , attributes = metric_labels )
232+ request_size_histogram .record (request_size , attributes = metric_labels )
233+ response_size_histogram .record (len (response .data ), attributes = metric_labels )
234+
183235 return response
184236
185237 wrapt .wrap_function_wrapper (
0 commit comments