skpbench: support pixel phone

Adds a script for pixel hardware with conservatively low clocks.

Bug: skia:
Change-Id: I1ade703ab9f0b4aefc9cf630e3d2efb996afd69f
Reviewed-on: https://skia-review.googlesource.com/62343
Reviewed-by: Eric Boren <borenet@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/tools/skpbench/_adb.py b/tools/skpbench/_adb.py
index b3b10b7..3bf61bc 100644
--- a/tools/skpbench/_adb.py
+++ b/tools/skpbench/_adb.py
@@ -5,6 +5,7 @@
 
 from __future__ import print_function
 import re
+import time
 import subprocess
 import sys
 
@@ -44,6 +45,13 @@
   def remount(self):
     self.__invoke('remount')
 
+  def reboot(self):
+    self.__is_root = None
+    self.shell('reboot')
+    self.__invoke('wait-for-device')
+    while '1' != self.check('getprop sys.boot_completed').strip():
+      time.sleep(1)
+
   def __echo_shell_cmd(self, cmd):
     escaped = [re.sub(r'([^a-zA-Z0-9])', r'\\\1', x)
                for x in cmd.strip().splitlines()]
diff --git a/tools/skpbench/_hardware.py b/tools/skpbench/_hardware.py
index de8848d..9283243 100644
--- a/tools/skpbench/_hardware.py
+++ b/tools/skpbench/_hardware.py
@@ -41,10 +41,6 @@
     """Prints any info that may help improve or debug hardware monitoring."""
     pass
 
-  def sleep(self, sleeptime):
-    """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.
diff --git a/tools/skpbench/_hardware_android.py b/tools/skpbench/_hardware_android.py
index ebaba0a..fd001b2 100644
--- a/tools/skpbench/_hardware_android.py
+++ b/tools/skpbench/_hardware_android.py
@@ -20,6 +20,10 @@
         self._adb.check('cat /proc/sys/kernel/randomize_va_space')
 
   def __enter__(self):
+    Hardware.__enter__(self)
+    if not self._adb.is_root() and self._adb.root():
+      self._adb.remount()
+
     self._adb.shell('\n'.join([
       # turn on airplane mode.
       '''
@@ -53,7 +57,7 @@
       print("WARNING: no adb root access; results may be unreliable.",
             file=sys.stderr)
 
-    return Hardware.__enter__(self)
+    return self
 
   def __exit__(self, exception_type, exception_value, traceback):
     Hardware.__exit__(self, exception_type, exception_value, traceback)
@@ -102,6 +106,3 @@
       done''')
 
     Hardware.print_debug_diagnostics(self)
-
-  def sleep(self, sleeptime):
-    Hardware.sleep(self, sleeptime)
diff --git a/tools/skpbench/_hardware_nexus_6p.py b/tools/skpbench/_hardware_nexus_6p.py
index 077933b..58eb52f 100644
--- a/tools/skpbench/_hardware_nexus_6p.py
+++ b/tools/skpbench/_hardware_nexus_6p.py
@@ -14,13 +14,14 @@
     HardwareAndroid.__init__(self, adb)
 
   def __enter__(self):
+    HardwareAndroid.__enter__(self)
     self._lock_clocks()
-    return HardwareAndroid.__enter__(self)
+    return self
 
   def __exit__(self, exception_type, exception_value, exception_traceback):
+    self._unlock_clocks()
     HardwareAndroid.__exit__(self, exception_type,
                              exception_value, exception_traceback)
-    self._unlock_clocks()
 
   def _lock_clocks(self):
     if not self._adb.is_root():
@@ -148,8 +149,3 @@
        for i in range(4, 7)]
 
     Expectation.check_all(expectations, result.splitlines())
-
-  def sleep(self, sleeptime):
-    self._unlock_clocks()
-    HardwareAndroid.sleep(self, sleeptime)
-    self._lock_clocks()
diff --git a/tools/skpbench/_hardware_pixel.py b/tools/skpbench/_hardware_pixel.py
new file mode 100644
index 0000000..aa75c5f
--- /dev/null
+++ b/tools/skpbench/_hardware_pixel.py
@@ -0,0 +1,217 @@
+# Copyright 2017 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from _hardware import HardwareException, Expectation
+from _hardware_android import HardwareAndroid
+from collections import namedtuple
+import itertools
+
+CPU_CLOCK_RATE = 1670400
+GPU_CLOCK_RATE = 510000000
+
+DEVFREQ_DIRNAME = '/sys/class/devfreq'
+DEVFREQ_THROTTLE = 0.74
+DEVFREQ_BLACKLIST = ('b00000.qcom,kgsl-3d0', 'soc:qcom,kgsl-busmon',
+                     'soc:qcom,m4m')
+DevfreqKnobs = namedtuple('knobs',
+                          ('available_governors', 'available_frequencies',
+                           'governor', 'min_freq', 'max_freq', 'cur_freq'))
+
+class HardwarePixel(HardwareAndroid):
+  def __init__(self, adb):
+    HardwareAndroid.__init__(self, adb)
+    self._discover_devfreqs()
+
+  def __enter__(self):
+    HardwareAndroid.__enter__(self)
+    self._lock_clocks()
+    return self
+
+  def __exit__(self, exception_type, exception_value, exception_traceback):
+    # pixel struggles waking up; just pull a hard reboot.
+    self._adb.reboot()
+
+  def _lock_clocks(self):
+    if not self._adb.is_root():
+      return
+
+    self._adb.shell('\n'.join(['''\
+      stop thermal-engine
+      stop thermald
+      stop perfd
+      stop mpdecision''',
+
+      # enable and lock the two fast cores.
+      '''
+      for N in 3 2; do
+        echo 1 > /sys/devices/system/cpu/cpu$N/online
+        echo userspace > /sys/devices/system/cpu/cpu$N/cpufreq/scaling_governor
+        echo %i > /sys/devices/system/cpu/cpu$N/cpufreq/scaling_max_freq
+        echo %i > /sys/devices/system/cpu/cpu$N/cpufreq/scaling_min_freq
+        echo %i > /sys/devices/system/cpu/cpu$N/cpufreq/scaling_setspeed
+      done''' % tuple(CPU_CLOCK_RATE for _ in range(3)),
+
+      # turn off the two slow cores
+      '''
+      for N in 1 0; do
+        echo 0 > /sys/devices/system/cpu/cpu$N/online
+      done''',
+
+      # gpu perf commands from
+      # https://developer.qualcomm.com/qfile/28823/lm80-p0436-11_adb_commands.pdf
+      '''
+      echo 0 > /sys/class/kgsl/kgsl-3d0/bus_split
+      echo 1 > /sys/class/kgsl/kgsl-3d0/force_bus_on
+      echo 1 > /sys/class/kgsl/kgsl-3d0/force_rail_on
+      echo 1 > /sys/class/kgsl/kgsl-3d0/force_clk_on
+      echo 1000000 > /sys/class/kgsl/kgsl-3d0/idle_timer
+      echo userspace > /sys/class/kgsl/kgsl-3d0/devfreq/governor
+      echo 2 > /sys/class/kgsl/kgsl-3d0/max_pwrlevel
+      echo 2 > /sys/class/kgsl/kgsl-3d0/min_pwrlevel
+      echo 2 > /sys/class/kgsl/kgsl-3d0/thermal_pwrlevel
+      echo %i > /sys/class/kgsl/kgsl-3d0/devfreq/max_freq
+      echo %i > /sys/class/kgsl/kgsl-3d0/devfreq/min_freq
+      echo %i > /sys/class/kgsl/kgsl-3d0/max_gpuclk
+      echo %i > /sys/class/kgsl/kgsl-3d0/gpuclk''' %
+      tuple(GPU_CLOCK_RATE for _ in range(4))] + \
+
+      self._devfreq_lock_cmds))
+
+  def _unlock_clocks(self):
+    if not self._adb.is_root():
+      return
+
+    self._adb.shell('\n'.join(
+      self._devfreq_unlock_cmds + [
+
+      # restore gpu settings to default.
+      '''
+      echo 133000000 > /sys/class/kgsl/kgsl-3d0/devfreq/min_freq
+      echo 600000000 > /sys/class/kgsl/kgsl-3d0/devfreq/max_freq
+      echo 0 > /sys/class/kgsl/kgsl-3d0/gpuclk
+      echo msm-adreno-tz > /sys/class/kgsl/kgsl-3d0/devfreq/governor
+      echo 6 > /sys/class/kgsl/kgsl-3d0/min_pwrlevel
+      echo 0 > /sys/class/kgsl/kgsl-3d0/max_pwrlevel
+      echo 1 > /sys/class/kgsl/kgsl-3d0/thermal_pwrlevel
+      echo 0 > /sys/class/kgsl/kgsl-3d0/idle_timer
+      echo 0 > /sys/class/kgsl/kgsl-3d0/force_clk_on
+      echo 0 > /sys/class/kgsl/kgsl-3d0/force_rail_on
+      echo 0 > /sys/class/kgsl/kgsl-3d0/force_bus_on
+      echo 1 > /sys/class/kgsl/kgsl-3d0/bus_split''',
+
+      # turn the disabled cores back on.
+      '''
+      for N in 0 1; do
+        echo 1 > /sys/devices/system/cpu/cpu$N/online
+      done''',
+
+      # unlock the 2 enabled big cores.
+      '''
+      for N in 2 3; do
+        echo 307200 > /sys/devices/system/cpu/cpu$N/cpufreq/scaling_min_freq
+        echo 2150400 > /sys/devices/system/cpu/cpu$N/cpufreq/scaling_max_freq
+        echo 0 > /sys/devices/system/cpu/cpu$N/cpufreq/scaling_setspeed
+        echo sched > /sys/devices/system/cpu/cpu$N/cpufreq/scaling_governor
+      done''',
+
+      '''
+      start mpdecision
+      start perfd
+      start thermald
+      start thermal-engine''']))
+
+  def sanity_check(self):
+    HardwareAndroid.sanity_check(self)
+
+    if not self._adb.is_root():
+      return
+
+    result = self._adb.check(' '.join(
+      ['cat',
+       '/sys/class/power_supply/battery/capacity',
+       '/sys/devices/system/cpu/online'] + \
+      ['/sys/devices/system/cpu/cpu%i/cpufreq/scaling_cur_freq' % i
+       for i in range(2, 4)] + \
+      ['/sys/class/kgsl/kgsl-3d0/thermal_pwrlevel',
+       '/sys/kernel/debug/clk/gpu_gx_gfx3d_clk/measure',
+       '/sys/kernel/debug/clk/bimc_clk/measure',
+       '/sys/class/thermal/thermal_zone22/temp',
+       '/sys/class/thermal/thermal_zone23/temp'] + \
+      self._devfreq_sanity_knobs))
+
+    expectations = \
+      [Expectation(int, min_value=30, name='battery', sleeptime=30*60),
+       Expectation(str, exact_value='2-3', name='online cpus')] + \
+      [Expectation(int, exact_value=CPU_CLOCK_RATE, name='cpu_%i clock rate' %i)
+       for i in range(2, 4)] + \
+      [Expectation(int, exact_value=2, name='gpu thermal power level'),
+       Expectation(long, min_value=(GPU_CLOCK_RATE - 5000),
+                   max_value=(GPU_CLOCK_RATE + 5000),
+                   name='measured gpu clock'),
+       Expectation(long, min_value=902390000, max_value=902409999,
+                   name='measured ddr clock', sleeptime=10),
+       Expectation(int, max_value=41000, name='pm8994_tz temperature'),
+       Expectation(int, max_value=40, name='msm_therm temperature')] + \
+      self._devfreq_sanity_expectations
+
+    Expectation.check_all(expectations, result.splitlines())
+
+  def _discover_devfreqs(self):
+    self._devfreq_lock_cmds = list()
+    self._devfreq_unlock_cmds = list()
+    self._devfreq_sanity_knobs = list()
+    self._devfreq_sanity_expectations = list()
+
+    results = iter(self._adb.check('''\
+      KNOBS='%s'
+      for DEVICE in %s/*; do
+        if cd $DEVICE && ls $KNOBS >/dev/null; then
+          basename $DEVICE
+          cat $KNOBS
+        fi
+      done 2>/dev/null''' %
+      (' '.join(DevfreqKnobs._fields), DEVFREQ_DIRNAME)).splitlines())
+
+    while True:
+      batch = tuple(itertools.islice(results, 1 + len(DevfreqKnobs._fields)))
+      if not batch:
+        break
+
+      devfreq = batch[0]
+      if devfreq in DEVFREQ_BLACKLIST:
+        continue
+
+      path = '%s/%s' % (DEVFREQ_DIRNAME, devfreq)
+
+      knobs = DevfreqKnobs(*batch[1:])
+      if not 'performance' in knobs.available_governors.split():
+        print('WARNING: devfreq %s does not have performance governor' % path)
+        continue
+
+      self._devfreq_lock_cmds.append('echo performance > %s/governor' % path)
+      self._devfreq_unlock_cmds.append('echo %s > %s/governor' %
+                                       (knobs.governor, path))
+
+      frequencies = map(int, knobs.available_frequencies.split())
+      if frequencies:
+        # choose the lowest frequency that is >= DEVFREQ_THROTTLE * max.
+        frequencies.sort()
+        target = DEVFREQ_THROTTLE * frequencies[-1]
+        idx = len(frequencies) - 1
+        while idx > 0 and frequencies[idx - 1] >= target:
+          idx -= 1
+        bench_frequency = frequencies[idx]
+        self._devfreq_lock_cmds.append('echo %i > %s/min_freq' %
+                                      (bench_frequency, path))
+        self._devfreq_lock_cmds.append('echo %i > %s/max_freq' %
+                                      (bench_frequency, path))
+        self._devfreq_unlock_cmds.append('echo %s > %s/min_freq' %
+                                        (knobs.min_freq, path))
+        self._devfreq_unlock_cmds.append('echo %s > %s/max_freq' %
+                                        (knobs.max_freq, path))
+        self._devfreq_sanity_knobs.append('%s/cur_freq' % path)
+        self._devfreq_sanity_expectations.append(
+          Expectation(int, exact_value=bench_frequency,
+                      name='%s/cur_freq' % path))
diff --git a/tools/skpbench/_hardware_pixel_c.py b/tools/skpbench/_hardware_pixel_c.py
index a1cd17a..cdd9ff6 100644
--- a/tools/skpbench/_hardware_pixel_c.py
+++ b/tools/skpbench/_hardware_pixel_c.py
@@ -15,13 +15,14 @@
     HardwareAndroid.__init__(self, adb)
 
   def __enter__(self):
+    HardwareAndroid.__enter__(self)
     self._lock_clocks()
-    return HardwareAndroid.__enter__(self)
+    return self
 
   def __exit__(self, exception_type, exception_value, exception_traceback):
+    self._unlock_clocks()
     HardwareAndroid.__exit__(self, exception_type,
                              exception_value, exception_traceback)
-    self._unlock_clocks()
 
   def filter_line(self, line):
     JUNK = ['NvRmPrivGetChipPlatform: Could not read platform information',
@@ -109,8 +110,3 @@
       [Expectation(str, exact_value=GPU_EMC_PROFILE, name='gpu/emc profile')]
 
     Expectation.check_all(expectations, result.splitlines())
-
-  def sleep(self, sleeptime):
-    self._unlock_clocks()
-    HardwareAndroid.sleep(self, sleeptime)
-    self._lock_clocks()
diff --git a/tools/skpbench/skpbench.py b/tools/skpbench/skpbench.py
index e9c7ec0..b8bae74 100755
--- a/tools/skpbench/skpbench.py
+++ b/tools/skpbench/skpbench.py
@@ -243,45 +243,53 @@
     resultsfile.flush()
 
 def run_benchmarks(configs, skps, hardware, resultsfile=None):
-  emit_result(SKPBench.get_header(), resultsfile)
+  hasheader = False
   benches = collections.deque([(skp, config, FLAGS.max_stddev)
                                for skp in skps
                                for config in configs])
   while benches:
-    benchargs = benches.popleft()
-    with SKPBench(*benchargs) as skpbench:
-      try:
-        skpbench.execute(hardware)
-        if skpbench.best_result:
-          emit_result(skpbench.best_result.format(FLAGS.suffix), resultsfile)
-        else:
-          print("WARNING: no result for %s with config %s" %
-                (skpbench.skp, skpbench.config), file=sys.stderr)
-
-      except StddevException:
-        retry_max_stddev = skpbench.max_stddev * math.sqrt(2)
-        if FLAGS.verbosity >= 1:
-          print("stddev is too high for %s/%s (%s%%, max=%.2f%%), "
-                "re-queuing with max=%.2f%%." %
-                (skpbench.best_result.config, skpbench.best_result.bench,
-                 skpbench.best_result.stddev, skpbench.max_stddev,
-                 retry_max_stddev),
-                file=sys.stderr)
-        benches.append((skpbench.skp, skpbench.config, retry_max_stddev,
-                        skpbench.best_result))
-
-      except HardwareException as exception:
-        skpbench.terminate()
-        if FLAGS.verbosity >= 4:
-          hardware.print_debug_diagnostics()
-        if FLAGS.verbosity >= 1:
-          print("%s; taking a %i second nap..." %
-                (exception.message, exception.sleeptime), file=sys.stderr)
-        benches.appendleft(benchargs) # retry the same bench next time.
-        hardware.sleep(exception.sleeptime)
-        if FLAGS.verbosity >= 4:
-          hardware.print_debug_diagnostics()
+    try:
+      with hardware:
         SKPBench.run_warmup(hardware.warmup_time, configs[0])
+        if not hasheader:
+          emit_result(SKPBench.get_header(), resultsfile)
+          hasheader = True
+        while benches:
+          benchargs = benches.popleft()
+          with SKPBench(*benchargs) as skpbench:
+            try:
+              skpbench.execute(hardware)
+              if skpbench.best_result:
+                emit_result(skpbench.best_result.format(FLAGS.suffix),
+                            resultsfile)
+              else:
+                print("WARNING: no result for %s with config %s" %
+                      (skpbench.skp, skpbench.config), file=sys.stderr)
+
+            except StddevException:
+              retry_max_stddev = skpbench.max_stddev * math.sqrt(2)
+              if FLAGS.verbosity >= 1:
+                print("stddev is too high for %s/%s (%s%%, max=%.2f%%), "
+                      "re-queuing with max=%.2f%%." %
+                      (skpbench.best_result.config, skpbench.best_result.bench,
+                       skpbench.best_result.stddev, skpbench.max_stddev,
+                       retry_max_stddev),
+                      file=sys.stderr)
+              benches.append((skpbench.skp, skpbench.config, retry_max_stddev,
+                              skpbench.best_result))
+
+            except HardwareException as exception:
+              skpbench.terminate()
+              if FLAGS.verbosity >= 4:
+                hardware.print_debug_diagnostics()
+              if FLAGS.verbosity >= 1:
+                print("%s; exiting benchmark mode to take a %i second nap..." %
+                      (exception.message, exception.sleeptime), file=sys.stderr)
+              benches.appendleft(benchargs) # retry the same bench next time.
+              raise # wake hw up from benchmarking mode before the nap.
+
+    except HardwareException as exception:
+      time.sleep(exception.sleeptime)
 
 def main():
   # Delimiter is ',' or ' ', skip if nested inside parens (e.g. gpu(a=b,c=d)).
@@ -296,6 +304,9 @@
     if model == 'Pixel C':
       from _hardware_pixel_c import HardwarePixelC
       hardware = HardwarePixelC(adb)
+    elif model == 'Pixel':
+      from _hardware_pixel import HardwarePixel
+      hardware = HardwarePixel(adb)
     elif model == 'Nexus 6P':
       from _hardware_nexus_6p import HardwareNexus6P
       hardware = HardwareNexus6P(adb)
@@ -307,13 +318,11 @@
   else:
     hardware = Hardware()
 
-  with hardware:
-    SKPBench.run_warmup(hardware.warmup_time, configs[0])
-    if FLAGS.resultsfile:
-      with open(FLAGS.resultsfile, mode='a+') as resultsfile:
-        run_benchmarks(configs, skps, hardware, resultsfile=resultsfile)
-    else:
-      run_benchmarks(configs, skps, hardware)
+  if FLAGS.resultsfile:
+    with open(FLAGS.resultsfile, mode='a+') as resultsfile:
+      run_benchmarks(configs, skps, hardware, resultsfile=resultsfile)
+  else:
+    run_benchmarks(configs, skps, hardware)
 
 
 if __name__ == '__main__':