We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[proposal] remove PARSL_TRACKING environment variable
**Is your feature request related to a problem? Please describe.**

Currently Parsl takes usage tracking configuration from both `Config` (like other configuration) and from the `$PARSL_TRACKING` environment variable.

The interactions between these two parameters, as well as two conflicting defaults (one in `Config` and one in `usage.py`), are quite subtle and hard to understand intuitively and in the code.

PR #3400 makes that configuration more complicated: instead of a `bool`, the usage tracking level becomes an `int` (of restricted range). In reviewing that PR, along with @khk-globus, it's become pretty apparent that fusing two configurations and defaults together is even more complex in this situation.

Other configuration options in Parsl do not have this "specify multiple configuration values, fuse them together" behaviour: configuration comes from the `Config` object (and objects contained within that class).

**Describe the solution you'd like**

I would like to simplify this piece of Parsl by removing `$PARSL_TRACKING`: this removes a bunch of complexity both for user understanding of what usage level they have actually configured, and for programmers trying to understand these code paths.

**Describe alternatives you've considered**
A more generalised fuseable configuration mechanism that can fuse *any* configuration options, not only usage tracking level.

**Additional context**
This is part of a funded deliverable for usage tracking coordinated by @kylechard and @yadudoc, which @NishchayKarle is working on right now.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `parsl/usage_tracking/usage.py`
Content:
```
1 import json
2 import logging
3 import os
4 import platform
5 import socket
6 import sys
7 import time
8 import uuid
9 
10 from parsl.dataflow.states import States
11 from parsl.multiprocessing import ForkProcess
12 from parsl.usage_tracking.api import get_parsl_usage
13 from parsl.utils import setproctitle
14 from parsl.version import VERSION as PARSL_VERSION
15 
16 logger = logging.getLogger(__name__)
17 
18 from typing import Callable
19 
20 from typing_extensions import ParamSpec
21 
22 # protocol version byte: when (for example) compression parameters are changed
23 # that cannot be inferred from the compressed message itself, this version
24 # ID needs to imply those parameters.
25 
26 # Earlier protocol versions: b'{' - the original pure-JSON protocol pre-March 2024
27 PROTOCOL_VERSION = b'1'
28 
29 P = ParamSpec("P")
30 
31 
32 def async_process(fn: Callable[P, None]) -> Callable[P, None]:
33     """ Decorator function to launch a function as a separate process """
34 
35     def run(*args, **kwargs):
36         proc = ForkProcess(target=fn, args=args, kwargs=kwargs, name="Usage-Tracking")
37         proc.start()
38         return proc
39 
40     return run
41 
42 
43 @async_process
44 def udp_messenger(domain_name: str, UDP_PORT: int, sock_timeout: int, message: bytes) -> None:
45     """Send UDP messages to usage tracker asynchronously
46 
47     This multiprocessing based messenger was written to overcome the limitations
48     of signalling/terminating a thread that is blocked on a system call.
49 
50     Args:
51           - domain_name (str) : Domain name string
52           - UDP_PORT (int) : UDP port to send out on
53           - sock_timeout (int) : Socket timeout
54     """
55     setproctitle("parsl: Usage tracking")
56 
57     try:
58         UDP_IP = socket.gethostbyname(domain_name)
59 
60         sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # UDP
61         sock.settimeout(sock_timeout)
62         sock.sendto(message, (UDP_IP, UDP_PORT))
63         sock.close()
64 
65     except socket.timeout:
66         logger.debug("Failed to send usage tracking data: socket timeout")
67     except OSError as e:
68         logger.debug("Failed to send usage tracking data: OSError: {}".format(e))
69     except Exception as e:
70         logger.debug("Failed to send usage tracking data: Exception: {}".format(e))
71 
72 
73 class UsageTracker:
74     """Usage Tracking for Parsl.
75 
76     The server for this is here: https://github.com/Parsl/parsl_tracking
77     This issue captures the discussion that went into functionality
78     implemented here: https://github.com/Parsl/parsl/issues/34
79 
80     """
81 
82     def __init__(self, dfk, port=50077,
83                  domain_name='tracking.parsl-project.org'):
84         """Initialize usage tracking unless the user has opted-out.
85 
86         We will try to resolve the hostname specified in kwarg:domain_name
87         and if that fails attempt to use the kwarg:ip. Determining the
88         IP and sending message happens in an asynchronous processs to avoid
89         slowing down DFK initialization.
90 
91         Tracks usage stats by inspecting the internal state of the dfk.
92 
93         Args:
94              - dfk (DFK object) : Data Flow Kernel object
95 
96         KWargs:
97              - port (int) : Port number, Default:50077
98              - domain_name (string) : Domain name, will override IP
99                   Default: tracking.parsl-project.org
100         """
101 
102         self.domain_name = domain_name
103         # The sock timeout will only apply to UDP send and not domain resolution
104         self.sock_timeout = 5
105         self.UDP_PORT = port
106         self.procs = []
107         self.dfk = dfk
108         self.config = self.dfk.config
109         self.correlator_uuid = str(uuid.uuid4())
110         self.parsl_version = PARSL_VERSION
111         self.python_version = "{}.{}.{}".format(sys.version_info.major,
112                                                 sys.version_info.minor,
113                                                 sys.version_info.micro)
114         self.tracking_enabled = self.check_tracking_enabled()
115         logger.debug("Tracking status: {}".format(self.tracking_enabled))
116 
117     def check_tracking_enabled(self):
118         """Check if tracking is enabled.
119 
120         Tracking will be enabled unless either of these is true:
121 
122             1. dfk.config.usage_tracking is set to False
123             2. Environment variable PARSL_TRACKING is set to false (case insensitive)
124 
125         """
126         track = True
127 
128         if not self.config.usage_tracking:
129             track = False
130 
131         envvar = str(os.environ.get("PARSL_TRACKING", True)).lower()
132         if envvar == "false":
133             track = False
134 
135         return track
136 
137     def construct_start_message(self) -> bytes:
138         """Collect preliminary run info at the start of the DFK.
139 
140         Returns :
141               - Message dict dumped as json string, ready for UDP
142         """
143         message = {'correlator': self.correlator_uuid,
144                    'parsl_v': self.parsl_version,
145                    'python_v': self.python_version,
146                    'platform.system': platform.system(),
147                    'start': int(time.time()),
148                    'components': get_parsl_usage(self.dfk._config)}
149         logger.debug(f"Usage tracking start message: {message}")
150 
151         return self.encode_message(message)
152 
153     def construct_end_message(self) -> bytes:
154         """Collect the final run information at the time of DFK cleanup.
155 
156         Returns:
157              - Message dict dumped as json string, ready for UDP
158         """
159         app_count = self.dfk.task_count
160 
161         app_fails = self.dfk.task_state_counts[States.failed] + self.dfk.task_state_counts[States.dep_fail]
162 
163         # the DFK is tangled into this code as a god-object, so it is
164         # handled separately from the usual traversal code, but presenting
165         # the same protocol-level report.
166         dfk_component = {'c': type(self.dfk).__module__ + "." + type(self.dfk).__name__,
167                          'app_count': app_count,
168                          'app_fails': app_fails}
169 
170         message = {'correlator': self.correlator_uuid,
171                    'end': int(time.time()),
172                    'components': [dfk_component] + get_parsl_usage(self.dfk._config)}
173         logger.debug(f"Usage tracking end message (unencoded): {message}")
174 
175         return self.encode_message(message)
176 
177     def encode_message(self, obj):
178         return PROTOCOL_VERSION + json.dumps(obj).encode()
179 
180     def send_UDP_message(self, message: bytes) -> None:
181         """Send UDP message."""
182         if self.tracking_enabled:
183             try:
184                 proc = udp_messenger(self.domain_name, self.UDP_PORT, self.sock_timeout, message)
185                 self.procs.append(proc)
186             except Exception as e:
187                 logger.debug("Usage tracking failed: {}".format(e))
188 
189     def send_start_message(self) -> None:
190         message = self.construct_start_message()
191         self.send_UDP_message(message)
192 
193     def send_end_message(self) -> None:
194         message = self.construct_end_message()
195         self.send_UDP_message(message)
196 
197     def close(self, timeout: float = 10.0) -> None:
198         """First give each process one timeout period to finish what it is
199         doing, then kill it (SIGKILL). There's no softer SIGTERM step,
200         because that adds one join period of delay for what is almost
201         definitely either: going to behave broadly the same as to SIGKILL,
202         or won't respond to SIGTERM.
203         """
204         for proc in self.procs:
205             logger.debug("Joining usage tracking process %s", proc)
206             proc.join(timeout=timeout)
207             if proc.is_alive():
208                 logger.warning("Usage tracking process did not end itself; sending SIGKILL")
209                 proc.kill()
210 
211             proc.close()
212
```
--- 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/parsl/usage_tracking/usage.py b/parsl/usage_tracking/usage.py
--- a/parsl/usage_tracking/usage.py
+++ b/parsl/usage_tracking/usage.py
@@ -1,6 +1,5 @@
 import json
 import logging
-import os
 import platform
 import socket
 import sys
@@ -117,22 +116,12 @@
     def check_tracking_enabled(self):
         """Check if tracking is enabled.
 
-        Tracking will be enabled unless either of these is true:
+        Tracking will be enabled unless the following is true:
 
             1. dfk.config.usage_tracking is set to False
-            2. Environment variable PARSL_TRACKING is set to false (case insensitive)
 
         """
-        track = True
-
-        if not self.config.usage_tracking:
-            track = False
-
-        envvar = str(os.environ.get("PARSL_TRACKING", True)).lower()
-        if envvar == "false":
-            track = False
-
-        return track
+        return self.config.usage_tracking
 
     def construct_start_message(self) -> bytes:
         """Collect preliminary run info at the start of the DFK.

{"golden_diff": "diff --git a/parsl/usage_tracking/usage.py b/parsl/usage_tracking/usage.py\n--- a/parsl/usage_tracking/usage.py\n+++ b/parsl/usage_tracking/usage.py\n@@ -1,6 +1,5 @@\n import json\n import logging\n-import os\n import platform\n import socket\n import sys\n@@ -117,22 +116,12 @@\n     def check_tracking_enabled(self):\n         \"\"\"Check if tracking is enabled.\n \n-        Tracking will be enabled unless either of these is true:\n+        Tracking will be enabled unless the following is true:\n \n             1. dfk.config.usage_tracking is set to False\n-            2. Environment variable PARSL_TRACKING is set to false (case insensitive)\n \n         \"\"\"\n-        track = True\n-\n-        if not self.config.usage_tracking:\n-            track = False\n-\n-        envvar = str(os.environ.get(\"PARSL_TRACKING\", True)).lower()\n-        if envvar == \"false\":\n-            track = False\n-\n-        return track\n+        return self.config.usage_tracking\n \n     def construct_start_message(self) -> bytes:\n         \"\"\"Collect preliminary run info at the start of the DFK.\n", "issue": "[proposal] remove PARSL_TRACKING environment variable\n**Is your feature request related to a problem? Please describe.**\r\n\r\nCurrently Parsl takes usage tracking configuration from both `Config` (like other configuration) and from the `$PARSL_TRACKING` environment variable.\r\n\r\nThe interactions between these two parameters, as well as two conflicting defaults (one in `Config` and one in `usage.py`), are quite subtle and hard to understand intuitively and in the code.\r\n\r\nPR #3400 makes that configuration more complicated: instead of a `bool`, the usage tracking level becomes an `int` (of restricted range). In reviewing that PR, along with @khk-globus, it's become pretty apparent that fusing two configurations and defaults together is even more complex in this situation.\r\n\r\nOther configuration options in Parsl do not have this \"specify multiple configuration values, fuse them together\" behaviour: configuration comes from the `Config` object (and objects contained within that class).\r\n\r\n**Describe the solution you'd like**\r\n\r\nI would like to simplify this piece of Parsl by removing `$PARSL_TRACKING`: this removes a bunch of complexity both for user understanding of what usage level they have actually configured, and for programmers trying to understand these code paths.\r\n\r\n**Describe alternatives you've considered**\r\nA more generalised fuseable configuration mechanism that can fuse *any* configuration options, not only usage tracking level.\r\n\r\n**Additional context**\r\nThis is part of a funded deliverable for usage tracking coordinated by @kylechard and @yadudoc, which @NishchayKarle is working on right now.\n", "before_files": [{"content": "import json\nimport logging\nimport os\nimport platform\nimport socket\nimport sys\nimport time\nimport uuid\n\nfrom parsl.dataflow.states import States\nfrom parsl.multiprocessing import ForkProcess\nfrom parsl.usage_tracking.api import get_parsl_usage\nfrom parsl.utils import setproctitle\nfrom parsl.version import VERSION as PARSL_VERSION\n\nlogger = logging.getLogger(__name__)\n\nfrom typing import Callable\n\nfrom typing_extensions import ParamSpec\n\n# protocol version byte: when (for example) compression parameters are changed\n# that cannot be inferred from the compressed message itself, this version\n# ID needs to imply those parameters.\n\n# Earlier protocol versions: b'{' - the original pure-JSON protocol pre-March 2024\nPROTOCOL_VERSION = b'1'\n\nP = ParamSpec(\"P\")\n\n\ndef async_process(fn: Callable[P, None]) -> Callable[P, None]:\n    \"\"\" Decorator function to launch a function as a separate process \"\"\"\n\n    def run(*args, **kwargs):\n        proc = ForkProcess(target=fn, args=args, kwargs=kwargs, name=\"Usage-Tracking\")\n        proc.start()\n        return proc\n\n    return run\n\n\n@async_process\ndef udp_messenger(domain_name: str, UDP_PORT: int, sock_timeout: int, message: bytes) -> None:\n    \"\"\"Send UDP messages to usage tracker asynchronously\n\n    This multiprocessing based messenger was written to overcome the limitations\n    of signalling/terminating a thread that is blocked on a system call.\n\n    Args:\n          - domain_name (str) : Domain name string\n          - UDP_PORT (int) : UDP port to send out on\n          - sock_timeout (int) : Socket timeout\n    \"\"\"\n    setproctitle(\"parsl: Usage tracking\")\n\n    try:\n        UDP_IP = socket.gethostbyname(domain_name)\n\n        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # UDP\n        sock.settimeout(sock_timeout)\n        sock.sendto(message, (UDP_IP, UDP_PORT))\n        sock.close()\n\n    except socket.timeout:\n        logger.debug(\"Failed to send usage tracking data: socket timeout\")\n    except OSError as e:\n        logger.debug(\"Failed to send usage tracking data: OSError: {}\".format(e))\n    except Exception as e:\n        logger.debug(\"Failed to send usage tracking data: Exception: {}\".format(e))\n\n\nclass UsageTracker:\n    \"\"\"Usage Tracking for Parsl.\n\n    The server for this is here: https://github.com/Parsl/parsl_tracking\n    This issue captures the discussion that went into functionality\n    implemented here: https://github.com/Parsl/parsl/issues/34\n\n    \"\"\"\n\n    def __init__(self, dfk, port=50077,\n                 domain_name='tracking.parsl-project.org'):\n        \"\"\"Initialize usage tracking unless the user has opted-out.\n\n        We will try to resolve the hostname specified in kwarg:domain_name\n        and if that fails attempt to use the kwarg:ip. Determining the\n        IP and sending message happens in an asynchronous processs to avoid\n        slowing down DFK initialization.\n\n        Tracks usage stats by inspecting the internal state of the dfk.\n\n        Args:\n             - dfk (DFK object) : Data Flow Kernel object\n\n        KWargs:\n             - port (int) : Port number, Default:50077\n             - domain_name (string) : Domain name, will override IP\n                  Default: tracking.parsl-project.org\n        \"\"\"\n\n        self.domain_name = domain_name\n        # The sock timeout will only apply to UDP send and not domain resolution\n        self.sock_timeout = 5\n        self.UDP_PORT = port\n        self.procs = []\n        self.dfk = dfk\n        self.config = self.dfk.config\n        self.correlator_uuid = str(uuid.uuid4())\n        self.parsl_version = PARSL_VERSION\n        self.python_version = \"{}.{}.{}\".format(sys.version_info.major,\n                                                sys.version_info.minor,\n                                                sys.version_info.micro)\n        self.tracking_enabled = self.check_tracking_enabled()\n        logger.debug(\"Tracking status: {}\".format(self.tracking_enabled))\n\n    def check_tracking_enabled(self):\n        \"\"\"Check if tracking is enabled.\n\n        Tracking will be enabled unless either of these is true:\n\n            1. dfk.config.usage_tracking is set to False\n            2. Environment variable PARSL_TRACKING is set to false (case insensitive)\n\n        \"\"\"\n        track = True\n\n        if not self.config.usage_tracking:\n            track = False\n\n        envvar = str(os.environ.get(\"PARSL_TRACKING\", True)).lower()\n        if envvar == \"false\":\n            track = False\n\n        return track\n\n    def construct_start_message(self) -> bytes:\n        \"\"\"Collect preliminary run info at the start of the DFK.\n\n        Returns :\n              - Message dict dumped as json string, ready for UDP\n        \"\"\"\n        message = {'correlator': self.correlator_uuid,\n                   'parsl_v': self.parsl_version,\n                   'python_v': self.python_version,\n                   'platform.system': platform.system(),\n                   'start': int(time.time()),\n                   'components': get_parsl_usage(self.dfk._config)}\n        logger.debug(f\"Usage tracking start message: {message}\")\n\n        return self.encode_message(message)\n\n    def construct_end_message(self) -> bytes:\n        \"\"\"Collect the final run information at the time of DFK cleanup.\n\n        Returns:\n             - Message dict dumped as json string, ready for UDP\n        \"\"\"\n        app_count = self.dfk.task_count\n\n        app_fails = self.dfk.task_state_counts[States.failed] + self.dfk.task_state_counts[States.dep_fail]\n\n        # the DFK is tangled into this code as a god-object, so it is\n        # handled separately from the usual traversal code, but presenting\n        # the same protocol-level report.\n        dfk_component = {'c': type(self.dfk).__module__ + \".\" + type(self.dfk).__name__,\n                         'app_count': app_count,\n                         'app_fails': app_fails}\n\n        message = {'correlator': self.correlator_uuid,\n                   'end': int(time.time()),\n                   'components': [dfk_component] + get_parsl_usage(self.dfk._config)}\n        logger.debug(f\"Usage tracking end message (unencoded): {message}\")\n\n        return self.encode_message(message)\n\n    def encode_message(self, obj):\n        return PROTOCOL_VERSION + json.dumps(obj).encode()\n\n    def send_UDP_message(self, message: bytes) -> None:\n        \"\"\"Send UDP message.\"\"\"\n        if self.tracking_enabled:\n            try:\n                proc = udp_messenger(self.domain_name, self.UDP_PORT, self.sock_timeout, message)\n                self.procs.append(proc)\n            except Exception as e:\n                logger.debug(\"Usage tracking failed: {}\".format(e))\n\n    def send_start_message(self) -> None:\n        message = self.construct_start_message()\n        self.send_UDP_message(message)\n\n    def send_end_message(self) -> None:\n        message = self.construct_end_message()\n        self.send_UDP_message(message)\n\n    def close(self, timeout: float = 10.0) -> None:\n        \"\"\"First give each process one timeout period to finish what it is\n        doing, then kill it (SIGKILL). There's no softer SIGTERM step,\n        because that adds one join period of delay for what is almost\n        definitely either: going to behave broadly the same as to SIGKILL,\n        or won't respond to SIGTERM.\n        \"\"\"\n        for proc in self.procs:\n            logger.debug(\"Joining usage tracking process %s\", proc)\n            proc.join(timeout=timeout)\n            if proc.is_alive():\n                logger.warning(\"Usage tracking process did not end itself; sending SIGKILL\")\n                proc.kill()\n\n            proc.close()\n", "path": "parsl/usage_tracking/usage.py"}], "after_files": [{"content": "import json\nimport logging\nimport platform\nimport socket\nimport sys\nimport time\nimport uuid\n\nfrom parsl.dataflow.states import States\nfrom parsl.multiprocessing import ForkProcess\nfrom parsl.usage_tracking.api import get_parsl_usage\nfrom parsl.utils import setproctitle\nfrom parsl.version import VERSION as PARSL_VERSION\n\nlogger = logging.getLogger(__name__)\n\nfrom typing import Callable\n\nfrom typing_extensions import ParamSpec\n\n# protocol version byte: when (for example) compression parameters are changed\n# that cannot be inferred from the compressed message itself, this version\n# ID needs to imply those parameters.\n\n# Earlier protocol versions: b'{' - the original pure-JSON protocol pre-March 2024\nPROTOCOL_VERSION = b'1'\n\nP = ParamSpec(\"P\")\n\n\ndef async_process(fn: Callable[P, None]) -> Callable[P, None]:\n    \"\"\" Decorator function to launch a function as a separate process \"\"\"\n\n    def run(*args, **kwargs):\n        proc = ForkProcess(target=fn, args=args, kwargs=kwargs, name=\"Usage-Tracking\")\n        proc.start()\n        return proc\n\n    return run\n\n\n@async_process\ndef udp_messenger(domain_name: str, UDP_PORT: int, sock_timeout: int, message: bytes) -> None:\n    \"\"\"Send UDP messages to usage tracker asynchronously\n\n    This multiprocessing based messenger was written to overcome the limitations\n    of signalling/terminating a thread that is blocked on a system call.\n\n    Args:\n          - domain_name (str) : Domain name string\n          - UDP_PORT (int) : UDP port to send out on\n          - sock_timeout (int) : Socket timeout\n    \"\"\"\n    setproctitle(\"parsl: Usage tracking\")\n\n    try:\n        UDP_IP = socket.gethostbyname(domain_name)\n\n        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # UDP\n        sock.settimeout(sock_timeout)\n        sock.sendto(message, (UDP_IP, UDP_PORT))\n        sock.close()\n\n    except socket.timeout:\n        logger.debug(\"Failed to send usage tracking data: socket timeout\")\n    except OSError as e:\n        logger.debug(\"Failed to send usage tracking data: OSError: {}\".format(e))\n    except Exception as e:\n        logger.debug(\"Failed to send usage tracking data: Exception: {}\".format(e))\n\n\nclass UsageTracker:\n    \"\"\"Usage Tracking for Parsl.\n\n    The server for this is here: https://github.com/Parsl/parsl_tracking\n    This issue captures the discussion that went into functionality\n    implemented here: https://github.com/Parsl/parsl/issues/34\n\n    \"\"\"\n\n    def __init__(self, dfk, port=50077,\n                 domain_name='tracking.parsl-project.org'):\n        \"\"\"Initialize usage tracking unless the user has opted-out.\n\n        We will try to resolve the hostname specified in kwarg:domain_name\n        and if that fails attempt to use the kwarg:ip. Determining the\n        IP and sending message happens in an asynchronous processs to avoid\n        slowing down DFK initialization.\n\n        Tracks usage stats by inspecting the internal state of the dfk.\n\n        Args:\n             - dfk (DFK object) : Data Flow Kernel object\n\n        KWargs:\n             - port (int) : Port number, Default:50077\n             - domain_name (string) : Domain name, will override IP\n                  Default: tracking.parsl-project.org\n        \"\"\"\n\n        self.domain_name = domain_name\n        # The sock timeout will only apply to UDP send and not domain resolution\n        self.sock_timeout = 5\n        self.UDP_PORT = port\n        self.procs = []\n        self.dfk = dfk\n        self.config = self.dfk.config\n        self.correlator_uuid = str(uuid.uuid4())\n        self.parsl_version = PARSL_VERSION\n        self.python_version = \"{}.{}.{}\".format(sys.version_info.major,\n                                                sys.version_info.minor,\n                                                sys.version_info.micro)\n        self.tracking_enabled = self.check_tracking_enabled()\n        logger.debug(\"Tracking status: {}\".format(self.tracking_enabled))\n\n    def check_tracking_enabled(self):\n        \"\"\"Check if tracking is enabled.\n\n        Tracking will be enabled unless the following is true:\n\n            1. dfk.config.usage_tracking is set to False\n\n        \"\"\"\n        return self.config.usage_tracking\n\n    def construct_start_message(self) -> bytes:\n        \"\"\"Collect preliminary run info at the start of the DFK.\n\n        Returns :\n              - Message dict dumped as json string, ready for UDP\n        \"\"\"\n        message = {'correlator': self.correlator_uuid,\n                   'parsl_v': self.parsl_version,\n                   'python_v': self.python_version,\n                   'platform.system': platform.system(),\n                   'start': int(time.time()),\n                   'components': get_parsl_usage(self.dfk._config)}\n        logger.debug(f\"Usage tracking start message: {message}\")\n\n        return self.encode_message(message)\n\n    def construct_end_message(self) -> bytes:\n        \"\"\"Collect the final run information at the time of DFK cleanup.\n\n        Returns:\n             - Message dict dumped as json string, ready for UDP\n        \"\"\"\n        app_count = self.dfk.task_count\n\n        app_fails = self.dfk.task_state_counts[States.failed] + self.dfk.task_state_counts[States.dep_fail]\n\n        # the DFK is tangled into this code as a god-object, so it is\n        # handled separately from the usual traversal code, but presenting\n        # the same protocol-level report.\n        dfk_component = {'c': type(self.dfk).__module__ + \".\" + type(self.dfk).__name__,\n                         'app_count': app_count,\n                         'app_fails': app_fails}\n\n        message = {'correlator': self.correlator_uuid,\n                   'end': int(time.time()),\n                   'components': [dfk_component] + get_parsl_usage(self.dfk._config)}\n        logger.debug(f\"Usage tracking end message (unencoded): {message}\")\n\n        return self.encode_message(message)\n\n    def encode_message(self, obj):\n        return PROTOCOL_VERSION + json.dumps(obj).encode()\n\n    def send_UDP_message(self, message: bytes) -> None:\n        \"\"\"Send UDP message.\"\"\"\n        if self.tracking_enabled:\n            try:\n                proc = udp_messenger(self.domain_name, self.UDP_PORT, self.sock_timeout, message)\n                self.procs.append(proc)\n            except Exception as e:\n                logger.debug(\"Usage tracking failed: {}\".format(e))\n\n    def send_start_message(self) -> None:\n        message = self.construct_start_message()\n        self.send_UDP_message(message)\n\n    def send_end_message(self) -> None:\n        message = self.construct_end_message()\n        self.send_UDP_message(message)\n\n    def close(self, timeout: float = 10.0) -> None:\n        \"\"\"First give each process one timeout period to finish what it is\n        doing, then kill it (SIGKILL). There's no softer SIGTERM step,\n        because that adds one join period of delay for what is almost\n        definitely either: going to behave broadly the same as to SIGKILL,\n        or won't respond to SIGTERM.\n        \"\"\"\n        for proc in self.procs:\n            logger.debug(\"Joining usage tracking process %s\", proc)\n            proc.join(timeout=timeout)\n            if proc.is_alive():\n                logger.warning(\"Usage tracking process did not end itself; sending SIGKILL\")\n                proc.kill()\n\n            proc.close()\n", "path": "parsl/usage_tracking/usage.py"}]}