Source code for pycif.plugins.obsoperators.standard.transforms.utils.dask.graph_status
import json
import logging
import os
import time
from datetime import datetime
from dask.callbacks import Callback
[docs]
class GraphStatus(Callback):
"""Record the progress of the transforms and dump it to ``dask_status.js``.
The file is loaded by ``dask_graph.html`` when the page is opened or
reloaded. It is written when the computation starts, then at most every
``every_seconds`` seconds and/or every ``every_ntasks`` completed
transforms (0 disables each criterion), and at the end of the
computation, including when it fails.
Only local schedulers (synchronous, threads, processes) trigger the
recording; a distributed client does not.
"""
def __init__(self, names, rundir, every_seconds=30, every_ntasks=0):
super().__init__()
# names maps Dask keys to graph node ids
self.names = names
self.out_file = os.path.join(rundir, "dask_status.js")
self.every_seconds = every_seconds
self.every_ntasks = every_ntasks
self.tasks = {}
self.t0 = time.monotonic()
self.last_write = self.t0
self.ndone_since_write = 0
def _start(self, dsk):
self.t0 = time.monotonic()
self._write()
def _pretask(self, key, dsk, state):
if key not in self.names:
return
self.tasks[self.names[key]] = ["running", self._now(), None]
self._maybe_write()
def _posttask(self, key, result, dsk, state, id):
if key not in self.names:
return
status = self.tasks.setdefault(self.names[key], [None, None, None])
status[0] = "done"
status[2] = self._now()
self.ndone_since_write += 1
self._maybe_write()
def _finish(self, dsk, state, errored):
# Tasks still running when the computation fails either raised
# the error or were abandoned by Dask
if errored:
for status in self.tasks.values():
if status[0] == "running":
status[0] = "failed"
self._write(final=True, errored=errored)
def _now(self):
return round(time.monotonic() - self.t0, 3)
def _maybe_write(self):
if (self.every_ntasks
and self.ndone_since_write >= self.every_ntasks) \
or (self.every_seconds
and time.monotonic() - self.last_write
>= self.every_seconds):
self._write()
def _write(self, final=False, errored=False):
status = {
"updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"final": final,
"errored": errored,
"tasks": self.tasks,
}
# Write then rename, so that the page never reads a partial file.
# Never let a failed write break the computation
tmp_file = f"{self.out_file}.tmp"
try:
with open(tmp_file, "w") as f:
f.write(f"window.cifStatus = {json.dumps(status)};\n")
os.replace(tmp_file, self.out_file)
except OSError as e:
logging.warning(f"Could not write the Dask graph status: {e}")
self.last_write = time.monotonic()
self.ndone_since_write = 0