sentry-sdk 3.0.0a1__py2.py3-none-any.whl → 3.0.0a3__py2.py3-none-any.whl
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.
Potentially problematic release.
This version of sentry-sdk might be problematic. Click here for more details.
- sentry_sdk/__init__.py +2 -0
- sentry_sdk/_compat.py +5 -12
- sentry_sdk/_init_implementation.py +7 -7
- sentry_sdk/_log_batcher.py +17 -29
- sentry_sdk/_lru_cache.py +7 -9
- sentry_sdk/_queue.py +2 -4
- sentry_sdk/_types.py +11 -18
- sentry_sdk/_werkzeug.py +5 -7
- sentry_sdk/ai/monitoring.py +44 -31
- sentry_sdk/ai/utils.py +3 -4
- sentry_sdk/api.py +75 -87
- sentry_sdk/attachments.py +10 -12
- sentry_sdk/client.py +137 -155
- sentry_sdk/consts.py +430 -174
- sentry_sdk/crons/api.py +16 -17
- sentry_sdk/crons/decorator.py +25 -27
- sentry_sdk/debug.py +4 -6
- sentry_sdk/envelope.py +46 -112
- sentry_sdk/feature_flags.py +9 -15
- sentry_sdk/integrations/__init__.py +24 -19
- sentry_sdk/integrations/_asgi_common.py +15 -18
- sentry_sdk/integrations/_wsgi_common.py +22 -33
- sentry_sdk/integrations/aiohttp.py +32 -30
- sentry_sdk/integrations/anthropic.py +42 -37
- sentry_sdk/integrations/argv.py +3 -4
- sentry_sdk/integrations/ariadne.py +16 -18
- sentry_sdk/integrations/arq.py +21 -29
- sentry_sdk/integrations/asgi.py +63 -37
- sentry_sdk/integrations/asyncio.py +14 -16
- sentry_sdk/integrations/atexit.py +6 -10
- sentry_sdk/integrations/aws_lambda.py +26 -36
- sentry_sdk/integrations/beam.py +10 -18
- sentry_sdk/integrations/boto3.py +18 -16
- sentry_sdk/integrations/bottle.py +25 -34
- sentry_sdk/integrations/celery/__init__.py +41 -61
- sentry_sdk/integrations/celery/beat.py +23 -27
- sentry_sdk/integrations/celery/utils.py +15 -17
- sentry_sdk/integrations/chalice.py +8 -10
- sentry_sdk/integrations/clickhouse_driver.py +21 -31
- sentry_sdk/integrations/cloud_resource_context.py +9 -16
- sentry_sdk/integrations/cohere.py +27 -33
- sentry_sdk/integrations/dedupe.py +5 -8
- sentry_sdk/integrations/django/__init__.py +57 -72
- sentry_sdk/integrations/django/asgi.py +26 -34
- sentry_sdk/integrations/django/caching.py +23 -19
- sentry_sdk/integrations/django/middleware.py +17 -20
- sentry_sdk/integrations/django/signals_handlers.py +11 -10
- sentry_sdk/integrations/django/templates.py +19 -16
- sentry_sdk/integrations/django/transactions.py +16 -11
- sentry_sdk/integrations/django/views.py +6 -10
- sentry_sdk/integrations/dramatiq.py +21 -21
- sentry_sdk/integrations/excepthook.py +10 -10
- sentry_sdk/integrations/executing.py +3 -4
- sentry_sdk/integrations/falcon.py +27 -42
- sentry_sdk/integrations/fastapi.py +13 -16
- sentry_sdk/integrations/flask.py +31 -38
- sentry_sdk/integrations/gcp.py +13 -16
- sentry_sdk/integrations/gnu_backtrace.py +4 -6
- sentry_sdk/integrations/gql.py +16 -17
- sentry_sdk/integrations/graphene.py +13 -12
- sentry_sdk/integrations/grpc/__init__.py +19 -1
- sentry_sdk/integrations/grpc/aio/server.py +15 -14
- sentry_sdk/integrations/grpc/client.py +19 -9
- sentry_sdk/integrations/grpc/consts.py +2 -0
- sentry_sdk/integrations/grpc/server.py +12 -8
- sentry_sdk/integrations/httpx.py +9 -12
- sentry_sdk/integrations/huey.py +13 -20
- sentry_sdk/integrations/huggingface_hub.py +18 -18
- sentry_sdk/integrations/langchain.py +203 -113
- sentry_sdk/integrations/launchdarkly.py +13 -10
- sentry_sdk/integrations/litestar.py +37 -35
- sentry_sdk/integrations/logging.py +52 -65
- sentry_sdk/integrations/loguru.py +127 -57
- sentry_sdk/integrations/modules.py +3 -4
- sentry_sdk/integrations/openai.py +100 -88
- sentry_sdk/integrations/openai_agents/__init__.py +49 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
- sentry_sdk/integrations/openai_agents/utils.py +201 -0
- sentry_sdk/integrations/openfeature.py +11 -6
- sentry_sdk/integrations/pure_eval.py +6 -10
- sentry_sdk/integrations/pymongo.py +13 -17
- sentry_sdk/integrations/pyramid.py +31 -36
- sentry_sdk/integrations/quart.py +23 -28
- sentry_sdk/integrations/ray.py +73 -64
- sentry_sdk/integrations/redis/__init__.py +7 -4
- sentry_sdk/integrations/redis/_async_common.py +25 -12
- sentry_sdk/integrations/redis/_sync_common.py +19 -13
- sentry_sdk/integrations/redis/modules/caches.py +17 -8
- sentry_sdk/integrations/redis/modules/queries.py +9 -8
- sentry_sdk/integrations/redis/rb.py +3 -2
- sentry_sdk/integrations/redis/redis.py +4 -4
- sentry_sdk/integrations/redis/redis_cluster.py +21 -13
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
- sentry_sdk/integrations/redis/utils.py +23 -24
- sentry_sdk/integrations/rq.py +13 -16
- sentry_sdk/integrations/rust_tracing.py +9 -6
- sentry_sdk/integrations/sanic.py +34 -46
- sentry_sdk/integrations/serverless.py +22 -27
- sentry_sdk/integrations/socket.py +27 -15
- sentry_sdk/integrations/spark/__init__.py +1 -0
- sentry_sdk/integrations/spark/spark_driver.py +45 -83
- sentry_sdk/integrations/spark/spark_worker.py +7 -11
- sentry_sdk/integrations/sqlalchemy.py +22 -19
- sentry_sdk/integrations/starlette.py +86 -90
- sentry_sdk/integrations/starlite.py +28 -34
- sentry_sdk/integrations/statsig.py +5 -4
- sentry_sdk/integrations/stdlib.py +28 -24
- sentry_sdk/integrations/strawberry.py +62 -49
- sentry_sdk/integrations/sys_exit.py +7 -11
- sentry_sdk/integrations/threading.py +12 -14
- sentry_sdk/integrations/tornado.py +28 -32
- sentry_sdk/integrations/trytond.py +4 -3
- sentry_sdk/integrations/typer.py +8 -6
- sentry_sdk/integrations/unleash.py +5 -4
- sentry_sdk/integrations/wsgi.py +47 -46
- sentry_sdk/logger.py +41 -10
- sentry_sdk/monitor.py +16 -28
- sentry_sdk/opentelemetry/consts.py +11 -4
- sentry_sdk/opentelemetry/contextvars_context.py +26 -16
- sentry_sdk/opentelemetry/propagator.py +38 -21
- sentry_sdk/opentelemetry/sampler.py +51 -34
- sentry_sdk/opentelemetry/scope.py +36 -37
- sentry_sdk/opentelemetry/span_processor.py +48 -58
- sentry_sdk/opentelemetry/tracing.py +58 -14
- sentry_sdk/opentelemetry/utils.py +186 -194
- sentry_sdk/profiler/continuous_profiler.py +108 -97
- sentry_sdk/profiler/transaction_profiler.py +70 -97
- sentry_sdk/profiler/utils.py +11 -15
- sentry_sdk/scope.py +251 -273
- sentry_sdk/scrubber.py +22 -26
- sentry_sdk/serializer.py +40 -54
- sentry_sdk/session.py +44 -61
- sentry_sdk/sessions.py +35 -49
- sentry_sdk/spotlight.py +15 -21
- sentry_sdk/tracing.py +121 -187
- sentry_sdk/tracing_utils.py +104 -122
- sentry_sdk/transport.py +131 -157
- sentry_sdk/utils.py +232 -309
- sentry_sdk/worker.py +16 -28
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
- sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
- sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import atexit
|
|
2
3
|
import os
|
|
3
4
|
import random
|
|
@@ -60,18 +61,19 @@ try:
|
|
|
60
61
|
from gevent.monkey import get_original
|
|
61
62
|
from gevent.threadpool import ThreadPool as _ThreadPool
|
|
62
63
|
|
|
63
|
-
ThreadPool
|
|
64
|
+
ThreadPool: Optional[Type[_ThreadPool]] = _ThreadPool
|
|
64
65
|
thread_sleep = get_original("time", "sleep")
|
|
65
66
|
except ImportError:
|
|
66
67
|
thread_sleep = time.sleep
|
|
67
68
|
ThreadPool = None
|
|
68
69
|
|
|
69
70
|
|
|
70
|
-
_scheduler
|
|
71
|
+
_scheduler: Optional[ContinuousScheduler] = None
|
|
71
72
|
|
|
72
73
|
|
|
73
|
-
def setup_continuous_profiler(
|
|
74
|
-
|
|
74
|
+
def setup_continuous_profiler(
|
|
75
|
+
options: Dict[str, Any], sdk_info: SDKInfo, capture_func: Callable[[Envelope], None]
|
|
76
|
+
) -> bool:
|
|
75
77
|
global _scheduler
|
|
76
78
|
|
|
77
79
|
if _scheduler is not None:
|
|
@@ -115,8 +117,7 @@ def setup_continuous_profiler(options, sdk_info, capture_func):
|
|
|
115
117
|
return True
|
|
116
118
|
|
|
117
119
|
|
|
118
|
-
def try_autostart_continuous_profiler():
|
|
119
|
-
# type: () -> None
|
|
120
|
+
def try_autostart_continuous_profiler() -> None:
|
|
120
121
|
|
|
121
122
|
# TODO: deprecate this as it'll be replaced by the auto lifecycle option
|
|
122
123
|
|
|
@@ -129,47 +130,43 @@ def try_autostart_continuous_profiler():
|
|
|
129
130
|
_scheduler.manual_start()
|
|
130
131
|
|
|
131
132
|
|
|
132
|
-
def try_profile_lifecycle_trace_start():
|
|
133
|
-
# type: () -> Union[ContinuousProfile, None]
|
|
133
|
+
def try_profile_lifecycle_trace_start() -> Union[ContinuousProfile, None]:
|
|
134
134
|
if _scheduler is None:
|
|
135
135
|
return None
|
|
136
136
|
|
|
137
137
|
return _scheduler.auto_start()
|
|
138
138
|
|
|
139
139
|
|
|
140
|
-
def start_profiler():
|
|
141
|
-
# type: () -> None
|
|
140
|
+
def start_profiler() -> None:
|
|
142
141
|
if _scheduler is None:
|
|
143
142
|
return
|
|
144
143
|
|
|
145
144
|
_scheduler.manual_start()
|
|
146
145
|
|
|
147
146
|
|
|
148
|
-
def stop_profiler():
|
|
149
|
-
# type: () -> None
|
|
147
|
+
def stop_profiler() -> None:
|
|
150
148
|
if _scheduler is None:
|
|
151
149
|
return
|
|
152
150
|
|
|
153
151
|
_scheduler.manual_stop()
|
|
154
152
|
|
|
155
153
|
|
|
156
|
-
def teardown_continuous_profiler():
|
|
157
|
-
# type: () -> None
|
|
154
|
+
def teardown_continuous_profiler() -> None:
|
|
158
155
|
stop_profiler()
|
|
159
156
|
|
|
160
157
|
global _scheduler
|
|
161
158
|
_scheduler = None
|
|
162
159
|
|
|
163
160
|
|
|
164
|
-
def get_profiler_id():
|
|
165
|
-
# type: () -> Union[str, None]
|
|
161
|
+
def get_profiler_id() -> Union[str, None]:
|
|
166
162
|
if _scheduler is None:
|
|
167
163
|
return None
|
|
168
164
|
return _scheduler.profiler_id
|
|
169
165
|
|
|
170
166
|
|
|
171
|
-
def determine_profile_session_sampling_decision(
|
|
172
|
-
|
|
167
|
+
def determine_profile_session_sampling_decision(
|
|
168
|
+
sample_rate: Union[float, None],
|
|
169
|
+
) -> bool:
|
|
173
170
|
|
|
174
171
|
# `None` is treated as `0.0`
|
|
175
172
|
if not sample_rate:
|
|
@@ -181,16 +178,20 @@ def determine_profile_session_sampling_decision(sample_rate):
|
|
|
181
178
|
class ContinuousProfile:
|
|
182
179
|
active: bool = True
|
|
183
180
|
|
|
184
|
-
def stop(self):
|
|
185
|
-
# type: () -> None
|
|
181
|
+
def stop(self) -> None:
|
|
186
182
|
self.active = False
|
|
187
183
|
|
|
188
184
|
|
|
189
185
|
class ContinuousScheduler:
|
|
190
|
-
mode = "unknown"
|
|
191
|
-
|
|
192
|
-
def __init__(
|
|
193
|
-
|
|
186
|
+
mode: ContinuousProfilerMode = "unknown"
|
|
187
|
+
|
|
188
|
+
def __init__(
|
|
189
|
+
self,
|
|
190
|
+
frequency: int,
|
|
191
|
+
options: Dict[str, Any],
|
|
192
|
+
sdk_info: SDKInfo,
|
|
193
|
+
capture_func: Callable[[Envelope], None],
|
|
194
|
+
) -> None:
|
|
194
195
|
self.interval = 1.0 / frequency
|
|
195
196
|
self.options = options
|
|
196
197
|
self.sdk_info = sdk_info
|
|
@@ -203,16 +204,16 @@ class ContinuousScheduler:
|
|
|
203
204
|
)
|
|
204
205
|
|
|
205
206
|
self.sampler = self.make_sampler()
|
|
206
|
-
self.buffer
|
|
207
|
-
self.pid
|
|
207
|
+
self.buffer: Optional[ProfileBuffer] = None
|
|
208
|
+
self.pid: Optional[int] = None
|
|
208
209
|
|
|
209
210
|
self.running = False
|
|
211
|
+
self.soft_shutdown = False
|
|
210
212
|
|
|
211
|
-
self.new_profiles = deque(maxlen=128)
|
|
212
|
-
self.active_profiles = set()
|
|
213
|
+
self.new_profiles: Deque[ContinuousProfile] = deque(maxlen=128)
|
|
214
|
+
self.active_profiles: Set[ContinuousProfile] = set()
|
|
213
215
|
|
|
214
|
-
def is_auto_start_enabled(self):
|
|
215
|
-
# type: () -> bool
|
|
216
|
+
def is_auto_start_enabled(self) -> bool:
|
|
216
217
|
|
|
217
218
|
# Ensure that the scheduler only autostarts once per process.
|
|
218
219
|
# This is necessary because many web servers use forks to spawn
|
|
@@ -228,8 +229,7 @@ class ContinuousScheduler:
|
|
|
228
229
|
|
|
229
230
|
return experiments.get("continuous_profiling_auto_start")
|
|
230
231
|
|
|
231
|
-
def auto_start(self):
|
|
232
|
-
# type: () -> Union[ContinuousProfile, None]
|
|
232
|
+
def auto_start(self) -> Union[ContinuousProfile, None]:
|
|
233
233
|
if not self.sampled:
|
|
234
234
|
return None
|
|
235
235
|
|
|
@@ -245,8 +245,7 @@ class ContinuousScheduler:
|
|
|
245
245
|
|
|
246
246
|
return profile
|
|
247
247
|
|
|
248
|
-
def manual_start(self):
|
|
249
|
-
# type: () -> None
|
|
248
|
+
def manual_start(self) -> None:
|
|
250
249
|
if not self.sampled:
|
|
251
250
|
return
|
|
252
251
|
|
|
@@ -255,48 +254,40 @@ class ContinuousScheduler:
|
|
|
255
254
|
|
|
256
255
|
self.ensure_running()
|
|
257
256
|
|
|
258
|
-
def manual_stop(self):
|
|
259
|
-
# type: () -> None
|
|
257
|
+
def manual_stop(self) -> None:
|
|
260
258
|
if self.lifecycle != "manual":
|
|
261
259
|
return
|
|
262
260
|
|
|
263
261
|
self.teardown()
|
|
264
262
|
|
|
265
|
-
def ensure_running(self):
|
|
266
|
-
# type: () -> None
|
|
263
|
+
def ensure_running(self) -> None:
|
|
267
264
|
raise NotImplementedError
|
|
268
265
|
|
|
269
|
-
def teardown(self):
|
|
270
|
-
# type: () -> None
|
|
266
|
+
def teardown(self) -> None:
|
|
271
267
|
raise NotImplementedError
|
|
272
268
|
|
|
273
|
-
def pause(self):
|
|
274
|
-
# type: () -> None
|
|
269
|
+
def pause(self) -> None:
|
|
275
270
|
raise NotImplementedError
|
|
276
271
|
|
|
277
|
-
def reset_buffer(self):
|
|
278
|
-
# type: () -> None
|
|
272
|
+
def reset_buffer(self) -> None:
|
|
279
273
|
self.buffer = ProfileBuffer(
|
|
280
274
|
self.options, self.sdk_info, PROFILE_BUFFER_SECONDS, self.capture_func
|
|
281
275
|
)
|
|
282
276
|
|
|
283
277
|
@property
|
|
284
|
-
def profiler_id(self):
|
|
285
|
-
# type: () -> Union[str, None]
|
|
278
|
+
def profiler_id(self) -> Union[str, None]:
|
|
286
279
|
if self.buffer is None:
|
|
287
280
|
return None
|
|
288
281
|
return self.buffer.profiler_id
|
|
289
282
|
|
|
290
|
-
def make_sampler(self):
|
|
291
|
-
# type: () -> Callable[..., None]
|
|
283
|
+
def make_sampler(self) -> Callable[..., bool]:
|
|
292
284
|
cwd = os.getcwd()
|
|
293
285
|
|
|
294
286
|
cache = LRUCache(max_size=256)
|
|
295
287
|
|
|
296
288
|
if self.lifecycle == "trace":
|
|
297
289
|
|
|
298
|
-
def _sample_stack(*args, **kwargs):
|
|
299
|
-
# type: (*Any, **Any) -> None
|
|
290
|
+
def _sample_stack(*args: Any, **kwargs: Any) -> bool:
|
|
300
291
|
"""
|
|
301
292
|
Take a sample of the stack on all the threads in the process.
|
|
302
293
|
This should be called at a regular interval to collect samples.
|
|
@@ -304,8 +295,7 @@ class ContinuousScheduler:
|
|
|
304
295
|
|
|
305
296
|
# no profiles taking place, so we can stop early
|
|
306
297
|
if not self.new_profiles and not self.active_profiles:
|
|
307
|
-
|
|
308
|
-
return
|
|
298
|
+
return True
|
|
309
299
|
|
|
310
300
|
# This is the number of profiles we want to pop off.
|
|
311
301
|
# It's possible another thread adds a new profile to
|
|
@@ -328,7 +318,7 @@ class ContinuousScheduler:
|
|
|
328
318
|
# For some reason, the frame we get doesn't have certain attributes.
|
|
329
319
|
# When this happens, we abandon the current sample as it's bad.
|
|
330
320
|
capture_internal_exception(sys.exc_info())
|
|
331
|
-
return
|
|
321
|
+
return False
|
|
332
322
|
|
|
333
323
|
# Move the new profiles into the active_profiles set.
|
|
334
324
|
#
|
|
@@ -345,9 +335,7 @@ class ContinuousScheduler:
|
|
|
345
335
|
inactive_profiles = []
|
|
346
336
|
|
|
347
337
|
for profile in self.active_profiles:
|
|
348
|
-
if profile.active:
|
|
349
|
-
pass
|
|
350
|
-
else:
|
|
338
|
+
if not profile.active:
|
|
351
339
|
# If a profile is marked inactive, we buffer it
|
|
352
340
|
# to `inactive_profiles` so it can be removed.
|
|
353
341
|
# We cannot remove it here as it would result
|
|
@@ -360,10 +348,11 @@ class ContinuousScheduler:
|
|
|
360
348
|
if self.buffer is not None:
|
|
361
349
|
self.buffer.write(ts, sample)
|
|
362
350
|
|
|
351
|
+
return False
|
|
352
|
+
|
|
363
353
|
else:
|
|
364
354
|
|
|
365
|
-
def _sample_stack(*args, **kwargs):
|
|
366
|
-
# type: (*Any, **Any) -> None
|
|
355
|
+
def _sample_stack(*args: Any, **kwargs: Any) -> bool:
|
|
367
356
|
"""
|
|
368
357
|
Take a sample of the stack on all the threads in the process.
|
|
369
358
|
This should be called at a regular interval to collect samples.
|
|
@@ -380,19 +369,20 @@ class ContinuousScheduler:
|
|
|
380
369
|
# For some reason, the frame we get doesn't have certain attributes.
|
|
381
370
|
# When this happens, we abandon the current sample as it's bad.
|
|
382
371
|
capture_internal_exception(sys.exc_info())
|
|
383
|
-
return
|
|
372
|
+
return False
|
|
384
373
|
|
|
385
374
|
if self.buffer is not None:
|
|
386
375
|
self.buffer.write(ts, sample)
|
|
387
376
|
|
|
377
|
+
return False
|
|
378
|
+
|
|
388
379
|
return _sample_stack
|
|
389
380
|
|
|
390
|
-
def run(self):
|
|
391
|
-
# type: () -> None
|
|
381
|
+
def run(self) -> None:
|
|
392
382
|
last = time.perf_counter()
|
|
393
383
|
|
|
394
384
|
while self.running:
|
|
395
|
-
self.sampler()
|
|
385
|
+
self.soft_shutdown = self.sampler()
|
|
396
386
|
|
|
397
387
|
# some time may have elapsed since the last time
|
|
398
388
|
# we sampled, so we need to account for that and
|
|
@@ -401,6 +391,15 @@ class ContinuousScheduler:
|
|
|
401
391
|
if elapsed < self.interval:
|
|
402
392
|
thread_sleep(self.interval - elapsed)
|
|
403
393
|
|
|
394
|
+
# the soft shutdown happens here to give it a chance
|
|
395
|
+
# for the profiler to be reused
|
|
396
|
+
if self.soft_shutdown:
|
|
397
|
+
self.running = False
|
|
398
|
+
|
|
399
|
+
# make sure to explicitly exit the profiler here or there might
|
|
400
|
+
# be multiple profilers at once
|
|
401
|
+
break
|
|
402
|
+
|
|
404
403
|
# after sleeping, make sure to take the current
|
|
405
404
|
# timestamp so we can use it next iteration
|
|
406
405
|
last = time.perf_counter()
|
|
@@ -416,18 +415,24 @@ class ThreadContinuousScheduler(ContinuousScheduler):
|
|
|
416
415
|
the sampler at a regular interval.
|
|
417
416
|
"""
|
|
418
417
|
|
|
419
|
-
mode = "thread"
|
|
418
|
+
mode: ContinuousProfilerMode = "thread"
|
|
420
419
|
name = "sentry.profiler.ThreadContinuousScheduler"
|
|
421
420
|
|
|
422
|
-
def __init__(
|
|
423
|
-
|
|
421
|
+
def __init__(
|
|
422
|
+
self,
|
|
423
|
+
frequency: int,
|
|
424
|
+
options: Dict[str, Any],
|
|
425
|
+
sdk_info: SDKInfo,
|
|
426
|
+
capture_func: Callable[[Envelope], None],
|
|
427
|
+
) -> None:
|
|
424
428
|
super().__init__(frequency, options, sdk_info, capture_func)
|
|
425
429
|
|
|
426
|
-
self.thread
|
|
430
|
+
self.thread: Optional[threading.Thread] = None
|
|
427
431
|
self.lock = threading.Lock()
|
|
428
432
|
|
|
429
|
-
def ensure_running(self):
|
|
430
|
-
|
|
433
|
+
def ensure_running(self) -> None:
|
|
434
|
+
|
|
435
|
+
self.soft_shutdown = False
|
|
431
436
|
|
|
432
437
|
pid = os.getpid()
|
|
433
438
|
|
|
@@ -462,8 +467,7 @@ class ThreadContinuousScheduler(ContinuousScheduler):
|
|
|
462
467
|
self.running = False
|
|
463
468
|
self.thread = None
|
|
464
469
|
|
|
465
|
-
def teardown(self):
|
|
466
|
-
# type: () -> None
|
|
470
|
+
def teardown(self) -> None:
|
|
467
471
|
if self.running:
|
|
468
472
|
self.running = False
|
|
469
473
|
|
|
@@ -488,21 +492,28 @@ class GeventContinuousScheduler(ContinuousScheduler):
|
|
|
488
492
|
results in a sample containing only the sampler's code.
|
|
489
493
|
"""
|
|
490
494
|
|
|
491
|
-
mode = "gevent"
|
|
495
|
+
mode: ContinuousProfilerMode = "gevent"
|
|
492
496
|
|
|
493
|
-
def __init__(
|
|
494
|
-
|
|
497
|
+
def __init__(
|
|
498
|
+
self,
|
|
499
|
+
frequency: int,
|
|
500
|
+
options: Dict[str, Any],
|
|
501
|
+
sdk_info: SDKInfo,
|
|
502
|
+
capture_func: Callable[[Envelope], None],
|
|
503
|
+
) -> None:
|
|
495
504
|
|
|
496
505
|
if ThreadPool is None:
|
|
497
506
|
raise ValueError("Profiler mode: {} is not available".format(self.mode))
|
|
498
507
|
|
|
499
508
|
super().__init__(frequency, options, sdk_info, capture_func)
|
|
500
509
|
|
|
501
|
-
self.thread
|
|
510
|
+
self.thread: Optional[_ThreadPool] = None
|
|
502
511
|
self.lock = threading.Lock()
|
|
503
512
|
|
|
504
|
-
def ensure_running(self):
|
|
505
|
-
|
|
513
|
+
def ensure_running(self) -> None:
|
|
514
|
+
|
|
515
|
+
self.soft_shutdown = False
|
|
516
|
+
|
|
506
517
|
pid = os.getpid()
|
|
507
518
|
|
|
508
519
|
# is running on the right process
|
|
@@ -532,8 +543,7 @@ class GeventContinuousScheduler(ContinuousScheduler):
|
|
|
532
543
|
self.running = False
|
|
533
544
|
self.thread = None
|
|
534
545
|
|
|
535
|
-
def teardown(self):
|
|
536
|
-
# type: () -> None
|
|
546
|
+
def teardown(self) -> None:
|
|
537
547
|
if self.running:
|
|
538
548
|
self.running = False
|
|
539
549
|
|
|
@@ -548,8 +558,13 @@ PROFILE_BUFFER_SECONDS = 60
|
|
|
548
558
|
|
|
549
559
|
|
|
550
560
|
class ProfileBuffer:
|
|
551
|
-
def __init__(
|
|
552
|
-
|
|
561
|
+
def __init__(
|
|
562
|
+
self,
|
|
563
|
+
options: Dict[str, Any],
|
|
564
|
+
sdk_info: SDKInfo,
|
|
565
|
+
buffer_size: int,
|
|
566
|
+
capture_func: Callable[[Envelope], None],
|
|
567
|
+
) -> None:
|
|
553
568
|
self.options = options
|
|
554
569
|
self.sdk_info = sdk_info
|
|
555
570
|
self.buffer_size = buffer_size
|
|
@@ -571,8 +586,7 @@ class ProfileBuffer:
|
|
|
571
586
|
datetime.now(timezone.utc).timestamp() - self.start_monotonic_time
|
|
572
587
|
)
|
|
573
588
|
|
|
574
|
-
def write(self, monotonic_time, sample):
|
|
575
|
-
# type: (float, ExtractedSample) -> None
|
|
589
|
+
def write(self, monotonic_time: float, sample: ExtractedSample) -> None:
|
|
576
590
|
if self.should_flush(monotonic_time):
|
|
577
591
|
self.flush()
|
|
578
592
|
self.chunk = ProfileChunk()
|
|
@@ -580,15 +594,13 @@ class ProfileBuffer:
|
|
|
580
594
|
|
|
581
595
|
self.chunk.write(self.start_timestamp + monotonic_time, sample)
|
|
582
596
|
|
|
583
|
-
def should_flush(self, monotonic_time):
|
|
584
|
-
# type: (float) -> bool
|
|
597
|
+
def should_flush(self, monotonic_time: float) -> bool:
|
|
585
598
|
|
|
586
599
|
# If the delta between the new monotonic time and the start monotonic time
|
|
587
600
|
# exceeds the buffer size, it means we should flush the chunk
|
|
588
601
|
return monotonic_time - self.start_monotonic_time >= self.buffer_size
|
|
589
602
|
|
|
590
|
-
def flush(self):
|
|
591
|
-
# type: () -> None
|
|
603
|
+
def flush(self) -> None:
|
|
592
604
|
chunk = self.chunk.to_json(self.profiler_id, self.options, self.sdk_info)
|
|
593
605
|
envelope = Envelope()
|
|
594
606
|
envelope.add_profile_chunk(chunk)
|
|
@@ -596,18 +608,16 @@ class ProfileBuffer:
|
|
|
596
608
|
|
|
597
609
|
|
|
598
610
|
class ProfileChunk:
|
|
599
|
-
def __init__(self):
|
|
600
|
-
# type: () -> None
|
|
611
|
+
def __init__(self) -> None:
|
|
601
612
|
self.chunk_id = uuid.uuid4().hex
|
|
602
613
|
|
|
603
|
-
self.indexed_frames
|
|
604
|
-
self.indexed_stacks
|
|
605
|
-
self.frames
|
|
606
|
-
self.stacks
|
|
607
|
-
self.samples
|
|
614
|
+
self.indexed_frames: Dict[FrameId, int] = {}
|
|
615
|
+
self.indexed_stacks: Dict[StackId, int] = {}
|
|
616
|
+
self.frames: List[ProcessedFrame] = []
|
|
617
|
+
self.stacks: List[ProcessedStack] = []
|
|
618
|
+
self.samples: List[ProcessedSample] = []
|
|
608
619
|
|
|
609
|
-
def write(self, ts, sample):
|
|
610
|
-
# type: (float, ExtractedSample) -> None
|
|
620
|
+
def write(self, ts: float, sample: ExtractedSample) -> None:
|
|
611
621
|
for tid, (stack_id, frame_ids, frames) in sample:
|
|
612
622
|
try:
|
|
613
623
|
# Check if the stack is indexed first, this lets us skip
|
|
@@ -635,8 +645,9 @@ class ProfileChunk:
|
|
|
635
645
|
# When this happens, we abandon the current sample as it's bad.
|
|
636
646
|
capture_internal_exception(sys.exc_info())
|
|
637
647
|
|
|
638
|
-
def to_json(
|
|
639
|
-
|
|
648
|
+
def to_json(
|
|
649
|
+
self, profiler_id: str, options: Dict[str, Any], sdk_info: SDKInfo
|
|
650
|
+
) -> Dict[str, Any]:
|
|
640
651
|
profile = {
|
|
641
652
|
"frames": self.frames,
|
|
642
653
|
"stacks": self.stacks,
|