2025-12-01
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
from sentry_sdk.profiler.continuous_profiler import start_profiler, stop_profiler
|
||||
from sentry_sdk.profiler.continuous_profiler import (
|
||||
start_profile_session,
|
||||
start_profiler,
|
||||
stop_profile_session,
|
||||
stop_profiler,
|
||||
)
|
||||
from sentry_sdk.profiler.transaction_profiler import (
|
||||
MAX_PROFILE_DURATION_NS,
|
||||
PROFILE_MINIMUM_SAMPLES,
|
||||
@@ -20,7 +25,9 @@ from sentry_sdk.profiler.utils import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"start_profile_session", # TODO: Deprecate this in favor of `start_profiler`
|
||||
"start_profiler",
|
||||
"stop_profile_session", # TODO: Deprecate this in favor of `stop_profiler`
|
||||
"stop_profiler",
|
||||
# DEPRECATED: The following was re-exported for backwards compatibility. It
|
||||
# will be removed from sentry_sdk.profiler in a future release.
|
||||
|
||||
+63
-14
@@ -5,6 +5,7 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
import warnings
|
||||
from collections import deque
|
||||
from datetime import datetime, timezone
|
||||
|
||||
@@ -74,9 +75,11 @@ def setup_continuous_profiler(options, sdk_info, capture_func):
|
||||
# type: (Dict[str, Any], SDKInfo, Callable[[Envelope], None]) -> bool
|
||||
global _scheduler
|
||||
|
||||
if _scheduler is not None:
|
||||
already_initialized = _scheduler is not None
|
||||
|
||||
if already_initialized:
|
||||
logger.debug("[Profiling] Continuous Profiler is already setup")
|
||||
return False
|
||||
teardown_continuous_profiler()
|
||||
|
||||
if is_gevent():
|
||||
# If gevent has patched the threading modules then we cannot rely on
|
||||
@@ -116,11 +119,19 @@ def setup_continuous_profiler(options, sdk_info, capture_func):
|
||||
)
|
||||
)
|
||||
|
||||
atexit.register(teardown_continuous_profiler)
|
||||
if not already_initialized:
|
||||
atexit.register(teardown_continuous_profiler)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def is_profile_session_sampled():
|
||||
# type: () -> bool
|
||||
if _scheduler is None:
|
||||
return False
|
||||
return _scheduler.sampled
|
||||
|
||||
|
||||
def try_autostart_continuous_profiler():
|
||||
# type: () -> None
|
||||
|
||||
@@ -151,6 +162,17 @@ def start_profiler():
|
||||
_scheduler.manual_start()
|
||||
|
||||
|
||||
def start_profile_session():
|
||||
# type: () -> None
|
||||
|
||||
warnings.warn(
|
||||
"The `start_profile_session` function is deprecated. Please use `start_profile` instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
start_profiler()
|
||||
|
||||
|
||||
def stop_profiler():
|
||||
# type: () -> None
|
||||
if _scheduler is None:
|
||||
@@ -159,6 +181,17 @@ def stop_profiler():
|
||||
_scheduler.manual_stop()
|
||||
|
||||
|
||||
def stop_profile_session():
|
||||
# type: () -> None
|
||||
|
||||
warnings.warn(
|
||||
"The `stop_profile_session` function is deprecated. Please use `stop_profile` instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
stop_profiler()
|
||||
|
||||
|
||||
def teardown_continuous_profiler():
|
||||
# type: () -> None
|
||||
stop_profiler()
|
||||
@@ -213,6 +246,7 @@ class ContinuousScheduler:
|
||||
self.pid = None # type: Optional[int]
|
||||
|
||||
self.running = False
|
||||
self.soft_shutdown = False
|
||||
|
||||
self.new_profiles = deque(maxlen=128) # type: Deque[ContinuousProfile]
|
||||
self.active_profiles = set() # type: Set[ContinuousProfile]
|
||||
@@ -294,7 +328,7 @@ class ContinuousScheduler:
|
||||
return self.buffer.profiler_id
|
||||
|
||||
def make_sampler(self):
|
||||
# type: () -> Callable[..., None]
|
||||
# type: () -> Callable[..., bool]
|
||||
cwd = os.getcwd()
|
||||
|
||||
cache = LRUCache(max_size=256)
|
||||
@@ -302,7 +336,7 @@ class ContinuousScheduler:
|
||||
if self.lifecycle == "trace":
|
||||
|
||||
def _sample_stack(*args, **kwargs):
|
||||
# type: (*Any, **Any) -> None
|
||||
# type: (*Any, **Any) -> bool
|
||||
"""
|
||||
Take a sample of the stack on all the threads in the process.
|
||||
This should be called at a regular interval to collect samples.
|
||||
@@ -310,8 +344,7 @@ class ContinuousScheduler:
|
||||
|
||||
# no profiles taking place, so we can stop early
|
||||
if not self.new_profiles and not self.active_profiles:
|
||||
self.running = False
|
||||
return
|
||||
return True
|
||||
|
||||
# This is the number of profiles we want to pop off.
|
||||
# It's possible another thread adds a new profile to
|
||||
@@ -334,7 +367,7 @@ class ContinuousScheduler:
|
||||
# For some reason, the frame we get doesn't have certain attributes.
|
||||
# When this happens, we abandon the current sample as it's bad.
|
||||
capture_internal_exception(sys.exc_info())
|
||||
return
|
||||
return False
|
||||
|
||||
# Move the new profiles into the active_profiles set.
|
||||
#
|
||||
@@ -351,9 +384,7 @@ class ContinuousScheduler:
|
||||
inactive_profiles = []
|
||||
|
||||
for profile in self.active_profiles:
|
||||
if profile.active:
|
||||
pass
|
||||
else:
|
||||
if not profile.active:
|
||||
# If a profile is marked inactive, we buffer it
|
||||
# to `inactive_profiles` so it can be removed.
|
||||
# We cannot remove it here as it would result
|
||||
@@ -366,10 +397,12 @@ class ContinuousScheduler:
|
||||
if self.buffer is not None:
|
||||
self.buffer.write(ts, sample)
|
||||
|
||||
return False
|
||||
|
||||
else:
|
||||
|
||||
def _sample_stack(*args, **kwargs):
|
||||
# type: (*Any, **Any) -> None
|
||||
# type: (*Any, **Any) -> bool
|
||||
"""
|
||||
Take a sample of the stack on all the threads in the process.
|
||||
This should be called at a regular interval to collect samples.
|
||||
@@ -386,11 +419,13 @@ class ContinuousScheduler:
|
||||
# For some reason, the frame we get doesn't have certain attributes.
|
||||
# When this happens, we abandon the current sample as it's bad.
|
||||
capture_internal_exception(sys.exc_info())
|
||||
return
|
||||
return False
|
||||
|
||||
if self.buffer is not None:
|
||||
self.buffer.write(ts, sample)
|
||||
|
||||
return False
|
||||
|
||||
return _sample_stack
|
||||
|
||||
def run(self):
|
||||
@@ -398,7 +433,7 @@ class ContinuousScheduler:
|
||||
last = time.perf_counter()
|
||||
|
||||
while self.running:
|
||||
self.sampler()
|
||||
self.soft_shutdown = self.sampler()
|
||||
|
||||
# some time may have elapsed since the last time
|
||||
# we sampled, so we need to account for that and
|
||||
@@ -407,6 +442,15 @@ class ContinuousScheduler:
|
||||
if elapsed < self.interval:
|
||||
thread_sleep(self.interval - elapsed)
|
||||
|
||||
# the soft shutdown happens here to give it a chance
|
||||
# for the profiler to be reused
|
||||
if self.soft_shutdown:
|
||||
self.running = False
|
||||
|
||||
# make sure to explicitly exit the profiler here or there might
|
||||
# be multiple profilers at once
|
||||
break
|
||||
|
||||
# after sleeping, make sure to take the current
|
||||
# timestamp so we can use it next iteration
|
||||
last = time.perf_counter()
|
||||
@@ -435,6 +479,8 @@ class ThreadContinuousScheduler(ContinuousScheduler):
|
||||
def ensure_running(self):
|
||||
# type: () -> None
|
||||
|
||||
self.soft_shutdown = False
|
||||
|
||||
pid = os.getpid()
|
||||
|
||||
# is running on the right process
|
||||
@@ -509,6 +555,9 @@ class GeventContinuousScheduler(ContinuousScheduler):
|
||||
|
||||
def ensure_running(self):
|
||||
# type: () -> None
|
||||
|
||||
self.soft_shutdown = False
|
||||
|
||||
pid = os.getpid()
|
||||
|
||||
# is running on the right process
|
||||
|
||||
+6
-4
@@ -45,6 +45,7 @@ from sentry_sdk.profiler.utils import (
|
||||
)
|
||||
from sentry_sdk.utils import (
|
||||
capture_internal_exception,
|
||||
capture_internal_exceptions,
|
||||
get_current_thread_meta,
|
||||
is_gevent,
|
||||
is_valid_sample_rate,
|
||||
@@ -369,12 +370,13 @@ class Profile:
|
||||
|
||||
def __exit__(self, ty, value, tb):
|
||||
# type: (Optional[Any], Optional[Any], Optional[Any]) -> None
|
||||
self.stop()
|
||||
with capture_internal_exceptions():
|
||||
self.stop()
|
||||
|
||||
scope, old_profile = self._context_manager_state
|
||||
del self._context_manager_state
|
||||
scope, old_profile = self._context_manager_state
|
||||
del self._context_manager_state
|
||||
|
||||
scope.profile = old_profile
|
||||
scope.profile = old_profile
|
||||
|
||||
def write(self, ts, sample):
|
||||
# type: (int, ExtractedSample) -> None
|
||||
|
||||
@@ -85,9 +85,7 @@ else:
|
||||
if (
|
||||
# the co_varnames start with the frame's positional arguments
|
||||
# and we expect the first to be `self` if its an instance method
|
||||
co_varnames
|
||||
and co_varnames[0] == "self"
|
||||
and "self" in frame.f_locals
|
||||
co_varnames and co_varnames[0] == "self" and "self" in frame.f_locals
|
||||
):
|
||||
for cls in type(frame.f_locals["self"]).__mro__:
|
||||
if name in cls.__dict__:
|
||||
@@ -101,9 +99,7 @@ else:
|
||||
if (
|
||||
# the co_varnames start with the frame's positional arguments
|
||||
# and we expect the first to be `cls` if its a class method
|
||||
co_varnames
|
||||
and co_varnames[0] == "cls"
|
||||
and "cls" in frame.f_locals
|
||||
co_varnames and co_varnames[0] == "cls" and "cls" in frame.f_locals
|
||||
):
|
||||
for cls in frame.f_locals["cls"].__mro__:
|
||||
if name in cls.__dict__:
|
||||
|
||||
Reference in New Issue
Block a user