We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add ARM support
We've added **linux** ARM support (aarch64) for the core agent binary in version 1.3.1. We need to add ARM architecture detection on linux for the Python agent.

On AWS graviton instance:

```
ubuntu@ip-172-31-58-208:~$ uname -a
Linux ip-172-31-58-208 5.4.0-1045-aws #47-Ubuntu SMP Tue Apr 13 07:04:23 UTC 2021 aarch64 aarch64 aarch64 GNU/Linux
```

Note: I didn't try 3.9
Python 3.8
```
ubuntu@ip-172-31-58-208:~$ python3 --version
Python 3.8.10
ubuntu@ip-172-31-58-208:~$ python3
Python 3.8.10 (default, Jun  2 2021, 10:49:15)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import platform;
>>> platform.machine()
'aarch64'
>>>
```

Python 2.7:
```
ubuntu@ip-172-31-58-208:~$ python --version
Python 2.7.18

ubuntu@ip-172-31-58-208:~$ python
Python 2.7.18 (default, Mar  8 2021, 13:02:45)
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import platform;
>>> platform.machine()
'aarch64'
>>>
```

The core agent 1.3.1 builds available:
```
core-agent-v1.3.1-aarch64-unknown-linux-gnu
core-agent-v1.3.1-aarch64-unknown-linux-musl
core-agent-v1.3.1-i686-unknown-linux-gnu
core-agent-v1.3.1-i686-unknown-linux-musl
core-agent-v1.3.1-x86_64-apple-darwin
core-agent-v1.3.1-x86_64-unknown-linux-gnu
core-agent-v1.3.1-x86_64-unknown-linux-musl

```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/core/platform_detection.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import platform
5 
6 
7 def is_valid_triple(triple):
8     values = triple.split("-", 1)
9     return (
10         len(values) == 2
11         and values[0] in ("i686", "x86_64", "unknown")
12         and values[1]
13         in ("unknown-linux-gnu", "unknown-linux-musl", "apple-darwin", "unknown")
14     )
15 
16 
17 def get_triple():
18     return "{arch}-{platform}".format(arch=get_arch(), platform=get_platform())
19 
20 
21 def get_arch():
22     """
23     What CPU are we on?
24     """
25     arch = platform.machine()
26     if arch == "i686":
27         return "i686"
28     elif arch == "x86_64":
29         return "x86_64"
30     else:
31         return "unknown"
32 
33 
34 def get_platform():
35     """
36     What Operating System (and sub-system like glibc / musl)
37     """
38     system_name = platform.system()
39     if system_name == "Linux":
40         # Previously we'd use either "-gnu" or "-musl" indicate which version
41         # of libc we were built against. We now default to musl since it
42         # reliably works on all platforms.
43         return "unknown-linux-musl"
44     elif system_name == "Darwin":
45         return "apple-darwin"
46     else:
47         return "unknown"
48
```

Path: `src/scout_apm/core/config.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import logging
5 import os
6 import re
7 import warnings
8 
9 from scout_apm.compat import string_type
10 from scout_apm.core import platform_detection
11 
12 logger = logging.getLogger(__name__)
13 
14 key_regex = re.compile(r"[a-zA-Z0-9]{16}")
15 
16 
17 class ScoutConfig(object):
18     """
19     Configuration object for the ScoutApm agent.
20 
21     Contains a list of configuration "layers". When a configuration key is
22     looked up, each layer is asked in turn if it knows the value. The first one
23     to answer affirmatively returns the value.
24     """
25 
26     def __init__(self):
27         self.layers = [
28             Env(),
29             Python(),
30             Derived(self),
31             Defaults(),
32             Null(),
33         ]
34 
35     def value(self, key):
36         value = self.locate_layer_for_key(key).value(key)
37         if key in CONVERSIONS:
38             return CONVERSIONS[key](value)
39         return value
40 
41     def locate_layer_for_key(self, key):
42         for layer in self.layers:
43             if layer.has_config(key):
44                 return layer
45 
46         # Should be unreachable because Null returns None for all keys.
47         raise ValueError("key {!r} not found in any layer".format(key))
48 
49     def log(self):
50         logger.debug("Configuration Loaded:")
51         for key in self.known_keys:
52             if key in self.secret_keys:
53                 continue
54 
55             layer = self.locate_layer_for_key(key)
56             logger.debug(
57                 "%-9s: %s = %s",
58                 layer.__class__.__name__,
59                 key,
60                 layer.value(key),
61             )
62 
63     known_keys = [
64         "app_server",
65         "application_root",
66         "collect_remote_ip",
67         "core_agent_config_file",
68         "core_agent_dir",
69         "core_agent_download",
70         "core_agent_launch",
71         "core_agent_log_file",
72         "core_agent_log_level",
73         "core_agent_permissions",
74         "core_agent_socket_path",
75         "core_agent_version",
76         "disabled_instruments",
77         "download_url",
78         "framework",
79         "framework_version",
80         "hostname",
81         "ignore",
82         "key",
83         "log_level",
84         "monitor",
85         "name",
86         "revision_sha",
87         "scm_subdirectory",
88         "shutdown_message_enabled",
89         "shutdown_timeout_seconds",
90     ]
91 
92     secret_keys = {"key"}
93 
94     def core_agent_permissions(self):
95         try:
96             return int(str(self.value("core_agent_permissions")), 8)
97         except ValueError:
98             logger.exception(
99                 "Invalid core_agent_permissions value, using default of 0o700"
100             )
101             return 0o700
102 
103     @classmethod
104     def set(cls, **kwargs):
105         """
106         Sets a configuration value for the Scout agent. Values set here will
107         not override values set in ENV.
108         """
109         for key, value in kwargs.items():
110             SCOUT_PYTHON_VALUES[key] = value
111 
112     @classmethod
113     def unset(cls, *keys):
114         """
115         Removes a configuration value for the Scout agent.
116         """
117         for key in keys:
118             SCOUT_PYTHON_VALUES.pop(key, None)
119 
120     @classmethod
121     def reset_all(cls):
122         """
123         Remove all configuration settings set via `ScoutConfig.set(...)`.
124 
125         This is meant for use in testing.
126         """
127         SCOUT_PYTHON_VALUES.clear()
128 
129 
130 # Module-level data, the ScoutConfig.set(key="value") adds to this
131 SCOUT_PYTHON_VALUES = {}
132 
133 
134 class Python(object):
135     """
136     A configuration overlay that lets other parts of python set values.
137     """
138 
139     def has_config(self, key):
140         return key in SCOUT_PYTHON_VALUES
141 
142     def value(self, key):
143         return SCOUT_PYTHON_VALUES[key]
144 
145 
146 class Env(object):
147     """
148     Reads configuration from environment by prefixing the key
149     requested with "SCOUT_"
150 
151     Example: the `key` config looks for SCOUT_KEY
152     environment variable
153     """
154 
155     def has_config(self, key):
156         env_key = self.modify_key(key)
157         return env_key in os.environ
158 
159     def value(self, key):
160         env_key = self.modify_key(key)
161         return os.environ[env_key]
162 
163     def modify_key(self, key):
164         env_key = ("SCOUT_" + key).upper()
165         return env_key
166 
167 
168 class Derived(object):
169     """
170     A configuration overlay that calculates from other values.
171     """
172 
173     def __init__(self, config):
174         """
175         config argument is the overall ScoutConfig var, so we can lookup the
176         components of the derived info.
177         """
178         self.config = config
179 
180     def has_config(self, key):
181         return self.lookup_func(key) is not None
182 
183     def value(self, key):
184         return self.lookup_func(key)()
185 
186     def lookup_func(self, key):
187         """
188         Returns the derive_#{key} function, or None if it isn't defined
189         """
190         func_name = "derive_" + key
191         return getattr(self, func_name, None)
192 
193     def derive_core_agent_full_name(self):
194         triple = self.config.value("core_agent_triple")
195         if not platform_detection.is_valid_triple(triple):
196             warnings.warn("Invalid value for core_agent_triple: {}".format(triple))
197         return "{name}-{version}-{triple}".format(
198             name="scout_apm_core",
199             version=self.config.value("core_agent_version"),
200             triple=triple,
201         )
202 
203     def derive_core_agent_triple(self):
204         return platform_detection.get_triple()
205 
206 
207 class Defaults(object):
208     """
209     Provides default values for important configurations
210     """
211 
212     def __init__(self):
213         self.defaults = {
214             "app_server": "",
215             "application_root": os.getcwd(),
216             "collect_remote_ip": True,
217             "core_agent_dir": "/tmp/scout_apm_core",
218             "core_agent_download": True,
219             "core_agent_launch": True,
220             "core_agent_log_level": "info",
221             "core_agent_permissions": 700,
222             "core_agent_socket_path": "tcp://127.0.0.1:6590",
223             "core_agent_version": "v1.3.0",  # can be an exact tag name, or 'latest'
224             "disabled_instruments": [],
225             "download_url": "https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release",  # noqa: B950
226             "errors_batch_size": 5,
227             "errors_enabled": True,
228             "errors_ignored_exceptions": (),
229             "errors_host": "https://errors.scoutapm.com",
230             "framework": "",
231             "framework_version": "",
232             "hostname": None,
233             "key": "",
234             "monitor": False,
235             "name": "Python App",
236             "revision_sha": self._git_revision_sha(),
237             "scm_subdirectory": "",
238             "shutdown_message_enabled": True,
239             "shutdown_timeout_seconds": 2.0,
240             "uri_reporting": "filtered_params",
241         }
242 
243     def _git_revision_sha(self):
244         # N.B. The environment variable SCOUT_REVISION_SHA may also be used,
245         # but that will be picked up by Env
246         return os.environ.get("HEROKU_SLUG_COMMIT", "")
247 
248     def has_config(self, key):
249         return key in self.defaults
250 
251     def value(self, key):
252         return self.defaults[key]
253 
254 
255 class Null(object):
256     """
257     Always answers that a key is present, but the value is None
258 
259     Used as the last step of the layered configuration.
260     """
261 
262     def has_config(self, key):
263         return True
264 
265     def value(self, key):
266         return None
267 
268 
269 def convert_to_bool(value):
270     if isinstance(value, bool):
271         return value
272     if isinstance(value, string_type):
273         return value.lower() in ("yes", "true", "t", "1")
274     # Unknown type - default to false?
275     return False
276 
277 
278 def convert_to_float(value):
279     try:
280         return float(value)
281     except ValueError:
282         return 0.0
283 
284 
285 def convert_to_list(value):
286     if isinstance(value, list):
287         return value
288     if isinstance(value, tuple):
289         return list(value)
290     if isinstance(value, string_type):
291         # Split on commas
292         return [item.strip() for item in value.split(",") if item]
293     # Unknown type - default to empty?
294     return []
295 
296 
297 CONVERSIONS = {
298     "collect_remote_ip": convert_to_bool,
299     "core_agent_download": convert_to_bool,
300     "core_agent_launch": convert_to_bool,
301     "disabled_instruments": convert_to_list,
302     "ignore": convert_to_list,
303     "monitor": convert_to_bool,
304     "shutdown_message_enabled": convert_to_bool,
305     "shutdown_timeout_seconds": convert_to_float,
306 }
307 
308 
309 scout_config = ScoutConfig()
310
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/scout_apm/core/config.py b/src/scout_apm/core/config.py
--- a/src/scout_apm/core/config.py
+++ b/src/scout_apm/core/config.py
@@ -220,7 +220,7 @@
             "core_agent_log_level": "info",
             "core_agent_permissions": 700,
             "core_agent_socket_path": "tcp://127.0.0.1:6590",
-            "core_agent_version": "v1.3.0",  # can be an exact tag name, or 'latest'
+            "core_agent_version": "v1.3.1",  # can be an exact tag name, or 'latest'
             "disabled_instruments": [],
             "download_url": "https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release",  # noqa: B950
             "errors_batch_size": 5,
diff --git a/src/scout_apm/core/platform_detection.py b/src/scout_apm/core/platform_detection.py
--- a/src/scout_apm/core/platform_detection.py
+++ b/src/scout_apm/core/platform_detection.py
@@ -8,14 +8,28 @@
     values = triple.split("-", 1)
     return (
         len(values) == 2
-        and values[0] in ("i686", "x86_64", "unknown")
+        and values[0] in ("i686", "x86_64", "aarch64", "unknown")
         and values[1]
         in ("unknown-linux-gnu", "unknown-linux-musl", "apple-darwin", "unknown")
+        # Validate _apple_darwin_aarch64_override was applied.
+        and triple != "aarch64-apple-darwin"
     )
 
 
+def _apple_darwin_aarch64_override(triple):
+    """
+    If using M1 ARM64 machine, still use x86_64.
+    See https://github.com/scoutapp/scout_apm_python/issues/683
+    """
+    if triple == "aarch64-apple-darwin":
+        return "x86_64-apple-darwin"
+    return triple
+
+
 def get_triple():
-    return "{arch}-{platform}".format(arch=get_arch(), platform=get_platform())
+    return _apple_darwin_aarch64_override(
+        "{arch}-{platform}".format(arch=get_arch(), platform=get_platform())
+    )
 
 
 def get_arch():
@@ -27,6 +41,8 @@
         return "i686"
     elif arch == "x86_64":
         return "x86_64"
+    elif arch == "aarch64":
+        return "aarch64"
     else:
         return "unknown"

{"golden_diff": "diff --git a/src/scout_apm/core/config.py b/src/scout_apm/core/config.py\n--- a/src/scout_apm/core/config.py\n+++ b/src/scout_apm/core/config.py\n@@ -220,7 +220,7 @@\n             \"core_agent_log_level\": \"info\",\n             \"core_agent_permissions\": 700,\n             \"core_agent_socket_path\": \"tcp://127.0.0.1:6590\",\n-            \"core_agent_version\": \"v1.3.0\",  # can be an exact tag name, or 'latest'\n+            \"core_agent_version\": \"v1.3.1\",  # can be an exact tag name, or 'latest'\n             \"disabled_instruments\": [],\n             \"download_url\": \"https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release\",  # noqa: B950\n             \"errors_batch_size\": 5,\ndiff --git a/src/scout_apm/core/platform_detection.py b/src/scout_apm/core/platform_detection.py\n--- a/src/scout_apm/core/platform_detection.py\n+++ b/src/scout_apm/core/platform_detection.py\n@@ -8,14 +8,28 @@\n     values = triple.split(\"-\", 1)\n     return (\n         len(values) == 2\n-        and values[0] in (\"i686\", \"x86_64\", \"unknown\")\n+        and values[0] in (\"i686\", \"x86_64\", \"aarch64\", \"unknown\")\n         and values[1]\n         in (\"unknown-linux-gnu\", \"unknown-linux-musl\", \"apple-darwin\", \"unknown\")\n+        # Validate _apple_darwin_aarch64_override was applied.\n+        and triple != \"aarch64-apple-darwin\"\n     )\n \n \n+def _apple_darwin_aarch64_override(triple):\n+    \"\"\"\n+    If using M1 ARM64 machine, still use x86_64.\n+    See https://github.com/scoutapp/scout_apm_python/issues/683\n+    \"\"\"\n+    if triple == \"aarch64-apple-darwin\":\n+        return \"x86_64-apple-darwin\"\n+    return triple\n+\n+\n def get_triple():\n-    return \"{arch}-{platform}\".format(arch=get_arch(), platform=get_platform())\n+    return _apple_darwin_aarch64_override(\n+        \"{arch}-{platform}\".format(arch=get_arch(), platform=get_platform())\n+    )\n \n \n def get_arch():\n@@ -27,6 +41,8 @@\n         return \"i686\"\n     elif arch == \"x86_64\":\n         return \"x86_64\"\n+    elif arch == \"aarch64\":\n+        return \"aarch64\"\n     else:\n         return \"unknown\"\n", "issue": "Add ARM support\nWe've added **linux** ARM support (aarch64) for the core agent binary in version 1.3.1. We need to add ARM architecture detection on linux for the Python agent.\r\n\r\nOn AWS graviton instance:\r\n\r\n```\r\nubuntu@ip-172-31-58-208:~$ uname -a\r\nLinux ip-172-31-58-208 5.4.0-1045-aws #47-Ubuntu SMP Tue Apr 13 07:04:23 UTC 2021 aarch64 aarch64 aarch64 GNU/Linux\r\n```\r\n\r\nNote: I didn't try 3.9\r\nPython 3.8\r\n```\r\nubuntu@ip-172-31-58-208:~$ python3 --version\r\nPython 3.8.10\r\nubuntu@ip-172-31-58-208:~$ python3\r\nPython 3.8.10 (default, Jun  2 2021, 10:49:15)\r\n[GCC 9.4.0] on linux\r\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\r\n>>> import platform;\r\n>>> platform.machine()\r\n'aarch64'\r\n>>>\r\n```\r\n\r\nPython 2.7:\r\n```\r\nubuntu@ip-172-31-58-208:~$ python --version\r\nPython 2.7.18\r\n\r\nubuntu@ip-172-31-58-208:~$ python\r\nPython 2.7.18 (default, Mar  8 2021, 13:02:45)\r\n[GCC 9.3.0] on linux2\r\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\r\n>>> import platform;\r\n>>> platform.machine()\r\n'aarch64'\r\n>>>\r\n```\r\n\r\nThe core agent 1.3.1 builds available:\r\n```\r\ncore-agent-v1.3.1-aarch64-unknown-linux-gnu\r\ncore-agent-v1.3.1-aarch64-unknown-linux-musl\r\ncore-agent-v1.3.1-i686-unknown-linux-gnu\r\ncore-agent-v1.3.1-i686-unknown-linux-musl\r\ncore-agent-v1.3.1-x86_64-apple-darwin\r\ncore-agent-v1.3.1-x86_64-unknown-linux-gnu\r\ncore-agent-v1.3.1-x86_64-unknown-linux-musl\r\n\r\n```\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport platform\n\n\ndef is_valid_triple(triple):\n    values = triple.split(\"-\", 1)\n    return (\n        len(values) == 2\n        and values[0] in (\"i686\", \"x86_64\", \"unknown\")\n        and values[1]\n        in (\"unknown-linux-gnu\", \"unknown-linux-musl\", \"apple-darwin\", \"unknown\")\n    )\n\n\ndef get_triple():\n    return \"{arch}-{platform}\".format(arch=get_arch(), platform=get_platform())\n\n\ndef get_arch():\n    \"\"\"\n    What CPU are we on?\n    \"\"\"\n    arch = platform.machine()\n    if arch == \"i686\":\n        return \"i686\"\n    elif arch == \"x86_64\":\n        return \"x86_64\"\n    else:\n        return \"unknown\"\n\n\ndef get_platform():\n    \"\"\"\n    What Operating System (and sub-system like glibc / musl)\n    \"\"\"\n    system_name = platform.system()\n    if system_name == \"Linux\":\n        # Previously we'd use either \"-gnu\" or \"-musl\" indicate which version\n        # of libc we were built against. We now default to musl since it\n        # reliably works on all platforms.\n        return \"unknown-linux-musl\"\n    elif system_name == \"Darwin\":\n        return \"apple-darwin\"\n    else:\n        return \"unknown\"\n", "path": "src/scout_apm/core/platform_detection.py"}, {"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nimport os\nimport re\nimport warnings\n\nfrom scout_apm.compat import string_type\nfrom scout_apm.core import platform_detection\n\nlogger = logging.getLogger(__name__)\n\nkey_regex = re.compile(r\"[a-zA-Z0-9]{16}\")\n\n\nclass ScoutConfig(object):\n    \"\"\"\n    Configuration object for the ScoutApm agent.\n\n    Contains a list of configuration \"layers\". When a configuration key is\n    looked up, each layer is asked in turn if it knows the value. The first one\n    to answer affirmatively returns the value.\n    \"\"\"\n\n    def __init__(self):\n        self.layers = [\n            Env(),\n            Python(),\n            Derived(self),\n            Defaults(),\n            Null(),\n        ]\n\n    def value(self, key):\n        value = self.locate_layer_for_key(key).value(key)\n        if key in CONVERSIONS:\n            return CONVERSIONS[key](value)\n        return value\n\n    def locate_layer_for_key(self, key):\n        for layer in self.layers:\n            if layer.has_config(key):\n                return layer\n\n        # Should be unreachable because Null returns None for all keys.\n        raise ValueError(\"key {!r} not found in any layer\".format(key))\n\n    def log(self):\n        logger.debug(\"Configuration Loaded:\")\n        for key in self.known_keys:\n            if key in self.secret_keys:\n                continue\n\n            layer = self.locate_layer_for_key(key)\n            logger.debug(\n                \"%-9s: %s = %s\",\n                layer.__class__.__name__,\n                key,\n                layer.value(key),\n            )\n\n    known_keys = [\n        \"app_server\",\n        \"application_root\",\n        \"collect_remote_ip\",\n        \"core_agent_config_file\",\n        \"core_agent_dir\",\n        \"core_agent_download\",\n        \"core_agent_launch\",\n        \"core_agent_log_file\",\n        \"core_agent_log_level\",\n        \"core_agent_permissions\",\n        \"core_agent_socket_path\",\n        \"core_agent_version\",\n        \"disabled_instruments\",\n        \"download_url\",\n        \"framework\",\n        \"framework_version\",\n        \"hostname\",\n        \"ignore\",\n        \"key\",\n        \"log_level\",\n        \"monitor\",\n        \"name\",\n        \"revision_sha\",\n        \"scm_subdirectory\",\n        \"shutdown_message_enabled\",\n        \"shutdown_timeout_seconds\",\n    ]\n\n    secret_keys = {\"key\"}\n\n    def core_agent_permissions(self):\n        try:\n            return int(str(self.value(\"core_agent_permissions\")), 8)\n        except ValueError:\n            logger.exception(\n                \"Invalid core_agent_permissions value, using default of 0o700\"\n            )\n            return 0o700\n\n    @classmethod\n    def set(cls, **kwargs):\n        \"\"\"\n        Sets a configuration value for the Scout agent. Values set here will\n        not override values set in ENV.\n        \"\"\"\n        for key, value in kwargs.items():\n            SCOUT_PYTHON_VALUES[key] = value\n\n    @classmethod\n    def unset(cls, *keys):\n        \"\"\"\n        Removes a configuration value for the Scout agent.\n        \"\"\"\n        for key in keys:\n            SCOUT_PYTHON_VALUES.pop(key, None)\n\n    @classmethod\n    def reset_all(cls):\n        \"\"\"\n        Remove all configuration settings set via `ScoutConfig.set(...)`.\n\n        This is meant for use in testing.\n        \"\"\"\n        SCOUT_PYTHON_VALUES.clear()\n\n\n# Module-level data, the ScoutConfig.set(key=\"value\") adds to this\nSCOUT_PYTHON_VALUES = {}\n\n\nclass Python(object):\n    \"\"\"\n    A configuration overlay that lets other parts of python set values.\n    \"\"\"\n\n    def has_config(self, key):\n        return key in SCOUT_PYTHON_VALUES\n\n    def value(self, key):\n        return SCOUT_PYTHON_VALUES[key]\n\n\nclass Env(object):\n    \"\"\"\n    Reads configuration from environment by prefixing the key\n    requested with \"SCOUT_\"\n\n    Example: the `key` config looks for SCOUT_KEY\n    environment variable\n    \"\"\"\n\n    def has_config(self, key):\n        env_key = self.modify_key(key)\n        return env_key in os.environ\n\n    def value(self, key):\n        env_key = self.modify_key(key)\n        return os.environ[env_key]\n\n    def modify_key(self, key):\n        env_key = (\"SCOUT_\" + key).upper()\n        return env_key\n\n\nclass Derived(object):\n    \"\"\"\n    A configuration overlay that calculates from other values.\n    \"\"\"\n\n    def __init__(self, config):\n        \"\"\"\n        config argument is the overall ScoutConfig var, so we can lookup the\n        components of the derived info.\n        \"\"\"\n        self.config = config\n\n    def has_config(self, key):\n        return self.lookup_func(key) is not None\n\n    def value(self, key):\n        return self.lookup_func(key)()\n\n    def lookup_func(self, key):\n        \"\"\"\n        Returns the derive_#{key} function, or None if it isn't defined\n        \"\"\"\n        func_name = \"derive_\" + key\n        return getattr(self, func_name, None)\n\n    def derive_core_agent_full_name(self):\n        triple = self.config.value(\"core_agent_triple\")\n        if not platform_detection.is_valid_triple(triple):\n            warnings.warn(\"Invalid value for core_agent_triple: {}\".format(triple))\n        return \"{name}-{version}-{triple}\".format(\n            name=\"scout_apm_core\",\n            version=self.config.value(\"core_agent_version\"),\n            triple=triple,\n        )\n\n    def derive_core_agent_triple(self):\n        return platform_detection.get_triple()\n\n\nclass Defaults(object):\n    \"\"\"\n    Provides default values for important configurations\n    \"\"\"\n\n    def __init__(self):\n        self.defaults = {\n            \"app_server\": \"\",\n            \"application_root\": os.getcwd(),\n            \"collect_remote_ip\": True,\n            \"core_agent_dir\": \"/tmp/scout_apm_core\",\n            \"core_agent_download\": True,\n            \"core_agent_launch\": True,\n            \"core_agent_log_level\": \"info\",\n            \"core_agent_permissions\": 700,\n            \"core_agent_socket_path\": \"tcp://127.0.0.1:6590\",\n            \"core_agent_version\": \"v1.3.0\",  # can be an exact tag name, or 'latest'\n            \"disabled_instruments\": [],\n            \"download_url\": \"https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release\",  # noqa: B950\n            \"errors_batch_size\": 5,\n            \"errors_enabled\": True,\n            \"errors_ignored_exceptions\": (),\n            \"errors_host\": \"https://errors.scoutapm.com\",\n            \"framework\": \"\",\n            \"framework_version\": \"\",\n            \"hostname\": None,\n            \"key\": \"\",\n            \"monitor\": False,\n            \"name\": \"Python App\",\n            \"revision_sha\": self._git_revision_sha(),\n            \"scm_subdirectory\": \"\",\n            \"shutdown_message_enabled\": True,\n            \"shutdown_timeout_seconds\": 2.0,\n            \"uri_reporting\": \"filtered_params\",\n        }\n\n    def _git_revision_sha(self):\n        # N.B. The environment variable SCOUT_REVISION_SHA may also be used,\n        # but that will be picked up by Env\n        return os.environ.get(\"HEROKU_SLUG_COMMIT\", \"\")\n\n    def has_config(self, key):\n        return key in self.defaults\n\n    def value(self, key):\n        return self.defaults[key]\n\n\nclass Null(object):\n    \"\"\"\n    Always answers that a key is present, but the value is None\n\n    Used as the last step of the layered configuration.\n    \"\"\"\n\n    def has_config(self, key):\n        return True\n\n    def value(self, key):\n        return None\n\n\ndef convert_to_bool(value):\n    if isinstance(value, bool):\n        return value\n    if isinstance(value, string_type):\n        return value.lower() in (\"yes\", \"true\", \"t\", \"1\")\n    # Unknown type - default to false?\n    return False\n\n\ndef convert_to_float(value):\n    try:\n        return float(value)\n    except ValueError:\n        return 0.0\n\n\ndef convert_to_list(value):\n    if isinstance(value, list):\n        return value\n    if isinstance(value, tuple):\n        return list(value)\n    if isinstance(value, string_type):\n        # Split on commas\n        return [item.strip() for item in value.split(\",\") if item]\n    # Unknown type - default to empty?\n    return []\n\n\nCONVERSIONS = {\n    \"collect_remote_ip\": convert_to_bool,\n    \"core_agent_download\": convert_to_bool,\n    \"core_agent_launch\": convert_to_bool,\n    \"disabled_instruments\": convert_to_list,\n    \"ignore\": convert_to_list,\n    \"monitor\": convert_to_bool,\n    \"shutdown_message_enabled\": convert_to_bool,\n    \"shutdown_timeout_seconds\": convert_to_float,\n}\n\n\nscout_config = ScoutConfig()\n", "path": "src/scout_apm/core/config.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport platform\n\n\ndef is_valid_triple(triple):\n    values = triple.split(\"-\", 1)\n    return (\n        len(values) == 2\n        and values[0] in (\"i686\", \"x86_64\", \"aarch64\", \"unknown\")\n        and values[1]\n        in (\"unknown-linux-gnu\", \"unknown-linux-musl\", \"apple-darwin\", \"unknown\")\n        # Validate _apple_darwin_aarch64_override was applied.\n        and triple != \"aarch64-apple-darwin\"\n    )\n\n\ndef _apple_darwin_aarch64_override(triple):\n    \"\"\"\n    If using M1 ARM64 machine, still use x86_64.\n    See https://github.com/scoutapp/scout_apm_python/issues/683\n    \"\"\"\n    if triple == \"aarch64-apple-darwin\":\n        return \"x86_64-apple-darwin\"\n    return triple\n\n\ndef get_triple():\n    return _apple_darwin_aarch64_override(\n        \"{arch}-{platform}\".format(arch=get_arch(), platform=get_platform())\n    )\n\n\ndef get_arch():\n    \"\"\"\n    What CPU are we on?\n    \"\"\"\n    arch = platform.machine()\n    if arch == \"i686\":\n        return \"i686\"\n    elif arch == \"x86_64\":\n        return \"x86_64\"\n    elif arch == \"aarch64\":\n        return \"aarch64\"\n    else:\n        return \"unknown\"\n\n\ndef get_platform():\n    \"\"\"\n    What Operating System (and sub-system like glibc / musl)\n    \"\"\"\n    system_name = platform.system()\n    if system_name == \"Linux\":\n        # Previously we'd use either \"-gnu\" or \"-musl\" indicate which version\n        # of libc we were built against. We now default to musl since it\n        # reliably works on all platforms.\n        return \"unknown-linux-musl\"\n    elif system_name == \"Darwin\":\n        return \"apple-darwin\"\n    else:\n        return \"unknown\"\n", "path": "src/scout_apm/core/platform_detection.py"}, {"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nimport os\nimport re\nimport warnings\n\nfrom scout_apm.compat import string_type\nfrom scout_apm.core import platform_detection\n\nlogger = logging.getLogger(__name__)\n\nkey_regex = re.compile(r\"[a-zA-Z0-9]{16}\")\n\n\nclass ScoutConfig(object):\n    \"\"\"\n    Configuration object for the ScoutApm agent.\n\n    Contains a list of configuration \"layers\". When a configuration key is\n    looked up, each layer is asked in turn if it knows the value. The first one\n    to answer affirmatively returns the value.\n    \"\"\"\n\n    def __init__(self):\n        self.layers = [\n            Env(),\n            Python(),\n            Derived(self),\n            Defaults(),\n            Null(),\n        ]\n\n    def value(self, key):\n        value = self.locate_layer_for_key(key).value(key)\n        if key in CONVERSIONS:\n            return CONVERSIONS[key](value)\n        return value\n\n    def locate_layer_for_key(self, key):\n        for layer in self.layers:\n            if layer.has_config(key):\n                return layer\n\n        # Should be unreachable because Null returns None for all keys.\n        raise ValueError(\"key {!r} not found in any layer\".format(key))\n\n    def log(self):\n        logger.debug(\"Configuration Loaded:\")\n        for key in self.known_keys:\n            if key in self.secret_keys:\n                continue\n\n            layer = self.locate_layer_for_key(key)\n            logger.debug(\n                \"%-9s: %s = %s\",\n                layer.__class__.__name__,\n                key,\n                layer.value(key),\n            )\n\n    known_keys = [\n        \"app_server\",\n        \"application_root\",\n        \"collect_remote_ip\",\n        \"core_agent_config_file\",\n        \"core_agent_dir\",\n        \"core_agent_download\",\n        \"core_agent_launch\",\n        \"core_agent_log_file\",\n        \"core_agent_log_level\",\n        \"core_agent_permissions\",\n        \"core_agent_socket_path\",\n        \"core_agent_version\",\n        \"disabled_instruments\",\n        \"download_url\",\n        \"framework\",\n        \"framework_version\",\n        \"hostname\",\n        \"ignore\",\n        \"key\",\n        \"log_level\",\n        \"monitor\",\n        \"name\",\n        \"revision_sha\",\n        \"scm_subdirectory\",\n        \"shutdown_message_enabled\",\n        \"shutdown_timeout_seconds\",\n    ]\n\n    secret_keys = {\"key\"}\n\n    def core_agent_permissions(self):\n        try:\n            return int(str(self.value(\"core_agent_permissions\")), 8)\n        except ValueError:\n            logger.exception(\n                \"Invalid core_agent_permissions value, using default of 0o700\"\n            )\n            return 0o700\n\n    @classmethod\n    def set(cls, **kwargs):\n        \"\"\"\n        Sets a configuration value for the Scout agent. Values set here will\n        not override values set in ENV.\n        \"\"\"\n        for key, value in kwargs.items():\n            SCOUT_PYTHON_VALUES[key] = value\n\n    @classmethod\n    def unset(cls, *keys):\n        \"\"\"\n        Removes a configuration value for the Scout agent.\n        \"\"\"\n        for key in keys:\n            SCOUT_PYTHON_VALUES.pop(key, None)\n\n    @classmethod\n    def reset_all(cls):\n        \"\"\"\n        Remove all configuration settings set via `ScoutConfig.set(...)`.\n\n        This is meant for use in testing.\n        \"\"\"\n        SCOUT_PYTHON_VALUES.clear()\n\n\n# Module-level data, the ScoutConfig.set(key=\"value\") adds to this\nSCOUT_PYTHON_VALUES = {}\n\n\nclass Python(object):\n    \"\"\"\n    A configuration overlay that lets other parts of python set values.\n    \"\"\"\n\n    def has_config(self, key):\n        return key in SCOUT_PYTHON_VALUES\n\n    def value(self, key):\n        return SCOUT_PYTHON_VALUES[key]\n\n\nclass Env(object):\n    \"\"\"\n    Reads configuration from environment by prefixing the key\n    requested with \"SCOUT_\"\n\n    Example: the `key` config looks for SCOUT_KEY\n    environment variable\n    \"\"\"\n\n    def has_config(self, key):\n        env_key = self.modify_key(key)\n        return env_key in os.environ\n\n    def value(self, key):\n        env_key = self.modify_key(key)\n        return os.environ[env_key]\n\n    def modify_key(self, key):\n        env_key = (\"SCOUT_\" + key).upper()\n        return env_key\n\n\nclass Derived(object):\n    \"\"\"\n    A configuration overlay that calculates from other values.\n    \"\"\"\n\n    def __init__(self, config):\n        \"\"\"\n        config argument is the overall ScoutConfig var, so we can lookup the\n        components of the derived info.\n        \"\"\"\n        self.config = config\n\n    def has_config(self, key):\n        return self.lookup_func(key) is not None\n\n    def value(self, key):\n        return self.lookup_func(key)()\n\n    def lookup_func(self, key):\n        \"\"\"\n        Returns the derive_#{key} function, or None if it isn't defined\n        \"\"\"\n        func_name = \"derive_\" + key\n        return getattr(self, func_name, None)\n\n    def derive_core_agent_full_name(self):\n        triple = self.config.value(\"core_agent_triple\")\n        if not platform_detection.is_valid_triple(triple):\n            warnings.warn(\"Invalid value for core_agent_triple: {}\".format(triple))\n        return \"{name}-{version}-{triple}\".format(\n            name=\"scout_apm_core\",\n            version=self.config.value(\"core_agent_version\"),\n            triple=triple,\n        )\n\n    def derive_core_agent_triple(self):\n        return platform_detection.get_triple()\n\n\nclass Defaults(object):\n    \"\"\"\n    Provides default values for important configurations\n    \"\"\"\n\n    def __init__(self):\n        self.defaults = {\n            \"app_server\": \"\",\n            \"application_root\": os.getcwd(),\n            \"collect_remote_ip\": True,\n            \"core_agent_dir\": \"/tmp/scout_apm_core\",\n            \"core_agent_download\": True,\n            \"core_agent_launch\": True,\n            \"core_agent_log_level\": \"info\",\n            \"core_agent_permissions\": 700,\n            \"core_agent_socket_path\": \"tcp://127.0.0.1:6590\",\n            \"core_agent_version\": \"v1.3.1\",  # can be an exact tag name, or 'latest'\n            \"disabled_instruments\": [],\n            \"download_url\": \"https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release\",  # noqa: B950\n            \"errors_batch_size\": 5,\n            \"errors_enabled\": True,\n            \"errors_ignored_exceptions\": (),\n            \"errors_host\": \"https://errors.scoutapm.com\",\n            \"framework\": \"\",\n            \"framework_version\": \"\",\n            \"hostname\": None,\n            \"key\": \"\",\n            \"monitor\": False,\n            \"name\": \"Python App\",\n            \"revision_sha\": self._git_revision_sha(),\n            \"scm_subdirectory\": \"\",\n            \"shutdown_message_enabled\": True,\n            \"shutdown_timeout_seconds\": 2.0,\n            \"uri_reporting\": \"filtered_params\",\n        }\n\n    def _git_revision_sha(self):\n        # N.B. The environment variable SCOUT_REVISION_SHA may also be used,\n        # but that will be picked up by Env\n        return os.environ.get(\"HEROKU_SLUG_COMMIT\", \"\")\n\n    def has_config(self, key):\n        return key in self.defaults\n\n    def value(self, key):\n        return self.defaults[key]\n\n\nclass Null(object):\n    \"\"\"\n    Always answers that a key is present, but the value is None\n\n    Used as the last step of the layered configuration.\n    \"\"\"\n\n    def has_config(self, key):\n        return True\n\n    def value(self, key):\n        return None\n\n\ndef convert_to_bool(value):\n    if isinstance(value, bool):\n        return value\n    if isinstance(value, string_type):\n        return value.lower() in (\"yes\", \"true\", \"t\", \"1\")\n    # Unknown type - default to false?\n    return False\n\n\ndef convert_to_float(value):\n    try:\n        return float(value)\n    except ValueError:\n        return 0.0\n\n\ndef convert_to_list(value):\n    if isinstance(value, list):\n        return value\n    if isinstance(value, tuple):\n        return list(value)\n    if isinstance(value, string_type):\n        # Split on commas\n        return [item.strip() for item in value.split(\",\") if item]\n    # Unknown type - default to empty?\n    return []\n\n\nCONVERSIONS = {\n    \"collect_remote_ip\": convert_to_bool,\n    \"core_agent_download\": convert_to_bool,\n    \"core_agent_launch\": convert_to_bool,\n    \"disabled_instruments\": convert_to_list,\n    \"ignore\": convert_to_list,\n    \"monitor\": convert_to_bool,\n    \"shutdown_message_enabled\": convert_to_bool,\n    \"shutdown_timeout_seconds\": convert_to_float,\n}\n\n\nscout_config = ScoutConfig()\n", "path": "src/scout_apm/core/config.py"}]}