Skip to content
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
14 changes: 6 additions & 8 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ commands:
name: Run Tests With Coverage Report
environment:
CASSANDRA_TEST: "<<parameters.cassandra>>"
GEVENT_STARLETTE_TEST: "<<parameters.gevent>>"
GEVENT_TEST: "<<parameters.gevent>>"
KAFKA_TEST: "<<parameters.kafka>>"
command: |
. venv/bin/activate
Expand Down Expand Up @@ -208,7 +208,7 @@ jobs:
- store-pytest-results
- store-coverage-report

py39gevent_starlette:
py39gevent:
docker:
- image: public.ecr.aws/docker/library/python:3.9
working_directory: ~/repo
Expand All @@ -219,10 +219,8 @@ jobs:
- pip-install-tests-deps:
requirements: "tests/requirements-gevent-starlette.txt"
- run-tests-with-coverage-report:
# TODO: uncomment once gevent instrumentation is done
# gevent: "true"
# tests: "tests/frameworks/test_gevent.py tests/frameworks/test_starlette.py"
tests: "tests/frameworks/test_starlette.py"
gevent: "true"
tests: "tests/frameworks/test_gevent.py"
- store-pytest-results
- store-coverage-report

Expand Down Expand Up @@ -307,7 +305,7 @@ workflows:
py-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
- python314
- py39cassandra
- py39gevent_starlette
- py39gevent
- py312aws
- py312kafka
- autowrapt:
Expand All @@ -320,7 +318,7 @@ workflows:
# Uncomment the following when giving real support to 3.14
# - python314
- py39cassandra
- py39gevent_starlette
- py39gevent
- py312aws
- py312kafka
- autowrapt
5 changes: 2 additions & 3 deletions src/instana/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def key_to_bool(k: str) -> bool:
import inspect

all_accepted_patch_all_args = inspect.getfullargspec(monkey.patch_all)[0]
provided_options.replace(" ", "").replace("--", "").split(",")
provided_options = provided_options.replace(" ", "").replace("--", "").split(",")

provided_options = [
k for k in provided_options if short_key(k) in all_accepted_patch_all_args
Expand Down Expand Up @@ -182,6 +182,7 @@ def boot_agent() -> None:
sqlalchemy, # noqa: F401
starlette, # noqa: F401
urllib3, # noqa: F401
gevent, # noqa: F401
)
from instana.instrumentation.aiohttp import (
client as aiohttp_client, # noqa: F401
Expand Down Expand Up @@ -209,8 +210,6 @@ def boot_agent() -> None:
server as tornado_server, # noqa: F401
)

# from instana.instrumentation import gevent_inst # noqa: F401

# Hooks
from instana.hooks import (
hook_gunicorn, # noqa: F401
Expand Down
36 changes: 11 additions & 25 deletions src/instana/instrumentation/gevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
"""

import sys
from ..log import logger
from ..singletons import tracer

from opentelemetry import context
import contextvars

from instana.log import logger


def instrument_gevent():
Expand All @@ -16,38 +19,21 @@ def instrument_gevent():
logger.debug("Instrumenting gevent")

import gevent
from opentracing.scope_managers.gevent import GeventScopeManager
from opentracing.scope_managers.gevent import _GeventScope

def spawn_callback(new_greenlet):
"""Handles context propagation for newly spawning greenlets"""
parent_scope = tracer.scope_manager.active
if parent_scope is not None:
# New greenlet, new clean slate. Clone and make active in this new greenlet
# the currently active scope (but don't finish() the span on close - it's a
# clone/not the original and we don't want to close it prematurely)
# TODO: Change to our own ScopeManagers
parent_scope_clone = _GeventScope(
parent_scope.manager, parent_scope.span, finish_on_close=False
)
tracer._scope_manager._set_greenlet_scope(
parent_scope_clone, new_greenlet
)

logger.debug(" -> Updating tracer to use gevent based context management")
tracer._scope_manager = GeventScopeManager()
parent_context = context.get_current()
new_context = contextvars.Context()

new_context.run(lambda: context.attach(parent_context))
new_greenlet.gr_context = new_context

gevent.Greenlet.add_spawn_callback(spawn_callback)
except Exception:
logger.debug("instrument_gevent: ", exc_info=True)


if "gevent" not in sys.modules:
logger.debug("Instrumenting gevent: gevent not detected or loaded. Nothing done.")
elif not hasattr(sys.modules["gevent"], "version_info"):
logger.debug("gevent module has no 'version_info'. Skipping instrumentation.")
elif sys.modules["gevent"].version_info < (1, 4):
logger.debug(
"gevent < 1.4 detected. The Instana package supports gevent versions 1.4 and greater."
)
else:
instrument_gevent()
2 changes: 1 addition & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import os

if os.environ.get('GEVENT_STARLETTE_TEST'):
if os.environ.get('GEVENT_TEST'):
from gevent import monkey
monkey.patch_all()

2 changes: 1 addition & 1 deletion tests/apps/aiohttp_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

APP_THREAD = None

if not any((os.environ.get('GEVENT_STARLETTE_TEST'),
if not any((os.environ.get('GEVENT_TEST'),
os.environ.get('CASSANDRA_TEST'),
sys.version_info < (3, 5, 3))):
APP_THREAD = launch_background_thread(server, "AIOHTTP")
2 changes: 1 addition & 1 deletion tests/apps/aiohttp_app2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

APP_THREAD = None

if not any((os.environ.get('GEVENT_STARLETTE_TEST'),
if not any((os.environ.get('GEVENT_TEST'),
os.environ.get('CASSANDRA_TEST'),
sys.version_info < (3, 5, 3))):
APP_THREAD = launch_background_thread(server, "AIOHTTP")
2 changes: 1 addition & 1 deletion tests/apps/grpc_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import time
import threading

if not any((os.environ.get('GEVENT_STARLETTE_TEST'),
if not any((os.environ.get('GEVENT_TEST'),
os.environ.get('CASSANDRA_TEST'),
sys.version_info < (3, 5, 3))):
# Background RPC application
Expand Down
2 changes: 1 addition & 1 deletion tests/apps/tornado_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

app_thread = None

if not any((app_thread, os.environ.get('GEVENT_STARLETTE_TEST'), os.environ.get('CASSANDRA_TEST'))):
if not any((app_thread, os.environ.get('GEVENT_TEST'), os.environ.get('CASSANDRA_TEST'))):
testenv["tornado_port"] = 10813
testenv["tornado_server"] = ("http://127.0.0.1:" + str(testenv["tornado_port"]))

Expand Down
4 changes: 1 addition & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from instana.util.runtime import is_ppc64, is_s390x

collect_ignore_glob = [
"*test_gevent*",
"*collector/test_gcr*",
"*agent/test_google*",
]
Expand All @@ -51,11 +50,10 @@
if not os.environ.get("COUCHBASE_TEST"):
collect_ignore_glob.append("*test_couchbase*")

if not os.environ.get("GEVENT_STARLETTE_TEST"):
if not os.environ.get("GEVENT_TEST"):
collect_ignore_glob.extend(
[
"*test_gevent*",
"*test_starlette*",
]
)

Expand Down
125 changes: 61 additions & 64 deletions tests/frameworks/test_gevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,123 +2,120 @@
# (c) Copyright Instana Inc. 2020

import os
import unittest
import pytest

import urllib3
import gevent
from gevent.pool import Group
import urllib3
from opentracing.scope_managers.gevent import GeventScopeManager
from typing import Generator

import tests.apps.flask_app
from instana.span import SDKSpan
from instana.singletons import tracer
from ..helpers import testenv, get_spans_by_filter
from tests.helpers import testenv, get_spans_by_filter, filter_test_span


@unittest.skipIf(not os.environ.get("GEVENT_STARLETTE_TEST"), reason="")
class TestGEvent(unittest.TestCase):
def setUp(self):
self.http = urllib3.HTTPConnectionPool('127.0.0.1', port=testenv["flask_port"], maxsize=20)
self.recorder = tracer.recorder
self.recorder.clear_spans()
tracer._scope_manager = GeventScopeManager()
# Skip the tests if the environment variable `GEVENT_TEST` is not set
pytestmark = pytest.mark.skipif(not os.environ.get("GEVENT_TEST"), reason="GEVENT_TEST not set")


def tearDown(self):
""" Do nothing for now """
pass
class TestGEvent:
@classmethod
def setup_class(cls) -> None:
"""Setup that runs once before all tests in the class"""
cls.http = urllib3.HTTPConnectionPool('127.0.0.1', port=testenv["flask_port"], maxsize=20)
cls.recorder = tracer.span_processor

@pytest.fixture(autouse=True)
def setUp(self) -> Generator[None, None, None]:
"""Clear all spans before each test run"""
self.recorder.clear_spans()

def make_http_call(self, n=None):
"""Helper function to make HTTP calls"""
return self.http.request('GET', testenv["flask_server"] + '/')

def spawn_calls(self):
with tracer.start_active_span('spawn_calls'):
"""Helper function to spawn multiple HTTP calls"""
with tracer.start_as_current_span('spawn_calls'):
jobs = []
jobs.append(gevent.spawn(self.make_http_call))
jobs.append(gevent.spawn(self.make_http_call))
jobs.append(gevent.spawn(self.make_http_call))
gevent.joinall(jobs, timeout=2)

def spawn_imap_unordered(self):
"""Helper function to test imap_unordered"""
igroup = Group()
result = []
with tracer.start_active_span('test'):
with tracer.start_as_current_span('test'):
for i in igroup.imap_unordered(self.make_http_call, range(3)):
result.append(i)

def launch_gevent_chain(self):
with tracer.start_active_span('test'):
"""Helper function to launch a chain of gevent calls"""
with tracer.start_as_current_span('test'):
gevent.spawn(self.spawn_calls).join()

def test_spawning(self):
gevent.spawn(self.launch_gevent_chain)

gevent.sleep(2)

spans = self.recorder.queued_spans()

self.assertEqual(8, len(spans))

span_filter = lambda span: span.n == "sdk" \
and span.data['sdk']['name'] == 'test' and span.p == None
test_spans = get_spans_by_filter(spans, span_filter)
self.assertIsNotNone(test_spans)
self.assertEqual(len(test_spans), 1)


assert len(spans) == 8

test_spans = get_spans_by_filter(spans, filter_test_span)
assert test_spans
assert len(test_spans) == 1

test_span = test_spans[0]
self.assertTrue(type(test_spans[0]) is SDKSpan)


span_filter = lambda span: span.n == "sdk" \
and span.data['sdk']['name'] == 'spawn_calls' and span.p == test_span.s
and span.data['sdk']['name'] == 'spawn_calls' and span.p == test_span.s
spawn_spans = get_spans_by_filter(spans, span_filter)
self.assertIsNotNone(spawn_spans)
self.assertEqual(len(spawn_spans), 1)

assert spawn_spans
assert len(spawn_spans) == 1
spawn_span = spawn_spans[0]
self.assertTrue(type(spawn_spans[0]) is SDKSpan)


span_filter = lambda span: span.n == "urllib3"
urllib3_spans = get_spans_by_filter(spans, span_filter)

for urllib3_span in urllib3_spans:
# spans should all have the same test span parent
self.assertEqual(urllib3_span.t, spawn_span.t)
self.assertEqual(urllib3_span.p, spawn_span.s)

assert urllib3_span.t == spawn_span.t
assert urllib3_span.p == spawn_span.s
# find the wsgi span generated from this urllib3 request
span_filter = lambda span: span.n == "wsgi" and span.p == urllib3_span.s
wsgi_spans = get_spans_by_filter(spans, span_filter)
self.assertIsNotNone(wsgi_spans)
self.assertEqual(len(wsgi_spans), 1)
assert wsgi_spans is not None
assert len(wsgi_spans) == 1

def test_imap_unordered(self):
gevent.spawn(self.spawn_imap_unordered())

gevent.spawn(self.spawn_imap_unordered)
gevent.sleep(2)

spans = self.recorder.queued_spans()
self.assertEqual(7, len(spans))

span_filter = lambda span: span.n == "sdk" \
and span.data['sdk']['name'] == 'test' and span.p == None
test_spans = get_spans_by_filter(spans, span_filter)
self.assertIsNotNone(test_spans)
self.assertEqual(len(test_spans), 1)

assert len(spans) == 7

test_spans = get_spans_by_filter(spans, filter_test_span)
assert test_spans is not None
assert len(test_spans) == 1

test_span = test_spans[0]
self.assertTrue(type(test_spans[0]) is SDKSpan)


span_filter = lambda span: span.n == "urllib3"
urllib3_spans = get_spans_by_filter(spans, span_filter)
self.assertEqual(len(urllib3_spans), 3)

assert len(urllib3_spans) == 3
for urllib3_span in urllib3_spans:
# spans should all have the same test span parent
self.assertEqual(urllib3_span.t, test_span.t)
self.assertEqual(urllib3_span.p, test_span.s)

assert urllib3_span.t == test_span.t
assert urllib3_span.p == test_span.s
# find the wsgi span generated from this urllib3 request
span_filter = lambda span: span.n == "wsgi" and span.p == urllib3_span.s
wsgi_spans = get_spans_by_filter(spans, span_filter)
self.assertIsNotNone(wsgi_spans)
self.assertEqual(len(wsgi_spans), 1)

assert wsgi_spans is not None
assert len(wsgi_spans) == 1
Loading
Loading