Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(profiling): Add basic profiling tests #1677

Merged
merged 3 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
eventlet = None

import sentry_sdk
from sentry_sdk._compat import reraise, string_types, iteritems
from sentry_sdk.transport import Transport
from sentry_sdk._compat import iteritems, reraise, string_types
from sentry_sdk.envelope import Envelope
from sentry_sdk.utils import capture_internal_exceptions
from sentry_sdk.integrations import _installed_integrations # noqa: F401
from sentry_sdk.profiler import teardown_profiler
from sentry_sdk.transport import Transport
from sentry_sdk.utils import capture_internal_exceptions

from tests import _warning_recorder, _warning_recorder_mgr

Expand Down Expand Up @@ -554,3 +555,9 @@ def __ne__(self, test_obj):
return not self.__eq__(test_obj)

return ObjectDescribedBy


@pytest.fixture
def teardown_profiling():
yield
teardown_profiler()
74 changes: 39 additions & 35 deletions tests/integrations/wsgi/test_wsgi.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import sys

from werkzeug.test import Client

import pytest

import sentry_sdk
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
from sentry_sdk.profiler import teardown_profiler
from collections import Counter
from sentry_sdk.utils import PY33

try:
from unittest import mock # python 3.3 and above
Expand Down Expand Up @@ -284,38 +284,42 @@ def sample_app(environ, start_response):
assert len(session_aggregates) == 1


if PY33:

@pytest.fixture
def profiling():
yield
teardown_profiler()
@pytest.mark.skipif(
sys.version_info < (3, 3), reason="Profiling is only supported in Python >= 3.3"
)
@pytest.mark.parametrize(
"profiles_sample_rate,profile_count",
[
pytest.param(1.0, 1, id="profiler sampled at 1.0"),
pytest.param(0.75, 1, id="profiler sampled at 0.75"),
pytest.param(0.25, 0, id="profiler not sampled at 0.25"),
pytest.param(None, 0, id="profiler not enabled"),
],
)
def test_profile_sent(
capture_envelopes,
sentry_init,
teardown_profiling,
profiles_sample_rate,
profile_count,
):
def test_app(environ, start_response):
start_response("200 OK", [])
return ["Go get the ball! Good dog!"]

@pytest.mark.parametrize(
"profiles_sample_rate,should_send",
[(1.0, True), (0.75, True), (0.25, False), (None, False)],
sentry_init(
traces_sample_rate=1.0,
_experiments={"profiles_sample_rate": profiles_sample_rate},
)
def test_profile_sent_when_profiling_enabled(
capture_envelopes, sentry_init, profiling, profiles_sample_rate, should_send
):
def test_app(environ, start_response):
start_response("200 OK", [])
return ["Go get the ball! Good dog!"]

sentry_init(
traces_sample_rate=1.0,
_experiments={"profiles_sample_rate": profiles_sample_rate},
)
app = SentryWsgiMiddleware(test_app)
envelopes = capture_envelopes()

with mock.patch("sentry_sdk.profiler.random.random", return_value=0.5):
client = Client(app)
client.get("/")

profile_sent = False
for item in envelopes[0].items:
if item.headers["type"] == "profile":
profile_sent = True
break
assert profile_sent == should_send
app = SentryWsgiMiddleware(test_app)
envelopes = capture_envelopes()

with mock.patch("sentry_sdk.profiler.random.random", return_value=0.5):
client = Client(app)
client.get("/")

count_item_types = Counter()
for envelope in envelopes:
for item in envelope.items:
count_item_types[item.type] += 1
assert count_item_types["profile"] == profile_count
61 changes: 61 additions & 0 deletions tests/test_profiler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import platform
import sys
import threading

import pytest

from sentry_sdk.profiler import setup_profiler


minimum_python_33 = pytest.mark.skipif(
sys.version_info < (3, 3), reason="Profiling is only supported in Python >= 3.3"
)

unix_only = pytest.mark.skipif(
platform.system().lower() not in {"linux", "darwin"}, reason="UNIX only"
)


@minimum_python_33
def test_profiler_invalid_mode(teardown_profiling):
with pytest.raises(ValueError):
setup_profiler({"_experiments": {"profiler_mode": "magic"}})
# make sure to clean up at the end of the test


@unix_only
@minimum_python_33
@pytest.mark.parametrize("mode", ["sigprof", "sigalrm"])
def test_profiler_signal_mode_none_main_thread(mode, teardown_profiling):
"""
signal based profiling must be initialized from the main thread because
of how the signal library in python works
"""

class ProfilerThread(threading.Thread):
def run(self):
self.exc = None
try:
setup_profiler({"_experiments": {"profiler_mode": mode}})
except Exception as e:
# store the exception so it can be raised in the caller
self.exc = e

def join(self, timeout=None):
ret = super(ProfilerThread, self).join(timeout=timeout)
if self.exc:
raise self.exc
return ret

with pytest.raises(ValueError):
thread = ProfilerThread()
thread.start()
thread.join()

# make sure to clean up at the end of the test


@pytest.mark.parametrize("mode", ["sleep", "event", "sigprof", "sigalrm"])
def test_profiler_valid_mode(mode, teardown_profiling):
# should not raise any exceptions
setup_profiler({"_experiments": {"profiler_mode": mode}})