opentelemetry-instrumentation-tornado 0.56b0__tar.gz → 0.58b0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (13) hide show
  1. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/PKG-INFO +4 -4
  2. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/pyproject.toml +3 -3
  3. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/src/opentelemetry/instrumentation/tornado/__init__.py +13 -6
  4. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/src/opentelemetry/instrumentation/tornado/version.py +1 -1
  5. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/tests/test_metrics_instrumentation.py +88 -0
  6. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/tests/tornado_test_app.py +9 -0
  7. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/.gitignore +0 -0
  8. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/LICENSE +0 -0
  9. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/README.rst +0 -0
  10. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/src/opentelemetry/instrumentation/tornado/client.py +0 -0
  11. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/src/opentelemetry/instrumentation/tornado/package.py +0 -0
  12. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/tests/__init__.py +0 -0
  13. {opentelemetry_instrumentation_tornado-0.56b0 → opentelemetry_instrumentation_tornado-0.58b0}/tests/test_instrumentation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opentelemetry-instrumentation-tornado
3
- Version: 0.56b0
3
+ Version: 0.58b0
4
4
  Summary: Tornado instrumentation for OpenTelemetry
5
5
  Project-URL: Homepage, https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-tornado
6
6
  Project-URL: Repository, https://github.com/open-telemetry/opentelemetry-python-contrib
@@ -18,9 +18,9 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Requires-Python: >=3.9
20
20
  Requires-Dist: opentelemetry-api~=1.12
21
- Requires-Dist: opentelemetry-instrumentation==0.56b0
22
- Requires-Dist: opentelemetry-semantic-conventions==0.56b0
23
- Requires-Dist: opentelemetry-util-http==0.56b0
21
+ Requires-Dist: opentelemetry-instrumentation==0.58b0
22
+ Requires-Dist: opentelemetry-semantic-conventions==0.58b0
23
+ Requires-Dist: opentelemetry-util-http==0.58b0
24
24
  Provides-Extra: instruments
25
25
  Requires-Dist: tornado>=5.1.1; extra == 'instruments'
26
26
  Description-Content-Type: text/x-rst
@@ -25,9 +25,9 @@ classifiers = [
25
25
  ]
26
26
  dependencies = [
27
27
  "opentelemetry-api ~= 1.12",
28
- "opentelemetry-instrumentation == 0.56b0",
29
- "opentelemetry-semantic-conventions == 0.56b0",
30
- "opentelemetry-util-http == 0.56b0",
28
+ "opentelemetry-instrumentation == 0.58b0",
29
+ "opentelemetry-semantic-conventions == 0.58b0",
30
+ "opentelemetry-util-http == 0.58b0",
31
31
  ]
32
32
 
33
33
  [project.optional-dependencies]
@@ -211,6 +211,7 @@ from .client import fetch_async # pylint: disable=E0401
211
211
 
212
212
  _logger = getLogger(__name__)
213
213
  _TraceContext = namedtuple("TraceContext", ["activation", "span", "token"])
214
+ _HANDLER_STATE_KEY = "_otel_state_key"
214
215
  _HANDLER_CONTEXT_KEY = "_otel_trace_context_key"
215
216
  _OTEL_PATCHED_KEY = "_otel_patched_key"
216
217
 
@@ -402,10 +403,14 @@ def _wrap(cls, method_name, wrapper):
402
403
  def _prepare(
403
404
  tracer, server_histograms, request_hook, func, handler, args, kwargs
404
405
  ):
405
- server_histograms[_START_TIME] = default_timer()
406
-
407
406
  request = handler.request
408
- if _excluded_urls.url_disabled(request.uri):
407
+ otel_handler_state = {
408
+ _START_TIME: default_timer(),
409
+ "exclude_request": _excluded_urls.url_disabled(request.uri),
410
+ }
411
+ setattr(handler, _HANDLER_STATE_KEY, otel_handler_state)
412
+
413
+ if otel_handler_state["exclude_request"]:
409
414
  return func(*args, **kwargs)
410
415
 
411
416
  _record_prepare_metrics(server_histograms, handler)
@@ -622,9 +627,11 @@ def _record_prepare_metrics(server_histograms, handler):
622
627
 
623
628
 
624
629
  def _record_on_finish_metrics(server_histograms, handler, error=None):
625
- elapsed_time = round(
626
- (default_timer() - server_histograms[_START_TIME]) * 1000
627
- )
630
+ otel_handler_state = getattr(handler, _HANDLER_STATE_KEY, None) or {}
631
+ if otel_handler_state.get("exclude_request"):
632
+ return
633
+ start_time = otel_handler_state.get(_START_TIME, None) or default_timer()
634
+ elapsed_time = round((default_timer() - start_time) * 1000)
628
635
 
629
636
  response_size = int(handler._headers.get("Content-Length", 0))
630
637
  metric_attributes = _create_metric_attributes(handler)
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "0.56b0"
15
+ __version__ = "0.58b0"
@@ -13,8 +13,11 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
+ import asyncio
16
17
  from timeit import default_timer
17
18
 
19
+ import tornado.testing
20
+
18
21
  from opentelemetry.instrumentation.tornado import TornadoInstrumentor
19
22
  from opentelemetry.sdk.metrics.export import HistogramDataPoint
20
23
 
@@ -165,6 +168,78 @@ class TestTornadoMetricsInstrumentation(TornadoTest):
165
168
  ),
166
169
  )
167
170
 
171
+ @tornado.testing.gen_test
172
+ async def test_metrics_concurrent_requests(self):
173
+ """
174
+ Test that metrics can handle concurrent requests and calculate in an async-safe way.
175
+ """
176
+ req1 = self.http_client.fetch(self.get_url("/slow?duration=1.0"))
177
+ req2 = self.http_client.fetch(self.get_url("/async"))
178
+ await asyncio.gather(req1, req2)
179
+
180
+ metrics = self.get_sorted_metrics()
181
+ self.assertEqual(len(metrics), 7)
182
+
183
+ client_duration = metrics[0]
184
+ server_duration = metrics[4]
185
+ self.assertEqual(client_duration.name, "http.client.duration")
186
+ self.assertEqual(server_duration.name, "http.server.duration")
187
+
188
+ # Calculating duration requires tracking state via `_HANDLER_STATE_KEY`, so we want to make sure
189
+ # duration is calculated properly per request, and doesn't affect concurrent requests.
190
+ req1_client_duration_data_point = next(
191
+ dp
192
+ for dp in client_duration.data.data_points
193
+ if "/slow" in dp.attributes.get("http.url")
194
+ )
195
+ req1_server_duration_data_point = next(
196
+ dp
197
+ for dp in server_duration.data.data_points
198
+ if "/slow" in dp.attributes.get("http.target")
199
+ )
200
+ req2_client_duration_data_point = next(
201
+ dp
202
+ for dp in client_duration.data.data_points
203
+ if "/async" in dp.attributes.get("http.url")
204
+ )
205
+ req2_server_duration_data_point = next(
206
+ dp
207
+ for dp in server_duration.data.data_points
208
+ if "/async" in dp.attributes.get("http.target")
209
+ )
210
+
211
+ # Server and client durations should be similar (adjusting for msecs vs secs)
212
+ self.assertAlmostEqual(
213
+ abs(
214
+ req1_server_duration_data_point.sum / 1000.0
215
+ - req1_client_duration_data_point.sum
216
+ ),
217
+ 0.0,
218
+ delta=0.01,
219
+ )
220
+ self.assertAlmostEqual(
221
+ abs(
222
+ req2_server_duration_data_point.sum / 1000.0
223
+ - req2_client_duration_data_point.sum
224
+ ),
225
+ 0.0,
226
+ delta=0.01,
227
+ )
228
+
229
+ # Make sure duration is roughly equivalent to expected (req1/slow) should be around 1 second
230
+ self.assertAlmostEqual(
231
+ req1_server_duration_data_point.sum / 1000.0,
232
+ 1.0,
233
+ delta=0.1,
234
+ msg="Should have been about 1 second",
235
+ )
236
+ self.assertAlmostEqual(
237
+ req2_server_duration_data_point.sum / 1000.0,
238
+ 0.0,
239
+ delta=0.1,
240
+ msg="Should have been really short",
241
+ )
242
+
168
243
  def test_metric_uninstrument(self):
169
244
  self.fetch("/")
170
245
  TornadoInstrumentor().uninstrument()
@@ -177,3 +252,16 @@ class TestTornadoMetricsInstrumentation(TornadoTest):
177
252
  for point in list(metric.data.data_points):
178
253
  if isinstance(point, HistogramDataPoint):
179
254
  self.assertEqual(point.count, 1)
255
+
256
+ def test_exclude_lists(self):
257
+ def test_excluded(path):
258
+ self.fetch(path)
259
+
260
+ # Verify no server metrics written (only client ones should exist)
261
+ metrics = self.get_sorted_metrics()
262
+ for metric in metrics:
263
+ self.assertTrue("http.server" not in metric.name, metric)
264
+ self.assertEqual(len(metrics), 3, metrics)
265
+
266
+ test_excluded("/healthz")
267
+ test_excluded("/ping")
@@ -1,4 +1,5 @@
1
1
  # pylint: disable=W0223,R0201
2
+ import asyncio
2
3
  import time
3
4
 
4
5
  import tornado.web
@@ -106,6 +107,13 @@ class CustomResponseHeaderHandler(tornado.web.RequestHandler):
106
107
  self.set_status(200)
107
108
 
108
109
 
110
+ class SlowHandler(tornado.web.RequestHandler):
111
+ async def get(self):
112
+ with self.application.tracer.start_as_current_span("slow"):
113
+ duration = float(self.get_argument("duration", "1.0"))
114
+ await asyncio.sleep(duration)
115
+
116
+
109
117
  class RaiseHTTPErrorHandler(tornado.web.RequestHandler):
110
118
  def get(self):
111
119
  raise tornado.web.HTTPError(403)
@@ -133,6 +141,7 @@ def make_app(tracer):
133
141
  (r"/ping", HealthCheckHandler),
134
142
  (r"/test_custom_response_headers", CustomResponseHeaderHandler),
135
143
  (r"/raise_403", RaiseHTTPErrorHandler),
144
+ (r"/slow", SlowHandler),
136
145
  (r"/echo_socket", EchoWebSocketHandler),
137
146
  ]
138
147
  )