scout-apm 3.3.0__cp38-cp38-musllinux_1_2_i686.whl
Sign up to get free protection for your applications and to get access to all the features.
- scout_apm/__init__.py +0 -0
- scout_apm/api/__init__.py +197 -0
- scout_apm/async_/__init__.py +1 -0
- scout_apm/async_/api.py +41 -0
- scout_apm/async_/instruments/__init__.py +0 -0
- scout_apm/async_/instruments/jinja2.py +13 -0
- scout_apm/async_/starlette.py +101 -0
- scout_apm/bottle.py +86 -0
- scout_apm/celery.py +153 -0
- scout_apm/compat.py +104 -0
- scout_apm/core/__init__.py +99 -0
- scout_apm/core/_objtrace.cpython-38-i386-linux-gnu.so +0 -0
- scout_apm/core/agent/__init__.py +0 -0
- scout_apm/core/agent/commands.py +250 -0
- scout_apm/core/agent/manager.py +319 -0
- scout_apm/core/agent/socket.py +211 -0
- scout_apm/core/backtrace.py +116 -0
- scout_apm/core/cli/__init__.py +0 -0
- scout_apm/core/cli/core_agent_manager.py +32 -0
- scout_apm/core/config.py +404 -0
- scout_apm/core/context.py +140 -0
- scout_apm/core/error.py +95 -0
- scout_apm/core/error_service.py +167 -0
- scout_apm/core/metadata.py +66 -0
- scout_apm/core/n_plus_one_tracker.py +41 -0
- scout_apm/core/objtrace.py +24 -0
- scout_apm/core/platform_detection.py +66 -0
- scout_apm/core/queue_time.py +99 -0
- scout_apm/core/sampler.py +149 -0
- scout_apm/core/samplers/__init__.py +0 -0
- scout_apm/core/samplers/cpu.py +76 -0
- scout_apm/core/samplers/memory.py +23 -0
- scout_apm/core/samplers/thread.py +41 -0
- scout_apm/core/stacktracer.py +30 -0
- scout_apm/core/threading.py +56 -0
- scout_apm/core/tracked_request.py +328 -0
- scout_apm/core/web_requests.py +167 -0
- scout_apm/django/__init__.py +7 -0
- scout_apm/django/apps.py +137 -0
- scout_apm/django/instruments/__init__.py +0 -0
- scout_apm/django/instruments/huey.py +30 -0
- scout_apm/django/instruments/sql.py +140 -0
- scout_apm/django/instruments/template.py +35 -0
- scout_apm/django/middleware.py +211 -0
- scout_apm/django/request.py +144 -0
- scout_apm/dramatiq.py +42 -0
- scout_apm/falcon.py +142 -0
- scout_apm/flask/__init__.py +118 -0
- scout_apm/flask/sqlalchemy.py +28 -0
- scout_apm/huey.py +54 -0
- scout_apm/hug.py +40 -0
- scout_apm/instruments/__init__.py +21 -0
- scout_apm/instruments/elasticsearch.py +263 -0
- scout_apm/instruments/jinja2.py +127 -0
- scout_apm/instruments/pymongo.py +105 -0
- scout_apm/instruments/redis.py +77 -0
- scout_apm/instruments/urllib3.py +80 -0
- scout_apm/rq.py +85 -0
- scout_apm/sqlalchemy.py +38 -0
- scout_apm-3.3.0.dist-info/LICENSE +21 -0
- scout_apm-3.3.0.dist-info/METADATA +82 -0
- scout_apm-3.3.0.dist-info/RECORD +65 -0
- scout_apm-3.3.0.dist-info/WHEEL +5 -0
- scout_apm-3.3.0.dist-info/entry_points.txt +2 -0
- scout_apm-3.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import atexit
|
4
|
+
import logging
|
5
|
+
import os
|
6
|
+
import sys
|
7
|
+
|
8
|
+
from scout_apm import instruments
|
9
|
+
from scout_apm.compat import kwargs_only
|
10
|
+
from scout_apm.core import objtrace
|
11
|
+
from scout_apm.core.agent.manager import CoreAgentManager
|
12
|
+
from scout_apm.core.agent.socket import CoreAgentSocketThread
|
13
|
+
from scout_apm.core.config import scout_config
|
14
|
+
from scout_apm.core.error_service import ErrorServiceThread
|
15
|
+
from scout_apm.core.metadata import report_app_metadata
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
@kwargs_only
|
21
|
+
def install(config=None):
|
22
|
+
global shutdown_registered
|
23
|
+
if config is not None:
|
24
|
+
scout_config.set(**config)
|
25
|
+
scout_config.log()
|
26
|
+
|
27
|
+
if os.name == "nt":
|
28
|
+
logger.info(
|
29
|
+
"APM Not Launching on PID: %s - Windows is not supported", os.getpid()
|
30
|
+
)
|
31
|
+
return False
|
32
|
+
|
33
|
+
if not scout_config.value("monitor"):
|
34
|
+
logger.info(
|
35
|
+
"APM Not Launching on PID: %s - Configuration 'monitor' is not true",
|
36
|
+
os.getpid(),
|
37
|
+
)
|
38
|
+
return False
|
39
|
+
|
40
|
+
instruments.ensure_all_installed()
|
41
|
+
objtrace.enable()
|
42
|
+
|
43
|
+
logger.debug("APM Launching on PID: %s", os.getpid())
|
44
|
+
launched = CoreAgentManager().launch()
|
45
|
+
|
46
|
+
report_app_metadata()
|
47
|
+
if launched:
|
48
|
+
# Stop the thread to avoid running threads pre-fork
|
49
|
+
CoreAgentSocketThread.ensure_stopped()
|
50
|
+
|
51
|
+
if scout_config.value("shutdown_timeout_seconds") > 0.0 and not shutdown_registered:
|
52
|
+
atexit.register(shutdown)
|
53
|
+
shutdown_registered = True
|
54
|
+
|
55
|
+
return True
|
56
|
+
|
57
|
+
|
58
|
+
shutdown_registered = False
|
59
|
+
|
60
|
+
|
61
|
+
def shutdown():
|
62
|
+
timeout_seconds = scout_config.value("shutdown_timeout_seconds")
|
63
|
+
|
64
|
+
def apm_callback(queue_size):
|
65
|
+
if scout_config.value("shutdown_message_enabled"):
|
66
|
+
print( # noqa: T001,T201
|
67
|
+
(
|
68
|
+
"Scout draining {queue_size} event{s} for up to"
|
69
|
+
+ " {timeout_seconds} seconds"
|
70
|
+
).format(
|
71
|
+
queue_size=queue_size,
|
72
|
+
s=("" if queue_size == 1 else "s"),
|
73
|
+
timeout_seconds=timeout_seconds,
|
74
|
+
),
|
75
|
+
file=sys.stderr,
|
76
|
+
)
|
77
|
+
|
78
|
+
def error_callback(queue_size):
|
79
|
+
if scout_config.value("shutdown_message_enabled"):
|
80
|
+
print( # noqa: T001,T201
|
81
|
+
(
|
82
|
+
"Scout draining {queue_size} error{s} for up to"
|
83
|
+
+ " {timeout_seconds} seconds"
|
84
|
+
).format(
|
85
|
+
queue_size=queue_size,
|
86
|
+
s=("" if queue_size == 1 else "s"),
|
87
|
+
timeout_seconds=timeout_seconds,
|
88
|
+
),
|
89
|
+
file=sys.stderr,
|
90
|
+
)
|
91
|
+
|
92
|
+
CoreAgentSocketThread.wait_until_drained(
|
93
|
+
timeout_seconds=timeout_seconds, callback=apm_callback
|
94
|
+
)
|
95
|
+
|
96
|
+
if scout_config.value("errors_enabled"):
|
97
|
+
ErrorServiceThread.wait_until_drained(
|
98
|
+
timeout_seconds=timeout_seconds, callback=error_callback
|
99
|
+
)
|
Binary file
|
File without changes
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import datetime as dt
|
4
|
+
import logging
|
5
|
+
import re
|
6
|
+
|
7
|
+
from scout_apm.compat import iteritems
|
8
|
+
|
9
|
+
logger = logging.getLogger(__name__)
|
10
|
+
|
11
|
+
key_regex = re.compile(r"^[a-zA-Z0-9]{20}$")
|
12
|
+
|
13
|
+
|
14
|
+
def format_dt_for_core_agent(event_time: dt.datetime) -> str:
|
15
|
+
"""
|
16
|
+
Returns expected format for Core Agent compatibility.
|
17
|
+
Coerce any tz-aware datetime to UTC just in case.
|
18
|
+
"""
|
19
|
+
# if we somehow got a naive datetime, convert it to UTC
|
20
|
+
if event_time.tzinfo is None:
|
21
|
+
logger.warning("Naive datetime passed to format_dt_for_core_agent")
|
22
|
+
event_time = event_time.astimezone(dt.timezone.utc)
|
23
|
+
return event_time.isoformat()
|
24
|
+
|
25
|
+
|
26
|
+
class Register(object):
|
27
|
+
__slots__ = ("app", "key", "hostname")
|
28
|
+
|
29
|
+
def __init__(self, app, key, hostname):
|
30
|
+
self.app = app
|
31
|
+
self.key = key
|
32
|
+
self.hostname = hostname
|
33
|
+
|
34
|
+
def message(self):
|
35
|
+
key_prefix = self.key[:3]
|
36
|
+
key_matches_regex = bool(key_regex.match(self.key))
|
37
|
+
logger.info(
|
38
|
+
"Registering with app=%s key_prefix=%s key_format_validated=%s host=%s"
|
39
|
+
% (self.app, key_prefix, key_matches_regex, self.hostname)
|
40
|
+
)
|
41
|
+
return {
|
42
|
+
"Register": {
|
43
|
+
"app": self.app,
|
44
|
+
"key": self.key,
|
45
|
+
"host": self.hostname,
|
46
|
+
"language": "python",
|
47
|
+
"api_version": "1.0",
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
class StartSpan(object):
|
53
|
+
__slots__ = ("timestamp", "request_id", "span_id", "parent", "operation")
|
54
|
+
|
55
|
+
def __init__(self, timestamp, request_id, span_id, parent, operation):
|
56
|
+
self.timestamp = timestamp
|
57
|
+
self.request_id = request_id
|
58
|
+
self.span_id = span_id
|
59
|
+
self.parent = parent
|
60
|
+
self.operation = operation
|
61
|
+
|
62
|
+
def message(self):
|
63
|
+
return {
|
64
|
+
"StartSpan": {
|
65
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
66
|
+
"request_id": self.request_id,
|
67
|
+
"span_id": self.span_id,
|
68
|
+
"parent_id": self.parent,
|
69
|
+
"operation": self.operation,
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
|
74
|
+
class StopSpan(object):
|
75
|
+
__slots__ = ("timestamp", "request_id", "span_id")
|
76
|
+
|
77
|
+
def __init__(self, timestamp, request_id, span_id):
|
78
|
+
self.timestamp = timestamp
|
79
|
+
self.request_id = request_id
|
80
|
+
self.span_id = span_id
|
81
|
+
|
82
|
+
def message(self):
|
83
|
+
return {
|
84
|
+
"StopSpan": {
|
85
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
86
|
+
"request_id": self.request_id,
|
87
|
+
"span_id": self.span_id,
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
|
92
|
+
class StartRequest(object):
|
93
|
+
__slots__ = ("timestamp", "request_id")
|
94
|
+
|
95
|
+
def __init__(self, timestamp, request_id):
|
96
|
+
self.timestamp = timestamp
|
97
|
+
self.request_id = request_id
|
98
|
+
|
99
|
+
def message(self):
|
100
|
+
return {
|
101
|
+
"StartRequest": {
|
102
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
103
|
+
"request_id": self.request_id,
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
|
108
|
+
class FinishRequest(object):
|
109
|
+
__slots__ = ("timestamp", "request_id")
|
110
|
+
|
111
|
+
def __init__(self, timestamp, request_id):
|
112
|
+
self.timestamp = timestamp
|
113
|
+
self.request_id = request_id
|
114
|
+
|
115
|
+
def message(self):
|
116
|
+
return {
|
117
|
+
"FinishRequest": {
|
118
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
119
|
+
"request_id": self.request_id,
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
|
124
|
+
class TagSpan(object):
|
125
|
+
__slots__ = ("timestamp", "request_id", "span_id", "tag", "value")
|
126
|
+
|
127
|
+
def __init__(self, timestamp, request_id, span_id, tag, value):
|
128
|
+
self.timestamp = timestamp
|
129
|
+
self.request_id = request_id
|
130
|
+
self.span_id = span_id
|
131
|
+
self.tag = tag
|
132
|
+
self.value = value
|
133
|
+
|
134
|
+
def message(self):
|
135
|
+
return {
|
136
|
+
"TagSpan": {
|
137
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
138
|
+
"request_id": self.request_id,
|
139
|
+
"span_id": self.span_id,
|
140
|
+
"tag": self.tag,
|
141
|
+
"value": self.value,
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
|
146
|
+
class TagRequest(object):
|
147
|
+
__slots__ = ("timestamp", "request_id", "tag", "value")
|
148
|
+
|
149
|
+
def __init__(self, timestamp, request_id, tag, value):
|
150
|
+
self.timestamp = timestamp
|
151
|
+
self.request_id = request_id
|
152
|
+
self.tag = tag
|
153
|
+
self.value = value
|
154
|
+
|
155
|
+
def message(self):
|
156
|
+
return {
|
157
|
+
"TagRequest": {
|
158
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
159
|
+
"request_id": self.request_id,
|
160
|
+
"tag": self.tag,
|
161
|
+
"value": self.value,
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
|
166
|
+
class ApplicationEvent(object):
|
167
|
+
__slots__ = ("event_type", "event_value", "source", "timestamp")
|
168
|
+
|
169
|
+
def __init__(self, event_type, event_value, source, timestamp):
|
170
|
+
self.event_type = event_type
|
171
|
+
self.event_value = event_value
|
172
|
+
self.source = source
|
173
|
+
self.timestamp = timestamp
|
174
|
+
|
175
|
+
def message(self):
|
176
|
+
return {
|
177
|
+
"ApplicationEvent": {
|
178
|
+
"timestamp": format_dt_for_core_agent(self.timestamp),
|
179
|
+
"event_type": self.event_type,
|
180
|
+
"event_value": self.event_value,
|
181
|
+
"source": self.source,
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
|
186
|
+
class BatchCommand(object):
|
187
|
+
__slots__ = ("commands",)
|
188
|
+
|
189
|
+
def __init__(self, commands):
|
190
|
+
self.commands = commands
|
191
|
+
|
192
|
+
def message(self):
|
193
|
+
return {
|
194
|
+
"BatchCommand": {
|
195
|
+
"commands": [command.message() for command in self.commands]
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
199
|
+
@classmethod
|
200
|
+
def from_tracked_request(cls, request):
|
201
|
+
# The TrackedRequest must be finished
|
202
|
+
commands = []
|
203
|
+
commands.append(
|
204
|
+
StartRequest(timestamp=request.start_time, request_id=request.request_id)
|
205
|
+
)
|
206
|
+
for key, value in iteritems(request.tags):
|
207
|
+
commands.append(
|
208
|
+
TagRequest(
|
209
|
+
timestamp=request.start_time,
|
210
|
+
request_id=request.request_id,
|
211
|
+
tag=key,
|
212
|
+
value=value,
|
213
|
+
)
|
214
|
+
)
|
215
|
+
|
216
|
+
for span in request.complete_spans:
|
217
|
+
commands.append(
|
218
|
+
StartSpan(
|
219
|
+
timestamp=span.start_time,
|
220
|
+
request_id=span.request_id,
|
221
|
+
span_id=span.span_id,
|
222
|
+
parent=span.parent,
|
223
|
+
operation=span.operation,
|
224
|
+
)
|
225
|
+
)
|
226
|
+
|
227
|
+
for key, value in iteritems(span.tags):
|
228
|
+
commands.append(
|
229
|
+
TagSpan(
|
230
|
+
timestamp=span.start_time,
|
231
|
+
request_id=request.request_id,
|
232
|
+
span_id=span.span_id,
|
233
|
+
tag=key,
|
234
|
+
value=value,
|
235
|
+
)
|
236
|
+
)
|
237
|
+
|
238
|
+
commands.append(
|
239
|
+
StopSpan(
|
240
|
+
timestamp=span.end_time,
|
241
|
+
request_id=span.request_id,
|
242
|
+
span_id=span.span_id,
|
243
|
+
)
|
244
|
+
)
|
245
|
+
|
246
|
+
commands.append(
|
247
|
+
FinishRequest(timestamp=request.end_time, request_id=request.request_id)
|
248
|
+
)
|
249
|
+
|
250
|
+
return cls(commands)
|
@@ -0,0 +1,319 @@
|
|
1
|
+
# coding=utf-8
|
2
|
+
|
3
|
+
import errno
|
4
|
+
import hashlib
|
5
|
+
import json
|
6
|
+
import logging
|
7
|
+
import os
|
8
|
+
import subprocess
|
9
|
+
import tarfile
|
10
|
+
import time
|
11
|
+
|
12
|
+
from urllib3.exceptions import HTTPError
|
13
|
+
|
14
|
+
from scout_apm.compat import urllib3_cert_pool_manager
|
15
|
+
from scout_apm.core.config import scout_config
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
CA_ALREADY_RUNNING_EXIT_CODE = 3
|
20
|
+
|
21
|
+
|
22
|
+
class CoreAgentManager(object):
|
23
|
+
def __init__(self):
|
24
|
+
self.core_agent_bin_path = None
|
25
|
+
self.core_agent_bin_version = None
|
26
|
+
self.core_agent_dir = "{}/{}".format(
|
27
|
+
scout_config.value("core_agent_dir"),
|
28
|
+
scout_config.value("core_agent_full_name"),
|
29
|
+
)
|
30
|
+
self.downloader = CoreAgentDownloader(
|
31
|
+
self.core_agent_dir, scout_config.value("core_agent_full_name")
|
32
|
+
)
|
33
|
+
|
34
|
+
def launch(self):
|
35
|
+
if not scout_config.value("core_agent_launch"):
|
36
|
+
logger.debug(
|
37
|
+
"Not attempting to launch Core Agent "
|
38
|
+
"due to 'core_agent_launch' setting."
|
39
|
+
)
|
40
|
+
return False
|
41
|
+
|
42
|
+
if not self.verify():
|
43
|
+
if not scout_config.value("core_agent_download"):
|
44
|
+
logger.debug(
|
45
|
+
"Not attempting to download Core Agent due "
|
46
|
+
"to 'core_agent_download' setting."
|
47
|
+
)
|
48
|
+
return False
|
49
|
+
|
50
|
+
self.download()
|
51
|
+
|
52
|
+
if not self.verify():
|
53
|
+
logger.debug("Failed to verify Core Agent. Not launching Core Agent.")
|
54
|
+
return False
|
55
|
+
|
56
|
+
return self.run()
|
57
|
+
|
58
|
+
def download(self):
|
59
|
+
self.downloader.download()
|
60
|
+
|
61
|
+
def run(self):
|
62
|
+
try:
|
63
|
+
with open(os.devnull) as devnull:
|
64
|
+
subprocess.check_call(
|
65
|
+
(
|
66
|
+
self.agent_binary()
|
67
|
+
+ self.daemonize_flag()
|
68
|
+
+ self.log_level()
|
69
|
+
+ self.log_file()
|
70
|
+
+ self.config_file()
|
71
|
+
+ self.socket_path()
|
72
|
+
),
|
73
|
+
close_fds=True,
|
74
|
+
stdout=devnull,
|
75
|
+
)
|
76
|
+
except subprocess.CalledProcessError as err:
|
77
|
+
if err.returncode == CA_ALREADY_RUNNING_EXIT_CODE:
|
78
|
+
# Other processes may have already started the core agent.
|
79
|
+
logger.debug("Core agent already running.")
|
80
|
+
return True
|
81
|
+
else:
|
82
|
+
logger.exception("CalledProcessError running Core Agent")
|
83
|
+
return False
|
84
|
+
except Exception:
|
85
|
+
# TODO detect failure of launch properly
|
86
|
+
logger.exception("Error running Core Agent")
|
87
|
+
return False
|
88
|
+
return True
|
89
|
+
|
90
|
+
def agent_binary(self):
|
91
|
+
return [self.core_agent_bin_path, "start"]
|
92
|
+
|
93
|
+
def daemonize_flag(self):
|
94
|
+
return ["--daemonize", "true"]
|
95
|
+
|
96
|
+
def socket_path(self):
|
97
|
+
path = get_socket_path()
|
98
|
+
if path.is_tcp:
|
99
|
+
return ["--tcp", path.tcp_address]
|
100
|
+
else:
|
101
|
+
return ["--socket", path]
|
102
|
+
|
103
|
+
def log_level(self):
|
104
|
+
# Old deprecated name "log_level"
|
105
|
+
log_level = scout_config.value("log_level")
|
106
|
+
if log_level is None:
|
107
|
+
log_level = scout_config.value("core_agent_log_level")
|
108
|
+
return ["--log-level", log_level]
|
109
|
+
|
110
|
+
def log_file(self):
|
111
|
+
# Old deprecated name "log_file"
|
112
|
+
path = scout_config.value("log_file")
|
113
|
+
if path is None:
|
114
|
+
path = scout_config.value("core_agent_log_file")
|
115
|
+
|
116
|
+
if path is not None:
|
117
|
+
return ["--log-file", path]
|
118
|
+
else:
|
119
|
+
return []
|
120
|
+
|
121
|
+
def config_file(self):
|
122
|
+
# Old deprecated name "config_file"
|
123
|
+
path = scout_config.value("config_file")
|
124
|
+
if path is None:
|
125
|
+
path = scout_config.value("core_agent_config_file")
|
126
|
+
|
127
|
+
if path is not None:
|
128
|
+
return ["--config-file", path]
|
129
|
+
else:
|
130
|
+
return []
|
131
|
+
|
132
|
+
def verify(self):
|
133
|
+
manifest = parse_manifest(self.core_agent_dir + "/manifest.json")
|
134
|
+
if manifest is None:
|
135
|
+
logger.debug(
|
136
|
+
"Core Agent verification failed: CoreAgentManifest is not valid."
|
137
|
+
)
|
138
|
+
self.core_agent_bin_path = None
|
139
|
+
self.core_agent_bin_version = None
|
140
|
+
return False
|
141
|
+
|
142
|
+
bin_path = os.path.join(self.core_agent_dir, manifest.bin_name)
|
143
|
+
if sha256_digest(bin_path) == manifest.sha256:
|
144
|
+
self.core_agent_bin_path = bin_path
|
145
|
+
self.core_agent_bin_version = manifest.bin_version
|
146
|
+
return True
|
147
|
+
else:
|
148
|
+
logger.debug("Core Agent verification failed: SHA mismatch.")
|
149
|
+
self.core_agent_bin_path = None
|
150
|
+
self.core_agent_bin_version = None
|
151
|
+
return False
|
152
|
+
|
153
|
+
|
154
|
+
class CoreAgentDownloader(object):
|
155
|
+
def __init__(self, download_destination, core_agent_full_name):
|
156
|
+
self.stale_download_secs = 120
|
157
|
+
self.destination = download_destination
|
158
|
+
self.core_agent_full_name = core_agent_full_name
|
159
|
+
self.package_location = self.destination + "/{}.tgz".format(
|
160
|
+
self.core_agent_full_name
|
161
|
+
)
|
162
|
+
self.download_lock_path = self.destination + "/download.lock"
|
163
|
+
self.download_lock_fd = None
|
164
|
+
|
165
|
+
def download(self):
|
166
|
+
self.create_core_agent_dir()
|
167
|
+
self.obtain_download_lock()
|
168
|
+
if self.download_lock_fd is not None:
|
169
|
+
try:
|
170
|
+
downloaded = self.download_package()
|
171
|
+
if downloaded:
|
172
|
+
self.untar()
|
173
|
+
else:
|
174
|
+
logger.debug("Core Agent download failed: bad http response.")
|
175
|
+
except (OSError, HTTPError):
|
176
|
+
logger.exception("Exception raised while downloading Core Agent")
|
177
|
+
finally:
|
178
|
+
self.release_download_lock()
|
179
|
+
|
180
|
+
def create_core_agent_dir(self):
|
181
|
+
try:
|
182
|
+
os.makedirs(self.destination, scout_config.core_agent_permissions())
|
183
|
+
except OSError:
|
184
|
+
pass
|
185
|
+
|
186
|
+
def obtain_download_lock(self):
|
187
|
+
self.clean_stale_download_lock()
|
188
|
+
try:
|
189
|
+
self.download_lock_fd = os.open(
|
190
|
+
self.download_lock_path,
|
191
|
+
os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NONBLOCK,
|
192
|
+
)
|
193
|
+
except OSError as exc:
|
194
|
+
logger.debug(
|
195
|
+
"Could not obtain download lock on %s",
|
196
|
+
self.download_lock_path,
|
197
|
+
exc_info=exc,
|
198
|
+
)
|
199
|
+
self.download_lock_fd = None
|
200
|
+
|
201
|
+
def clean_stale_download_lock(self):
|
202
|
+
try:
|
203
|
+
delta = time.time() - os.stat(self.download_lock_path).st_ctime
|
204
|
+
if delta > self.stale_download_secs:
|
205
|
+
logger.debug("Clearing stale download lock file.")
|
206
|
+
os.unlink(self.download_lock_path)
|
207
|
+
except OSError:
|
208
|
+
pass
|
209
|
+
|
210
|
+
def release_download_lock(self):
|
211
|
+
if self.download_lock_fd is not None:
|
212
|
+
os.unlink(self.download_lock_path)
|
213
|
+
os.close(self.download_lock_fd)
|
214
|
+
|
215
|
+
def download_package(self):
|
216
|
+
full_url = self.full_url()
|
217
|
+
logger.debug("Downloading: %s to %s", full_url, self.package_location)
|
218
|
+
http = urllib3_cert_pool_manager()
|
219
|
+
response = http.request(
|
220
|
+
"GET", full_url, preload_content=False, timeout=10.0, retries=3
|
221
|
+
)
|
222
|
+
try:
|
223
|
+
if response.status != 200:
|
224
|
+
return False
|
225
|
+
with open(self.package_location, "wb") as fp:
|
226
|
+
for chunk in response.stream():
|
227
|
+
fp.write(chunk)
|
228
|
+
finally:
|
229
|
+
response.release_conn()
|
230
|
+
http.clear()
|
231
|
+
return True
|
232
|
+
|
233
|
+
def untar(self):
|
234
|
+
t = tarfile.open(self.package_location, "r")
|
235
|
+
t.extractall(self.destination)
|
236
|
+
|
237
|
+
def full_url(self):
|
238
|
+
return "{root_url}/{core_agent_full_name}.tgz".format(
|
239
|
+
root_url=self.root_url(), core_agent_full_name=self.core_agent_full_name
|
240
|
+
)
|
241
|
+
|
242
|
+
def root_url(self):
|
243
|
+
return scout_config.value("download_url")
|
244
|
+
|
245
|
+
|
246
|
+
def parse_manifest(path):
|
247
|
+
try:
|
248
|
+
manifest_file = open(path)
|
249
|
+
except OSError as exc:
|
250
|
+
if exc.errno == errno.ENOENT:
|
251
|
+
logger.debug("Core Agent Manifest does not exist at %s", path)
|
252
|
+
else:
|
253
|
+
logger.debug("Error opening Core Agent Manifest at %s", path, exc_info=exc)
|
254
|
+
return None
|
255
|
+
|
256
|
+
try:
|
257
|
+
with manifest_file:
|
258
|
+
data = json.load(manifest_file)
|
259
|
+
logger.debug("Core Agent manifest json: %s", data)
|
260
|
+
|
261
|
+
bin_name = data["core_agent_binary"]
|
262
|
+
if not isinstance(bin_name, str):
|
263
|
+
raise TypeError("core_agent_binary should be a string.")
|
264
|
+
bin_version = data["core_agent_version"]
|
265
|
+
if not isinstance(bin_version, str):
|
266
|
+
raise TypeError("core_agent_version should be a string.")
|
267
|
+
sha256 = data["core_agent_binary_sha256"]
|
268
|
+
if not isinstance(sha256, str):
|
269
|
+
raise TypeError("core_agent_binary_sha256 should be a string.")
|
270
|
+
|
271
|
+
return CoreAgentManifest(
|
272
|
+
bin_name=bin_name,
|
273
|
+
bin_version=bin_version,
|
274
|
+
sha256=sha256,
|
275
|
+
)
|
276
|
+
|
277
|
+
# IOError => OSError on Python 3
|
278
|
+
except (KeyError, ValueError, TypeError, OSError, IOError) as exc: # noqa: B014
|
279
|
+
logger.debug("Error parsing Core Agent Manifest", exc_info=exc)
|
280
|
+
return None
|
281
|
+
|
282
|
+
|
283
|
+
class CoreAgentManifest(object):
|
284
|
+
__slots__ = ("bin_name", "bin_version", "sha256")
|
285
|
+
|
286
|
+
def __init__(self, bin_name, bin_version, sha256):
|
287
|
+
self.bin_name = bin_name
|
288
|
+
self.bin_version = bin_version
|
289
|
+
self.sha256 = sha256
|
290
|
+
|
291
|
+
|
292
|
+
def sha256_digest(filename, block_size=65536):
|
293
|
+
try:
|
294
|
+
sha256 = hashlib.sha256()
|
295
|
+
with open(filename, "rb") as f:
|
296
|
+
for block in iter(lambda: f.read(block_size), b""):
|
297
|
+
sha256.update(block)
|
298
|
+
return sha256.hexdigest()
|
299
|
+
except OSError as exc:
|
300
|
+
logger.debug("Error on digest", exc_info=exc)
|
301
|
+
return None
|
302
|
+
|
303
|
+
|
304
|
+
class SocketPath(str):
|
305
|
+
@property
|
306
|
+
def is_tcp(self):
|
307
|
+
return self.startswith("tcp://")
|
308
|
+
|
309
|
+
@property
|
310
|
+
def tcp_address(self):
|
311
|
+
return self[len("tcp://") :]
|
312
|
+
|
313
|
+
|
314
|
+
def get_socket_path():
|
315
|
+
# Old deprecated name "socket_path"
|
316
|
+
socket_path = scout_config.value("socket_path")
|
317
|
+
if socket_path is None:
|
318
|
+
socket_path = scout_config.value("core_agent_socket_path")
|
319
|
+
return SocketPath(socket_path)
|