bootperf: tolerate different occurrences of optional keyval

showbootdata aborts and prints nothing if the results dir contains
different occurrences of some keyval. For example
seconds_network_wifi_ready and seconds_network_ethernet_ready are likely
to have different occurrences, which is working as intended. Support
such data and prints a warning when this happens.

BUG=b:161488133
TEST=showbootdata displays boot statistics for the bug attachment

Change-Id: I2f314d731eaa141751aa2a2614f5c551459efa97
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crostestutils/+/2784350
Reviewed-by: Kuang-che Wu <kcwu@chromium.org>
Tested-by: Chinglin Yu <chinglinyu@chromium.org>
Commit-Queue: Chinglin Yu <chinglinyu@chromium.org>
diff --git a/performance/bootperf-bin/perfprinter.py b/performance/bootperf-bin/perfprinter.py
index 66beb96..2e025e8 100644
--- a/performance/bootperf-bin/perfprinter.py
+++ b/performance/bootperf-bin/perfprinter.py
@@ -76,10 +76,13 @@
       (valueavg, valuedev) = keyset.Statistics(stat)
       valuepct = int(100.0 * valuedev / valueavg + 0.5)
       if prevstat:
-        (deltaavg, deltadev) = keyset.DeltaStatistics(prevstat, stat)
-        if deltaavg == 0.0:
-          deltaavg = 1.0
-          print('deltaavg is zero! (delta is {} to {})'.format(prevstat, stat))
+        try:
+          (deltaavg, deltadev) = keyset.DeltaStatistics(prevstat, stat)
+          if deltaavg == 0.0:
+            deltaavg = 1.0
+            print(f'deltaavg is zero! (delta is {prevstat} to {stat})')
+        except ValueError as ve:
+          print('Warning:', ve)
 
         deltapct = int(100.0 * deltadev / deltaavg + 0.5)
       else:
diff --git a/performance/bootperf-bin/resultset.py b/performance/bootperf-bin/resultset.py
index db7858b..4f324eb 100644
--- a/performance/bootperf-bin/resultset.py
+++ b/performance/bootperf-bin/resultset.py
@@ -168,19 +168,16 @@
   def _CheckCounts(self):
     """Check the validity of the keyvals results dictionary.
 
-    Note that each keyval must have occurred the same number of times.
+    Note that keyvals might have occurred different number of times.
 
     Returns:
-      When this check succeeds, it returns the total number of occurrences;
+      When this check succeeds, it returns the max number of occurrences;
       on failure return `None`.
     """
-    check = [len(v) for v in self._keyvals.values()]
-    if not check:
+    occurrences = [len(v) for v in self._keyvals.values()]
+    if not occurrences:
       return None
-    for i in range(1, len(check)):
-      if check[i] != check[i-1]:
-        return None
-    return check[0]
+    return max(occurrences)
 
   def AddIterationResults(self, runkeys):
     """Add results for one iteration.
@@ -238,7 +235,9 @@
     Returns:
       A vector of difference.
     """
-    assert len(self._keyvals[key0]) == len(self._keyvals[key1])
+    if len(self._keyvals[key0]) != len(self._keyvals[key1]):
+      raise ValueError(f'cannot calculate deltas between {key0} and {key1} '
+                       'of different occurrences')
     return [b-a for a, b in zip(self._keyvals[key0], self._keyvals[key1])]
 
   def Statistics(self, key):