skpbench: add warmup run

Does actual work while waiting for hardware settings to kick in,
rather than just sleeping.

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

Review-Url: https://codereview.chromium.org/2410373002
diff --git a/tools/skpbench/_hardware.py b/tools/skpbench/_hardware.py
index 9c929c4..525c4c1 100644
--- a/tools/skpbench/_hardware.py
+++ b/tools/skpbench/_hardware.py
@@ -21,7 +21,7 @@
   """
 
   def __init__(self):
-    self.kick_in_time = 0
+    self.warmup_time = 0
 
   def __enter__(self):
     return self
diff --git a/tools/skpbench/_hardware_android.py b/tools/skpbench/_hardware_android.py
index d7990dc..abeab71 100644
--- a/tools/skpbench/_hardware_android.py
+++ b/tools/skpbench/_hardware_android.py
@@ -11,7 +11,7 @@
 class HardwareAndroid(Hardware):
   def __init__(self, adb):
     Hardware.__init__(self)
-    self.kick_in_time = 5
+    self.warmup_time = 5
     self._adb = adb
     self._is_root = self._adb.attempt_root()
     if self._is_root:
diff --git a/tools/skpbench/skpbench.cpp b/tools/skpbench/skpbench.cpp
index 6d0381a..f452d9d 100644
--- a/tools/skpbench/skpbench.cpp
+++ b/tools/skpbench/skpbench.cpp
@@ -9,11 +9,14 @@
 #include "GrContextFactory.h"
 #include "SkCanvas.h"
 #include "SkOSFile.h"
+#include "SkPerlinNoiseShader.h"
 #include "SkPicture.h"
+#include "SkPictureRecorder.h"
 #include "SkStream.h"
 #include "SkSurface.h"
 #include "SkSurfaceProps.h"
 #include "picture_utils.h"
+#include "sk_tool_utils.h"
 #include "flags/SkCommandLineFlags.h"
 #include "flags/SkCommonFlagsConfig.h"
 #include <stdlib.h>
@@ -38,7 +41,7 @@
 DEFINE_int32(sampleMs, 50, "minimum duration of a sample");
 DEFINE_bool(gpuClock, false, "time on the gpu clock (gpu work only)");
 DEFINE_bool(fps, false, "use fps instead of ms");
-DEFINE_string(skp, "", "path to a single .skp file to benchmark");
+DEFINE_string(skp, "", "path to a single .skp file, or 'warmup' for a builtin warmup run");
 DEFINE_string(png, "", "if set, save a .png proof to disk at this file location");
 DEFINE_int32(verbosity, 4, "level of verbosity (0=none to 5=debug)");
 DEFINE_bool(suppressHeader, false, "don't print a header row before the results");
@@ -86,6 +89,7 @@
 };
 
 static void draw_skp_and_flush(SkCanvas*, const SkPicture*);
+static sk_sp<SkPicture> create_warmup_skp();
 static bool mkdir_p(const SkString& name);
 static SkString join(const SkCommandLineFlags::StringArray&);
 static void exitf(ExitErr, const char* format, ...);
@@ -230,31 +234,38 @@
     SkCommandLineConfigArray configs;
     ParseConfigs(FLAGS_config, &configs);
     if (configs.count() != 1 || !(config = configs[0]->asConfigGpu())) {
-        exitf(ExitErr::kUsage, "invalid config %s, must specify one (and only one) GPU config",
+        exitf(ExitErr::kUsage, "invalid config '%s': must specify one (and only one) GPU config",
                                join(FLAGS_config).c_str());
     }
 
     // Parse the skp.
     if (FLAGS_skp.count() != 1) {
-        exitf(ExitErr::kUsage, "invalid skp %s, must specify (and only one) skp path name.",
+        exitf(ExitErr::kUsage, "invalid skp '%s': must specify a single skp file, or 'warmup'",
                                join(FLAGS_skp).c_str());
     }
-    const char* skpfile = FLAGS_skp[0];
-    std::unique_ptr<SkStream> skpstream(SkStream::MakeFromFile(skpfile));
-    if (!skpstream) {
-        exitf(ExitErr::kIO, "failed to open skp file %s", skpfile);
-    }
-    sk_sp<SkPicture> skp = SkPicture::MakeFromStream(skpstream.get());
-    if (!skp) {
-        exitf(ExitErr::kData, "failed to parse skp file %s", skpfile);
+    sk_sp<SkPicture> skp;
+    SkString skpname;
+    if (0 == strcmp(FLAGS_skp[0], "warmup")) {
+        skp = create_warmup_skp();
+        skpname = "warmup";
+    } else {
+        const char* skpfile = FLAGS_skp[0];
+        std::unique_ptr<SkStream> skpstream(SkStream::MakeFromFile(skpfile));
+        if (!skpstream) {
+            exitf(ExitErr::kIO, "failed to open skp file %s", skpfile);
+        }
+        skp = SkPicture::MakeFromStream(skpstream.get());
+        if (!skp) {
+            exitf(ExitErr::kData, "failed to parse skp file %s", skpfile);
+        }
+        skpname = SkOSPath::Basename(skpfile);
     }
     int width = SkTMin(SkScalarCeilToInt(skp->cullRect().width()), 2048),
         height = SkTMin(SkScalarCeilToInt(skp->cullRect().height()), 2048);
     if (FLAGS_verbosity >= 3 &&
         (width != skp->cullRect().width() || height != skp->cullRect().height())) {
         fprintf(stderr, "%s is too large (%ix%i), cropping to %ix%i.\n",
-                        SkOSPath::Basename(skpfile).c_str(),
-                        SkScalarCeilToInt(skp->cullRect().width()),
+                        skpname.c_str(), SkScalarCeilToInt(skp->cullRect().width()),
                         SkScalarCeilToInt(skp->cullRect().height()), width, height);
     }
 
@@ -295,6 +306,7 @@
                                      width, height, config->getTag().c_str());
     }
 
+    // Run the benchmark.
     std::vector<Sample> samples;
     if (FLAGS_sampleMs > 0) {
         // +1 because we might take one more sample in order to have an odd number.
@@ -302,8 +314,6 @@
     } else {
         samples.reserve(2 * FLAGS_duration);
     }
-
-    // Run the benchmark.
     SkCanvas* canvas = surface->getCanvas();
     canvas->translate(-skp->cullRect().x(), -skp->cullRect().y());
     if (!FLAGS_gpuClock) {
@@ -315,7 +325,7 @@
         run_gpu_time_benchmark(testCtx->gpuTimer(), testCtx->fenceSync(), canvas, skp.get(),
                                &samples);
     }
-    print_result(samples, config->getTag().c_str(), SkOSPath::Basename(skpfile).c_str());
+    print_result(samples, config->getTag().c_str(), skpname.c_str());
 
     // Save a proof (if one was requested).
     if (!FLAGS_png.isEmpty()) {
@@ -342,6 +352,30 @@
     canvas->flush();
 }
 
+static sk_sp<SkPicture> create_warmup_skp() {
+    static constexpr SkRect bounds{0, 0, 500, 500};
+    SkPictureRecorder recorder;
+    SkCanvas* recording = recorder.beginRecording(bounds);
+
+    recording->clear(SK_ColorWHITE);
+
+    SkPaint stroke;
+    stroke.setStyle(SkPaint::kStroke_Style);
+    stroke.setStrokeWidth(2);
+
+    // Use a big path to (theoretically) warmup the CPU.
+    SkPath bigPath;
+    sk_tool_utils::make_big_path(bigPath);
+    recording->drawPath(bigPath, stroke);
+
+    // Use a perlin shader to warmup the GPU.
+    SkPaint perlin;
+    perlin.setShader(SkPerlinNoiseShader::MakeTurbulence(0.1f, 0.1f, 1, 0, nullptr));
+    recording->drawRect(bounds, perlin);
+
+    return recorder.finishRecordingAsPicture();
+}
+
 bool mkdir_p(const SkString& dirname) {
     if (dirname.isEmpty()) {
         return true;
@@ -351,8 +385,8 @@
 
 static SkString join(const SkCommandLineFlags::StringArray& stringArray) {
     SkString joined;
-    for (int i = 0; i < FLAGS_config.count(); ++i) {
-        joined.appendf(i ? " %s" : "%s", FLAGS_config[i]);
+    for (int i = 0; i < stringArray.count(); ++i) {
+        joined.appendf(i ? " %s" : "%s", stringArray[i]);
     }
     return joined;
 }
diff --git a/tools/skpbench/skpbench.py b/tools/skpbench/skpbench.py
index 932d8d0..9598162 100755
--- a/tools/skpbench/skpbench.py
+++ b/tools/skpbench/skpbench.py
@@ -67,6 +67,11 @@
 else:
   import _os_path as _path
 
+def dump_commandline_if_verbose(commandline):
+  if FLAGS.verbosity >= 4:
+    quoted = ['\'%s\'' % re.sub(r'([\\\'])', r'\\\1', x) for x in commandline]
+    print(' '.join(quoted), file=sys.stderr)
+
 
 class StddevException(Exception):
   pass
@@ -111,7 +116,26 @@
 
   @classmethod
   def print_header(cls):
-    subprocess.call(cls.ARGV + ['--duration', '0'])
+    commandline = cls.ARGV + ['--duration', '0']
+    dump_commandline_if_verbose(commandline)
+    subprocess.call(commandline)
+
+  @classmethod
+  def run_warmup(cls, warmup_time):
+    if not warmup_time:
+      return
+    print('running %i second warmup...' % warmup_time)
+    commandline = cls.ARGV + ['--duration', str(warmup_time * 1000),
+                              '--config', 'gpu',
+                              '--skp', 'warmup']
+    dump_commandline_if_verbose(commandline)
+    output = subprocess.check_output(commandline).decode('utf-8')
+    # validate the warmup run output.
+    for line in output.split('\n'):
+      match = BenchResult.match(line.rstrip())
+      if match and match.bench == 'warmup':
+        return
+    raise Exception('Invalid warmup output:\n%s' % output)
 
   def __init__(self, skp, config, max_stddev, best_result=None):
     self.skp = skp
@@ -143,9 +167,7 @@
       pngfile = _path.join(FLAGS.write_path, self.config,
                            _path.basename(self.skp) + '.png')
       commandline.extend(['--png', pngfile])
-    if (FLAGS.verbosity >= 4):
-      quoted = ['\'%s\'' % re.sub(r'([\\\'])', r'\\\1', x) for x in commandline]
-      print(' '.join(quoted), file=sys.stderr)
+    dump_commandline_if_verbose(commandline)
     self._proc = subprocess.Popen(commandline, stdout=subprocess.PIPE)
     self._monitor = SubprocessMonitor(self._queue, self._proc)
     self._monitor.start()
@@ -233,13 +255,12 @@
         if FLAGS.verbosity >= 5:
           hardware.print_debug_diagnostics()
         skpbench.terminate()
-        naptime = max(hardware.kick_in_time, exception.sleeptime)
         if FLAGS.verbosity >= 1:
           print("%s; taking a %i second nap..." %
-                (exception.message, naptime), file=sys.stderr)
+                (exception.message, exception.sleeptime), file=sys.stderr)
         benches.appendleft(benchargs) # retry the same bench next time.
-        hardware.sleep(naptime - hardware.kick_in_time)
-        time.sleep(hardware.kick_in_time)
+        hardware.sleep(exception.sleeptime)
+        SKPBench.run_warmup(hardware.warmup_time)
 
 
 def main():
@@ -263,10 +284,7 @@
     hardware = Hardware()
 
   with hardware:
-    if hardware.kick_in_time:
-      print("sleeping %i seconds to allow hardware settings to kick in..." %
-            hardware.kick_in_time, file=sys.stderr)
-      time.sleep(hardware.kick_in_time)
+    SKPBench.run_warmup(hardware.warmup_time)
     run_benchmarks(configs, skps, hardware)