From 84d96fd797a1c53592710796b7c1917b804f02ca Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Fri, 18 Nov 2022 14:09:38 +0100 Subject: [PATCH] ovirt-img: support json output Add json as an output option, to print the progress in jsonlines[1] format. A consistent machine readable format, that can be later parsed. $ ovirt-img download-disk -c engine --output json download.raw {"transferred": 0, elapsed: 0.0, "description": "setting up"} ... {"transferred": 249561088, "size": 261095424, elapsed: 1.234567, "description": "downloading image"} {"transferred": 256901120, "size": 261095424, elapsed: 1.345678, "description": "downloading image"} {"transferred": 261095424, "size": 261095424, elapsed: 1.456789, "description": "finalizing transfer"} ... [1] https://jsonlines.org/ Fixes: #154 Signed-off-by: Albert Esteve --- ovirt_imageio/client/_options.py | 2 +- ovirt_imageio/client/_ui.py | 19 ++++++++++ test/client_options_test.py | 1 + test/client_ui_test.py | 65 +++++++++++++++++++++++++++++++- 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/ovirt_imageio/client/_options.py b/ovirt_imageio/client/_options.py index e6be676c..2661f500 100644 --- a/ovirt_imageio/client/_options.py +++ b/ovirt_imageio/client/_options.py @@ -46,7 +46,7 @@ def __repr__(self): log_level = Choices("log_level", ("debug", "info", "warning", "error")) -output_format = Choices("output_format", ("text",)) +output_format = Choices("output_format", ("text", "json")) def bool_string(value): diff --git a/ovirt_imageio/client/_ui.py b/ovirt_imageio/client/_ui.py index a0ffdc3a..354d361a 100644 --- a/ovirt_imageio/client/_ui.py +++ b/ovirt_imageio/client/_ui.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: Red Hat, Inc. # SPDX-License-Identifier: GPL-2.0-or-later +import json import sys import threading import time @@ -9,6 +10,7 @@ FORMAT_TEXT = "text" +FORMAT_JSON = "json" DEFAULT_WIDTH = 79 @@ -54,8 +56,25 @@ def draw(self, elapsed, value, transferred, size=None, self._write_line(line, end) +class JsonFormat(OutputFormat): + + def draw(self, elapsed, value, transferred, size=None, + phase=None, last=False): + progress = { + 'transferred': transferred, + 'elapsed': elapsed, + 'description': phase or "", + } + if size is not None: + progress["size"] = size + + line = json.dumps(progress) + self._write_line(line) + + OUTPUT_FORMAT = { FORMAT_TEXT: TextFormat, + FORMAT_JSON: JsonFormat, } diff --git a/test/client_options_test.py b/test/client_options_test.py index 15b64328..b42b73ca 100644 --- a/test/client_options_test.py +++ b/test/client_options_test.py @@ -346,6 +346,7 @@ def test_transfer_options_disabled(config): @pytest.mark.parametrize("output", [ "text", + "json", ]) def test_output_option(config, output): parser = _options.Parser() diff --git a/test/client_ui_test.py b/test/client_ui_test.py index 06e4a0c0..0f855a56 100644 --- a/test/client_ui_test.py +++ b/test/client_ui_test.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later import pytest +import json from ovirt_imageio import client from ovirt_imageio._internal.units import MiB, GiB @@ -28,7 +29,7 @@ def flush(self): pass -def test_draw(): +def test_draw_text(): fake_time = FakeTime() f = FakeFile() @@ -84,6 +85,68 @@ def test_draw(): assert f.last == line.ljust(79) + "\n" +def test_draw_json(): + fake_time = FakeTime() + f = FakeFile() + + # Size is unknown at this point. + pb = client.ProgressBar( + phase="setting up", output=f, format="json", now=fake_time) + assert json.loads(f.last) == { + "transferred": 0, + "elapsed": 0.0, + "description": "setting up", + } + + # Size was updated, but no bytes were transferred yet. + fake_time.now += 0.1 + pb.size = 3 * GiB + assert json.loads(f.last) == { + "transferred": 0, + "elapsed": fake_time.now, + "description": "setting up", + "size": 3 * GiB, + } + + # Phase was updated. + fake_time.now += 0.2 + pb.phase = "downloading image" + assert json.loads(f.last) == { + "transferred": 0, + "elapsed": fake_time.now, + "description": "downloading image", + "size": 3 * GiB, + } + + # All data transferred. + fake_time.now += 2.0 + pb.update(3 * GiB) + assert json.loads(f.last) == { + "transferred": 3 * GiB, + "elapsed": fake_time.now, + "description": "downloading image", + "size": 3 * GiB, + } + + # Cleaning up after download. + pb.phase = "cleaning up" + assert json.loads(f.last) == { + "transferred": 3 * GiB, + "elapsed": fake_time.now, + "description": "cleaning up", + "size": 3 * GiB, + } + + # Closing prints the final line. + pb.close() + assert json.loads(f.last) == { + "transferred": 3 * GiB, + "elapsed": fake_time.now, + "description": "cleaning up", + "size": 3 * GiB, + } + + def test_with_size(): fake_time = FakeTime() f = FakeFile()