Add Pixel C knobs to skpbench

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2369533002

Review-Url: https://codereview.chromium.org/2369533002
diff --git a/tools/skpbench/_hardware.py b/tools/skpbench/_hardware.py
index 23cfc82..f1c8c26 100644
--- a/tools/skpbench/_hardware.py
+++ b/tools/skpbench/_hardware.py
@@ -5,12 +5,21 @@
 
 import time
 
-class HardwareException(Exception):
-  def __init__(self, message, sleeptime=60):
-    Exception.__init__(self, message)
-    self.sleeptime = sleeptime
-
 class Hardware:
+  """Locks down and monitors hardware for benchmarking.
+
+  This is a common base for classes that can control the specific hardware
+  we are running on. Its purpose is to lock the hardware into a constant
+  benchmarking mode for the duration of a 'with' block. e.g.:
+
+    with hardware:
+      run_benchmark()
+
+  While benchmarking, the caller must call sanity_check() frequently to verify
+  the hardware state has not changed.
+
+  """
+
   def __init__(self):
     self.kick_in_time = 0
 
@@ -21,9 +30,60 @@
     pass
 
   def sanity_check(self):
-    '''Raises a HardwareException if any hardware state is not as expected.'''
+    """Raises a HardwareException if any hardware state is not as expected."""
     pass
 
   def sleep(self, sleeptime):
-    '''Puts the hardware into a resting state for a fixed amount of time.'''
+    """Puts the hardware into a resting state for a fixed amount of time."""
     time.sleep(sleeptime)
+
+
+class HardwareException(Exception):
+  """Gets thrown when certain hardware state is not what we expect.
+
+  Generally this happens because of thermal conditions or other variables beyond
+  our control, and the appropriate course of action is to take a short nap
+  before resuming the benchmark.
+
+  """
+
+  def __init__(self, message, sleeptime=60):
+    Exception.__init__(self, message)
+    self.sleeptime = sleeptime
+
+
+class Expectation:
+  """Simple helper for checking the readings on hardware gauges."""
+  def __init__(self, value_type, min_value=None, max_value=None,
+               exact_value=None, name=None, sleeptime=60):
+    self.value_type = value_type
+    self.min_value = min_value
+    self.max_value = max_value
+    self.exact_value = exact_value
+    self.name = name
+    self.sleeptime = sleeptime
+
+  def check(self, stringvalue):
+    typedvalue = self.value_type(stringvalue)
+    if self.min_value is not None and typedvalue < self.min_value:
+       raise HardwareException("%s is too low (%s, min=%s)" %
+                               (self.name, stringvalue, str(self.min_value)),
+                               sleeptime=self.sleeptime)
+    if self.max_value is not None and typedvalue > self.max_value:
+       raise HardwareException("%s is too high (%s, max=%s)" %
+                               (self.name, stringvalue, str(self.max_value)),
+                               sleeptime=self.sleeptime)
+    if self.exact_value is not None and typedvalue != self.exact_value:
+       raise HardwareException("unexpected %s (%s, expected=%s)" %
+                               (self.name, stringvalue, str(self.exact_value)),
+                               sleeptime=self.sleeptime)
+
+  @staticmethod
+  def check_all(expectations, stringvalues):
+    if len(stringvalues) != len(expectations):
+      raise Exception("unexpected reading from hardware gauges "
+                      "(expected %i values):\n%s" %
+                      (len(expectations), '\n'.join(stringvalues)))
+
+    for value, expected in zip(stringvalues, expectations):
+      expected.check(value)