libpipe 0.9.1__tar.gz → 0.10__tar.gz
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.
- {libpipe-0.9.1 → libpipe-0.10}/PKG-INFO +5 -4
- {libpipe-0.9.1 → libpipe-0.10}/libpipe/worker.py +30 -6
- {libpipe-0.9.1 → libpipe-0.10}/pyproject.toml +1 -1
- {libpipe-0.9.1 → libpipe-0.10}/LICENSE +0 -0
- {libpipe-0.9.1 → libpipe-0.10}/README.md +0 -0
- {libpipe-0.9.1 → libpipe-0.10}/libpipe/__init__.py +0 -0
- {libpipe-0.9.1 → libpipe-0.10}/libpipe/attrmap.py +0 -0
- {libpipe-0.9.1 → libpipe-0.10}/libpipe/futils.py +0 -0
- {libpipe-0.9.1 → libpipe-0.10}/libpipe/msutils.py +0 -0
- {libpipe-0.9.1 → libpipe-0.10}/libpipe/settings.py +0 -0
- {libpipe-0.9.1 → libpipe-0.10}/libpipe/taskrun.py +0 -0
- {libpipe-0.9.1 → libpipe-0.10}/libpipe/tools/__init__.py +0 -0
- {libpipe-0.9.1 → libpipe-0.10}/libpipe/tools/libnodes_bash.sh +0 -0
- {libpipe-0.9.1 → libpipe-0.10}/libpipe/tools/nodetool.py +0 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: libpipe
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10
|
|
4
4
|
Summary: Pipeline orchestration and node management utilities
|
|
5
|
-
License: GPL-3.0-or-later
|
|
5
|
+
License-Expression: GPL-3.0-or-later
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Author: Florent Mertens
|
|
7
8
|
Author-email: florent.mertens@gmail.com
|
|
8
9
|
Requires-Python: >=3.10,<4.0
|
|
9
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
16
|
Provides-Extra: dev
|
|
16
17
|
Provides-Extra: docs
|
|
17
18
|
Requires-Dist: asyncssh (>=2.0)
|
|
@@ -51,6 +51,10 @@ ALLOC_MAX_REQUESTS = 3
|
|
|
51
51
|
ALLOC_WORKERS_PER_NODE = 32
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def now_str():
|
|
55
|
+
return datetime.datetime.now().isoformat(sep=' ', timespec='milliseconds')
|
|
56
|
+
|
|
57
|
+
|
|
54
58
|
def mem_mb(text):
|
|
55
59
|
'''Memory such as 40GB, 40G or 512MB in MB (GB is 1024 MB, as in Slurm).'''
|
|
56
60
|
m = re.fullmatch(r'\s*(\d+(?:\.\d+)?)\s*([KMGT]?)(?:i?B)?\s*', str(text), re.IGNORECASE)
|
|
@@ -259,7 +263,13 @@ class Task:
|
|
|
259
263
|
line = style(line, fg='red')
|
|
260
264
|
print(f'[{self.name}:{host}] {line}')
|
|
261
265
|
|
|
262
|
-
|
|
266
|
+
def log_event(self, text):
|
|
267
|
+
"""Write a timestamped libpipe event to the log file only."""
|
|
268
|
+
if self.fd is not None:
|
|
269
|
+
self.fd.write(f'[libpipe] {now_str()} {text}\n')
|
|
270
|
+
|
|
271
|
+
async def log_stream(self, stream, host, err=False, stamp_prefix=None):
|
|
272
|
+
"""Log the lines of stream; those starting with stamp_prefix get the current time."""
|
|
263
273
|
while True:
|
|
264
274
|
try:
|
|
265
275
|
line = await stream.readline()
|
|
@@ -268,6 +278,8 @@ class Task:
|
|
|
268
278
|
continue
|
|
269
279
|
if not line:
|
|
270
280
|
break
|
|
281
|
+
if stamp_prefix and line.startswith(stamp_prefix):
|
|
282
|
+
line = f'{now_str()} '.encode() + line
|
|
271
283
|
self.log(line, host, err=err)
|
|
272
284
|
|
|
273
285
|
def close_log(self):
|
|
@@ -618,15 +630,14 @@ class SlurmClient:
|
|
|
618
630
|
if self.alloc_id:
|
|
619
631
|
args.append(f'--jobid={self.alloc_id}')
|
|
620
632
|
if self.exact:
|
|
621
|
-
|
|
622
|
-
args += ['--exact', '--quiet']
|
|
633
|
+
args.append('--exact')
|
|
623
634
|
if task.cpus:
|
|
624
635
|
args += [f'--cpus-per-task={task.cpus}', f'--mem={task.mem_mb}M']
|
|
625
636
|
threads = ' '.join(f'{v}={task.cpus}' for v in THREAD_VARS)
|
|
626
637
|
command = f'export {threads}; {command}'
|
|
627
638
|
job_id, options = '${SLURM_JOB_ID}.${SLURM_STEP_ID}', []
|
|
628
639
|
args += self._time_option(options) + options
|
|
629
|
-
marker = f'echo "{SLURM_MARKER}{job_id} on $(hostname -s)"'
|
|
640
|
+
marker = f'echo "{SLURM_MARKER}{job_id} on $(hostname -s) at $(date \'+%F %T.%3N\')"'
|
|
630
641
|
return f'{shlex.join(args)} bash -c {shlex.quote(f"{marker}; {command}")}'
|
|
631
642
|
|
|
632
643
|
async def _run(self, *args):
|
|
@@ -700,22 +711,35 @@ class SlurmClient:
|
|
|
700
711
|
task.slurm_job_id = None
|
|
701
712
|
if self.alloc_nodes:
|
|
702
713
|
await self.ensure_allocation()
|
|
703
|
-
cpus =
|
|
714
|
+
cpus = 0
|
|
715
|
+
if self.steps and self.cpus_total is not None:
|
|
716
|
+
task.log_event(f'waiting for {min(task.cpus or 1, self.cpus_total)} CPUs '
|
|
717
|
+
f'({self.cpus_free} of {self.cpus_total} free)')
|
|
718
|
+
cpus = await self._acquire_cpus(task.cpus or 1)
|
|
704
719
|
try:
|
|
705
720
|
process = await asyncio.create_subprocess_shell(
|
|
706
721
|
self.srun_command(task), stdout=asyncio.subprocess.PIPE,
|
|
707
722
|
stderr=asyncio.subprocess.PIPE, preexec_fn=os.setpgrp, env=self.env)
|
|
723
|
+
task.log_event(f'srun started{self._cpus_state()}')
|
|
708
724
|
task.set_process(Process(self, process))
|
|
709
725
|
# srun waits for the job or the step to start: running from the marker line
|
|
710
726
|
task.start_time = None
|
|
711
727
|
|
|
712
728
|
await asyncio.gather(
|
|
713
729
|
self._log_stdout(task, process.stdout),
|
|
714
|
-
task.log_stream(process.stderr, self.host, err=True),
|
|
730
|
+
task.log_stream(process.stderr, self.host, err=True, stamp_prefix=b'srun: '),
|
|
715
731
|
)
|
|
716
732
|
task.returncode = await process.wait()
|
|
733
|
+
task.log_event(f'srun exited with code {task.returncode}')
|
|
717
734
|
finally:
|
|
718
735
|
await self._release_cpus(cpus)
|
|
736
|
+
if cpus:
|
|
737
|
+
task.log_event(f'{cpus} CPUs released{self._cpus_state()}')
|
|
738
|
+
|
|
739
|
+
def _cpus_state(self):
|
|
740
|
+
if self.cpus_total is None:
|
|
741
|
+
return ''
|
|
742
|
+
return f' ({self.cpus_free} of {self.cpus_total} CPUs free)'
|
|
719
743
|
|
|
720
744
|
async def terminate(self, task, sig=signal.SIGTERM):
|
|
721
745
|
job_id = getattr(task, 'slurm_job_id', None)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|