From 9fbfbb7b1a1bd958a04bf482b180861862b06c26 Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Sun, 23 Nov 2008 15:34:50 +0000 Subject: [PATCH] Enable auto-unit formatting in script output This patch enables by default the old 'human-readable' option, but in a slightly different model. The option is now called "units" and takes either: - 'h' for automatic formatting - 'm', 'g' or 't' for mebi/gibi/tebibytes If 'h' is used, we add a unit suffix, otherwise nothing is added so that parsing is easy. The default value of this unit is: - 'h' if a separator is not passed - 'm' if a separator is passed Reviewed-by: ultrotter --- lib/cli.py | 46 ++++++++++++++++++++++++++--------- lib/utils.py | 29 +++++++++++++++++----- scripts/gnt-instance | 8 ++---- scripts/gnt-job | 2 +- scripts/gnt-node | 14 +++-------- scripts/gnt-os | 3 ++- test/ganeti.utils_unittest.py | 41 +++++++++++++++++++++++-------- 7 files changed, 97 insertions(+), 46 deletions(-) diff --git a/lib/cli.py b/lib/cli.py index 6b0283414..39a080b34 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -171,9 +171,9 @@ SEP_OPT = make_option("--separator", default=None, help="Separator between output fields" " (defaults to one space)") -USEUNITS_OPT = make_option("--human-readable", default=False, - action="store_true", dest="human_readable", - help="Print sizes in human readable format") +USEUNITS_OPT = make_option("--units", default=None, + dest="units", choices=('h', 'm', 'g', 't'), + help="Specify units for output (one of hmgt)") FIELDS_OPT = make_option("-o", "--output", dest="output", action="store", type="string", help="Comma separated list of" @@ -747,18 +747,40 @@ def GenericMain(commands, override=None, aliases=None): def GenerateTable(headers, fields, separator, data, - numfields=None, unitfields=None): + numfields=None, unitfields=None, + units=None): """Prints a table with headers and different fields. - Args: - headers: Dict of header titles or None if no headers should be shown - fields: List of fields to show - separator: String used to separate fields or None for spaces - data: Data to be printed - numfields: List of fields to be aligned to right - unitfields: List of fields to be formatted as units + @type headers: dict + @param headers: dictionary mapping field names to headers for + the table + @type fields: list + @param fields: the field names corresponding to each row in + the data field + @param separator: the separator to be used; if this is None, + the default 'smart' algorithm is used which computes optimal + field width, otherwise just the separator is used between + each field + @type data: list + @param data: a list of lists, each sublist being one row to be output + @type numfields: list + @param numfields: a list with the fields that hold numeric + values and thus should be right-aligned + @type unitfields: list + @param unitfields: a list with the fields that hold numeric + values that should be formatted with the units field + @type units: string or None + @param units: the units we should use for formatting, or None for + automatic choice (human-readable for non-separator usage, otherwise + megabytes); this is a one-letter string """ + if units is None: + if separator: + units = "m" + else: + units = "h" + if numfields is None: numfields = [] if unitfields is None: @@ -795,7 +817,7 @@ def GenerateTable(headers, fields, separator, data, except ValueError: pass else: - val = row[idx] = utils.FormatUnit(val) + val = row[idx] = utils.FormatUnit(val, units) val = row[idx] = str(val) if separator is None: mlens[idx] = max(mlens[idx], len(val)) diff --git a/lib/utils.py b/lib/utils.py index 582b61a6d..92ef0aa96 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -638,23 +638,40 @@ def BuildShellCmd(template, *args): return template % args -def FormatUnit(value): +def FormatUnit(value, units): """Formats an incoming number of MiB with the appropriate unit. @type value: int @param value: integer representing the value in MiB (1048576) + @type units: char + @param units: the type of formatting we should do: + - 'h' for automatic scaling + - 'm' for MiBs + - 'g' for GiBs + - 't' for TiBs @rtype: str @return: the formatted value (with suffix) """ - if value < 1024: - return "%dM" % round(value, 0) + if units not in ('m', 'g', 't', 'h'): + raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units)) - elif value < (1024 * 1024): - return "%0.1fG" % round(float(value) / 1024, 1) + suffix = '' + + if units == 'm' or (units == 'h' and value < 1024): + if units == 'h': + suffix = 'M' + return "%d%s" % (round(value, 0), suffix) + + elif units == 'g' or (units == 'h' and value < (1024 * 1024)): + if units == 'h': + suffix = 'G' + return "%0.1f%s" % (round(float(value) / 1024, 1), suffix) else: - return "%0.1fT" % round(float(value) / 1024 / 1024, 1) + if units == 'h': + suffix = 'T' + return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix) def ParseUnit(input_string): diff --git a/scripts/gnt-instance b/scripts/gnt-instance index 928d4aa78..bec5d82c5 100755 --- a/scripts/gnt-instance +++ b/scripts/gnt-instance @@ -228,11 +228,7 @@ def ListInstances(opts, args): else: headers = None - if opts.human_readable: - unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"] - else: - unitfields = None - + unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"] numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus", "serial_no", "(disk|nic)\.count", "disk\.size/.*"] @@ -270,7 +266,7 @@ def ListInstances(opts, args): data = GenerateTable(separator=opts.separator, headers=headers, fields=selected_fields, unitfields=unitfields, - numfields=numfields, data=output) + numfields=numfields, data=output, units=opts.units) for line in data: ToStdout(line) diff --git a/scripts/gnt-job b/scripts/gnt-job index 39f074372..e1ee5604a 100755 --- a/scripts/gnt-job +++ b/scripts/gnt-job @@ -106,7 +106,7 @@ def ListJobs(opts, args): data = GenerateTable(separator=opts.separator, headers=headers, fields=selected_fields, unitfields=unitfields, - numfields=numfields, data=output) + numfields=numfields, data=output, units=opts.units) for line in data: ToStdout(line) diff --git a/scripts/gnt-node b/scripts/gnt-node index 12372ec97..823ecea91 100755 --- a/scripts/gnt-node +++ b/scripts/gnt-node @@ -115,10 +115,7 @@ def ListNodes(opts, args): else: headers = None - if opts.human_readable: - unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"] - else: - unitfields = None + unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"] numfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree", @@ -138,7 +135,7 @@ def ListNodes(opts, args): data = GenerateTable(separator=opts.separator, headers=headers, fields=selected_fields, unitfields=unitfields, - numfields=numfields, data=output) + numfields=numfields, data=output, units=opts.units) for line in data: ToStdout(line) @@ -343,16 +340,13 @@ def ListVolumes(opts, args): else: headers = None - if opts.human_readable: - unitfields = ["size"] - else: - unitfields = None + unitfields = ["size"] numfields = ["size"] data = GenerateTable(separator=opts.separator, headers=headers, fields=selected_fields, unitfields=unitfields, - numfields=numfields, data=output) + numfields=numfields, data=output, units=opts.units) for line in data: ToStdout(line) diff --git a/scripts/gnt-os b/scripts/gnt-os index 48c8336cf..a0c09a7ab 100755 --- a/scripts/gnt-os +++ b/scripts/gnt-os @@ -55,7 +55,8 @@ def ListOS(opts, args): headers = None data = GenerateTable(separator=None, headers=headers, fields=["name"], - data=[[row[0]] for row in result if row[1]]) + data=[[row[0]] for row in result if row[1]], + units=None) for line in data: ToStdout(line) diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py index ce54fd19c..34b992894 100755 --- a/test/ganeti.utils_unittest.py +++ b/test/ganeti.utils_unittest.py @@ -324,21 +324,42 @@ class TestFormatUnit(unittest.TestCase): """Test case for the FormatUnit function""" def testMiB(self): - self.assertEqual(FormatUnit(1), '1M') - self.assertEqual(FormatUnit(100), '100M') - self.assertEqual(FormatUnit(1023), '1023M') + self.assertEqual(FormatUnit(1, 'h'), '1M') + self.assertEqual(FormatUnit(100, 'h'), '100M') + self.assertEqual(FormatUnit(1023, 'h'), '1023M') + + self.assertEqual(FormatUnit(1, 'm'), '1') + self.assertEqual(FormatUnit(100, 'm'), '100') + self.assertEqual(FormatUnit(1023, 'm'), '1023') + + self.assertEqual(FormatUnit(1024, 'm'), '1024') + self.assertEqual(FormatUnit(1536, 'm'), '1536') + self.assertEqual(FormatUnit(17133, 'm'), '17133') + self.assertEqual(FormatUnit(1024 * 1024 - 1, 'm'), '1048575') def testGiB(self): - self.assertEqual(FormatUnit(1024), '1.0G') - self.assertEqual(FormatUnit(1536), '1.5G') - self.assertEqual(FormatUnit(17133), '16.7G') - self.assertEqual(FormatUnit(1024 * 1024 - 1), '1024.0G') + self.assertEqual(FormatUnit(1024, 'h'), '1.0G') + self.assertEqual(FormatUnit(1536, 'h'), '1.5G') + self.assertEqual(FormatUnit(17133, 'h'), '16.7G') + self.assertEqual(FormatUnit(1024 * 1024 - 1, 'h'), '1024.0G') + + self.assertEqual(FormatUnit(1024, 'g'), '1.0') + self.assertEqual(FormatUnit(1536, 'g'), '1.5') + self.assertEqual(FormatUnit(17133, 'g'), '16.7') + self.assertEqual(FormatUnit(1024 * 1024 - 1, 'g'), '1024.0') + + self.assertEqual(FormatUnit(1024 * 1024, 'g'), '1024.0') + self.assertEqual(FormatUnit(5120 * 1024, 'g'), '5120.0') + self.assertEqual(FormatUnit(29829 * 1024, 'g'), '29829.0') def testTiB(self): - self.assertEqual(FormatUnit(1024 * 1024), '1.0T') - self.assertEqual(FormatUnit(5120 * 1024), '5.0T') - self.assertEqual(FormatUnit(29829 * 1024), '29.1T') + self.assertEqual(FormatUnit(1024 * 1024, 'h'), '1.0T') + self.assertEqual(FormatUnit(5120 * 1024, 'h'), '5.0T') + self.assertEqual(FormatUnit(29829 * 1024, 'h'), '29.1T') + self.assertEqual(FormatUnit(1024 * 1024, 't'), '1.0') + self.assertEqual(FormatUnit(5120 * 1024, 't'), '5.0') + self.assertEqual(FormatUnit(29829 * 1024, 't'), '29.1') class TestParseUnit(unittest.TestCase): """Test case for the ParseUnit function""" -- GitLab