toil 6.1.0a1__py3-none-any.whl → 8.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. toil/__init__.py +122 -315
  2. toil/batchSystems/__init__.py +1 -0
  3. toil/batchSystems/abstractBatchSystem.py +173 -89
  4. toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
  5. toil/batchSystems/awsBatch.py +244 -135
  6. toil/batchSystems/cleanup_support.py +26 -16
  7. toil/batchSystems/contained_executor.py +31 -28
  8. toil/batchSystems/gridengine.py +86 -50
  9. toil/batchSystems/htcondor.py +166 -89
  10. toil/batchSystems/kubernetes.py +632 -382
  11. toil/batchSystems/local_support.py +20 -15
  12. toil/batchSystems/lsf.py +134 -81
  13. toil/batchSystems/lsfHelper.py +13 -11
  14. toil/batchSystems/mesos/__init__.py +41 -29
  15. toil/batchSystems/mesos/batchSystem.py +290 -151
  16. toil/batchSystems/mesos/executor.py +79 -50
  17. toil/batchSystems/mesos/test/__init__.py +31 -23
  18. toil/batchSystems/options.py +46 -28
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +296 -125
  21. toil/batchSystems/slurm.py +603 -138
  22. toil/batchSystems/torque.py +47 -33
  23. toil/bus.py +186 -76
  24. toil/common.py +664 -368
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1136 -483
  27. toil/cwl/utils.py +17 -22
  28. toil/deferred.py +63 -42
  29. toil/exceptions.py +5 -3
  30. toil/fileStores/__init__.py +5 -5
  31. toil/fileStores/abstractFileStore.py +140 -60
  32. toil/fileStores/cachingFileStore.py +717 -269
  33. toil/fileStores/nonCachingFileStore.py +116 -87
  34. toil/job.py +1225 -368
  35. toil/jobStores/abstractJobStore.py +416 -266
  36. toil/jobStores/aws/jobStore.py +863 -477
  37. toil/jobStores/aws/utils.py +201 -120
  38. toil/jobStores/conftest.py +3 -2
  39. toil/jobStores/fileJobStore.py +292 -154
  40. toil/jobStores/googleJobStore.py +140 -74
  41. toil/jobStores/utils.py +36 -15
  42. toil/leader.py +668 -272
  43. toil/lib/accelerators.py +115 -18
  44. toil/lib/aws/__init__.py +74 -31
  45. toil/lib/aws/ami.py +122 -87
  46. toil/lib/aws/iam.py +284 -108
  47. toil/lib/aws/s3.py +31 -0
  48. toil/lib/aws/session.py +214 -39
  49. toil/lib/aws/utils.py +287 -231
  50. toil/lib/bioio.py +13 -5
  51. toil/lib/compatibility.py +11 -6
  52. toil/lib/conversions.py +104 -47
  53. toil/lib/docker.py +131 -103
  54. toil/lib/ec2.py +361 -199
  55. toil/lib/ec2nodes.py +174 -106
  56. toil/lib/encryption/_dummy.py +5 -3
  57. toil/lib/encryption/_nacl.py +10 -6
  58. toil/lib/encryption/conftest.py +1 -0
  59. toil/lib/exceptions.py +26 -7
  60. toil/lib/expando.py +5 -3
  61. toil/lib/ftp_utils.py +217 -0
  62. toil/lib/generatedEC2Lists.py +127 -19
  63. toil/lib/humanize.py +6 -2
  64. toil/lib/integration.py +341 -0
  65. toil/lib/io.py +141 -15
  66. toil/lib/iterables.py +4 -2
  67. toil/lib/memoize.py +12 -8
  68. toil/lib/misc.py +66 -21
  69. toil/lib/objects.py +2 -2
  70. toil/lib/resources.py +68 -15
  71. toil/lib/retry.py +126 -81
  72. toil/lib/threading.py +299 -82
  73. toil/lib/throttle.py +16 -15
  74. toil/options/common.py +843 -409
  75. toil/options/cwl.py +175 -90
  76. toil/options/runner.py +50 -0
  77. toil/options/wdl.py +73 -17
  78. toil/provisioners/__init__.py +117 -46
  79. toil/provisioners/abstractProvisioner.py +332 -157
  80. toil/provisioners/aws/__init__.py +70 -33
  81. toil/provisioners/aws/awsProvisioner.py +1145 -715
  82. toil/provisioners/clusterScaler.py +541 -279
  83. toil/provisioners/gceProvisioner.py +282 -179
  84. toil/provisioners/node.py +155 -79
  85. toil/realtimeLogger.py +34 -22
  86. toil/resource.py +137 -75
  87. toil/server/app.py +128 -62
  88. toil/server/celery_app.py +3 -1
  89. toil/server/cli/wes_cwl_runner.py +82 -53
  90. toil/server/utils.py +54 -28
  91. toil/server/wes/abstract_backend.py +64 -26
  92. toil/server/wes/amazon_wes_utils.py +21 -15
  93. toil/server/wes/tasks.py +121 -63
  94. toil/server/wes/toil_backend.py +142 -107
  95. toil/server/wsgi_app.py +4 -3
  96. toil/serviceManager.py +58 -22
  97. toil/statsAndLogging.py +224 -70
  98. toil/test/__init__.py +282 -183
  99. toil/test/batchSystems/batchSystemTest.py +460 -210
  100. toil/test/batchSystems/batch_system_plugin_test.py +90 -0
  101. toil/test/batchSystems/test_gridengine.py +173 -0
  102. toil/test/batchSystems/test_lsf_helper.py +67 -58
  103. toil/test/batchSystems/test_slurm.py +110 -49
  104. toil/test/cactus/__init__.py +0 -0
  105. toil/test/cactus/test_cactus_integration.py +56 -0
  106. toil/test/cwl/cwlTest.py +496 -287
  107. toil/test/cwl/measure_default_memory.cwl +12 -0
  108. toil/test/cwl/not_run_required_input.cwl +29 -0
  109. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  110. toil/test/cwl/seqtk_seq.cwl +1 -1
  111. toil/test/docs/scriptsTest.py +69 -46
  112. toil/test/jobStores/jobStoreTest.py +427 -264
  113. toil/test/lib/aws/test_iam.py +118 -50
  114. toil/test/lib/aws/test_s3.py +16 -9
  115. toil/test/lib/aws/test_utils.py +5 -6
  116. toil/test/lib/dockerTest.py +118 -141
  117. toil/test/lib/test_conversions.py +113 -115
  118. toil/test/lib/test_ec2.py +58 -50
  119. toil/test/lib/test_integration.py +104 -0
  120. toil/test/lib/test_misc.py +12 -5
  121. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  122. toil/test/mesos/helloWorld.py +7 -6
  123. toil/test/mesos/stress.py +25 -20
  124. toil/test/options/__init__.py +13 -0
  125. toil/test/options/options.py +42 -0
  126. toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
  127. toil/test/provisioners/clusterScalerTest.py +440 -250
  128. toil/test/provisioners/clusterTest.py +166 -44
  129. toil/test/provisioners/gceProvisionerTest.py +174 -100
  130. toil/test/provisioners/provisionerTest.py +25 -13
  131. toil/test/provisioners/restartScript.py +5 -4
  132. toil/test/server/serverTest.py +188 -141
  133. toil/test/sort/restart_sort.py +137 -68
  134. toil/test/sort/sort.py +134 -66
  135. toil/test/sort/sortTest.py +91 -49
  136. toil/test/src/autoDeploymentTest.py +141 -101
  137. toil/test/src/busTest.py +20 -18
  138. toil/test/src/checkpointTest.py +8 -2
  139. toil/test/src/deferredFunctionTest.py +49 -35
  140. toil/test/src/dockerCheckTest.py +32 -24
  141. toil/test/src/environmentTest.py +135 -0
  142. toil/test/src/fileStoreTest.py +539 -272
  143. toil/test/src/helloWorldTest.py +7 -4
  144. toil/test/src/importExportFileTest.py +61 -31
  145. toil/test/src/jobDescriptionTest.py +46 -21
  146. toil/test/src/jobEncapsulationTest.py +2 -0
  147. toil/test/src/jobFileStoreTest.py +74 -50
  148. toil/test/src/jobServiceTest.py +187 -73
  149. toil/test/src/jobTest.py +121 -71
  150. toil/test/src/miscTests.py +19 -18
  151. toil/test/src/promisedRequirementTest.py +82 -36
  152. toil/test/src/promisesTest.py +7 -6
  153. toil/test/src/realtimeLoggerTest.py +10 -6
  154. toil/test/src/regularLogTest.py +71 -37
  155. toil/test/src/resourceTest.py +80 -49
  156. toil/test/src/restartDAGTest.py +36 -22
  157. toil/test/src/resumabilityTest.py +9 -2
  158. toil/test/src/retainTempDirTest.py +45 -14
  159. toil/test/src/systemTest.py +12 -8
  160. toil/test/src/threadingTest.py +44 -25
  161. toil/test/src/toilContextManagerTest.py +10 -7
  162. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  163. toil/test/src/workerTest.py +73 -23
  164. toil/test/utils/toilDebugTest.py +103 -33
  165. toil/test/utils/toilKillTest.py +4 -5
  166. toil/test/utils/utilsTest.py +245 -106
  167. toil/test/wdl/wdltoil_test.py +818 -149
  168. toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
  169. toil/toilState.py +120 -35
  170. toil/utils/toilConfig.py +13 -4
  171. toil/utils/toilDebugFile.py +44 -27
  172. toil/utils/toilDebugJob.py +214 -27
  173. toil/utils/toilDestroyCluster.py +11 -6
  174. toil/utils/toilKill.py +8 -3
  175. toil/utils/toilLaunchCluster.py +256 -140
  176. toil/utils/toilMain.py +37 -16
  177. toil/utils/toilRsyncCluster.py +32 -14
  178. toil/utils/toilSshCluster.py +49 -22
  179. toil/utils/toilStats.py +356 -273
  180. toil/utils/toilStatus.py +292 -139
  181. toil/utils/toilUpdateEC2Instances.py +3 -1
  182. toil/version.py +12 -12
  183. toil/wdl/utils.py +5 -5
  184. toil/wdl/wdltoil.py +3913 -1033
  185. toil/worker.py +367 -184
  186. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
  187. toil-8.0.0.dist-info/METADATA +173 -0
  188. toil-8.0.0.dist-info/RECORD +253 -0
  189. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
  190. toil-6.1.0a1.dist-info/METADATA +0 -125
  191. toil-6.1.0a1.dist-info/RECORD +0 -237
  192. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
  193. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
toil/lib/memoize.py CHANGED
@@ -17,7 +17,7 @@ import datetime
17
17
  import re
18
18
  from functools import lru_cache, wraps
19
19
  from threading import Lock
20
- from typing import Any, Callable, Dict, Tuple, TypeVar
20
+ from typing import Any, Callable, TypeVar
21
21
 
22
22
  memoize = lru_cache(maxsize=None)
23
23
  """
@@ -31,13 +31,14 @@ more than once with the same arguments.
31
31
  MAT = TypeVar("MAT")
32
32
  MRT = TypeVar("MRT")
33
33
 
34
+
34
35
  def sync_memoize(f: Callable[[MAT], MRT]) -> Callable[[MAT], MRT]:
35
36
  """
36
37
  Like memoize, but guarantees that decorated function is only called once, even when multiple
37
38
  threads are calling the decorating function with multiple parameters.
38
39
  """
39
40
  # TODO: Think about an f that is recursive
40
- memory: Dict[Tuple[Any, ...], Any] = {}
41
+ memory: dict[tuple[Any, ...], Any] = {}
41
42
  lock = Lock()
42
43
 
43
44
  @wraps(f)
@@ -53,13 +54,14 @@ def sync_memoize(f: Callable[[MAT], MRT]) -> Callable[[MAT], MRT]:
53
54
  r = f(*args)
54
55
  memory[args] = r
55
56
  return r
57
+
56
58
  return new_f
57
59
 
58
60
 
59
61
  def parse_iso_utc(s: str) -> datetime.datetime:
60
62
  """
61
63
  Parses an ISO time with a hard-coded Z for zulu-time (UTC) at the end. Other timezones are
62
- not supported. Returns a timezone-naive datetime object.
64
+ not supported. Returns a timezone-naive datetime object.
63
65
 
64
66
  :param s: The ISO-formatted time
65
67
 
@@ -74,20 +76,22 @@ def parse_iso_utc(s: str) -> datetime.datetime:
74
76
  ...
75
77
  ValueError: Not a valid ISO datetime in UTC: 2016-04-27T00:28:04X
76
78
  """
77
- rfc3339_datetime = re.compile(r'^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})$')
79
+ rfc3339_datetime = re.compile(
80
+ r"^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})$"
81
+ )
78
82
  m = rfc3339_datetime.match(s)
79
83
  if not m:
80
- raise ValueError(f'Not a valid ISO datetime in UTC: {s}')
84
+ raise ValueError(f"Not a valid ISO datetime in UTC: {s}")
81
85
  else:
82
- fmt = '%Y-%m-%dT%H:%M:%S' + ('.%f' if m.group(7) else '') + 'Z'
86
+ fmt = "%Y-%m-%dT%H:%M:%S" + (".%f" if m.group(7) else "") + "Z"
83
87
  return datetime.datetime.strptime(s, fmt)
84
88
 
85
89
 
86
90
  def strict_bool(s: str) -> bool:
87
91
  """Variant of bool() that only accepts two possible string values."""
88
- if s == 'True':
92
+ if s == "True":
89
93
  return True
90
- elif s == 'False':
94
+ elif s == "False":
91
95
  return False
92
96
  else:
93
97
  raise ValueError(s)
toil/lib/misc.py CHANGED
@@ -7,11 +7,9 @@ import socket
7
7
  import subprocess
8
8
  import sys
9
9
  import time
10
- import typing
10
+ from collections.abc import Iterator
11
11
  from contextlib import closing
12
- from typing import Iterator, List, Optional
13
-
14
- import pytz
12
+ from typing import Optional
15
13
 
16
14
  logger = logging.getLogger(__name__)
17
15
 
@@ -23,19 +21,20 @@ def get_public_ip() -> str:
23
21
  try:
24
22
  # Try to get the internet-facing IP by attempting a connection
25
23
  # to a non-existent server and reading what IP was used.
26
- ip = '127.0.0.1'
24
+ ip = "127.0.0.1"
27
25
  with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as sock:
28
26
  # 203.0.113.0/24 is reserved as TEST-NET-3 by RFC 5737, so
29
27
  # there is guaranteed to be no one listening on the other
30
28
  # end (and we won't accidentally DOS anyone).
31
- sock.connect(('203.0.113.1', 1))
29
+ sock.connect(("203.0.113.1", 1))
32
30
  ip = sock.getsockname()[0]
33
31
  return ip
34
32
  except:
35
33
  # Something went terribly wrong. Just give loopback rather
36
34
  # than killing everything, because this is often called just
37
35
  # to provide a default argument
38
- return '127.0.0.1'
36
+ return "127.0.0.1"
37
+
39
38
 
40
39
  def get_user_name() -> str:
41
40
  """
@@ -48,20 +47,23 @@ def get_user_name() -> str:
48
47
  except KeyError:
49
48
  # This is expected if the user isn't in /etc/passwd, such as in a
50
49
  # Docker container when running as a weird UID. Make something up.
51
- return 'UnknownUser' + str(os.getuid())
50
+ return "UnknownUser" + str(os.getuid())
52
51
  except Exception as e:
53
52
  # We can't get the UID, or something weird has gone wrong.
54
- logger.error('Unexpected error getting user name: %s', e)
55
- return 'UnknownUser'
53
+ logger.error("Unexpected error getting user name: %s", e)
54
+ return "UnknownUser"
55
+
56
56
 
57
57
  def utc_now() -> datetime.datetime:
58
58
  """Return a datetime in the UTC timezone corresponding to right now."""
59
- return datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
59
+ return datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
60
+
60
61
 
61
62
  def unix_now_ms() -> float:
62
63
  """Return the current time in milliseconds since the Unix epoch."""
63
64
  return time.time() * 1000
64
65
 
66
+
65
67
  def slow_down(seconds: float) -> float:
66
68
  """
67
69
  Toil jobs that have completed are not allowed to have taken 0 seconds, but
@@ -79,9 +81,25 @@ def slow_down(seconds: float) -> float:
79
81
 
80
82
  return max(seconds, sys.float_info.epsilon)
81
83
 
82
- def printq(msg: str, quiet: bool) -> None:
84
+
85
+ def printq(msg: str, quiet: bool, log: bool = False) -> None:
86
+ """
87
+ This is for functions used simultaneously in Toil proper and in the admin scripts.
88
+
89
+ Our admin scripts "print" to stdout, while Toil proper uses logging. For a script that,
90
+ for example, cleans up IAM, EC2, etc. cruft leftover after failed CI runs, we can call
91
+ an AWS delete IAM role function, and this prints or logs progress (unless quiet is True),
92
+ depending on whether the function is called in, say, the jobstore or a script.
93
+
94
+ :param msg: The string to print or log to stdout.
95
+ :param quiet: Silent output to stdout.
96
+ :param log: Use logging (else "print" to the screen).
97
+ """
83
98
  if not quiet:
84
- print(msg)
99
+ if not log:
100
+ print(msg)
101
+ else:
102
+ logger.debug(msg)
85
103
 
86
104
 
87
105
  def truncExpBackoff() -> Iterator[float]:
@@ -104,12 +122,23 @@ class CalledProcessErrorStderr(subprocess.CalledProcessError):
104
122
  if (self.returncode < 0) or (self.stderr is None):
105
123
  return str(super())
106
124
  else:
107
- err = self.stderr if isinstance(self.stderr, str) else self.stderr.decode("ascii", errors="replace")
125
+ err = (
126
+ self.stderr
127
+ if isinstance(self.stderr, str)
128
+ else self.stderr.decode("ascii", errors="replace")
129
+ )
108
130
  return "Command '%s' exit status %d: %s" % (self.cmd, self.returncode, err)
109
131
 
110
132
 
111
- def call_command(cmd: List[str], *args: str, input: Optional[str] = None, timeout: Optional[float] = None,
112
- useCLocale: bool = True, env: Optional[typing.Dict[str, str]] = None, quiet: Optional[bool] = False) -> str:
133
+ def call_command(
134
+ cmd: list[str],
135
+ *args: str,
136
+ input: Optional[str] = None,
137
+ timeout: Optional[float] = None,
138
+ useCLocale: bool = True,
139
+ env: Optional[dict[str, str]] = None,
140
+ quiet: Optional[bool] = False
141
+ ) -> str:
113
142
  """
114
143
  Simplified calling of external commands.
115
144
 
@@ -140,14 +169,30 @@ def call_command(cmd: List[str], *args: str, input: Optional[str] = None, timeou
140
169
 
141
170
  logger.debug("run command: {}".format(" ".join(cmd)))
142
171
  start_time = datetime.datetime.now()
143
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
144
- encoding='utf-8', errors="replace", env=env)
172
+ proc = subprocess.Popen(
173
+ cmd,
174
+ stdout=subprocess.PIPE,
175
+ stderr=subprocess.PIPE,
176
+ encoding="utf-8",
177
+ errors="replace",
178
+ env=env,
179
+ )
145
180
  stdout, stderr = proc.communicate(input=input, timeout=timeout)
146
181
  end_time = datetime.datetime.now()
147
182
  runtime = (end_time - start_time).total_seconds()
148
183
  sys.stderr.write(stderr)
149
184
  if proc.returncode != 0:
150
- logger.debug("command failed in {}s: {}: {}".format(runtime, " ".join(cmd), stderr.rstrip()))
151
- raise CalledProcessErrorStderr(proc.returncode, cmd, output=stdout, stderr=stderr)
152
- logger.debug("command succeeded in {}s: {}{}".format(runtime, " ".join(cmd), (': ' + stdout.rstrip()) if not quiet else ''))
185
+ logger.debug(
186
+ "command failed in {}s: {}: {}".format(
187
+ runtime, " ".join(cmd), stderr.rstrip()
188
+ )
189
+ )
190
+ raise CalledProcessErrorStderr(
191
+ proc.returncode, cmd, output=stdout, stderr=stderr
192
+ )
193
+ logger.debug(
194
+ "command succeeded in {}s: {}{}".format(
195
+ runtime, " ".join(cmd), (": " + stdout.rstrip()) if not quiet else ""
196
+ )
197
+ )
153
198
  return stdout
toil/lib/objects.py CHANGED
@@ -126,10 +126,10 @@ class InnerClass:
126
126
  if instance is None:
127
127
  return self.inner_class
128
128
  else:
129
- return self._bind( instance )
129
+ return self._bind(instance)
130
130
 
131
131
  @sync_memoize
132
- def _bind( self, _outer):
132
+ def _bind(self, _outer):
133
133
  class BoundInner(self.inner_class):
134
134
  outer = _outer
135
135
 
toil/lib/resources.py CHANGED
@@ -12,31 +12,84 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  import fnmatch
15
+ import math
15
16
  import os
16
17
  import resource
17
- from typing import List, Tuple
18
+ import sys
18
19
 
19
20
 
20
- def get_total_cpu_time_and_memory_usage() -> Tuple[float, int]:
21
+ class ResourceMonitor:
21
22
  """
22
- Gives the total cpu time of itself and all its children, and the maximum RSS memory usage of
23
- itself and its single largest child.
23
+ Global resource monitoring widget.
24
+
25
+ Presents class methods to get the resource usage of this process and child
26
+ processes, and other class methods to adjust the statistics so they can
27
+ account for e.g. resources used inside containers, or other resource usage
28
+ that *should* be billable to the current process.
24
29
  """
25
- me = resource.getrusage(resource.RUSAGE_SELF)
26
- children = resource.getrusage(resource.RUSAGE_CHILDREN)
27
- total_cpu_time = me.ru_utime + me.ru_stime + children.ru_utime + children.ru_stime
28
- total_memory_usage = me.ru_maxrss + children.ru_maxrss
29
- return total_cpu_time, total_memory_usage
30
30
 
31
+ # Store some extra usage to tack onto the stats as module-level globals
32
+ _extra_cpu_seconds: float = 0
33
+ _extra_memory_ki: int = 0
34
+
35
+ @classmethod
36
+ def record_extra_memory(cls, peak_ki: int) -> None:
37
+ """
38
+ Become responsible for the given peak memory usage, in kibibytes.
39
+
40
+ The memory will be treated as if it was used by a child process at the time
41
+ our real child processes were also using their peak memory.
42
+ """
43
+ cls._extra_memory_ki = max(cls._extra_memory_ki, peak_ki)
44
+
45
+ @classmethod
46
+ def record_extra_cpu(cls, seconds: float) -> None:
47
+ """
48
+ Become responsible for the given CPU time.
49
+
50
+ The CPU time will be treated as if it had been used by a child process.
51
+ """
52
+ cls._extra_cpu_seconds += seconds
53
+
54
+ @classmethod
55
+ def get_total_cpu_time_and_memory_usage(cls) -> tuple[float, int]:
56
+ """
57
+ Gives the total cpu time of itself and all its children, and the maximum RSS memory usage of
58
+ itself and its single largest child (in kibibytes).
59
+ """
60
+ me = resource.getrusage(resource.RUSAGE_SELF)
61
+ children = resource.getrusage(resource.RUSAGE_CHILDREN)
62
+ total_cpu_time = (
63
+ me.ru_utime
64
+ + me.ru_stime
65
+ + children.ru_utime
66
+ + children.ru_stime
67
+ + cls._extra_cpu_seconds
68
+ )
69
+ total_memory_usage = me.ru_maxrss + children.ru_maxrss
70
+ if sys.platform == "darwin":
71
+ # On Linux, getrusage works in "kilobytes" (really kibibytes), but on
72
+ # Mac it works in bytes. See
73
+ # <https://github.com/python/cpython/issues/74698>
74
+ total_memory_usage = int(math.ceil(total_memory_usage / 1024))
75
+ total_memory_usage += cls._extra_memory_ki
76
+ return total_cpu_time, total_memory_usage
31
77
 
32
- def get_total_cpu_time() -> float:
33
- """Gives the total cpu time, including the children."""
34
- me = resource.getrusage(resource.RUSAGE_SELF)
35
- childs = resource.getrusage(resource.RUSAGE_CHILDREN)
36
- return me.ru_utime + me.ru_stime + childs.ru_utime + childs.ru_stime
78
+ @classmethod
79
+ def get_total_cpu_time(cls) -> float:
80
+ """Gives the total cpu time, including the children."""
81
+ me = resource.getrusage(resource.RUSAGE_SELF)
82
+ childs = resource.getrusage(resource.RUSAGE_CHILDREN)
83
+ return (
84
+ me.ru_utime
85
+ + me.ru_stime
86
+ + childs.ru_utime
87
+ + childs.ru_stime
88
+ + cls._extra_cpu_seconds
89
+ )
37
90
 
38
91
 
39
- def glob(glob_pattern: str, directoryname: str) -> List[str]:
92
+ def glob(glob_pattern: str, directoryname: str) -> list[str]:
40
93
  """
41
94
  Walks through a directory and its subdirectories looking for files matching
42
95
  the glob_pattern and returns a list=[].