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.
- toil/__init__.py +122 -315
- toil/batchSystems/__init__.py +1 -0
- toil/batchSystems/abstractBatchSystem.py +173 -89
- toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
- toil/batchSystems/awsBatch.py +244 -135
- toil/batchSystems/cleanup_support.py +26 -16
- toil/batchSystems/contained_executor.py +31 -28
- toil/batchSystems/gridengine.py +86 -50
- toil/batchSystems/htcondor.py +166 -89
- toil/batchSystems/kubernetes.py +632 -382
- toil/batchSystems/local_support.py +20 -15
- toil/batchSystems/lsf.py +134 -81
- toil/batchSystems/lsfHelper.py +13 -11
- toil/batchSystems/mesos/__init__.py +41 -29
- toil/batchSystems/mesos/batchSystem.py +290 -151
- toil/batchSystems/mesos/executor.py +79 -50
- toil/batchSystems/mesos/test/__init__.py +31 -23
- toil/batchSystems/options.py +46 -28
- toil/batchSystems/registry.py +53 -19
- toil/batchSystems/singleMachine.py +296 -125
- toil/batchSystems/slurm.py +603 -138
- toil/batchSystems/torque.py +47 -33
- toil/bus.py +186 -76
- toil/common.py +664 -368
- toil/cwl/__init__.py +1 -1
- toil/cwl/cwltoil.py +1136 -483
- toil/cwl/utils.py +17 -22
- toil/deferred.py +63 -42
- toil/exceptions.py +5 -3
- toil/fileStores/__init__.py +5 -5
- toil/fileStores/abstractFileStore.py +140 -60
- toil/fileStores/cachingFileStore.py +717 -269
- toil/fileStores/nonCachingFileStore.py +116 -87
- toil/job.py +1225 -368
- toil/jobStores/abstractJobStore.py +416 -266
- toil/jobStores/aws/jobStore.py +863 -477
- toil/jobStores/aws/utils.py +201 -120
- toil/jobStores/conftest.py +3 -2
- toil/jobStores/fileJobStore.py +292 -154
- toil/jobStores/googleJobStore.py +140 -74
- toil/jobStores/utils.py +36 -15
- toil/leader.py +668 -272
- toil/lib/accelerators.py +115 -18
- toil/lib/aws/__init__.py +74 -31
- toil/lib/aws/ami.py +122 -87
- toil/lib/aws/iam.py +284 -108
- toil/lib/aws/s3.py +31 -0
- toil/lib/aws/session.py +214 -39
- toil/lib/aws/utils.py +287 -231
- toil/lib/bioio.py +13 -5
- toil/lib/compatibility.py +11 -6
- toil/lib/conversions.py +104 -47
- toil/lib/docker.py +131 -103
- toil/lib/ec2.py +361 -199
- toil/lib/ec2nodes.py +174 -106
- toil/lib/encryption/_dummy.py +5 -3
- toil/lib/encryption/_nacl.py +10 -6
- toil/lib/encryption/conftest.py +1 -0
- toil/lib/exceptions.py +26 -7
- toil/lib/expando.py +5 -3
- toil/lib/ftp_utils.py +217 -0
- toil/lib/generatedEC2Lists.py +127 -19
- toil/lib/humanize.py +6 -2
- toil/lib/integration.py +341 -0
- toil/lib/io.py +141 -15
- toil/lib/iterables.py +4 -2
- toil/lib/memoize.py +12 -8
- toil/lib/misc.py +66 -21
- toil/lib/objects.py +2 -2
- toil/lib/resources.py +68 -15
- toil/lib/retry.py +126 -81
- toil/lib/threading.py +299 -82
- toil/lib/throttle.py +16 -15
- toil/options/common.py +843 -409
- toil/options/cwl.py +175 -90
- toil/options/runner.py +50 -0
- toil/options/wdl.py +73 -17
- toil/provisioners/__init__.py +117 -46
- toil/provisioners/abstractProvisioner.py +332 -157
- toil/provisioners/aws/__init__.py +70 -33
- toil/provisioners/aws/awsProvisioner.py +1145 -715
- toil/provisioners/clusterScaler.py +541 -279
- toil/provisioners/gceProvisioner.py +282 -179
- toil/provisioners/node.py +155 -79
- toil/realtimeLogger.py +34 -22
- toil/resource.py +137 -75
- toil/server/app.py +128 -62
- toil/server/celery_app.py +3 -1
- toil/server/cli/wes_cwl_runner.py +82 -53
- toil/server/utils.py +54 -28
- toil/server/wes/abstract_backend.py +64 -26
- toil/server/wes/amazon_wes_utils.py +21 -15
- toil/server/wes/tasks.py +121 -63
- toil/server/wes/toil_backend.py +142 -107
- toil/server/wsgi_app.py +4 -3
- toil/serviceManager.py +58 -22
- toil/statsAndLogging.py +224 -70
- toil/test/__init__.py +282 -183
- toil/test/batchSystems/batchSystemTest.py +460 -210
- toil/test/batchSystems/batch_system_plugin_test.py +90 -0
- toil/test/batchSystems/test_gridengine.py +173 -0
- toil/test/batchSystems/test_lsf_helper.py +67 -58
- toil/test/batchSystems/test_slurm.py +110 -49
- toil/test/cactus/__init__.py +0 -0
- toil/test/cactus/test_cactus_integration.py +56 -0
- toil/test/cwl/cwlTest.py +496 -287
- toil/test/cwl/measure_default_memory.cwl +12 -0
- toil/test/cwl/not_run_required_input.cwl +29 -0
- toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
- toil/test/cwl/seqtk_seq.cwl +1 -1
- toil/test/docs/scriptsTest.py +69 -46
- toil/test/jobStores/jobStoreTest.py +427 -264
- toil/test/lib/aws/test_iam.py +118 -50
- toil/test/lib/aws/test_s3.py +16 -9
- toil/test/lib/aws/test_utils.py +5 -6
- toil/test/lib/dockerTest.py +118 -141
- toil/test/lib/test_conversions.py +113 -115
- toil/test/lib/test_ec2.py +58 -50
- toil/test/lib/test_integration.py +104 -0
- toil/test/lib/test_misc.py +12 -5
- toil/test/mesos/MesosDataStructuresTest.py +23 -10
- toil/test/mesos/helloWorld.py +7 -6
- toil/test/mesos/stress.py +25 -20
- toil/test/options/__init__.py +13 -0
- toil/test/options/options.py +42 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
- toil/test/provisioners/clusterScalerTest.py +440 -250
- toil/test/provisioners/clusterTest.py +166 -44
- toil/test/provisioners/gceProvisionerTest.py +174 -100
- toil/test/provisioners/provisionerTest.py +25 -13
- toil/test/provisioners/restartScript.py +5 -4
- toil/test/server/serverTest.py +188 -141
- toil/test/sort/restart_sort.py +137 -68
- toil/test/sort/sort.py +134 -66
- toil/test/sort/sortTest.py +91 -49
- toil/test/src/autoDeploymentTest.py +141 -101
- toil/test/src/busTest.py +20 -18
- toil/test/src/checkpointTest.py +8 -2
- toil/test/src/deferredFunctionTest.py +49 -35
- toil/test/src/dockerCheckTest.py +32 -24
- toil/test/src/environmentTest.py +135 -0
- toil/test/src/fileStoreTest.py +539 -272
- toil/test/src/helloWorldTest.py +7 -4
- toil/test/src/importExportFileTest.py +61 -31
- toil/test/src/jobDescriptionTest.py +46 -21
- toil/test/src/jobEncapsulationTest.py +2 -0
- toil/test/src/jobFileStoreTest.py +74 -50
- toil/test/src/jobServiceTest.py +187 -73
- toil/test/src/jobTest.py +121 -71
- toil/test/src/miscTests.py +19 -18
- toil/test/src/promisedRequirementTest.py +82 -36
- toil/test/src/promisesTest.py +7 -6
- toil/test/src/realtimeLoggerTest.py +10 -6
- toil/test/src/regularLogTest.py +71 -37
- toil/test/src/resourceTest.py +80 -49
- toil/test/src/restartDAGTest.py +36 -22
- toil/test/src/resumabilityTest.py +9 -2
- toil/test/src/retainTempDirTest.py +45 -14
- toil/test/src/systemTest.py +12 -8
- toil/test/src/threadingTest.py +44 -25
- toil/test/src/toilContextManagerTest.py +10 -7
- toil/test/src/userDefinedJobArgTypeTest.py +8 -5
- toil/test/src/workerTest.py +73 -23
- toil/test/utils/toilDebugTest.py +103 -33
- toil/test/utils/toilKillTest.py +4 -5
- toil/test/utils/utilsTest.py +245 -106
- toil/test/wdl/wdltoil_test.py +818 -149
- toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
- toil/toilState.py +120 -35
- toil/utils/toilConfig.py +13 -4
- toil/utils/toilDebugFile.py +44 -27
- toil/utils/toilDebugJob.py +214 -27
- toil/utils/toilDestroyCluster.py +11 -6
- toil/utils/toilKill.py +8 -3
- toil/utils/toilLaunchCluster.py +256 -140
- toil/utils/toilMain.py +37 -16
- toil/utils/toilRsyncCluster.py +32 -14
- toil/utils/toilSshCluster.py +49 -22
- toil/utils/toilStats.py +356 -273
- toil/utils/toilStatus.py +292 -139
- toil/utils/toilUpdateEC2Instances.py +3 -1
- toil/version.py +12 -12
- toil/wdl/utils.py +5 -5
- toil/wdl/wdltoil.py +3913 -1033
- toil/worker.py +367 -184
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
- toil-8.0.0.dist-info/METADATA +173 -0
- toil-8.0.0.dist-info/RECORD +253 -0
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
- toil-6.1.0a1.dist-info/METADATA +0 -125
- toil-6.1.0a1.dist-info/RECORD +0 -237
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
- {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,
|
|
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:
|
|
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(
|
|
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
|
|
84
|
+
raise ValueError(f"Not a valid ISO datetime in UTC: {s}")
|
|
81
85
|
else:
|
|
82
|
-
fmt =
|
|
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 ==
|
|
92
|
+
if s == "True":
|
|
89
93
|
return True
|
|
90
|
-
elif s ==
|
|
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
|
|
10
|
+
from collections.abc import Iterator
|
|
11
11
|
from contextlib import closing
|
|
12
|
-
from typing import
|
|
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 =
|
|
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((
|
|
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
|
|
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
|
|
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(
|
|
55
|
-
return
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
112
|
-
|
|
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(
|
|
144
|
-
|
|
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(
|
|
151
|
-
|
|
152
|
-
|
|
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(
|
|
129
|
+
return self._bind(instance)
|
|
130
130
|
|
|
131
131
|
@sync_memoize
|
|
132
|
-
def _bind(
|
|
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
|
-
|
|
18
|
+
import sys
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
class ResourceMonitor:
|
|
21
22
|
"""
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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) ->
|
|
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=[].
|