Add option to dump images from nanobench.
authorbsalomon <bsalomon@google.com>
Thu, 7 Aug 2014 21:28:50 +0000 (14:28 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 7 Aug 2014 21:28:50 +0000 (14:28 -0700)
Add option to set the repeat count to any number, replacs the --runOnce flag.

R=mtklein@google.com

Author: bsalomon@google.com

Review URL: https://codereview.chromium.org/450743002

bench/nanobench.cpp
include/core/SkOSFile.h
src/utils/SkOSFile.cpp
tests/OSPathTest.cpp

index c808183..245aa0e 100644 (file)
 
 __SK_FORCE_IMAGE_DECODER_LINKING;
 
-#if SK_DEBUG
-    DEFINE_bool(runOnce, true, "Run each benchmark just once?");
+static const int kAutoTuneLoops = -1;
+
+static const int kDefaultLoops = 
+#ifdef SK_DEBUG
+    1;
 #else
-    DEFINE_bool(runOnce, false, "Run each benchmark just once?");
+    kAutoTuneLoops;
 #endif
 
+static SkString loops_help_txt() {
+    SkString help;
+    help.printf("Number of times to run each bench. Set this to %d to auto-"
+                "tune for each bench. Timings are only reported when auto-tuning.",
+                kAutoTuneLoops);
+    return help;
+}
+
+DEFINE_int32(loops, kDefaultLoops, loops_help_txt().c_str());
+
+DEFINE_string2(writePath, w, "", "If set, write benches here as .pngs.");
+
 DEFINE_int32(samples, 10, "Number of samples to measure for each bench.");
 DEFINE_int32(overheadLoops, 100000, "Loops to estimate timer overhead.");
 DEFINE_double(overheadGoal, 0.0001,
@@ -67,6 +82,9 @@ static SkString humanize(double ms) {
 #define HUMANIZE(ms) humanize(ms).c_str()
 
 static double time(int loops, Benchmark* bench, SkCanvas* canvas, SkGLContextHelper* gl) {
+    if (canvas) {
+        canvas->clear(SK_ColorWHITE);
+    }
     WallTimer timer;
     timer.start();
     if (bench) {
@@ -105,17 +123,50 @@ static int clamp_loops(int loops) {
     return loops;
 }
 
+static bool write_canvas_png(SkCanvas* canvas, const SkString& filename) {
+    if (filename.isEmpty()) {
+        return false;
+    }
+    if (kUnknown_SkColorType == canvas->imageInfo().fColorType) {
+        return false;
+    }
+    SkBitmap bmp;
+    bmp.setInfo(canvas->imageInfo());
+    if (!canvas->readPixels(&bmp, 0, 0)) {
+        SkDebugf("Can't read canvas pixels.\n");
+        return false;
+    }
+    SkString dir = SkOSPath::Dirname(filename.c_str());
+    if (!sk_mkdir(dir.c_str())) {
+        SkDebugf("Can't make dir %s.\n", dir.c_str());
+        return false;
+    }
+    SkFILEWStream stream(filename.c_str());
+    if (!stream.isValid()) {
+        SkDebugf("Can't write %s.\n", filename.c_str());
+        return false;
+    }
+    if (!SkImageEncoder::EncodeStream(&stream, bmp, SkImageEncoder::kPNG_Type, 100)) {
+        SkDebugf("Can't encode a PNG.\n");
+        return false;
+    }
+    return true;
+}
+
+static int kFailedLoops = -2;
 static int cpu_bench(const double overhead, Benchmark* bench, SkCanvas* canvas, double* samples) {
     // First figure out approximately how many loops of bench it takes to make overhead negligible.
     double bench_plus_overhead = 0.0;
     int round = 0;
-    while (bench_plus_overhead < overhead) {
-        if (round++ == FLAGS_maxCalibrationAttempts) {
-            SkDebugf("WARNING: Can't estimate loops for %s (%s vs. %s); skipping.\n",
-                     bench->getName(), HUMANIZE(bench_plus_overhead), HUMANIZE(overhead));
-            return 0;
+    if (kAutoTuneLoops == FLAGS_loops) {
+        while (bench_plus_overhead < overhead) {
+            if (round++ == FLAGS_maxCalibrationAttempts) {
+                SkDebugf("WARNING: Can't estimate loops for %s (%s vs. %s); skipping.\n",
+                         bench->getName(), HUMANIZE(bench_plus_overhead), HUMANIZE(overhead));
+                return kFailedLoops;
+            }
+            bench_plus_overhead = time(1, bench, canvas, NULL);
         }
-        bench_plus_overhead = time(1, bench, canvas, NULL);
     }
 
     // Later we'll just start and stop the timer once but loop N times.
@@ -134,9 +185,13 @@ static int cpu_bench(const double overhead, Benchmark* bench, SkCanvas* canvas,
     //       bench_plus_overhead - overhead)
     //
     // Luckily, this also works well in practice. :)
-    const double numer = overhead / FLAGS_overheadGoal - overhead;
-    const double denom = bench_plus_overhead - overhead;
-    const int loops = clamp_loops(FLAGS_runOnce ? 1 : (int)ceil(numer / denom));
+    int loops = FLAGS_loops;
+    if (kAutoTuneLoops == loops) {
+        const double numer = overhead / FLAGS_overheadGoal - overhead;
+        const double denom = bench_plus_overhead - overhead;
+        loops = (int)ceil(numer / denom);
+    }
+    loops = clamp_loops(loops);
 
     for (int i = 0; i < FLAGS_samples; i++) {
         samples[i] = time(loops, bench, canvas, NULL) / loops;
@@ -154,8 +209,9 @@ static int gpu_bench(SkGLContextHelper* gl,
     SK_GL(*gl, Finish());
 
     // First, figure out how many loops it'll take to get a frame up to FLAGS_gpuMs.
-    int loops = 1;
-    if (!FLAGS_runOnce) {
+    int loops = FLAGS_loops;
+    if (kAutoTuneLoops == loops) {
+        loops = 1;
         double elapsed = 0;
         do {
             loops *= 2;
@@ -474,11 +530,19 @@ int nanobench_main() {
     SetupCrashHandler();
     SkAutoGraphics ag;
 
-    if (FLAGS_runOnce) {
+    if (kAutoTuneLoops != FLAGS_loops) {
         FLAGS_samples     = 1;
         FLAGS_gpuFrameLag = 0;
     }
 
+    if (!FLAGS_writePath.isEmpty()) {
+        SkDebugf("Writing files to %s.\n", FLAGS_writePath[0]);
+        if (!sk_mkdir(FLAGS_writePath[0])) {
+            SkDebugf("Could not create %s. Files won't be written.\n", FLAGS_writePath[0]);
+            FLAGS_writePath.set(0, NULL);
+        }
+    }
+
     MultiResultsWriter log;
     SkAutoTDelete<NanoJSONResultsWriter> json;
     if (!FLAGS_outResultsFile.isEmpty()) {
@@ -502,8 +566,8 @@ int nanobench_main() {
 
     SkAutoTMalloc<double> samples(FLAGS_samples);
 
-    if (FLAGS_runOnce) {
-        SkDebugf("--runOnce is true; times would only be misleading so we won't print them.\n");
+    if (kAutoTuneLoops != FLAGS_loops) {
+        SkDebugf("Fixed number of loops; times would only be misleading so we won't print them.\n");
     } else if (FLAGS_verbose) {
         // No header.
     } else if (FLAGS_quiet) {
@@ -549,7 +613,14 @@ int nanobench_main() {
 #endif
                  cpu_bench(       overhead, bench.get(), canvas, samples.get());
 
-            if (loops == 0) {
+            if (canvas && !FLAGS_writePath.isEmpty() && NULL != FLAGS_writePath[0]) {
+                SkString pngFilename = SkOSPath::Join(FLAGS_writePath[0], config);
+                pngFilename = SkOSPath::Join(pngFilename.c_str(), bench->getName());
+                pngFilename.append(".png");
+                write_canvas_png(canvas, pngFilename);
+            }
+
+            if (kFailedLoops == loops) {
                 // Can't be timed.  A warning note has already been printed.
                 continue;
             }
@@ -568,7 +639,7 @@ int nanobench_main() {
             log.timer("max_ms",    stats.max);
             log.timer("stddev_ms", sqrt(stats.var));
 
-            if (FLAGS_runOnce) {
+            if (kAutoTuneLoops != FLAGS_loops) {
                 if (targets.count() == 1) {
                     config = ""; // Only print the config if we run the same bench on more than one.
                 }
index 39eeb4b..69a74df 100644 (file)
@@ -123,7 +123,7 @@ public:
 /**
  *  Functions for modifying SkStrings which represent paths on the filesystem.
  */
-class SkOSPath {
+class SkOSPath   {
 public:
     /**
      * Assembles rootPath and relativePath into a single path, like this:
@@ -144,6 +144,17 @@ public:
      *      final slash, or the full name if there is no slash.
      */
     static SkString Basename(const char* fullPath);
+
+    /**
+     *  Given a qualified file name returns the directory.
+     *  Behaves like python's os.path.dirname. If the fullPath is
+     *  /dir/subdir/ the return will be /dir/subdir/
+     *  @param fullPath Full path to the file.
+     *  @return SkString The dir containing the file - anything preceding the
+     *      final slash, or the full name if ending in a slash.
+     */
+    static SkString Dirname(const char* fullPath);
+    
 };
 
 #endif
index 8baf08f..04a4fe9 100644 (file)
@@ -8,7 +8,7 @@
 
 SkString SkOSPath::Join(const char *rootPath, const char *relativePath) {
     SkString result(rootPath);
-    if (!result.endsWith(SkPATH_SEPARATOR)) {
+    if (!result.endsWith(SkPATH_SEPARATOR) && !result.isEmpty()) {
         result.appendUnichar(SkPATH_SEPARATOR);
     }
     result.append(relativePath);
@@ -28,6 +28,21 @@ SkString SkOSPath::Basename(const char* fullPath) {
     return SkString(filename);
 }
 
+SkString SkOSPath::Dirname(const char* fullPath) {
+    if (!fullPath) {
+        return SkString();
+    }
+    const char* end = strrchr(fullPath, SkPATH_SEPARATOR);
+    if (NULL == end) {
+        return SkString();
+    }
+    if (end == fullPath) {
+        SkASSERT(fullPath[0] == SkPATH_SEPARATOR);
+        ++end;
+    }
+    return SkString(fullPath, end - fullPath);
+}
+
 #ifdef SK_BUILD_FOR_WIN
 
 static uint16_t* concat_to_16(const char src[], const char suffix[])
index 1452c38..facc6ad 100644 (file)
@@ -10,7 +10,7 @@
 #include "Test.h"
 
 /**
- *  Test SkOSPath::Join and SkOSPath::Basename.
+ *  Test SkOSPath::Join, SkOSPath::Basename, and SkOSPath::Dirname.
  *  Will use SkOSPath::Join to append filename to dir, test that it works correctly,
  *  and tests using SkOSPath::Basename on the result.
  *  @param reporter Reporter for test conditions.
@@ -32,16 +32,28 @@ static void test_dir_with_file(skiatest::Reporter* reporter, SkString dir,
     // fullName should be the combined size of dir and file, plus one if
     // dir did not include the final path separator.
     size_t expectedSize = dir.size() + filename.size();
-    if (!dir.endsWith(SkPATH_SEPARATOR)) {
+    if (!dir.endsWith(SkPATH_SEPARATOR) && !dir.isEmpty()) {
         expectedSize++;
     }
     REPORTER_ASSERT(reporter, fullName.size() == expectedSize);
 
     SkString basename = SkOSPath::Basename(fullName.c_str());
+    SkString dirname = SkOSPath::Dirname(fullName.c_str());
 
     // basename should be the same as filename
     REPORTER_ASSERT(reporter, basename.equals(filename));
 
+    // dirname should be the same as dir with any trailing seperators removed.
+    // Except when the the string is just "/".
+    SkString strippedDir = dir;
+    while (strippedDir.size() > 2 && strippedDir[strippedDir.size() - 1] == SkPATH_SEPARATOR) {
+        strippedDir.remove(strippedDir.size() - 1, 1);
+    }
+    if (!dirname.equals(strippedDir)) {
+        SkDebugf("OOUCH %s %s %s\n", dir.c_str(), strippedDir.c_str(), dirname.c_str());
+    }
+    REPORTER_ASSERT(reporter, dirname.equals(strippedDir));
+
     // basename will not contain a path separator
     REPORTER_ASSERT(reporter, !basename.contains(SkPATH_SEPARATOR));
 
@@ -78,8 +90,16 @@ DEF_TEST(OSPath, reporter) {
     SkString empty = SkOSPath::Basename(NULL);
     REPORTER_ASSERT(reporter, empty.size() == 0);
 
+    // File in root dir
+    dir.printf("%c", SkPATH_SEPARATOR);
+    filename.set("file");
+    test_dir_with_file(reporter, dir, filename);
+
+    // Just the root dir
+    filename.reset();
+    test_dir_with_file(reporter, dir, filename);
+
     // Test that NULL can be used for the directory and filename.
     SkString emptyPath = SkOSPath::Join(NULL, NULL);
-    REPORTER_ASSERT(reporter, emptyPath.size() == 1);
-    REPORTER_ASSERT(reporter, emptyPath.contains(SkPATH_SEPARATOR));
+    REPORTER_ASSERT(reporter, emptyPath.isEmpty());
 }