Enable auto-unit formatting in script output
authorIustin Pop <iustin@google.com>
Sun, 23 Nov 2008 15:34:50 +0000 (15:34 +0000)
committerIustin Pop <iustin@google.com>
Sun, 23 Nov 2008 15:34:50 +0000 (15:34 +0000)
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
lib/utils.py
scripts/gnt-instance
scripts/gnt-job
scripts/gnt-node
scripts/gnt-os
test/ganeti.utils_unittest.py

index 6b02834..39a080b 100644 (file)
@@ -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))
index 582b61a..92ef0aa 100644 (file)
@@ -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):
index 928d4aa..bec5d82 100755 (executable)
@@ -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)
index 39f0743..e1ee560 100755 (executable)
@@ -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)
 
index 12372ec..823ecea 100755 (executable)
@@ -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)
index 48c8336..a0c09a7 100755 (executable)
@@ -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)
index ce54fd1..34b9928 100755 (executable)
@@ -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"""