Skip to content

Commit cfd9d17

Browse files
authored
New gevent Instrumentation (#232)
* Code comment update * Function docs and add get_spans_by_filter helper * Update tests to follow helper name change * Initial gevent instrumentation * Use cloned scopes to propagate context * Updated tests with dedicated gevent run * Update test python versions and workflow jobs * CircleCI config cleanup; Break tests out * Fix Cassandra tests dependencies * Conditional gevent imports * gevent tests require flask * Use stretch image for 3.8 tests * Add version check to gevent intrumentation * gevent based agent booting * Linter improvements and refactoring * Update warning re: uWSGI threads. Now gevent support. * Add tests for imap unordered * Code comments and updated debug log messages
1 parent 3ada977 commit cfd9d17

File tree

15 files changed

+333
-237
lines changed

15 files changed

+333
-237
lines changed

.circleci/config.yml

Lines changed: 27 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,15 @@ jobs:
77
python27:
88
docker:
99
- image: circleci/python:2.7.15
10-
11-
# Specify service dependencies here if necessary
12-
# CircleCI maintains a library of pre-built images
13-
# documented at https://circleci.com/docs/2.0/circleci-images/
1410
- image: circleci/postgres:9.6.5-alpine-ram
1511
- image: circleci/mariadb:10.1-ram
1612
- image: circleci/redis:5.0.4
1713
- image: rabbitmq:3.5.4
1814
- image: couchbase/server-sandbox:5.5.0
1915
- image: circleci/mongo:4.2.3-ram
20-
2116
working_directory: ~/repo
22-
2317
steps:
2418
- checkout
25-
26-
# Download and cache dependencies
27-
- restore_cache:
28-
keys:
29-
- v1-dependencies-{{ checksum "requirements.txt" }}
30-
# fallback to using the latest cache if no exact match is found
31-
- v1-dependencies-
32-
3319
- run:
3420
name: install dependencies
3521
command: |
@@ -46,45 +32,25 @@ jobs:
4632
. venv/bin/activate
4733
pip install -U pip
4834
python setup.py install_egg_info
49-
pip install -r requirements-test.txt
50-
51-
- save_cache:
52-
paths:
53-
- ./venv
54-
key: v1-dependencies-{{ checksum "requirements.txt" }}
55-
35+
pip install -e '.[test]'
5636
- run:
5737
name: run tests
5838
command: |
5939
. venv/bin/activate
6040
python runtests.py
6141
62-
python35:
42+
python38:
6343
docker:
64-
- image: circleci/python:3.5.6
65-
66-
# Specify service dependencies here if necessary
67-
# CircleCI maintains a library of pre-built images
68-
# documented at https://circleci.com/docs/2.0/circleci-images/
44+
- image: circleci/python:3.7.7-stretch
6945
- image: circleci/postgres:9.6.5-alpine-ram
7046
- image: circleci/mariadb:10-ram
7147
- image: circleci/redis:5.0.4
7248
- image: rabbitmq:3.5.4
7349
- image: couchbase/server-sandbox:5.5.0
7450
- image: circleci/mongo:4.2.3-ram
75-
7651
working_directory: ~/repo
77-
7852
steps:
7953
- checkout
80-
81-
# Download and cache dependencies
82-
- restore_cache:
83-
keys:
84-
- v1-dependencies-{{ checksum "requirements.txt" }}
85-
# fallback to using the latest cache if no exact match is found
86-
- v1-dependencies-
87-
8854
- run:
8955
name: install dependencies
9056
command: |
@@ -98,175 +64,89 @@ jobs:
9864
. venv/bin/activate
9965
pip install -U pip
10066
python setup.py install_egg_info
101-
pip install -r requirements.txt
102-
pip install -r requirements-test.txt
103-
104-
- save_cache:
105-
paths:
106-
- ./venv
107-
key: v1-dependencies-{{ checksum "requirements.txt" }}
108-
67+
pip install -e '.[test]'
10968
- run:
11069
name: run tests
11170
command: |
11271
. venv/bin/activate
11372
python runtests.py
11473
115-
python36:
74+
py27cassandra:
11675
docker:
117-
- image: circleci/python:3.6.8
118-
119-
# Specify service dependencies here if necessary
120-
# CircleCI maintains a library of pre-built images
121-
# documented at https://circleci.com/docs/2.0/circleci-images/
122-
- image: circleci/postgres:9.6.5-alpine-ram
123-
- image: circleci/mariadb:10-ram
124-
- image: circleci/redis:5.0.4
125-
- image: rabbitmq:3.5.4
126-
- image: couchbase/server-sandbox:5.5.0
127-
- image: circleci/mongo:4.2.3-ram
128-
76+
- image: circleci/python:2.7.15
77+
- image: circleci/cassandra:3.10
78+
environment:
79+
MAX_HEAP_SIZE: 2048m
80+
HEAP_NEWSIZE: 512m
12981
working_directory: ~/repo
130-
13182
steps:
13283
- checkout
133-
134-
# Download and cache dependencies
135-
- restore_cache:
136-
keys:
137-
- v1-dependencies-{{ checksum "requirements.txt" }}
138-
# fallback to using the latest cache if no exact match is found
139-
- v1-dependencies-
140-
14184
- run:
14285
name: install dependencies
14386
command: |
144-
sudo apt-get update
145-
sudo apt install lsb-release -y
146-
curl -O https://packages.couchbase.com/releases/couchbase-release/couchbase-release-1.0-6-amd64.deb
147-
sudo dpkg -i ./couchbase-release-1.0-6-amd64.deb
148-
sudo apt-get update
149-
sudo apt install libcouchbase-dev -y
150-
python -m venv venv
87+
rm -rf venv
88+
export PATH=/home/circleci/.local/bin:$PATH
89+
pip install --user -U pip setuptools virtualenv
90+
virtualenv --python=python2.7 --always-copy venv
15191
. venv/bin/activate
15292
pip install -U pip
15393
python setup.py install_egg_info
154-
pip install -r requirements.txt
155-
pip install -r requirements-test.txt
156-
157-
- save_cache:
158-
paths:
159-
- ./venv
160-
key: v1-dependencies-{{ checksum "requirements.txt" }}
161-
94+
pip install -e '.[test-cassandra]'
16295
- run:
16396
name: run tests
16497
command: |
16598
. venv/bin/activate
166-
python runtests.py
99+
CASSANDRA_TEST=1 nosetests -v tests/test_cassandra-driver.py:TestCassandra
167100
168-
py27cassandra:
101+
py36cassandra:
169102
docker:
170-
- image: circleci/python:2.7.15
103+
- image: circleci/python:3.6.8
171104
- image: circleci/cassandra:3.10
172105
environment:
173106
MAX_HEAP_SIZE: 2048m
174107
HEAP_NEWSIZE: 512m
175-
176108
working_directory: ~/repo
177-
178109
steps:
179110
- checkout
180-
181-
# Download and cache dependencies
182-
- restore_cache:
183-
keys:
184-
- v1-dependencies-{{ checksum "requirements.txt" }}
185-
# fallback to using the latest cache if no exact match is found
186-
- v1-dependencies-
187-
188111
- run:
189112
name: install dependencies
190113
command: |
191-
sudo apt-get update
192-
sudo apt install lsb-release -y
193-
curl -O https://packages.couchbase.com/releases/couchbase-release/couchbase-release-1.0-6-amd64.deb
194-
sudo dpkg -i ./couchbase-release-1.0-6-amd64.deb
195-
sudo apt-get update
196-
sudo apt install libcouchbase-dev -y
197-
rm -rf venv
198-
export PATH=/home/circleci/.local/bin:$PATH
199-
pip install --user -U pip setuptools virtualenv
200-
virtualenv --python=python2.7 --always-copy venv
114+
python -m venv venv
201115
. venv/bin/activate
202116
pip install -U pip
203117
python setup.py install_egg_info
204-
pip install -r requirements-test.txt
205-
206-
- save_cache:
207-
paths:
208-
- ./venv
209-
key: v1-dependencies-{{ checksum "requirements.txt" }}
210-
118+
pip install -e '.[test-cassandra]'
211119
- run:
212120
name: run tests
213121
command: |
214122
. venv/bin/activate
215-
nosetests -v tests/test_cassandra-driver.py:TestCassandra
123+
CASSANDRA_TEST=1 nosetests -v tests/test_cassandra-driver.py:TestCassandra
216124
217-
py36cassandra:
125+
gevent38:
218126
docker:
219-
- image: circleci/python:3.6.8
220-
- image: circleci/cassandra:3.10
221-
environment:
222-
MAX_HEAP_SIZE: 2048m
223-
HEAP_NEWSIZE: 512m
224-
127+
- image: circleci/python:3.8.2
225128
working_directory: ~/repo
226-
227129
steps:
228130
- checkout
229-
230-
# Download and cache dependencies
231-
- restore_cache:
232-
keys:
233-
- v1-dependencies-{{ checksum "requirements.txt" }}
234-
# fallback to using the latest cache if no exact match is found
235-
- v1-dependencies-
236-
237131
- run:
238132
name: install dependencies
239133
command: |
240-
sudo apt-get update
241-
sudo apt install lsb-release -y
242-
curl -O https://packages.couchbase.com/releases/couchbase-release/couchbase-release-1.0-6-amd64.deb
243-
sudo dpkg -i ./couchbase-release-1.0-6-amd64.deb
244-
sudo apt-get update
245-
sudo apt install libcouchbase-dev -y
246134
python -m venv venv
247135
. venv/bin/activate
248136
pip install -U pip
249137
python setup.py install_egg_info
250-
pip install -r requirements.txt
251-
pip install -r requirements-test.txt
252-
253-
- save_cache:
254-
paths:
255-
- ./venv
256-
key: v1-dependencies-{{ checksum "requirements.txt" }}
257-
138+
pip install -e '.[test-gevent]'
258139
- run:
259140
name: run tests
260141
command: |
261142
. venv/bin/activate
262-
nosetests -v tests/test_cassandra-driver.py:TestCassandra
263-
143+
GEVENT_TEST=1 nosetests -v tests/test_gevent.py
264144
workflows:
265145
version: 2
266146
build:
267147
jobs:
268148
- python27
269-
- python35
270-
- python36
149+
- python38
271150
- py27cassandra
272151
- py36cassandra
152+
- gevent38

instana/__init__.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ def lambda_handler(event, context):
9595
print("Couldn't determine and locate default function handler: %s.%s", module_name, function_name)
9696

9797

98+
def boot_agent_later():
99+
""" Executes <boot_agent> in the future! """
100+
if 'gevent' in sys.modules:
101+
import gevent
102+
gevent.spawn_later(2.0, boot_agent)
103+
else:
104+
t = Timer(2.0, boot_agent)
105+
t.start()
106+
107+
98108
def boot_agent():
99109
"""Initialize the Instana agent and conditionally load auto-instrumentation."""
100110
# Disable all the unused-import violations in this function
@@ -122,6 +132,7 @@ def boot_agent():
122132
from .instrumentation import cassandra_inst
123133
from .instrumentation import couchbase_inst
124134
from .instrumentation import flask
135+
from .instrumentation import gevent_inst
125136
from .instrumentation import grpcio
126137
from .instrumentation.tornado import client
127138
from .instrumentation.tornado import server
@@ -173,7 +184,6 @@ def boot_agent():
173184
else:
174185
if "INSTANA_MAGIC" in os.environ:
175186
# If we're being loaded into an already running process, then delay agent initialization
176-
t = Timer(2.0, boot_agent)
177-
t.start()
187+
boot_agent_later()
178188
else:
179189
boot_agent()

instana/hooks/hook_uwsgi.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
opt_master = uwsgi.opt.get('master', False)
1616
opt_lazy_apps = uwsgi.opt.get('lazy-apps', False)
1717

18-
if uwsgi.opt.get('enable-threads', False) is False:
19-
logger.warn("Required: uWSGI threads are not enabled. " +
20-
"Please enable by using the uWSGI --enable-threads option.")
18+
if uwsgi.opt.get('enable-threads', False) is False and uwsgi.opt.get('gevent', False) is False:
19+
logger.warn("Required: Neither uWSGI threads or gevent is enabled. " +
20+
"Please enable by using the uWSGI --enable-threads or --gevent option.")
2121

2222
if opt_master and opt_lazy_apps is False:
2323
# --master is supplied in uWSGI options (otherwise uwsgidecorators package won't be available)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
Instrumentation for the gevent package.
3+
"""
4+
from __future__ import absolute_import
5+
6+
import sys
7+
from ..log import logger
8+
from ..singletons import tracer
9+
10+
11+
def instrument_gevent():
12+
""" Adds context propagation to gevent greenlet spawning """
13+
try:
14+
logger.debug("Instrumenting gevent")
15+
16+
import gevent
17+
from opentracing.scope_managers.gevent import GeventScopeManager
18+
from opentracing.scope_managers.gevent import _GeventScope
19+
20+
def spawn_callback(new_greenlet):
21+
""" Handles context propagation for newly spawning greenlets """
22+
parent_scope = tracer.scope_manager.active
23+
if parent_scope is not None:
24+
# New greenlet, new clean slate. Clone and make active in this new greenlet
25+
# the currently active scope (but don't finish() the span on close - it's a
26+
# clone/not the original and we don't want to close it prematurely)
27+
# TODO: Change to our own ScopeManagers
28+
parent_scope_clone = _GeventScope(parent_scope.manager, parent_scope.span, finish_on_close=False)
29+
tracer._scope_manager._set_greenlet_scope(parent_scope_clone, new_greenlet)
30+
31+
logger.debug(" -> Updating tracer to use gevent based context management")
32+
tracer._scope_manager = GeventScopeManager()
33+
gevent.Greenlet.add_spawn_callback(spawn_callback)
34+
except:
35+
logger.debug("instrument_gevent: ", exc_info=True)
36+
37+
38+
if 'gevent' in sys.modules:
39+
if sys.modules['gevent'].version_info < (1, 4):
40+
logger.debug("gevent < 1.4 detected. The Instana package supports gevent versions 1.4 and greater.")
41+
else:
42+
instrument_gevent()
43+
else:
44+
logger.debug("Instrumenting gevent: gevent not detected or loaded. Nothing done.")

instana/instrumentation/urllib3.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
def collect(instance, args, kwargs):
1515
""" Build and return a fully qualified URL for this request """
16+
kvs = dict()
1617
try:
17-
kvs = dict()
1818
kvs['host'] = instance.host
1919
kvs['port'] = instance.port
2020

@@ -58,7 +58,6 @@ def collect_response(scope, response):
5858
except Exception:
5959
logger.debug("collect_response", exc_info=True)
6060

61-
6261
@wrapt.patch_function_wrapper('urllib3', 'HTTPConnectionPool.urlopen')
6362
def urlopen_with_instana(wrapped, instance, args, kwargs):
6463
parent_span = tracer.active_span

instana/meter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def start(self):
133133
"""
134134
This function can be called at first boot or after a fork. In either case, it will
135135
assure that the Meter is in a proper state (via reset()) and spawn a new background
136-
thread to periodically report queued spans
136+
thread to periodically report the metrics payload.
137137
138138
Note that this will abandon any previous thread object that (in the case of an `os.fork()`)
139139
should no longer exist in the forked process.

0 commit comments

Comments
 (0)