[shell-perf-web] Improve formatting of displayed values



commit 9e9933b07952a6032de78e5e8daccbe7f1e8ba11
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Wed May 26 18:40:10 2010 -0400

    Improve formatting of displayed values
    
    For each displayed metric in a row of an output table, use the
    actual metric values, the difference between the different values
    in the row, and the supplied units to come up with an appropriate
    number of significant figures.
    
    For seconds and bytes, pick between us/ms/s and B/KiB/MiB based on
    the values, not based upon the raw suppplied units.

 shell/perf/report_table.py          |  154 +++++++++++++++++++++++++++++++++-
 templates/include/report_table.html |    2 +-
 2 files changed, 150 insertions(+), 6 deletions(-)
---
diff --git a/shell/perf/report_table.py b/shell/perf/report_table.py
index a55ca90..fb5500a 100644
--- a/shell/perf/report_table.py
+++ b/shell/perf/report_table.py
@@ -1,6 +1,107 @@
 from models import Metric
+import math
 import sys
 
+BYTE_UNITS = {
+    'B':   1,
+    'KiB': 1024,
+    'MiB': 1024*1024
+}
+
+TIME_UNITS = {
+    's':  1,
+    'ms': 0.001,
+    'us': 0.000001
+}
+
+def format_values(values, units):
+    # This routine figures out how to display the values on a line
+    # of the report using three pieces of information
+    #
+    #  - The absolute magnitude of the values
+    #  - The difference between the values
+    #  - The units for the given metric
+    #
+    # I've marked various arbitrary parameters below as TWEAKABLE
+
+    if len(values) == 0:
+        return []
+
+    high = max(max(values), - min(values))
+    suffix = ''
+
+    if units in BYTE_UNITS:
+        mult = BYTE_UNITS[units]
+
+        # If we have a difference of only a few bytes, then
+        # we don't want to display fractional K/M with a lot
+        # of precision
+        diff = mult * (max(values) - min(values))
+        if diff == 0 or diff > 100:
+
+            # TWEAKABLE: transition points from B => K => M
+            if high * mult >= 1024 * 1024:
+                mult = mult / (1024. * 1024.)
+                suffix = 'M'
+                units = 'MiB'
+            elif high * mult >= 1024:
+                mult = mult / 1024.
+                suffix = 'K'
+                units = 'KiB'
+            else:
+                units = 'B'
+
+        high = high * mult
+        values = [ mult * v for v in values ]
+
+    elif units in TIME_UNITS:
+        mult = TIME_UNITS[units]
+
+        # TWEAKABLE: transition points from us => s => s
+        if high * mult >= 0.1:
+            units = suffix = 's'
+        elif high * mult >= 0.0001:
+            units = suffix = 'ms'
+            mult *= 1000
+        else:
+            units = suffix = 'us'
+            mult *= 1000000
+
+        high = high * mult
+        values = [ mult * v for v in values ]
+
+    # Determine how many digits we need to avoid scientific notation
+    if high == 0:
+        digits = 1
+    else:
+        digits = 1 + math.floor(math.log10(high))
+        # TWEAKABLE: minimum number of displayed significant digits
+        #   before we switch to scientific notation
+        digits = max(digits, 3)
+        # TWEAKABLE: maximum number of displayed significant digits
+        #   before we switch to scientific notation
+        digits = min(digits, 6)
+
+        # If we have multiple values, determine how many digits we need
+        # to distinguish the values
+        if len(values) > 1:
+            diff = max(values) - min(values)
+            if diff > 0:
+                diff_digits = 1 + math.floor(math.log10(high)) - math.floor(math.log10(diff))
+                digits = max(digits, diff_digits)
+
+        digits = int(digits)
+
+    format = '%.*g' + suffix
+    result = []
+    for v in values:
+        if v is None:
+            result.append("")
+        else:
+            result.append(format % (digits, v))
+
+    return result, units
+
 class ReportTable:
     def __init__(self):
         self.reports = []
@@ -39,10 +140,12 @@ class ReportTable:
                     m = report_metrics[metric.name]
                     values.append(m.value)
                 else:
-                    values.append(None)
+                    values.append(m.value)
 
+            formatted, units = format_values(values, metric.units)
             self.__rows.append({ 'metric': metric,
-                                 'values': values })
+                                 'units': units,
+                                 'values': formatted })
                 
     @property
     def rows(self):
@@ -81,10 +184,11 @@ class RunTable:
             metric = metrics[name]
             # For ReportTable, row.metric is a Metric object, here it's just a dictionary
             # that looks much the same to to the template
+            formatted, units = format_values(metric['values'], metric['units'])
             self.__rows.append({ 'metric': { 'name': name,
-                                             'description': metric['description'],
-                                             'units': metric['units'] },
-                                 'values': metric['values'] })
+                                             'description': metric['description'] },
+                                 'units': units,
+                                 'values': formatted })
 
     @property
     def col_headers(self):
@@ -95,3 +199,43 @@ class RunTable:
     def rows(self):
         self.__get_data()
         return self.__rows
+
+if __name__ == '__main__':
+    def test(values, units, expected):
+        results, new_units = format_values(values, units)
+        if results != expected:
+            raise AssertionError('Formatting %r (%s), expected %r, got %r' %
+                                 (values, units, expected, results))
+
+    # 0 length list
+    test([], '', [])
+
+    # Test points at which we give up and use scientific notation
+    test([0.00001], '', ['1e-05'])
+    test([0.0001], '', ['0.0001'])
+    test([100000], '', ['100000'])
+    test([1000000], '', ['1e+06'])
+
+    # Check points at which we switch between us/ms/s
+    test([90], 'us', ['90us'])
+    test([110], 'us', ['0.11ms'])
+    test([90], 'ms', ['90ms'])
+    test([110], 'ms', ['0.11s'])
+
+    # Check seconds converting to something else
+    test([0.09], 's', ['90ms'])
+
+    # Check points at which we switch between B/KB/MB
+    test([1023], 'B', ['1023'])
+    test([1024], 'B', ['1K'])
+    test([1024*1024 - 1], 'B', ['1024K'])
+    test([1024*1024], 'B', ['1M'])
+
+    # Check KB => B, MB => KB
+    test([0.1], 'KiB', ['102'])
+    test([0.1], 'MiB', ['102K'])
+
+    test([1100000,1200000], '', ['1.1e+06', '1.2e+06'])
+    test([1000001,1000002], '', ['1000001', '1000002'])
+    test([1.10000,1.20000], '', ['1.1', '1.2'])
+    test([1.00001,1.00002], '', ['1.00001', '1.00002'])
diff --git a/templates/include/report_table.html b/templates/include/report_table.html
index 6913acc..d972bf7 100644
--- a/templates/include/report_table.html
+++ b/templates/include/report_table.html
@@ -15,7 +15,7 @@
   <tbody>
     {% for row in report_table.rows %}
     <tr class="metric-row metric-row-{% cycle 'odd' 'even' %}">
-      <th title="{{ row.metric.description }} ({{row.metric.units}})">{{ row.metric.name }}</th>
+      <th title="{{ row.metric.description }} ({{row.units}})">{{ row.metric.name }}</th>
       {% for v in row.values %}
       <td>{{ v }}</td>
       {% endfor %}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]