monitor: tweak/simplify Progress

Tweak the Progress class to be simpler. Given that progress does
not need to support arbitrary depth but only has a single level
the class now just exposes "sub_progress" to the caller.

When the main progress is advanced the sub_progress is now fully
deleted instead of just reset. The rational is that when the main
progress is done and advances a step it is very likely that a
new sub_progress is required and it's most likely an error if
the same sub_progress will get re-used.

This means that `reset()` can be removed as it's not used anymore
(and YAGNI). We can add it back when we have a use-case.

It also change the code so that "total" starts with 0 instead
of `None` (principle of least surprise). This means that now
`progress.incr()` is called in the JSONSeqMonitor() for
`finish()` and `result()` to indicate that the pipeline/stage
is finished.
This commit is contained in:
Michael Vogt 2023-11-22 11:08:47 +01:00 committed by Ondřej Budai
parent de9ead53a2
commit 3fbd0b2a73
2 changed files with 42 additions and 36 deletions

View file

@ -113,30 +113,29 @@ class Context:
class Progress: class Progress:
"""Progress represents generic progress information.
A progress can contain a sub_progress to track more
nested progresses. Any increment of a parent progress
will the reset the sub_progress to None and a new
sub_progress needs to be provided.
Keyword arguments:
name -- user visible name for the progress
total -- total steps required to finish the progress
"""
def __init__(self, name: str, total: int): def __init__(self, name: str, total: int):
self.name = name self.name = name
self.total = total self.total = total
self.done = None self.done = 0
self._sub_progress: Optional[Progress] = None self.sub_progress: Optional[Progress] = None
def incr(self, depth=0): def incr(self):
if depth > 0: """Increment the "done" count"""
self._sub_progress.incr(depth - 1) self.done += 1
else: if self.sub_progress:
if self.done is None: self.sub_progress = None
self.done = 0
else:
self.done += 1
if self._sub_progress:
self._sub_progress.reset()
def reset(self):
self.done = None
if self._sub_progress:
self._sub_progress.reset()
def sub_progress(self, prog: "Progress"):
self._sub_progress = prog
def as_dict(self): def as_dict(self):
d = { d = {
@ -144,8 +143,8 @@ class Progress:
"total": self.total, "total": self.total,
"done": self.done, "done": self.done,
} }
if self._sub_progress: if self.sub_progress:
d["progress"] = self._sub_progress.as_dict() d["progress"] = self.sub_progress.as_dict()
return d return d
@ -299,23 +298,28 @@ class JSONSeqMonitor(BaseMonitor):
self._progress = Progress("pipelines", len(manifest.pipelines)) self._progress = Progress("pipelines", len(manifest.pipelines))
self._context = Context(origin="org.osbuild") self._context = Context(origin="org.osbuild")
def result(self, result): # result is for modules
pass def result(self, result: osbuild.pipeline.BuildResult):
# TODO: check pipeline id?
if self._progress.sub_progress:
self._progress.sub_progress.incr()
def begin(self, pipeline: osbuild.Pipeline): def begin(self, pipeline: osbuild.Pipeline):
self._context.set_pipeline(pipeline) self._context.set_pipeline(pipeline)
self._progress.sub_progress(Progress("stages", len(pipeline.stages))) self._progress.sub_progress = Progress("stages", len(pipeline.stages))
# finish is for pipelines
def finish(self, result: dict):
self._progress.incr() self._progress.incr()
def stage(self, stage: osbuild.Stage): def stage(self, stage: osbuild.Stage):
self._module(stage) self._module(stage)
def assembler(self, assembler): def assembler(self, assembler: osbuild.Stage):
self._module(assembler) self._module(assembler)
def _module(self, module): def _module(self, module: osbuild.Stage):
self._context.set_stage(module) self._context.set_stage(module)
self._progress.incr(depth=1)
def log(self, message, origin: Optional[str] = None): def log(self, message, origin: Optional[str] = None):
oo = self._context.origin oo = self._context.origin

View file

@ -160,25 +160,27 @@ def test_context():
def test_progress(): def test_progress():
prog = Progress("test", total=12) prog = Progress("test", total=12)
subprog = Progress("test-sub1", total=3) prog.sub_progress = Progress("test-sub1", total=3)
prog.sub_progress(subprog) # we start empty
assert prog.done is None # starts with None until the first incr()
prog.incr()
prog.incr(depth=1)
progdict = prog.as_dict() progdict = prog.as_dict()
assert progdict["done"] == 0 assert progdict["done"] == 0
assert progdict["progress"]["done"] == 0 assert progdict["progress"]["done"] == 0
prog.incr(depth=1) # incr a sub_progress only affect sub_progress
prog.sub_progress.incr()
progdict = prog.as_dict() progdict = prog.as_dict()
assert progdict["done"] == 0 assert progdict["done"] == 0
assert progdict["progress"]["done"] == 1 assert progdict["progress"]["done"] == 1
prog.sub_progress.incr()
progdict = prog.as_dict()
assert progdict["done"] == 0
assert progdict["progress"]["done"] == 2
prog.incr() prog.incr()
progdict = prog.as_dict() progdict = prog.as_dict()
assert progdict["done"] == 1 assert progdict["done"] == 1
assert progdict["progress"]["done"] is None, "sub-progress did not reset" assert progdict.get("progress") == None, "sub-progress did not reset"
def test_json_progress_monitor(): def test_json_progress_monitor():