tools: add example json-seq render based on tqdm

Add an example render to test/demo how the json-seq based progress
works. It needs the python `tqdm` package for the actual rendering.

See the output with:
```
$ sudo OSBUILD_TEST_STORE=/var/tmp/osbuild-test-store  \
    python3 -m osbuild --libdir=. --monitor=JSONSeqMonitor --export image \
      --output-dir=/tmp/output-dir ./test/data/manifests/fedora-boot.json | ./tools/osbuild-json-seq-progress-example-renderer
```
This commit is contained in:
Michael Vogt 2023-11-24 11:24:50 +01:00 committed by Ondřej Budai
parent 83e66839bc
commit f034bef127
2 changed files with 93 additions and 0 deletions

View file

@ -197,6 +197,8 @@ def osbuild_cli() -> int:
for pid in exports:
export(pid, output_directory, object_store, manifest)
# TODO: subpress when "--monitor=JSONSeqMontior" is used
# or (be explcit) have "--no-summary" or something
if args.json:
r = fmt.output(manifest, r, object_store)
json.dump(r, sys.stdout)

View file

@ -0,0 +1,91 @@
#!/usr/bin/python3
#
# example how to use the json-seq rendering
#
import json
import sys
import tqdm
class TqdmProgressRenderer:
BAR_FMT = "{desc} ({n_fmt}/{total_fmt}): {percentage:3.0f}%|{bar}|{elapsed}"
def __init__(self, inf, outf):
self._pbar = None
self._sub_pbar = None
self._inf = inf
self._outf = outf
self._last_done = 0
self._last_sub_done = 0
def _read_json_seq_rec(self):
# *sigh* we really should be using a proper json-seq reader here
while True:
line = self._inf.readline()
if not line:
return None
try:
payload = json.loads(line.strip("\x1e"))
except json.JSONDecodeError:
self.warn(f"WARN: invalid json: {line}")
continue
return payload
def warn(self, warn):
if self._pbar:
self._pbar.write(warn)
else:
print(warn, file=self._outf)
def _init_pbar(self, pbar_name, total, pos):
pbar = getattr(self, pbar_name, None)
if pbar is not None:
return
pbar = tqdm.tqdm(total=total, position=pos, bar_format=self.BAR_FMT)
setattr(self, pbar_name, pbar)
def render(self):
while True:
js = self._read_json_seq_rec()
if js is None:
return
# main progress
main_progress = js.get("progress", {})
total = main_progress.get("total", 0)
self._init_pbar("_pbar", total, pos=0)
ctx = js["context"]
pipeline_name = ctx.get("pipeline", {}).get("name")
if pipeline_name:
self._pbar.set_description(f"Pipeline {pipeline_name}")
done = main_progress.get("done", 0)
if self._last_done < done:
self._pbar.update()
self._last_done = done
# reset sub-progress
self._last_sub_done = 0
self._sub_pbar = None
# sub progress
sub_progress = main_progress.get("progress")
if sub_progress:
total = sub_progress.get("total")
self._init_pbar("_sub_pbar", total, pos=1)
stage_name = ctx.get("pipeline", {}).get("stage", {}).get("name")
if stage_name:
self._sub_pbar.set_description(f"Stage {stage_name}")
sub_done = sub_progress.get("done", 0)
if self._last_sub_done < sub_done:
self._sub_pbar.update()
self._last_sub_done = sub_done
# (naively) handle messages (could decorate with origin)
self._pbar.write(js.get("message", "").strip())
if __name__ == "__main__":
prg = TqdmProgressRenderer(sys.stdin, sys.stdout)
prg.render()