scout-apm 3.3.0__cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. scout_apm/__init__.py +0 -0
  2. scout_apm/api/__init__.py +197 -0
  3. scout_apm/async_/__init__.py +1 -0
  4. scout_apm/async_/api.py +41 -0
  5. scout_apm/async_/instruments/__init__.py +0 -0
  6. scout_apm/async_/instruments/jinja2.py +13 -0
  7. scout_apm/async_/starlette.py +101 -0
  8. scout_apm/bottle.py +86 -0
  9. scout_apm/celery.py +153 -0
  10. scout_apm/compat.py +104 -0
  11. scout_apm/core/__init__.py +99 -0
  12. scout_apm/core/_objtrace.cpython-313-aarch64-linux-gnu.so +0 -0
  13. scout_apm/core/agent/__init__.py +0 -0
  14. scout_apm/core/agent/commands.py +250 -0
  15. scout_apm/core/agent/manager.py +319 -0
  16. scout_apm/core/agent/socket.py +211 -0
  17. scout_apm/core/backtrace.py +116 -0
  18. scout_apm/core/cli/__init__.py +0 -0
  19. scout_apm/core/cli/core_agent_manager.py +32 -0
  20. scout_apm/core/config.py +404 -0
  21. scout_apm/core/context.py +140 -0
  22. scout_apm/core/error.py +95 -0
  23. scout_apm/core/error_service.py +167 -0
  24. scout_apm/core/metadata.py +66 -0
  25. scout_apm/core/n_plus_one_tracker.py +41 -0
  26. scout_apm/core/objtrace.py +24 -0
  27. scout_apm/core/platform_detection.py +66 -0
  28. scout_apm/core/queue_time.py +99 -0
  29. scout_apm/core/sampler.py +149 -0
  30. scout_apm/core/samplers/__init__.py +0 -0
  31. scout_apm/core/samplers/cpu.py +76 -0
  32. scout_apm/core/samplers/memory.py +23 -0
  33. scout_apm/core/samplers/thread.py +41 -0
  34. scout_apm/core/stacktracer.py +30 -0
  35. scout_apm/core/threading.py +56 -0
  36. scout_apm/core/tracked_request.py +328 -0
  37. scout_apm/core/web_requests.py +167 -0
  38. scout_apm/django/__init__.py +7 -0
  39. scout_apm/django/apps.py +137 -0
  40. scout_apm/django/instruments/__init__.py +0 -0
  41. scout_apm/django/instruments/huey.py +30 -0
  42. scout_apm/django/instruments/sql.py +140 -0
  43. scout_apm/django/instruments/template.py +35 -0
  44. scout_apm/django/middleware.py +211 -0
  45. scout_apm/django/request.py +144 -0
  46. scout_apm/dramatiq.py +42 -0
  47. scout_apm/falcon.py +142 -0
  48. scout_apm/flask/__init__.py +118 -0
  49. scout_apm/flask/sqlalchemy.py +28 -0
  50. scout_apm/huey.py +54 -0
  51. scout_apm/hug.py +40 -0
  52. scout_apm/instruments/__init__.py +21 -0
  53. scout_apm/instruments/elasticsearch.py +263 -0
  54. scout_apm/instruments/jinja2.py +127 -0
  55. scout_apm/instruments/pymongo.py +105 -0
  56. scout_apm/instruments/redis.py +77 -0
  57. scout_apm/instruments/urllib3.py +80 -0
  58. scout_apm/rq.py +85 -0
  59. scout_apm/sqlalchemy.py +38 -0
  60. scout_apm-3.3.0.dist-info/LICENSE +21 -0
  61. scout_apm-3.3.0.dist-info/METADATA +94 -0
  62. scout_apm-3.3.0.dist-info/RECORD +65 -0
  63. scout_apm-3.3.0.dist-info/WHEEL +7 -0
  64. scout_apm-3.3.0.dist-info/entry_points.txt +2 -0
  65. 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
+ )
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)