Skip to content

Commit c71823c

Browse files
authored
New Celery Instrumentation (#238)
* Exclude celery as parent * Updated ports * Add celery to test set * Break agent out into its own package * Update agent imports * Move app init to app package * Background Celery app; Clean up test apps * Updated redis config * Assure test env var is set * Error handler unification * No announce in tests * Celery instrumentation & tests * TestAgent override * Parent span exclusion * Pytest configuration file * Redis config * Update test imports * Tests cleanup * Test run with Pytest * No unicode characters for py2 * Update pytest ignore globs * Update min pytest version * Rename app packages to avoid naming conflicts * Update tests to run under Pytest * Fix log formatting * assertEquals instead of assert_equals * Moar assertEquals * Method docs and task_catalog_get * Retry and failure hooks * Update recorded tags * Update instrumentation message * Moar tests * Add skiptest
1 parent b682af0 commit c71823c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+1900
-1144
lines changed

.circleci/config.yml

+5-5
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
name: run tests
3838
command: |
3939
. venv/bin/activate
40-
python runtests.py
40+
pytest -v
4141
4242
python38:
4343
docker:
@@ -69,7 +69,7 @@ jobs:
6969
name: run tests
7070
command: |
7171
. venv/bin/activate
72-
python runtests.py
72+
pytest -v
7373
7474
py27cassandra:
7575
docker:
@@ -96,7 +96,7 @@ jobs:
9696
name: run tests
9797
command: |
9898
. venv/bin/activate
99-
CASSANDRA_TEST=1 nosetests -v tests/test_cassandra-driver.py:TestCassandra
99+
CASSANDRA_TEST=1 pytest -v tests/clients/test_cassandra-driver.py
100100
101101
py36cassandra:
102102
docker:
@@ -120,7 +120,7 @@ jobs:
120120
name: run tests
121121
command: |
122122
. venv/bin/activate
123-
CASSANDRA_TEST=1 nosetests -v tests/test_cassandra-driver.py:TestCassandra
123+
CASSANDRA_TEST=1 pytest -v tests/clients/test_cassandra-driver.py
124124
125125
gevent38:
126126
docker:
@@ -140,7 +140,7 @@ jobs:
140140
name: run tests
141141
command: |
142142
. venv/bin/activate
143-
GEVENT_TEST=1 nosetests -v tests/test_gevent.py
143+
GEVENT_TEST=1 pytest -v tests/frameworks/test_gevent.py
144144
workflows:
145145
version: 2
146146
build:

docker-compose.yml

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
version: '2'
22
services:
33
redis:
4-
image: 'bitnami/redis:latest'
5-
environment:
6-
- ALLOW_EMPTY_PASSWORD=yes
4+
image: redis:4.0.6
5+
#image: 'bitnami/redis:latest'
6+
#environment:
7+
# - ALLOW_EMPTY_PASSWORD=yes
8+
#volumes:
9+
# - ./tests/conf/redis.conf:/opt/bitnami/redis/mounted-etc/redis.conf
10+
volumes:
11+
- ./tests/conf/redis.conf:/usr/local/etc/redis/redis.conf
12+
command: redis-server /usr/local/etc/redis/redis.conf
713
ports:
8-
- 6379:6379
14+
- "0.0.0.0:6379:6379"
915

1016
#
1117
# Dev: Optionally enable to validate Redis Sentinel

instana/__init__.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,14 @@ def lambda_handler(event, context):
8585
# Import the module specified in module_name
8686
handler_module = importlib.import_module(module_name)
8787
except ImportError:
88-
print("Couldn't determine and locate default module handler: %s.%s", module_name, function_name)
88+
print("Couldn't determine and locate default module handler: %s.%s" % (module_name, function_name))
8989
else:
9090
# Now get the function and execute it
9191
if hasattr(handler_module, function_name):
9292
handler_function = getattr(handler_module, function_name)
9393
return handler_function(event, context)
9494
else:
95-
print("Couldn't determine and locate default function handler: %s.%s", module_name, function_name)
95+
print("Couldn't determine and locate default function handler: %s.%s" % (module_name, function_name))
9696

9797

9898
def boot_agent_later():
@@ -129,6 +129,8 @@ def boot_agent():
129129
else:
130130
from .instrumentation import mysqlclient
131131

132+
from .instrumentation.celery import hooks
133+
132134
from .instrumentation import cassandra_inst
133135
from .instrumentation import couchbase_inst
134136
from .instrumentation import flask

instana/agent/__init__.py

Whitespace-only changes.

instana/agent/aws_lambda.py

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""
2+
The Instana agent (for AWS Lambda functions) that manages
3+
monitoring state and reporting that data.
4+
"""
5+
import os
6+
import time
7+
from ..log import logger
8+
from ..util import to_json
9+
from .base import BaseAgent
10+
from instana.collector import Collector
11+
from instana.options import AWSLambdaOptions
12+
13+
14+
class AWSLambdaFrom(object):
15+
""" The source identifier for AWSLambdaAgent """
16+
hl = True
17+
cp = "aws"
18+
e = "qualifiedARN"
19+
20+
def __init__(self, **kwds):
21+
self.__dict__.update(kwds)
22+
23+
24+
class AWSLambdaAgent(BaseAgent):
25+
""" In-process agent for AWS Lambda """
26+
def __init__(self):
27+
super(AWSLambdaAgent, self).__init__()
28+
29+
self.from_ = AWSLambdaFrom()
30+
self.collector = None
31+
self.options = AWSLambdaOptions()
32+
self.report_headers = None
33+
self._can_send = False
34+
self.extra_headers = self.options.extra_http_headers
35+
36+
if self._validate_options():
37+
self._can_send = True
38+
self.collector = Collector(self)
39+
self.collector.start()
40+
else:
41+
logger.warning("Required INSTANA_AGENT_KEY and/or INSTANA_ENDPOINT_URL environment variables not set. "
42+
"We will not be able monitor this function.")
43+
44+
def can_send(self):
45+
"""
46+
Are we in a state where we can send data?
47+
@return: Boolean
48+
"""
49+
return self._can_send
50+
51+
def get_from_structure(self):
52+
"""
53+
Retrieves the From data that is reported alongside monitoring data.
54+
@return: dict()
55+
"""
56+
return {'hl': True, 'cp': 'aws', 'e': self.collector.context.invoked_function_arn}
57+
58+
def report_data_payload(self, payload):
59+
"""
60+
Used to report metrics and span data to the endpoint URL in self.options.endpoint_url
61+
"""
62+
response = None
63+
try:
64+
if self.report_headers is None:
65+
# Prepare request headers
66+
self.report_headers = dict()
67+
self.report_headers["Content-Type"] = "application/json"
68+
self.report_headers["X-Instana-Host"] = self.collector.context.invoked_function_arn
69+
self.report_headers["X-Instana-Key"] = self.options.agent_key
70+
self.report_headers["X-Instana-Time"] = str(round(time.time() * 1000))
71+
72+
# logger.debug("using these headers: %s", self.report_headers)
73+
74+
if 'INSTANA_DISABLE_CA_CHECK' in os.environ:
75+
ssl_verify = False
76+
else:
77+
ssl_verify = True
78+
79+
response = self.client.post(self.__data_bundle_url(),
80+
data=to_json(payload),
81+
headers=self.report_headers,
82+
timeout=self.options.timeout,
83+
verify=ssl_verify)
84+
85+
if 200 <= response.status_code < 300:
86+
logger.debug("report_data_payload: Instana responded with status code %s", response.status_code)
87+
else:
88+
logger.info("report_data_payload: Instana responded with status code %s", response.status_code)
89+
except Exception as e:
90+
logger.debug("report_data_payload: connection error (%s)", type(e))
91+
finally:
92+
return response
93+
94+
def _validate_options(self):
95+
"""
96+
Validate that the options used by this Agent are valid. e.g. can we report data?
97+
"""
98+
return self.options.endpoint_url is not None and self.options.agent_key is not None
99+
100+
def __data_bundle_url(self):
101+
"""
102+
URL for posting metrics to the host agent. Only valid when announced.
103+
"""
104+
return "%s/bundle" % self.options.endpoint_url

instana/agent/base.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import requests
2+
3+
4+
class BaseAgent(object):
5+
""" Base class for all agent flavors """
6+
client = None
7+
sensor = None
8+
secrets_matcher = 'contains-ignore-case'
9+
secrets_list = ['key', 'pass', 'secret']
10+
extra_headers = None
11+
options = None
12+
13+
def __init__(self):
14+
self.client = requests.Session()
15+

instana/agent.py renamed to instana/agent/host.py

+14-122
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
""" The in-process Instana agent that manages monitoring state and reporting that data. """
1+
"""
2+
The in-process Instana agent (for host based processes) that manages
3+
monitoring state and reporting that data.
4+
"""
25
from __future__ import absolute_import
36

47
import json
58
import os
6-
import time
79
from datetime import datetime
810
import threading
9-
import requests
1011

1112
import instana.singletons
12-
from instana.collector import Collector
1313

14-
from .fsm import TheMachine
15-
from .log import logger
16-
from .sensor import Sensor
17-
from .util import to_json, get_py_source, package_version
18-
from .options import StandardOptions, AWSLambdaOptions
14+
from ..fsm import TheMachine
15+
from ..log import logger
16+
from ..sensor import Sensor
17+
from ..util import to_json, get_py_source, package_version
18+
from ..options import StandardOptions
19+
20+
from .base import BaseAgent
1921

2022

2123
class AnnounceData(object):
@@ -27,30 +29,7 @@ def __init__(self, **kwds):
2729
self.__dict__.update(kwds)
2830

2931

30-
class AWSLambdaFrom(object):
31-
""" The source identifier for AWSLambdaAgent """
32-
hl = True
33-
cp = "aws"
34-
e = "qualifiedARN"
35-
36-
def __init__(self, **kwds):
37-
self.__dict__.update(kwds)
38-
39-
40-
class BaseAgent(object):
41-
""" Base class for all agent flavors """
42-
client = None
43-
sensor = None
44-
secrets_matcher = 'contains-ignore-case'
45-
secrets_list = ['key', 'pass', 'secret']
46-
extra_headers = None
47-
options = None
48-
49-
def __init__(self):
50-
self.client = requests.Session()
51-
52-
53-
class StandardAgent(BaseAgent):
32+
class HostAgent(BaseAgent):
5433
"""
5534
The Agent class is the central controlling entity for the Instana Python language sensor. The key
5635
parts it handles are the announce state and the collection and reporting of metrics and spans to the
@@ -75,7 +54,7 @@ class StandardAgent(BaseAgent):
7554
should_threads_shutdown = threading.Event()
7655

7756
def __init__(self):
78-
super(StandardAgent, self).__init__()
57+
super(HostAgent, self).__init__()
7958
logger.debug("initializing agent")
8059
self.sensor = Sensor(self)
8160
self.machine = TheMachine(self)
@@ -170,11 +149,7 @@ def get_from_structure(self):
170149
Retrieves the From data that is reported alongside monitoring data.
171150
@return: dict()
172151
"""
173-
if os.environ.get("INSTANA_TEST", False):
174-
from_data = {'e': os.getpid(), 'h': 'fake'}
175-
else:
176-
from_data = {'e': self.announce_data.pid, 'h': self.announce_data.agentUuid}
177-
return from_data
152+
return {'e': self.announce_data.pid, 'h': self.announce_data.agentUuid}
178153

179154
def is_agent_listening(self, host, port):
180155
"""
@@ -342,86 +317,3 @@ def __response_url(self, message_id):
342317
"""
343318
path = "com.instana.plugin.python/response.%d?messageId=%s" % (int(self.announce_data.pid), message_id)
344319
return "http://%s:%s/%s" % (self.options.agent_host, self.options.agent_port, path)
345-
346-
347-
class AWSLambdaAgent(BaseAgent):
348-
""" In-process agent for AWS Lambda """
349-
def __init__(self):
350-
super(AWSLambdaAgent, self).__init__()
351-
352-
self.from_ = AWSLambdaFrom()
353-
self.collector = None
354-
self.options = AWSLambdaOptions()
355-
self.report_headers = None
356-
self._can_send = False
357-
self.extra_headers = self.options.extra_http_headers
358-
359-
if self._validate_options():
360-
self._can_send = True
361-
self.collector = Collector(self)
362-
self.collector.start()
363-
else:
364-
logger.warning("Required INSTANA_AGENT_KEY and/or INSTANA_ENDPOINT_URL environment variables not set. "
365-
"We will not be able monitor this function.")
366-
367-
def can_send(self):
368-
"""
369-
Are we in a state where we can send data?
370-
@return: Boolean
371-
"""
372-
return self._can_send
373-
374-
def get_from_structure(self):
375-
"""
376-
Retrieves the From data that is reported alongside monitoring data.
377-
@return: dict()
378-
"""
379-
return {'hl': True, 'cp': 'aws', 'e': self.collector.context.invoked_function_arn}
380-
381-
def report_data_payload(self, payload):
382-
"""
383-
Used to report metrics and span data to the endpoint URL in self.options.endpoint_url
384-
"""
385-
response = None
386-
try:
387-
if self.report_headers is None:
388-
# Prepare request headers
389-
self.report_headers = dict()
390-
self.report_headers["Content-Type"] = "application/json"
391-
self.report_headers["X-Instana-Host"] = self.collector.context.invoked_function_arn
392-
self.report_headers["X-Instana-Key"] = self.options.agent_key
393-
self.report_headers["X-Instana-Time"] = str(round(time.time() * 1000))
394-
395-
# logger.debug("using these headers: %s", self.report_headers)
396-
397-
if 'INSTANA_DISABLE_CA_CHECK' in os.environ:
398-
ssl_verify = False
399-
else:
400-
ssl_verify = True
401-
402-
response = self.client.post(self.__data_bundle_url(),
403-
data=to_json(payload),
404-
headers=self.report_headers,
405-
timeout=self.options.timeout,
406-
verify=ssl_verify)
407-
408-
if 200 <= response.status_code < 300:
409-
logger.debug("report_data_payload: Instana responded with status code %s", response.status_code)
410-
else:
411-
logger.info("report_data_payload: Instana responded with status code %s", response.status_code)
412-
except Exception as e:
413-
logger.debug("report_data_payload: connection error (%s)", type(e))
414-
finally:
415-
return response
416-
417-
def _validate_options(self):
418-
"""
419-
Validate that the options used by this Agent are valid. e.g. can we report data?
420-
"""
421-
return self.options.endpoint_url is not None and self.options.agent_key is not None
422-
423-
def __data_bundle_url(self):
424-
"""
425-
URL for posting metrics to the host agent. Only valid when announced.
426-
"""
427-
return "%s/bundle" % self.options.endpoint_url

0 commit comments

Comments
 (0)