diff --git a/lib/cli.py b/lib/cli.py index 6b0283414b46e37f8f58c725361db8eff22a1631..39a080b34733db7570a8d2867937851caf911aec 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 582b61a6dc3cc23e83fac7db48eb02b7df8baaf3..92ef0aa96337a5260ca1540a1cd015715ce48d96 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 928d4aa7845314c3df1366b5642467111a30b2d6..bec5d82c53345c9a56ff4ed68fcf5946909cc0a9 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 39f074372b7d96e338ab2680614bc5fdaf9d7710..e1ee5604aa0d36fdbb401dee8d89b31a0bc25a2a 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 12372ec97206a96866e9878f2c69e7038b1e52c0..823ecea91465a8978363223e7721f7e0c6ff64b7 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 48c8336cf0a2b04d2f6f9484b77c6d07ba952426..a0c09a7abac0d4c49fb56ad437bacf22550ab6ff 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 ce54fd19c23396a9e64a7ddf0fba7cbd670962a4..34b992894f816f10e21696a01d9a31f4636df838 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"""