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
|
@@ -25,6 +25,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
25
25
|
SOFTWARE.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
+
from __future__ import annotations
|
|
28
29
|
import atexit
|
|
29
30
|
import os
|
|
30
31
|
import platform
|
|
@@ -99,7 +100,7 @@ try:
|
|
|
99
100
|
from gevent.monkey import get_original
|
|
100
101
|
from gevent.threadpool import ThreadPool as _ThreadPool
|
|
101
102
|
|
|
102
|
-
ThreadPool
|
|
103
|
+
ThreadPool: Optional[Type[_ThreadPool]] = _ThreadPool
|
|
103
104
|
thread_sleep = get_original("time", "sleep")
|
|
104
105
|
except ImportError:
|
|
105
106
|
thread_sleep = time.sleep
|
|
@@ -107,7 +108,7 @@ except ImportError:
|
|
|
107
108
|
ThreadPool = None
|
|
108
109
|
|
|
109
110
|
|
|
110
|
-
_scheduler
|
|
111
|
+
_scheduler: Optional[Scheduler] = None
|
|
111
112
|
|
|
112
113
|
|
|
113
114
|
# The minimum number of unique samples that must exist in a profile to be
|
|
@@ -115,8 +116,7 @@ _scheduler = None # type: Optional[Scheduler]
|
|
|
115
116
|
PROFILE_MINIMUM_SAMPLES = 2
|
|
116
117
|
|
|
117
118
|
|
|
118
|
-
def has_profiling_enabled(options):
|
|
119
|
-
# type: (Dict[str, Any]) -> bool
|
|
119
|
+
def has_profiling_enabled(options: Dict[str, Any]) -> bool:
|
|
120
120
|
profiles_sampler = options["profiles_sampler"]
|
|
121
121
|
if profiles_sampler is not None:
|
|
122
122
|
return True
|
|
@@ -128,8 +128,7 @@ def has_profiling_enabled(options):
|
|
|
128
128
|
return False
|
|
129
129
|
|
|
130
130
|
|
|
131
|
-
def setup_profiler(options):
|
|
132
|
-
# type: (Dict[str, Any]) -> bool
|
|
131
|
+
def setup_profiler(options: Dict[str, Any]) -> bool:
|
|
133
132
|
global _scheduler
|
|
134
133
|
|
|
135
134
|
if _scheduler is not None:
|
|
@@ -172,8 +171,7 @@ def setup_profiler(options):
|
|
|
172
171
|
return True
|
|
173
172
|
|
|
174
173
|
|
|
175
|
-
def teardown_profiler():
|
|
176
|
-
# type: () -> None
|
|
174
|
+
def teardown_profiler() -> None:
|
|
177
175
|
|
|
178
176
|
global _scheduler
|
|
179
177
|
|
|
@@ -189,40 +187,38 @@ MAX_PROFILE_DURATION_NS = int(3e10) # 30 seconds
|
|
|
189
187
|
class Profile:
|
|
190
188
|
def __init__(
|
|
191
189
|
self,
|
|
192
|
-
sampled
|
|
193
|
-
start_ns
|
|
194
|
-
scheduler
|
|
195
|
-
):
|
|
196
|
-
# type: (...) -> None
|
|
190
|
+
sampled: Optional[bool],
|
|
191
|
+
start_ns: int,
|
|
192
|
+
scheduler: Optional[Scheduler] = None,
|
|
193
|
+
) -> None:
|
|
197
194
|
self.scheduler = _scheduler if scheduler is None else scheduler
|
|
198
195
|
|
|
199
|
-
self.event_id = uuid.uuid4().hex
|
|
196
|
+
self.event_id: str = uuid.uuid4().hex
|
|
200
197
|
|
|
201
|
-
self.sampled
|
|
198
|
+
self.sampled: Optional[bool] = sampled
|
|
202
199
|
|
|
203
200
|
# Various framework integrations are capable of overwriting the active thread id.
|
|
204
201
|
# If it is set to `None` at the end of the profile, we fall back to the default.
|
|
205
|
-
self._default_active_thread_id = get_current_thread_meta()[0] or 0
|
|
206
|
-
self.active_thread_id
|
|
202
|
+
self._default_active_thread_id: int = get_current_thread_meta()[0] or 0
|
|
203
|
+
self.active_thread_id: Optional[int] = None
|
|
207
204
|
|
|
208
205
|
try:
|
|
209
|
-
self.start_ns = start_ns
|
|
206
|
+
self.start_ns: int = start_ns
|
|
210
207
|
except AttributeError:
|
|
211
208
|
self.start_ns = 0
|
|
212
209
|
|
|
213
|
-
self.stop_ns = 0
|
|
214
|
-
self.active = False
|
|
210
|
+
self.stop_ns: int = 0
|
|
211
|
+
self.active: bool = False
|
|
215
212
|
|
|
216
|
-
self.indexed_frames
|
|
217
|
-
self.indexed_stacks
|
|
218
|
-
self.frames
|
|
219
|
-
self.stacks
|
|
220
|
-
self.samples
|
|
213
|
+
self.indexed_frames: Dict[FrameId, int] = {}
|
|
214
|
+
self.indexed_stacks: Dict[StackId, int] = {}
|
|
215
|
+
self.frames: List[ProcessedFrame] = []
|
|
216
|
+
self.stacks: List[ProcessedStack] = []
|
|
217
|
+
self.samples: List[ProcessedSample] = []
|
|
221
218
|
|
|
222
219
|
self.unique_samples = 0
|
|
223
220
|
|
|
224
|
-
def update_active_thread_id(self):
|
|
225
|
-
# type: () -> None
|
|
221
|
+
def update_active_thread_id(self) -> None:
|
|
226
222
|
self.active_thread_id = get_current_thread_meta()[0]
|
|
227
223
|
logger.debug(
|
|
228
224
|
"[Profiling] updating active thread id to {tid}".format(
|
|
@@ -230,8 +226,7 @@ class Profile:
|
|
|
230
226
|
)
|
|
231
227
|
)
|
|
232
228
|
|
|
233
|
-
def _set_initial_sampling_decision(self, sampling_context):
|
|
234
|
-
# type: (SamplingContext) -> None
|
|
229
|
+
def _set_initial_sampling_decision(self, sampling_context: SamplingContext) -> None:
|
|
235
230
|
"""
|
|
236
231
|
Sets the profile's sampling decision according to the following
|
|
237
232
|
precedence rules:
|
|
@@ -281,7 +276,8 @@ class Profile:
|
|
|
281
276
|
self.sampled = False
|
|
282
277
|
return
|
|
283
278
|
|
|
284
|
-
|
|
279
|
+
sample_rate = is_valid_sample_rate(sample_rate, source="Profiling")
|
|
280
|
+
if sample_rate is None:
|
|
285
281
|
logger.warning(
|
|
286
282
|
"[Profiling] Discarding profile because of invalid sample rate."
|
|
287
283
|
)
|
|
@@ -291,19 +287,18 @@ class Profile:
|
|
|
291
287
|
# Now we roll the dice. random.random is inclusive of 0, but not of 1,
|
|
292
288
|
# so strict < is safe here. In case sample_rate is a boolean, cast it
|
|
293
289
|
# to a float (True becomes 1.0 and False becomes 0.0)
|
|
294
|
-
self.sampled = random.random() <
|
|
290
|
+
self.sampled = random.random() < sample_rate
|
|
295
291
|
|
|
296
292
|
if self.sampled:
|
|
297
293
|
logger.debug("[Profiling] Initializing profile")
|
|
298
294
|
else:
|
|
299
295
|
logger.debug(
|
|
300
296
|
"[Profiling] Discarding profile because it's not included in the random sample (sample rate = {sample_rate})".format(
|
|
301
|
-
sample_rate=
|
|
297
|
+
sample_rate=sample_rate
|
|
302
298
|
)
|
|
303
299
|
)
|
|
304
300
|
|
|
305
|
-
def start(self):
|
|
306
|
-
# type: () -> None
|
|
301
|
+
def start(self) -> None:
|
|
307
302
|
if not self.sampled or self.active:
|
|
308
303
|
return
|
|
309
304
|
|
|
@@ -314,8 +309,7 @@ class Profile:
|
|
|
314
309
|
self.start_ns = time.perf_counter_ns()
|
|
315
310
|
self.scheduler.start_profiling(self)
|
|
316
311
|
|
|
317
|
-
def stop(self):
|
|
318
|
-
# type: () -> None
|
|
312
|
+
def stop(self) -> None:
|
|
319
313
|
if not self.sampled or not self.active:
|
|
320
314
|
return
|
|
321
315
|
|
|
@@ -324,8 +318,7 @@ class Profile:
|
|
|
324
318
|
self.active = False
|
|
325
319
|
self.stop_ns = time.perf_counter_ns()
|
|
326
320
|
|
|
327
|
-
def __enter__(self):
|
|
328
|
-
# type: () -> Profile
|
|
321
|
+
def __enter__(self) -> Profile:
|
|
329
322
|
scope = sentry_sdk.get_isolation_scope()
|
|
330
323
|
old_profile = scope.profile
|
|
331
324
|
scope.profile = self
|
|
@@ -336,8 +329,9 @@ class Profile:
|
|
|
336
329
|
|
|
337
330
|
return self
|
|
338
331
|
|
|
339
|
-
def __exit__(
|
|
340
|
-
|
|
332
|
+
def __exit__(
|
|
333
|
+
self, ty: Optional[Any], value: Optional[Any], tb: Optional[Any]
|
|
334
|
+
) -> None:
|
|
341
335
|
self.stop()
|
|
342
336
|
|
|
343
337
|
scope, old_profile = self._context_manager_state
|
|
@@ -345,8 +339,7 @@ class Profile:
|
|
|
345
339
|
|
|
346
340
|
scope.profile = old_profile
|
|
347
341
|
|
|
348
|
-
def write(self, ts, sample):
|
|
349
|
-
# type: (int, ExtractedSample) -> None
|
|
342
|
+
def write(self, ts: int, sample: ExtractedSample) -> None:
|
|
350
343
|
if not self.active:
|
|
351
344
|
return
|
|
352
345
|
|
|
@@ -389,18 +382,17 @@ class Profile:
|
|
|
389
382
|
# When this happens, we abandon the current sample as it's bad.
|
|
390
383
|
capture_internal_exception(sys.exc_info())
|
|
391
384
|
|
|
392
|
-
def process(self):
|
|
393
|
-
# type: () -> ProcessedProfile
|
|
385
|
+
def process(self) -> ProcessedProfile:
|
|
394
386
|
|
|
395
387
|
# This collects the thread metadata at the end of a profile. Doing it
|
|
396
388
|
# this way means that any threads that terminate before the profile ends
|
|
397
389
|
# will not have any metadata associated with it.
|
|
398
|
-
thread_metadata = {
|
|
390
|
+
thread_metadata: Dict[str, ProcessedThreadMetadata] = {
|
|
399
391
|
str(thread.ident): {
|
|
400
392
|
"name": str(thread.name),
|
|
401
393
|
}
|
|
402
394
|
for thread in threading.enumerate()
|
|
403
|
-
}
|
|
395
|
+
}
|
|
404
396
|
|
|
405
397
|
return {
|
|
406
398
|
"frames": self.frames,
|
|
@@ -409,8 +401,7 @@ class Profile:
|
|
|
409
401
|
"thread_metadata": thread_metadata,
|
|
410
402
|
}
|
|
411
403
|
|
|
412
|
-
def to_json(self, event_opt, options):
|
|
413
|
-
# type: (Event, Dict[str, Any]) -> Dict[str, Any]
|
|
404
|
+
def to_json(self, event_opt: Event, options: Dict[str, Any]) -> Dict[str, Any]:
|
|
414
405
|
profile = self.process()
|
|
415
406
|
|
|
416
407
|
set_in_app_in_frames(
|
|
@@ -460,8 +451,7 @@ class Profile:
|
|
|
460
451
|
],
|
|
461
452
|
}
|
|
462
453
|
|
|
463
|
-
def valid(self):
|
|
464
|
-
# type: () -> bool
|
|
454
|
+
def valid(self) -> bool:
|
|
465
455
|
client = sentry_sdk.get_client()
|
|
466
456
|
if not client.is_active():
|
|
467
457
|
return False
|
|
@@ -488,39 +478,35 @@ class Profile:
|
|
|
488
478
|
|
|
489
479
|
|
|
490
480
|
class Scheduler(ABC):
|
|
491
|
-
mode = "unknown"
|
|
481
|
+
mode: ProfilerMode = "unknown"
|
|
492
482
|
|
|
493
|
-
def __init__(self, frequency):
|
|
494
|
-
# type: (int) -> None
|
|
483
|
+
def __init__(self, frequency: int) -> None:
|
|
495
484
|
self.interval = 1.0 / frequency
|
|
496
485
|
|
|
497
486
|
self.sampler = self.make_sampler()
|
|
498
487
|
|
|
499
488
|
# cap the number of new profiles at any time so it does not grow infinitely
|
|
500
|
-
self.new_profiles = deque(maxlen=128)
|
|
501
|
-
self.active_profiles = set()
|
|
489
|
+
self.new_profiles: Deque[Profile] = deque(maxlen=128)
|
|
490
|
+
self.active_profiles: Set[Profile] = set()
|
|
502
491
|
|
|
503
|
-
def __enter__(self):
|
|
504
|
-
# type: () -> Scheduler
|
|
492
|
+
def __enter__(self) -> Scheduler:
|
|
505
493
|
self.setup()
|
|
506
494
|
return self
|
|
507
495
|
|
|
508
|
-
def __exit__(
|
|
509
|
-
|
|
496
|
+
def __exit__(
|
|
497
|
+
self, ty: Optional[Any], value: Optional[Any], tb: Optional[Any]
|
|
498
|
+
) -> None:
|
|
510
499
|
self.teardown()
|
|
511
500
|
|
|
512
501
|
@abstractmethod
|
|
513
|
-
def setup(self):
|
|
514
|
-
# type: () -> None
|
|
502
|
+
def setup(self) -> None:
|
|
515
503
|
pass
|
|
516
504
|
|
|
517
505
|
@abstractmethod
|
|
518
|
-
def teardown(self):
|
|
519
|
-
# type: () -> None
|
|
506
|
+
def teardown(self) -> None:
|
|
520
507
|
pass
|
|
521
508
|
|
|
522
|
-
def ensure_running(self):
|
|
523
|
-
# type: () -> None
|
|
509
|
+
def ensure_running(self) -> None:
|
|
524
510
|
"""
|
|
525
511
|
Ensure the scheduler is running. By default, this method is a no-op.
|
|
526
512
|
The method should be overridden by any implementation for which it is
|
|
@@ -528,19 +514,16 @@ class Scheduler(ABC):
|
|
|
528
514
|
"""
|
|
529
515
|
return None
|
|
530
516
|
|
|
531
|
-
def start_profiling(self, profile):
|
|
532
|
-
# type: (Profile) -> None
|
|
517
|
+
def start_profiling(self, profile: Profile) -> None:
|
|
533
518
|
self.ensure_running()
|
|
534
519
|
self.new_profiles.append(profile)
|
|
535
520
|
|
|
536
|
-
def make_sampler(self):
|
|
537
|
-
# type: () -> Callable[..., None]
|
|
521
|
+
def make_sampler(self) -> Callable[..., None]:
|
|
538
522
|
cwd = os.getcwd()
|
|
539
523
|
|
|
540
524
|
cache = LRUCache(max_size=256)
|
|
541
525
|
|
|
542
|
-
def _sample_stack(*args, **kwargs):
|
|
543
|
-
# type: (*Any, **Any) -> None
|
|
526
|
+
def _sample_stack(*args: Any, **kwargs: Any) -> None:
|
|
544
527
|
"""
|
|
545
528
|
Take a sample of the stack on all the threads in the process.
|
|
546
529
|
This should be called at a regular interval to collect samples.
|
|
@@ -611,32 +594,28 @@ class ThreadScheduler(Scheduler):
|
|
|
611
594
|
the sampler at a regular interval.
|
|
612
595
|
"""
|
|
613
596
|
|
|
614
|
-
mode = "thread"
|
|
597
|
+
mode: ProfilerMode = "thread"
|
|
615
598
|
name = "sentry.profiler.ThreadScheduler"
|
|
616
599
|
|
|
617
|
-
def __init__(self, frequency):
|
|
618
|
-
# type: (int) -> None
|
|
600
|
+
def __init__(self, frequency: int) -> None:
|
|
619
601
|
super().__init__(frequency=frequency)
|
|
620
602
|
|
|
621
603
|
# used to signal to the thread that it should stop
|
|
622
604
|
self.running = False
|
|
623
|
-
self.thread
|
|
624
|
-
self.pid
|
|
605
|
+
self.thread: Optional[threading.Thread] = None
|
|
606
|
+
self.pid: Optional[int] = None
|
|
625
607
|
self.lock = threading.Lock()
|
|
626
608
|
|
|
627
|
-
def setup(self):
|
|
628
|
-
# type: () -> None
|
|
609
|
+
def setup(self) -> None:
|
|
629
610
|
pass
|
|
630
611
|
|
|
631
|
-
def teardown(self):
|
|
632
|
-
# type: () -> None
|
|
612
|
+
def teardown(self) -> None:
|
|
633
613
|
if self.running:
|
|
634
614
|
self.running = False
|
|
635
615
|
if self.thread is not None:
|
|
636
616
|
self.thread.join()
|
|
637
617
|
|
|
638
|
-
def ensure_running(self):
|
|
639
|
-
# type: () -> None
|
|
618
|
+
def ensure_running(self) -> None:
|
|
640
619
|
"""
|
|
641
620
|
Check that the profiler has an active thread to run in, and start one if
|
|
642
621
|
that's not the case.
|
|
@@ -674,8 +653,7 @@ class ThreadScheduler(Scheduler):
|
|
|
674
653
|
self.thread = None
|
|
675
654
|
return
|
|
676
655
|
|
|
677
|
-
def run(self):
|
|
678
|
-
# type: () -> None
|
|
656
|
+
def run(self) -> None:
|
|
679
657
|
last = time.perf_counter()
|
|
680
658
|
|
|
681
659
|
while self.running:
|
|
@@ -707,11 +685,10 @@ class GeventScheduler(Scheduler):
|
|
|
707
685
|
results in a sample containing only the sampler's code.
|
|
708
686
|
"""
|
|
709
687
|
|
|
710
|
-
mode = "gevent"
|
|
688
|
+
mode: ProfilerMode = "gevent"
|
|
711
689
|
name = "sentry.profiler.GeventScheduler"
|
|
712
690
|
|
|
713
|
-
def __init__(self, frequency):
|
|
714
|
-
# type: (int) -> None
|
|
691
|
+
def __init__(self, frequency: int) -> None:
|
|
715
692
|
|
|
716
693
|
if ThreadPool is None:
|
|
717
694
|
raise ValueError("Profiler mode: {} is not available".format(self.mode))
|
|
@@ -720,27 +697,24 @@ class GeventScheduler(Scheduler):
|
|
|
720
697
|
|
|
721
698
|
# used to signal to the thread that it should stop
|
|
722
699
|
self.running = False
|
|
723
|
-
self.thread
|
|
724
|
-
self.pid
|
|
700
|
+
self.thread: Optional[_ThreadPool] = None
|
|
701
|
+
self.pid: Optional[int] = None
|
|
725
702
|
|
|
726
703
|
# This intentionally uses the gevent patched threading.Lock.
|
|
727
704
|
# The lock will be required when first trying to start profiles
|
|
728
705
|
# as we need to spawn the profiler thread from the greenlets.
|
|
729
706
|
self.lock = threading.Lock()
|
|
730
707
|
|
|
731
|
-
def setup(self):
|
|
732
|
-
# type: () -> None
|
|
708
|
+
def setup(self) -> None:
|
|
733
709
|
pass
|
|
734
710
|
|
|
735
|
-
def teardown(self):
|
|
736
|
-
# type: () -> None
|
|
711
|
+
def teardown(self) -> None:
|
|
737
712
|
if self.running:
|
|
738
713
|
self.running = False
|
|
739
714
|
if self.thread is not None:
|
|
740
715
|
self.thread.join()
|
|
741
716
|
|
|
742
|
-
def ensure_running(self):
|
|
743
|
-
# type: () -> None
|
|
717
|
+
def ensure_running(self) -> None:
|
|
744
718
|
pid = os.getpid()
|
|
745
719
|
|
|
746
720
|
# is running on the right process
|
|
@@ -767,8 +741,7 @@ class GeventScheduler(Scheduler):
|
|
|
767
741
|
self.thread = None
|
|
768
742
|
return
|
|
769
743
|
|
|
770
|
-
def run(self):
|
|
771
|
-
# type: () -> None
|
|
744
|
+
def run(self) -> None:
|
|
772
745
|
last = time.perf_counter()
|
|
773
746
|
|
|
774
747
|
while self.running:
|
sentry_sdk/profiler/utils.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import os
|
|
2
3
|
from collections import deque
|
|
3
4
|
|
|
@@ -63,14 +64,12 @@ MAX_STACK_DEPTH = 128
|
|
|
63
64
|
|
|
64
65
|
if PY311:
|
|
65
66
|
|
|
66
|
-
def get_frame_name(frame):
|
|
67
|
-
# type: (FrameType) -> str
|
|
67
|
+
def get_frame_name(frame: FrameType) -> str:
|
|
68
68
|
return frame.f_code.co_qualname
|
|
69
69
|
|
|
70
70
|
else:
|
|
71
71
|
|
|
72
|
-
def get_frame_name(frame):
|
|
73
|
-
# type: (FrameType) -> str
|
|
72
|
+
def get_frame_name(frame: FrameType) -> str:
|
|
74
73
|
|
|
75
74
|
f_code = frame.f_code
|
|
76
75
|
co_varnames = f_code.co_varnames
|
|
@@ -117,13 +116,11 @@ else:
|
|
|
117
116
|
return name
|
|
118
117
|
|
|
119
118
|
|
|
120
|
-
def frame_id(raw_frame):
|
|
121
|
-
# type: (FrameType) -> FrameId
|
|
119
|
+
def frame_id(raw_frame: FrameType) -> FrameId:
|
|
122
120
|
return (raw_frame.f_code.co_filename, raw_frame.f_lineno, get_frame_name(raw_frame))
|
|
123
121
|
|
|
124
122
|
|
|
125
|
-
def extract_frame(fid, raw_frame, cwd):
|
|
126
|
-
# type: (FrameId, FrameType, str) -> ProcessedFrame
|
|
123
|
+
def extract_frame(fid: FrameId, raw_frame: FrameType, cwd: str) -> ProcessedFrame:
|
|
127
124
|
abs_path = raw_frame.f_code.co_filename
|
|
128
125
|
|
|
129
126
|
try:
|
|
@@ -152,12 +149,11 @@ def extract_frame(fid, raw_frame, cwd):
|
|
|
152
149
|
|
|
153
150
|
|
|
154
151
|
def extract_stack(
|
|
155
|
-
raw_frame
|
|
156
|
-
cache
|
|
157
|
-
cwd
|
|
158
|
-
max_stack_depth=MAX_STACK_DEPTH,
|
|
159
|
-
):
|
|
160
|
-
# type: (...) -> ExtractedStack
|
|
152
|
+
raw_frame: Optional[FrameType],
|
|
153
|
+
cache: LRUCache,
|
|
154
|
+
cwd: str,
|
|
155
|
+
max_stack_depth: int = MAX_STACK_DEPTH,
|
|
156
|
+
) -> ExtractedStack:
|
|
161
157
|
"""
|
|
162
158
|
Extracts the stack starting the specified frame. The extracted stack
|
|
163
159
|
assumes the specified frame is the top of the stack, and works back
|
|
@@ -167,7 +163,7 @@ def extract_stack(
|
|
|
167
163
|
only the first `MAX_STACK_DEPTH` frames will be returned.
|
|
168
164
|
"""
|
|
169
165
|
|
|
170
|
-
raw_frames = deque(maxlen=max_stack_depth)
|
|
166
|
+
raw_frames: Deque[FrameType] = deque(maxlen=max_stack_depth)
|
|
171
167
|
|
|
172
168
|
while raw_frame is not None:
|
|
173
169
|
f_back = raw_frame.f_back
|