Skip to content

Commit f8d0d47

Browse files
committed
Add try except and extra env variable for gevent monkey patch
1 parent cbd5f28 commit f8d0d47

File tree

3 files changed

+115
-15
lines changed

3 files changed

+115
-15
lines changed

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/aws_opentelemetry_distro.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
import sys
55
from logging import Logger, getLogger
66

7-
from amazon.opentelemetry.distro.patches._instrumentation_patch import apply_instrumentation_patches
7+
from amazon.opentelemetry.distro.patches._instrumentation_patch import (
8+
AWS_GEVENT_PATCH_MODULES,
9+
apply_instrumentation_patches,
10+
)
811
from opentelemetry.distro import OpenTelemetryDistro
912
from opentelemetry.environment_variables import OTEL_PROPAGATORS, OTEL_PYTHON_ID_GENERATOR
1013
from opentelemetry.sdk.environment_variables import (
@@ -65,5 +68,7 @@ def _configure(self, **kwargs):
6568
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, "base2_exponential_bucket_histogram"
6669
)
6770

71+
os.environ.setdefault(AWS_GEVENT_PATCH_MODULES, "all")
72+
6873
if kwargs.get("apply_patches", True):
6974
apply_instrumentation_patches()

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_instrumentation_patch.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License.
4+
import os
45
import sys
56
from logging import Logger, getLogger
67

78
import pkg_resources
89

910
from amazon.opentelemetry.distro.patches._resource_detector_patches import _apply_resource_detector_patches
1011

12+
# Env variable for determining whether we want to monkey patch gevent modules. Possible values are 'all', 'none', and
13+
# comma separated list 'os, thread, time, sys, socket, select, ssl, subprocess, builtins, signal, queue, contextvars'
14+
AWS_GEVENT_PATCH_MODULES = "AWS_GEVENT_PATCH_MODULES"
15+
1116
_logger: Logger = getLogger(__name__)
1217

1318

@@ -21,11 +26,39 @@ def apply_instrumentation_patches() -> None:
2126
Where possible, automated testing should be run to catch upstream changes resulting in broken patches
2227
"""
2328
if _is_installed("gevent"):
24-
# pylint: disable=import-outside-toplevel
25-
# Delay import to only occur if monkey patch is needed (e.g. gevent is used to run application).
26-
from gevent import monkey
29+
try:
30+
gevent_patch_module = os.environ.get(AWS_GEVENT_PATCH_MODULES)
31+
if gevent_patch_module == "all":
32+
# pylint: disable=import-outside-toplevel
33+
# Delay import to only occur if monkey patch is needed (e.g. gevent is used to run application).
34+
from gevent import monkey
35+
36+
monkey.patch_all()
37+
elif gevent_patch_module == "none":
38+
pass
39+
else:
40+
module_list = [module.strip() for module in gevent_patch_module.split(",")]
41+
42+
# pylint: disable=import-outside-toplevel
43+
# Delay import to only occur if monkey patch is needed (e.g. gevent is used to run application).
44+
from gevent import monkey
2745

28-
monkey.patch_ssl()
46+
monkey.patch_all(
47+
socket="socket" in module_list,
48+
time="time" in module_list,
49+
select="select" in module_list,
50+
thread="thread" in module_list,
51+
os="os" in module_list,
52+
ssl="ssl" in module_list,
53+
subprocess="subprocess" in module_list,
54+
sys="sys" in module_list,
55+
builtins="builtins" in module_list,
56+
signal="signal" in module_list,
57+
queue="queue" in module_list,
58+
contextvars="contextvars" in module_list,
59+
)
60+
except Exception as exc: # pylint: disable=broad-except
61+
_logger.debug("Failed to monkey patch gevent, exception: %s", exc)
2962

3063
if _is_installed("botocore ~= 1.0"):
3164
# pylint: disable=import-outside-toplevel

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_instrumentation_patch.py

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
3-
import sys
3+
import os
44
from typing import Dict
55
from unittest import TestCase
66
from unittest.mock import MagicMock, patch
77

8+
import gevent.monkey
89
import pkg_resources
910

10-
from amazon.opentelemetry.distro.patches._instrumentation_patch import apply_instrumentation_patches
11+
from amazon.opentelemetry.distro.patches._instrumentation_patch import (
12+
AWS_GEVENT_PATCH_MODULES,
13+
apply_instrumentation_patches,
14+
)
1115
from opentelemetry.instrumentation.botocore.extensions import _KNOWN_EXTENSIONS
1216
from opentelemetry.semconv.trace import SpanAttributes
1317

@@ -55,17 +59,37 @@ def test_instrumentation_patch(self):
5559
def _run_patch_behaviour_tests(self):
5660
# Test setup
5761
self.method_patches[GET_DISTRIBUTION_PATCH].return_value = "CorrectDistributionObject"
62+
# Test setup to not patch gevent
63+
os.environ[AWS_GEVENT_PATCH_MODULES] = "none"
5864

5965
# Validate unpatched upstream behaviour - important to detect upstream changes that may break instrumentation
6066
self._test_unpatched_botocore_instrumentation()
61-
self._test_unpatched_gevent_ssl_instrumentation()
67+
self._test_unpatched_gevent_instrumentation()
6268

6369
# Apply patches
6470
apply_instrumentation_patches()
6571

6672
# Validate patched upstream behaviour - important to detect downstream changes that may break instrumentation
6773
self._test_patched_botocore_instrumentation()
68-
self._test_patched_gevent_ssl_instrumentation()
74+
self._test_unpatched_gevent_instrumentation()
75+
76+
# Test setup to check whether only these two modules get patched by gevent monkey
77+
os.environ[AWS_GEVENT_PATCH_MODULES] = "os, ssl"
78+
79+
# Apply patches
80+
apply_instrumentation_patches()
81+
82+
# Validate that os and ssl gevent monkey patch modules were patched
83+
self._test_patched_gevent_os_ssl_instrumentation()
84+
85+
# Set the value to 'all' so that all the remaining gevent monkey patch modules are patched
86+
os.environ[AWS_GEVENT_PATCH_MODULES] = "all"
87+
88+
# Apply patches again.
89+
apply_instrumentation_patches()
90+
91+
# Validate that remaining gevent monkey patch modules were patched
92+
self._test_patched_gevent_instrumentation()
6993

7094
# Test teardown
7195
self._reset_mocks()
@@ -96,9 +120,19 @@ def _test_unpatched_botocore_instrumentation(self):
96120
self.assertFalse("aws.sqs.queue_url" in attributes)
97121
self.assertFalse("aws.sqs.queue_name" in attributes)
98122

99-
def _test_unpatched_gevent_ssl_instrumentation(self):
100-
# Ssl
101-
self.assertFalse("gevent.ssl" in sys.modules, "Upstream has added the gevent ssl patch")
123+
def _test_unpatched_gevent_instrumentation(self):
124+
self.assertFalse(gevent.monkey.is_module_patched("os"), "gevent os module has been patched")
125+
self.assertFalse(gevent.monkey.is_module_patched("thread"), "gevent thread module has been patched")
126+
self.assertFalse(gevent.monkey.is_module_patched("time"), "gevent time module has been patched")
127+
self.assertFalse(gevent.monkey.is_module_patched("sys"), "gevent sys module has been patched")
128+
self.assertFalse(gevent.monkey.is_module_patched("socket"), "gevent socket module has been patched")
129+
self.assertFalse(gevent.monkey.is_module_patched("select"), "gevent select module has been patched")
130+
self.assertFalse(gevent.monkey.is_module_patched("ssl"), "gevent ssl module has been patched")
131+
self.assertFalse(gevent.monkey.is_module_patched("subprocess"), "gevent subprocess module has been patched")
132+
self.assertFalse(gevent.monkey.is_module_patched("builtins"), "gevent builtins module has been patched")
133+
self.assertFalse(gevent.monkey.is_module_patched("signal"), "gevent signal module has been patched")
134+
self.assertFalse(gevent.monkey.is_module_patched("queue"), "gevent queue module has been patched")
135+
self.assertFalse(gevent.monkey.is_module_patched("contextvars"), "gevent contextvars module has been patched")
102136

103137
def _test_patched_botocore_instrumentation(self):
104138
# Kinesis
@@ -122,9 +156,37 @@ def _test_patched_botocore_instrumentation(self):
122156
self.assertTrue("aws.sqs.queue_name" in sqs_attributes)
123157
self.assertEqual(sqs_attributes["aws.sqs.queue_name"], _QUEUE_NAME)
124158

125-
def _test_patched_gevent_ssl_instrumentation(self):
126-
# Ssl
127-
self.assertTrue("gevent.ssl" in sys.modules)
159+
def _test_patched_gevent_os_ssl_instrumentation(self):
160+
# Only ssl and os module should have been patched since the environment variable was set to 'os, ssl'
161+
self.assertTrue(gevent.monkey.is_module_patched("ssl"), "gevent ssl module has not been patched")
162+
self.assertTrue(gevent.monkey.is_module_patched("os"), "gevent os module has not been patched")
163+
# Rest should still be unpatched
164+
self.assertFalse(gevent.monkey.is_module_patched("thread"), "gevent thread module has been patched")
165+
self.assertFalse(gevent.monkey.is_module_patched("time"), "gevent time module has been patched")
166+
self.assertFalse(gevent.monkey.is_module_patched("sys"), "gevent sys module has been patched")
167+
self.assertFalse(gevent.monkey.is_module_patched("socket"), "gevent socket module has been patched")
168+
self.assertFalse(gevent.monkey.is_module_patched("select"), "gevent select module has been patched")
169+
self.assertFalse(gevent.monkey.is_module_patched("subprocess"), "gevent subprocess module has been patched")
170+
self.assertFalse(gevent.monkey.is_module_patched("builtins"), "gevent builtins module has been patched")
171+
self.assertFalse(gevent.monkey.is_module_patched("signal"), "gevent signal module has been patched")
172+
self.assertFalse(gevent.monkey.is_module_patched("queue"), "gevent queue module has been patched")
173+
self.assertFalse(gevent.monkey.is_module_patched("contextvars"), "gevent contextvars module has been patched")
174+
175+
def _test_patched_gevent_instrumentation(self):
176+
self.assertTrue(gevent.monkey.is_module_patched("os"), "gevent os module has not been patched")
177+
self.assertTrue(gevent.monkey.is_module_patched("time"), "gevent time module has not been patched")
178+
self.assertTrue(gevent.monkey.is_module_patched("socket"), "gevent socket module has not been patched")
179+
self.assertTrue(gevent.monkey.is_module_patched("select"), "gevent select module has not been patched")
180+
self.assertTrue(gevent.monkey.is_module_patched("ssl"), "gevent ssl module has not been patched")
181+
self.assertTrue(gevent.monkey.is_module_patched("subprocess"), "gevent subprocess module has not been patched")
182+
self.assertTrue(gevent.monkey.is_module_patched("signal"), "gevent signal module has not been patched")
183+
self.assertTrue(gevent.monkey.is_module_patched("queue"), "gevent queue module has not been patched")
184+
185+
# Current version of gevent.monkey.patch_all() does not do anything to these modules despite being called
186+
self.assertFalse(gevent.monkey.is_module_patched("thread"), "gevent thread module has been patched")
187+
self.assertFalse(gevent.monkey.is_module_patched("sys"), "gevent sys module has been patched")
188+
self.assertFalse(gevent.monkey.is_module_patched("builtins"), "gevent builtins module not been patched")
189+
self.assertFalse(gevent.monkey.is_module_patched("contextvars"), "gevent contextvars module has been patched")
128190

129191
def _test_botocore_installed_flag(self):
130192
with patch(

0 commit comments

Comments
 (0)