Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 75332e2

Browse files
shalevrCircleCI
authored and
CircleCI
committedNov 13, 2022
metric instrumentation Tornado (open-telemetry#1252)
1 parent dbe53cb commit 75332e2

File tree

7 files changed

+464
-23
lines changed

7 files changed

+464
-23
lines changed
 

‎CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.13.0-0.34b0...HEAD)
9+
- Add metric instrumentation for tornado
10+
([#1252](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1252))
11+
912

1013
## [1.13.0-0.34b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.13.0-0.34b0) - 2022-09-26
1114

‎instrumentation/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
| [opentelemetry-instrumentation-sqlite3](./opentelemetry-instrumentation-sqlite3) | sqlite3 | No
3939
| [opentelemetry-instrumentation-starlette](./opentelemetry-instrumentation-starlette) | starlette ~= 0.13.0 | Yes
4040
| [opentelemetry-instrumentation-system-metrics](./opentelemetry-instrumentation-system-metrics) | psutil >= 5 | No
41-
| [opentelemetry-instrumentation-tornado](./opentelemetry-instrumentation-tornado) | tornado >= 5.1.1 | No
41+
| [opentelemetry-instrumentation-tornado](./opentelemetry-instrumentation-tornado) | tornado >= 5.1.1 | Yes
4242
| [opentelemetry-instrumentation-urllib](./opentelemetry-instrumentation-urllib) | urllib | No
4343
| [opentelemetry-instrumentation-urllib3](./opentelemetry-instrumentation-urllib3) | urllib3 >= 1.0.0, < 2.0.0 | Yes
4444
| [opentelemetry-instrumentation-wsgi](./opentelemetry-instrumentation-wsgi) | wsgi | Yes

‎instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/__init__.py

+171-15
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ def client_resposne_hook(span, future):
157157
from functools import partial
158158
from logging import getLogger
159159
from time import time_ns
160-
from typing import Collection
160+
from timeit import default_timer
161+
from typing import Collection, Dict
161162

162163
import tornado.web
163164
import wrapt
@@ -177,6 +178,8 @@ def client_resposne_hook(span, future):
177178
http_status_to_status_code,
178179
unwrap,
179180
)
181+
from opentelemetry.metrics import get_meter
182+
from opentelemetry.metrics._internal.instrument import Histogram
180183
from opentelemetry.propagators import textmap
181184
from opentelemetry.semconv.trace import SpanAttributes
182185
from opentelemetry.trace.status import Status, StatusCode
@@ -197,6 +200,14 @@ def client_resposne_hook(span, future):
197200
_HANDLER_CONTEXT_KEY = "_otel_trace_context_key"
198201
_OTEL_PATCHED_KEY = "_otel_patched_key"
199202

203+
_START_TIME = "start_time"
204+
_CLIENT_DURATION_HISTOGRAM = "http.client.duration"
205+
_CLIENT_REQUEST_SIZE_HISTOGRAM = "http.client.request.size"
206+
_CLIENT_RESPONSE_SIZE_HISTOGRAM = "http.client.response.size"
207+
_SERVER_DURATION_HISTOGRAM = "http.server.duration"
208+
_SERVER_REQUEST_SIZE_HISTOGRAM = "http.server.request.size"
209+
_SERVER_RESPONSE_SIZE_HISTOGRAM = "http.server.response.size"
210+
_SERVER_ACTIVE_REQUESTS_HISTOGRAM = "http.server.active_requests"
200211

201212
_excluded_urls = get_excluded_urls("TORNADO")
202213
_traced_request_attrs = get_traced_request_attrs("TORNADO")
@@ -233,13 +244,21 @@ def _instrument(self, **kwargs):
233244
tracer_provider = kwargs.get("tracer_provider")
234245
tracer = trace.get_tracer(__name__, __version__, tracer_provider)
235246

247+
meter_provider = kwargs.get("meter_provider")
248+
meter = get_meter(__name__, __version__, meter_provider)
249+
250+
client_histograms = _create_client_histograms(meter)
251+
server_histograms = _create_server_histograms(meter)
252+
236253
client_request_hook = kwargs.get("client_request_hook", None)
237254
client_response_hook = kwargs.get("client_response_hook", None)
238255
server_request_hook = kwargs.get("server_request_hook", None)
239256

240257
def handler_init(init, handler, args, kwargs):
241258
cls = handler.__class__
242-
if patch_handler_class(tracer, cls, server_request_hook):
259+
if patch_handler_class(
260+
tracer, server_histograms, cls, server_request_hook
261+
):
243262
self.patched_handlers.append(cls)
244263
return init(*args, **kwargs)
245264

@@ -250,7 +269,13 @@ def handler_init(init, handler, args, kwargs):
250269
"tornado.httpclient",
251270
"AsyncHTTPClient.fetch",
252271
partial(
253-
fetch_async, tracer, client_request_hook, client_response_hook
272+
fetch_async,
273+
tracer,
274+
client_request_hook,
275+
client_response_hook,
276+
client_histograms[_CLIENT_DURATION_HISTOGRAM],
277+
client_histograms[_CLIENT_REQUEST_SIZE_HISTOGRAM],
278+
client_histograms[_CLIENT_RESPONSE_SIZE_HISTOGRAM],
254279
),
255280
)
256281

@@ -262,14 +287,71 @@ def _uninstrument(self, **kwargs):
262287
self.patched_handlers = []
263288

264289

265-
def patch_handler_class(tracer, cls, request_hook=None):
290+
def _create_server_histograms(meter) -> Dict[str, Histogram]:
291+
histograms = {
292+
_SERVER_DURATION_HISTOGRAM: meter.create_histogram(
293+
name="http.server.duration",
294+
unit="ms",
295+
description="measures the duration outbound HTTP requests",
296+
),
297+
_SERVER_REQUEST_SIZE_HISTOGRAM: meter.create_histogram(
298+
name="http.server.request.size",
299+
unit="By",
300+
description="measures the size of HTTP request messages (compressed)",
301+
),
302+
_SERVER_RESPONSE_SIZE_HISTOGRAM: meter.create_histogram(
303+
name="http.server.response.size",
304+
unit="By",
305+
description="measures the size of HTTP response messages (compressed)",
306+
),
307+
_SERVER_ACTIVE_REQUESTS_HISTOGRAM: meter.create_up_down_counter(
308+
name="http.server.active_requests",
309+
unit="requests",
310+
description="measures the number of concurrent HTTP requests that are currently in-flight",
311+
),
312+
}
313+
314+
return histograms
315+
316+
317+
def _create_client_histograms(meter) -> Dict[str, Histogram]:
318+
histograms = {
319+
_CLIENT_DURATION_HISTOGRAM: meter.create_histogram(
320+
name="http.client.duration",
321+
unit="ms",
322+
description="measures the duration outbound HTTP requests",
323+
),
324+
_CLIENT_REQUEST_SIZE_HISTOGRAM: meter.create_histogram(
325+
name="http.client.request.size",
326+
unit="By",
327+
description="measures the size of HTTP request messages (compressed)",
328+
),
329+
_CLIENT_RESPONSE_SIZE_HISTOGRAM: meter.create_histogram(
330+
name="http.client.response.size",
331+
unit="By",
332+
description="measures the size of HTTP response messages (compressed)",
333+
),
334+
}
335+
336+
return histograms
337+
338+
339+
def patch_handler_class(tracer, server_histograms, cls, request_hook=None):
266340
if getattr(cls, _OTEL_PATCHED_KEY, False):
267341
return False
268342

269343
setattr(cls, _OTEL_PATCHED_KEY, True)
270-
_wrap(cls, "prepare", partial(_prepare, tracer, request_hook))
271-
_wrap(cls, "on_finish", partial(_on_finish, tracer))
272-
_wrap(cls, "log_exception", partial(_log_exception, tracer))
344+
_wrap(
345+
cls,
346+
"prepare",
347+
partial(_prepare, tracer, server_histograms, request_hook),
348+
)
349+
_wrap(cls, "on_finish", partial(_on_finish, tracer, server_histograms))
350+
_wrap(
351+
cls,
352+
"log_exception",
353+
partial(_log_exception, tracer, server_histograms),
354+
)
273355
return True
274356

275357

@@ -289,28 +371,40 @@ def _wrap(cls, method_name, wrapper):
289371
wrapt.apply_patch(cls, method_name, wrapper)
290372

291373

292-
def _prepare(tracer, request_hook, func, handler, args, kwargs):
293-
start_time = time_ns()
374+
def _prepare(
375+
tracer, server_histograms, request_hook, func, handler, args, kwargs
376+
):
377+
server_histograms[_START_TIME] = default_timer()
378+
294379
request = handler.request
295380
if _excluded_urls.url_disabled(request.uri):
296381
return func(*args, **kwargs)
297-
ctx = _start_span(tracer, handler, start_time)
382+
383+
_record_prepare_metrics(server_histograms, handler)
384+
385+
ctx = _start_span(tracer, handler)
298386
if request_hook:
299387
request_hook(ctx.span, handler)
300388
return func(*args, **kwargs)
301389

302390

303-
def _on_finish(tracer, func, handler, args, kwargs):
391+
def _on_finish(tracer, server_histograms, func, handler, args, kwargs):
304392
response = func(*args, **kwargs)
393+
394+
_record_on_finish_metrics(server_histograms, handler)
395+
305396
_finish_span(tracer, handler)
397+
306398
return response
307399

308400

309-
def _log_exception(tracer, func, handler, args, kwargs):
401+
def _log_exception(tracer, server_histograms, func, handler, args, kwargs):
310402
error = None
311403
if len(args) == 3:
312404
error = args[1]
313405

406+
_record_on_finish_metrics(server_histograms, handler, error)
407+
314408
_finish_span(tracer, handler, error)
315409
return func(*args, **kwargs)
316410

@@ -377,11 +471,11 @@ def _get_full_handler_name(handler):
377471
return f"{klass.__module__}.{klass.__qualname__}"
378472

379473

380-
def _start_span(tracer, handler, start_time) -> _TraceContext:
474+
def _start_span(tracer, handler) -> _TraceContext:
381475
span, token = _start_internal_or_server_span(
382476
tracer=tracer,
383477
span_name=_get_operation_name(handler, handler.request),
384-
start_time=start_time,
478+
start_time=time_ns(),
385479
context_carrier=handler.request.headers,
386480
context_getter=textmap.default_getter,
387481
)
@@ -423,7 +517,7 @@ def _finish_span(tracer, handler, error=None):
423517
if isinstance(error, tornado.web.HTTPError):
424518
status_code = error.status_code
425519
if not ctx and status_code == 404:
426-
ctx = _start_span(tracer, handler, time_ns())
520+
ctx = _start_span(tracer, handler)
427521
else:
428522
status_code = 500
429523
reason = None
@@ -462,3 +556,65 @@ def _finish_span(tracer, handler, error=None):
462556
if ctx.token:
463557
context.detach(ctx.token)
464558
delattr(handler, _HANDLER_CONTEXT_KEY)
559+
560+
561+
def _record_prepare_metrics(server_histograms, handler):
562+
request_size = int(handler.request.headers.get("Content-Length", 0))
563+
metric_attributes = _create_metric_attributes(handler)
564+
565+
server_histograms[_SERVER_REQUEST_SIZE_HISTOGRAM].record(
566+
request_size, attributes=metric_attributes
567+
)
568+
569+
active_requests_attributes = _create_active_requests_attributes(
570+
handler.request
571+
)
572+
server_histograms[_SERVER_ACTIVE_REQUESTS_HISTOGRAM].add(
573+
1, attributes=active_requests_attributes
574+
)
575+
576+
577+
def _record_on_finish_metrics(server_histograms, handler, error=None):
578+
elapsed_time = round(
579+
(default_timer() - server_histograms[_START_TIME]) * 1000
580+
)
581+
582+
response_size = int(handler._headers.get("Content-Length", 0))
583+
metric_attributes = _create_metric_attributes(handler)
584+
585+
if isinstance(error, tornado.web.HTTPError):
586+
metric_attributes[SpanAttributes.HTTP_STATUS_CODE] = error.status_code
587+
588+
server_histograms[_SERVER_RESPONSE_SIZE_HISTOGRAM].record(
589+
response_size, attributes=metric_attributes
590+
)
591+
592+
server_histograms[_SERVER_DURATION_HISTOGRAM].record(
593+
elapsed_time, attributes=metric_attributes
594+
)
595+
596+
active_requests_attributes = _create_active_requests_attributes(
597+
handler.request
598+
)
599+
server_histograms[_SERVER_ACTIVE_REQUESTS_HISTOGRAM].add(
600+
-1, attributes=active_requests_attributes
601+
)
602+
603+
604+
def _create_active_requests_attributes(request):
605+
metric_attributes = {
606+
SpanAttributes.HTTP_METHOD: request.method,
607+
SpanAttributes.HTTP_SCHEME: request.protocol,
608+
SpanAttributes.HTTP_FLAVOR: request.version,
609+
SpanAttributes.HTTP_HOST: request.host,
610+
SpanAttributes.HTTP_TARGET: request.path,
611+
}
612+
613+
return metric_attributes
614+
615+
616+
def _create_metric_attributes(handler):
617+
metric_attributes = _create_active_requests_attributes(handler.request)
618+
metric_attributes[SpanAttributes.HTTP_STATUS_CODE] = handler.get_status()
619+
620+
return metric_attributes

‎instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/client.py

+48-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,18 @@ def _normalize_request(args, kwargs):
4141
return (new_args, new_kwargs)
4242

4343

44-
def fetch_async(tracer, request_hook, response_hook, func, _, args, kwargs):
44+
def fetch_async(
45+
tracer,
46+
request_hook,
47+
response_hook,
48+
duration_histogram,
49+
request_size_histogram,
50+
response_size_histogram,
51+
func,
52+
_,
53+
args,
54+
kwargs,
55+
):
4556
start_time = time_ns()
4657

4758
# Return immediately if no args were provided (error)
@@ -78,21 +89,34 @@ def fetch_async(tracer, request_hook, response_hook, func, _, args, kwargs):
7889
_finish_tracing_callback,
7990
span=span,
8091
response_hook=response_hook,
92+
duration_histogram=duration_histogram,
93+
request_size_histogram=request_size_histogram,
94+
response_size_histogram=response_size_histogram,
8195
)
8296
)
8397
return future
8498

8599

86-
def _finish_tracing_callback(future, span, response_hook):
100+
def _finish_tracing_callback(
101+
future,
102+
span,
103+
response_hook,
104+
duration_histogram,
105+
request_size_histogram,
106+
response_size_histogram,
107+
):
87108
status_code = None
88109
description = None
89110
exc = future.exception()
111+
112+
response = future.result()
113+
90114
if span.is_recording() and exc:
91115
if isinstance(exc, HTTPError):
92116
status_code = exc.code
93117
description = f"{type(exc).__name__}: {exc}"
94118
else:
95-
status_code = future.result().code
119+
status_code = response.code
96120

97121
if status_code is not None:
98122
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
@@ -102,6 +126,27 @@ def _finish_tracing_callback(future, span, response_hook):
102126
description=description,
103127
)
104128
)
129+
130+
metric_attributes = _create_metric_attributes(response)
131+
request_size = int(response.request.headers.get("Content-Length", 0))
132+
response_size = int(response.headers.get("Content-Length", 0))
133+
134+
duration_histogram.record(
135+
response.request_time, attributes=metric_attributes
136+
)
137+
request_size_histogram.record(request_size, attributes=metric_attributes)
138+
response_size_histogram.record(response_size, attributes=metric_attributes)
139+
105140
if response_hook:
106141
response_hook(span, future)
107142
span.end()
143+
144+
145+
def _create_metric_attributes(response):
146+
metric_attributes = {
147+
SpanAttributes.HTTP_STATUS_CODE: response.code,
148+
SpanAttributes.HTTP_URL: remove_url_credentials(response.request.url),
149+
SpanAttributes.HTTP_METHOD: response.request.method,
150+
}
151+
152+
return metric_attributes

‎instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/package.py

+2
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@
1414

1515

1616
_instruments = ("tornado >= 5.1.1",)
17+
18+
_supports_metrics = True

‎instrumentation/opentelemetry-instrumentation-tornado/tests/test_instrumentation.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ def get_app(self):
5555
return app
5656

5757
def setUp(self):
58+
super().setUp()
5859
TornadoInstrumentor().instrument(
5960
server_request_hook=getattr(self, "server_request_hook", None),
6061
client_request_hook=getattr(self, "client_request_hook", None),
6162
client_response_hook=getattr(self, "client_response_hook", None),
6263
)
63-
super().setUp()
6464
# pylint: disable=protected-access
6565
self.env_patch = patch.dict(
6666
"os.environ",
@@ -110,9 +110,9 @@ def test_patch_references(self):
110110

111111
def test_patch_applied_only_once(self):
112112
tracer = trace.get_tracer(__name__)
113-
self.assertTrue(patch_handler_class(tracer, AsyncHandler))
114-
self.assertFalse(patch_handler_class(tracer, AsyncHandler))
115-
self.assertFalse(patch_handler_class(tracer, AsyncHandler))
113+
self.assertTrue(patch_handler_class(tracer, {}, AsyncHandler))
114+
self.assertFalse(patch_handler_class(tracer, {}, AsyncHandler))
115+
self.assertFalse(patch_handler_class(tracer, {}, AsyncHandler))
116116
unpatch_handler_class(AsyncHandler)
117117

118118

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
from timeit import default_timer
17+
18+
from tornado.testing import AsyncHTTPTestCase
19+
20+
from opentelemetry import trace
21+
from opentelemetry.instrumentation.tornado import TornadoInstrumentor
22+
from opentelemetry.sdk.metrics.export import (
23+
HistogramDataPoint,
24+
NumberDataPoint,
25+
)
26+
from opentelemetry.test.test_base import TestBase
27+
28+
from .tornado_test_app import make_app
29+
30+
31+
class TornadoTest(AsyncHTTPTestCase, TestBase):
32+
# pylint:disable=no-self-use
33+
def get_app(self):
34+
tracer = trace.get_tracer(__name__)
35+
app = make_app(tracer)
36+
return app
37+
38+
def get_sorted_metrics(self):
39+
resource_metrics = (
40+
self.memory_metrics_reader.get_metrics_data().resource_metrics
41+
)
42+
for metrics in resource_metrics:
43+
for scope_metrics in metrics.scope_metrics:
44+
all_metrics = list(scope_metrics.metrics)
45+
return self.sorted_metrics(all_metrics)
46+
47+
@staticmethod
48+
def sorted_metrics(metrics):
49+
"""
50+
Sorts metrics by metric name.
51+
"""
52+
return sorted(
53+
metrics,
54+
key=lambda m: m.name,
55+
)
56+
57+
def assert_metric_expected(
58+
self, metric, expected_value, expected_attributes
59+
):
60+
data_point = next(metric.data.data_points)
61+
62+
if isinstance(data_point, HistogramDataPoint):
63+
self.assertEqual(
64+
data_point.sum,
65+
expected_value,
66+
)
67+
elif isinstance(data_point, NumberDataPoint):
68+
self.assertEqual(
69+
data_point.value,
70+
expected_value,
71+
)
72+
73+
self.assertDictEqual(
74+
expected_attributes,
75+
dict(data_point.attributes),
76+
)
77+
78+
def assert_duration_metric_expected(
79+
self, metric, duration_estimated, expected_attributes
80+
):
81+
data_point = next(metric.data.data_points)
82+
83+
self.assertAlmostEqual(
84+
data_point.sum,
85+
duration_estimated,
86+
delta=200,
87+
)
88+
89+
self.assertDictEqual(
90+
expected_attributes,
91+
dict(data_point.attributes),
92+
)
93+
94+
def setUp(self):
95+
super().setUp()
96+
TornadoInstrumentor().instrument(
97+
server_request_hook=getattr(self, "server_request_hook", None),
98+
client_request_hook=getattr(self, "client_request_hook", None),
99+
client_response_hook=getattr(self, "client_response_hook", None),
100+
meter_provider=self.meter_provider,
101+
)
102+
103+
def tearDown(self):
104+
TornadoInstrumentor().uninstrument()
105+
super().tearDown()
106+
107+
108+
class TestTornadoInstrumentor(TornadoTest):
109+
def test_basic_metrics(self):
110+
start_time = default_timer()
111+
response = self.fetch("/")
112+
client_duration_estimated = (default_timer() - start_time) * 1000
113+
114+
metrics = self.get_sorted_metrics()
115+
self.assertEqual(len(metrics), 7)
116+
117+
(
118+
client_duration,
119+
client_request_size,
120+
client_response_size,
121+
) = metrics[:3]
122+
123+
(
124+
server_active_request,
125+
server_duration,
126+
server_request_size,
127+
server_response_size,
128+
) = metrics[3:]
129+
130+
self.assertEqual(
131+
server_active_request.name, "http.server.active_requests"
132+
)
133+
self.assert_metric_expected(
134+
server_active_request,
135+
0,
136+
{
137+
"http.method": "GET",
138+
"http.flavor": "HTTP/1.1",
139+
"http.scheme": "http",
140+
"http.target": "/",
141+
"http.host": response.request.headers["host"],
142+
},
143+
)
144+
145+
self.assertEqual(server_duration.name, "http.server.duration")
146+
self.assert_duration_metric_expected(
147+
server_duration,
148+
client_duration_estimated,
149+
{
150+
"http.status_code": response.code,
151+
"http.method": "GET",
152+
"http.flavor": "HTTP/1.1",
153+
"http.scheme": "http",
154+
"http.target": "/",
155+
"http.host": response.request.headers["host"],
156+
},
157+
)
158+
159+
self.assertEqual(server_request_size.name, "http.server.request.size")
160+
self.assert_metric_expected(
161+
server_request_size,
162+
0,
163+
{
164+
"http.status_code": 200,
165+
"http.method": "GET",
166+
"http.flavor": "HTTP/1.1",
167+
"http.scheme": "http",
168+
"http.target": "/",
169+
"http.host": response.request.headers["host"],
170+
},
171+
)
172+
173+
self.assertEqual(
174+
server_response_size.name, "http.server.response.size"
175+
)
176+
self.assert_metric_expected(
177+
server_response_size,
178+
len(response.body),
179+
{
180+
"http.status_code": response.code,
181+
"http.method": "GET",
182+
"http.flavor": "HTTP/1.1",
183+
"http.scheme": "http",
184+
"http.target": "/",
185+
"http.host": response.request.headers["host"],
186+
},
187+
)
188+
189+
self.assertEqual(client_duration.name, "http.client.duration")
190+
self.assert_duration_metric_expected(
191+
client_duration,
192+
client_duration_estimated,
193+
{
194+
"http.status_code": response.code,
195+
"http.method": "GET",
196+
"http.url": response.effective_url,
197+
},
198+
)
199+
200+
self.assertEqual(client_request_size.name, "http.client.request.size")
201+
self.assert_metric_expected(
202+
client_request_size,
203+
0,
204+
{
205+
"http.status_code": response.code,
206+
"http.method": "GET",
207+
"http.url": response.effective_url,
208+
},
209+
)
210+
211+
self.assertEqual(
212+
client_response_size.name, "http.client.response.size"
213+
)
214+
self.assert_metric_expected(
215+
client_response_size,
216+
len(response.body),
217+
{
218+
"http.status_code": response.code,
219+
"http.method": "GET",
220+
"http.url": response.effective_url,
221+
},
222+
)
223+
224+
def test_metric_uninstrument(self):
225+
self.fetch("/")
226+
TornadoInstrumentor().uninstrument()
227+
self.fetch("/")
228+
229+
metrics_list = self.memory_metrics_reader.get_metrics_data()
230+
for resource_metric in metrics_list.resource_metrics:
231+
for scope_metric in resource_metric.scope_metrics:
232+
for metric in scope_metric.metrics:
233+
for point in list(metric.data.data_points):
234+
if isinstance(point, HistogramDataPoint):
235+
self.assertEqual(point.count, 1)

0 commit comments

Comments
 (0)
Please sign in to comment.