scout-apm 3.3.0__cp38-cp38-musllinux_1_2_i686.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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)
|