Reland https://codereview.chromium.org/286903025

BUG=skia:
R=bensong@google.com, epoger@google.com

Author: kelvinly@google.com

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

git-svn-id: http://skia.googlecode.com/svn/trunk@14966 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/bench/ResultsWriter.cpp b/bench/ResultsWriter.cpp
new file mode 100644
index 0000000..0bfcba8
--- /dev/null
+++ b/bench/ResultsWriter.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Helper functions for result writing operations.
+ */
+
+#include "ResultsWriter.h"
+
+#ifdef SK_BUILD_JSON_WRITER
+
+Json::Value* SkFindNamedNode(Json::Value* root, const char name[]) {
+    Json::Value* search_results = NULL;
+    for(Json::Value::iterator iter = root->begin();
+            iter!= root->end(); ++iter) {
+        if(SkString(name).equals((*iter)["name"].asCString())) {
+            search_results = &(*iter);
+            break;
+        }
+    }
+
+    if(search_results != NULL) {
+        return search_results;
+    } else {
+        Json::Value* new_val = &(root->append(Json::Value()));
+        (*new_val)["name"] = name;
+        return new_val;
+    }
+}
+
+#endif // SK_BUILD_JSON_WRITER
diff --git a/bench/ResultsWriter.h b/bench/ResultsWriter.h
index a9eef80..c665938 100644
--- a/bench/ResultsWriter.h
+++ b/bench/ResultsWriter.h
@@ -101,26 +101,9 @@
  *         },
  *         ...
  */
-class JSONResultsWriter : public ResultsWriter {
-private:
-    Json::Value* find_named_node(Json::Value* root, const char name[]) {
-        Json::Value* search_results = NULL;
-        for(Json::Value::iterator iter = root->begin();
-                iter!= root->end(); ++iter) {
-            if(SkString(name).equals((*iter)["name"].asCString())) {
-                search_results = &(*iter);
-                break;
-            }
-        }
 
-        if(search_results != NULL) {
-            return search_results;
-        } else {
-            Json::Value* new_val = &(root->append(Json::Value()));
-            (*new_val)["name"] = name;
-            return new_val;
-        }
-    }
+Json::Value* SkFindNamedNode(Json::Value* root, const char name[]);
+class JSONResultsWriter : public ResultsWriter {
 public:
     explicit JSONResultsWriter(const char filename[])
         : fFilename(filename)
@@ -138,12 +121,12 @@
         sk_name.appendS32(x);
         sk_name.append("_");
         sk_name.appendS32(y);
-        Json::Value* bench_node = find_named_node(&fResults, sk_name.c_str());
+        Json::Value* bench_node = SkFindNamedNode(&fResults, sk_name.c_str());
         fBench = &(*bench_node)["results"];
     }
     virtual void config(const char name[]) {
         SkASSERT(NULL != fBench);
-        fConfig = find_named_node(fBench, name);
+        fConfig = SkFindNamedNode(fBench, name);
     }
     virtual void timer(const char name[], double ms) {
         SkASSERT(NULL != fConfig);
@@ -164,6 +147,7 @@
 };
 
 #endif // SK_BUILD_JSON_WRITER
+
 /**
  * This ResultsWriter writes out to multiple ResultsWriters.
  */
diff --git a/bench/TimerData.cpp b/bench/TimerData.cpp
index a86f293..ba682f0 100644
--- a/bench/TimerData.cpp
+++ b/bench/TimerData.cpp
@@ -5,7 +5,6 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-
 #include "TimerData.h"
 
 #include "BenchTimer.h"
@@ -140,3 +139,88 @@
     }
     return str;
 }
+
+#ifdef SK_BUILD_JSON_WRITER
+Json::Value TimerData::getJSON(uint32_t timerFlags, 
+                               Result result,
+                               int itersPerTiming) {
+    SkASSERT(itersPerTiming >= 1);
+    Json::Value dataNode;
+    Json::Value wallNode, truncWall, cpuNode, truncCpu, gpuNode;
+    if (!fCurrTiming) {
+        return dataNode;
+    }
+
+    int numTimings = fCurrTiming;
+
+    double wallMin = std::numeric_limits<double>::max();
+    double truncWallMin = std::numeric_limits<double>::max();
+    double cpuMin = std::numeric_limits<double>::max();
+    double truncCpuMin = std::numeric_limits<double>::max();
+    double gpuMin = std::numeric_limits<double>::max();
+
+    double wallSum = 0;
+    double truncWallSum = 0;
+    double cpuSum = 0;
+    double truncCpuSum = 0;
+    double gpuSum = 0;
+
+    for (int i = 0; i < numTimings; ++i) {
+        if (kPerIter_Result == result) {
+            wallNode.append(fWallTimes[i] / itersPerTiming);
+            truncWall.append(fTruncatedWallTimes[i] / itersPerTiming);
+            cpuNode.append(fCpuTimes[i] / itersPerTiming);
+            truncCpu.append(fTruncatedCpuTimes[i] / itersPerTiming);
+            gpuNode.append(fGpuTimes[i] / itersPerTiming);
+        } else if (kMin_Result == result) {
+            wallMin = SkTMin(wallMin, fWallTimes[i]);
+            truncWallMin = SkTMin(truncWallMin, fTruncatedWallTimes[i]);
+            cpuMin = SkTMin(cpuMin, fCpuTimes[i]);
+            truncCpuMin = SkTMin(truncCpuMin, fTruncatedCpuTimes[i]);
+            gpuMin = SkTMin(gpuMin, fGpuTimes[i]);
+        } else {
+            SkASSERT(kAvg_Result == result);
+            wallSum += fWallTimes[i];
+            truncWallSum += fTruncatedWallTimes[i];
+            cpuSum += fCpuTimes[i];
+            truncCpuSum += fTruncatedCpuTimes[i];
+        }
+
+        // We always track the GPU sum because whether it is non-zero indicates if valid gpu times
+        // were recorded at all.
+        gpuSum += fGpuTimes[i];
+    }
+
+    if (kMin_Result == result) {
+        wallNode.append(wallMin / itersPerTiming);
+        truncWall.append(truncWallMin / itersPerTiming);
+        cpuNode.append(cpuMin / itersPerTiming);
+        truncCpu.append(truncCpuMin / itersPerTiming);
+        gpuNode.append(gpuMin / itersPerTiming);
+    } else if (kAvg_Result == result) {
+        int divisor = numTimings * itersPerTiming;
+        wallNode.append(wallSum / divisor);
+        truncWall.append(truncWallSum / divisor);
+        cpuNode.append(cpuSum / divisor);
+        truncCpu.append(truncCpuSum / divisor);
+        gpuNode.append(gpuSum / divisor);
+    }
+
+    if (timerFlags & kWall_Flag) {
+        dataNode["wall"] = wallNode;
+    }
+    if (timerFlags & kTruncatedWall_Flag) {
+        dataNode["truncWall"] = truncWall;
+    }
+    if (timerFlags & kCpu_Flag) {
+        dataNode["cpu"] = cpuNode;
+    }
+    if (timerFlags & kTruncatedCpu_Flag) {
+        dataNode["trucCpu"] = truncCpu;
+    }
+    if ((timerFlags & kGpu_Flag) && gpuSum > 0) {
+        dataNode["gpu"] = gpuNode;
+    }
+    return dataNode;
+}
+#endif  // SK_BUILD_JSON_WRITER
diff --git a/bench/TimerData.h b/bench/TimerData.h
index ed0ee47..8f6a719 100644
--- a/bench/TimerData.h
+++ b/bench/TimerData.h
@@ -12,6 +12,16 @@
 #include "SkString.h"
 #include "SkTemplates.h"
 
+#ifdef SK_BUILD_FOR_WIN
+    #pragma warning(push)
+    #pragma warning(disable : 4530)
+#endif
+
+#include "SkJSONCPP.h"
+
+#ifdef SK_BUILD_FOR_WIN
+    #pragma warning(pop)
+#endif
 
 class BenchTimer;
 
@@ -58,6 +68,11 @@
                        const char* configName,
                        uint32_t timerFlags,
                        int itersPerTiming = 1);
+#ifdef SK_BUILD_JSON_WRITER
+    Json::Value getJSON(uint32_t timerFlags, 
+                        Result result,
+                        int itersPerTiming = 1);
+#endif // SK_BUILD_JSON_WRITER
 
 private:
     int fMaxNumTimings;
diff --git a/gyp/bench.gyp b/gyp/bench.gyp
index 553c0e7..651ee15 100644
--- a/gyp/bench.gyp
+++ b/gyp/bench.gyp
@@ -15,6 +15,7 @@
         'jsoncpp.gyp:jsoncpp',
       ],
       'sources': [
+        '../bench/ResultsWriter.cpp',
         '../bench/SkBenchLogger.cpp',
         '../bench/SkBenchLogger.h',
         '../bench/SkGMBench.cpp',
diff --git a/gyp/tools.gyp b/gyp/tools.gyp
index 63c2fbf..cd18415 100644
--- a/gyp/tools.gyp
+++ b/gyp/tools.gyp
@@ -286,12 +286,12 @@
       'target_name': 'bench_pictures',
       'type': 'executable',
       'sources': [
+        '../bench/ResultsWriter.cpp',
+        '../tools/PictureBenchmark.cpp',
+        '../tools/PictureResultsWriter.h',
         '../bench/SkBenchLogger.h',
         '../bench/SkBenchLogger.cpp',
-        '../bench/TimerData.h',
-        '../bench/TimerData.cpp',
         '../tools/bench_pictures_main.cpp',
-        '../tools/PictureBenchmark.cpp',
       ],
       'include_dirs': [
         '../src/core/',
@@ -301,9 +301,11 @@
       'dependencies': [
         'bench.gyp:bench_timer',
         'flags.gyp:flags',
+        'jsoncpp.gyp:jsoncpp',
         'skia_lib.gyp:skia_lib',
         'tools.gyp:picture_utils',
         'tools.gyp:picture_renderer',
+        'tools.gyp:timer_data',
       ],
     },
     {
@@ -505,12 +507,11 @@
         '../tools/bbh_shootout.cpp',
 
         # Bench code:
-        '../bench/TimerData.h',
-        '../bench/TimerData.cpp',
       ],
       'dependencies': [
         'bench.gyp:bench_timer',
         'flags.gyp:flags',
+        'tools.gyp:timer_data',
         'skia_lib.gyp:skia_lib',
         'tools.gyp:picture_renderer',
         'tools.gyp:picture_utils',
@@ -549,6 +550,17 @@
         'skia_lib.gyp:skia_lib',
       ],
     },
+    {
+      'target_name': 'timer_data',
+      'type': 'static_library',
+      'sources': [
+        '../bench/TimerData.cpp',
+      ],
+      'dependencies': [
+        'skia_lib.gyp:skia_lib',
+        'jsoncpp.gyp:jsoncpp'
+      ]
+    }
   ],
   'conditions': [
     ['skia_shared_lib',
diff --git a/tools/PictureBenchmark.cpp b/tools/PictureBenchmark.cpp
index 6c0325e..f1d0003 100644
--- a/tools/PictureBenchmark.cpp
+++ b/tools/PictureBenchmark.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "SkBenchLogger.h"
 #include "BenchTimer.h"
 #include "PictureBenchmark.h"
 #include "SkCanvas.h"
@@ -17,13 +16,13 @@
 
 PictureBenchmark::PictureBenchmark()
 : fRepeats(1)
-, fLogger(NULL)
 , fRenderer(NULL)
 , fTimerResult(TimerData::kAvg_Result)
 , fTimerTypes(0)
 , fTimeIndividualTiles(false)
 , fPurgeDecodedTex(false)
 , fPreprocess(false)
+, fWriter(NULL)
 {}
 
 PictureBenchmark::~PictureBenchmark() {
@@ -52,12 +51,6 @@
     return SkNEW_ARGS(BenchTimer, (NULL));
 }
 
-void PictureBenchmark::logProgress(const char msg[]) {
-    if (fLogger != NULL) {
-        fLogger->logProgress(msg);
-    }
-}
-
 PictureRenderer* PictureBenchmark::setRenderer(sk_tools::PictureRenderer* renderer) {
     SkRefCnt_SafeAssign(fRenderer, renderer);
     return renderer;
@@ -137,10 +130,6 @@
             return;
         }
 
-        // Insert a newline so that each tile is reported on its own line (separate from the line
-        // that describes the skp being run).
-        this->logProgress("\n");
-
         int x, y;
         while (tiledRenderer->nextTile(x, y)) {
             // There are two timers, which will behave slightly differently:
@@ -184,8 +173,8 @@
                 SkAssertResult(longRunningTimerData.appendTimes(longRunningTimer.get()));
             }
 
-            SkString configName = tiledRenderer->getConfigName();
-            configName.appendf(": tile [%i,%i] out of [%i,%i]", x, y, xTiles, yTiles);
+            fWriter->tileConfig(tiledRenderer->getConfigName());
+            fWriter->tileMeta(x, y, xTiles, yTiles);
 
             // TODO(borenet): Turn off per-iteration tile time reporting for now.
             // Avoiding logging the time for every iteration for each tile cuts
@@ -193,22 +182,23 @@
             // we're loading the bench data directly into a data store and are no
             // longer generating SVG graphs.
 #if 0
-            SkString result = perTileTimerData.getResult(timeFormat.c_str(), fTimerResult,
-                                                         configName.c_str(), timerTypes);
-            result.append("\n");
-            this->logProgress(result.c_str());
+            fWriter->tileData(
+                    &perTileTimerData, 
+                    timeFormat.c_str(), 
+                    fTimerResult,
+                    timerTypes);
 #endif
 
             if (fPurgeDecodedTex) {
-                configName.append(" <withPurging>");
+                fWriter->addTileFlag(PictureResultsWriter::kPurging);
             }
-            configName.append(" <averaged>");
-            SkString longRunningResult = longRunningTimerData.getResult(
+            fWriter->addTileFlag(PictureResultsWriter::kAvg);
+            fWriter->tileData(
+                &longRunningTimerData, 
                 tiledRenderer->getNormalTimeFormat().c_str(),
                 TimerData::kAvg_Result,
-                configName.c_str(), timerTypes, numInnerLoops);
-            longRunningResult.append("\n");
-            this->logProgress(longRunningResult.c_str());
+                timerTypes,
+                numInnerLoops);
         }
     } else {
         SkAutoTDelete<BenchTimer> longRunningTimer(this->setupTimer());
@@ -246,29 +236,26 @@
             SkAssertResult(longRunningTimerData.appendTimes(longRunningTimer.get()));
         }
 
-        SkString configName = fRenderer->getConfigName();
+        fWriter->tileConfig(fRenderer->getConfigName());
         if (fPurgeDecodedTex) {
-            configName.append(" <withPurging>");
+            fWriter->addTileFlag(PictureResultsWriter::kPurging);
         }
 
         // Beware - since the per-run-timer doesn't ever include a glFinish it can
         // report a lower time then the long-running-timer
 #if 0
-        SkString result = perRunTimerData.getResult(timeFormat.c_str(),
-                                                    fTimerResult,
-                                                    configName.c_str(),
-                                                    timerTypes);
-        result.append("\n");
-
-        this->logProgress(result.c_str());
+        fWriter->tileData(
+                &perRunTimerData,
+                timeFormat.c_str(),
+                fTimerResult,
+                timerTypes);
 #else
-        SkString result = longRunningTimerData.getResult(timeFormat.c_str(),
-                                                            fTimerResult,
-                                                            configName.c_str(),
-                                                            timerTypes,
-                                                            numInnerLoops);
-        result.append("\n");
-        this->logProgress(result.c_str());
+        fWriter->tileData(
+                &longRunningTimerData,
+                timeFormat.c_str(),
+                fTimerResult,
+                timerTypes,
+                numInnerLoops);
 #endif
     }
 
diff --git a/tools/PictureBenchmark.h b/tools/PictureBenchmark.h
index e149f8c..3ec3c66 100644
--- a/tools/PictureBenchmark.h
+++ b/tools/PictureBenchmark.h
@@ -11,6 +11,7 @@
 #include "SkTypes.h"
 #include "PictureRenderer.h"
 #include "TimerData.h"
+#include "PictureResultsWriter.h"
 
 class BenchTimer;
 class SkBenchLogger;
@@ -27,7 +28,7 @@
 
     /**
      * Draw the provided SkPicture fRepeats times while collecting timing data, and log the output
-     * via fLogger.
+     * via fWriter.
      */
     void run(SkPicture* pict);
 
@@ -55,11 +56,10 @@
 
     void setTimersToShow(bool wall, bool truncatedWall, bool cpu, bool truncatedCpu, bool gpu);
 
-    void setLogger(SkBenchLogger* logger) { fLogger = logger; }
+    void setWriter(PictureResultsWriter* writer) { fWriter = writer; }
 
 private:
     int               fRepeats;
-    SkBenchLogger*    fLogger;
     PictureRenderer*  fRenderer;
     TimerData::Result fTimerResult;
     uint32_t          fTimerTypes; // bitfield of TimerData::TimerFlags values
@@ -67,7 +67,7 @@
     bool              fPurgeDecodedTex;
     bool              fPreprocess;
 
-    void logProgress(const char msg[]);
+    PictureResultsWriter* fWriter;
 
     BenchTimer* setupTimer(bool useGLTimer = true);
 };
diff --git a/tools/PictureResultsWriter.h b/tools/PictureResultsWriter.h
new file mode 100644
index 0000000..0571e80
--- /dev/null
+++ b/tools/PictureResultsWriter.h
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Classes for writing out bench results in various formats.
+ */
+#ifndef SkPictureResultsWriter_DEFINED
+#define SkPictureResultsWriter_DEFINED
+
+#include "ResultsWriter.h"
+#include "SkBenchLogger.h"
+#include "SkJSONCPP.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkTArray.h"
+
+/**
+ * Base class for writing picture bench results.
+ */
+class PictureResultsWriter : SkNoncopyable {
+public:
+    enum TileFlags {kPurging, kAvg};
+
+    PictureResultsWriter() {}
+    virtual ~PictureResultsWriter() {}
+
+    virtual void bench(const char name[], int32_t x, int32_t y) = 0;
+    virtual void tileConfig(SkString configName) = 0;
+    virtual void tileMeta(int x, int y, int tx, int ty) = 0;
+    virtual void addTileFlag(PictureResultsWriter::TileFlags flag) = 0;
+    virtual void tileData(
+            TimerData* data, 
+            const char format[], 
+            const TimerData::Result result,
+            uint32_t timerTypes,
+            int numInnerLoops = 1) = 0;
+   virtual void end() = 0;
+};
+
+/**
+ * This class allows bench data to be piped into multiple
+ * PictureResultWriter classes. It does not own any classes
+ * passed to it, so the owner is required to manage any classes
+ * passed to PictureResultsMultiWriter */
+class PictureResultsMultiWriter : public PictureResultsWriter {
+public:
+    PictureResultsMultiWriter() 
+        : fWriters() {}
+    void add(PictureResultsWriter* newWriter) {
+        fWriters.push_back(newWriter);
+    }
+    virtual ~PictureResultsMultiWriter() {}
+    virtual void bench(const char name[], int32_t x, int32_t y) {
+        for(int i=0; i<fWriters.count(); ++i) {
+            fWriters[i]->bench(name, x, y);
+        }
+    }
+    virtual void tileConfig(SkString configName) {
+        for(int i=0; i<fWriters.count(); ++i) {
+            fWriters[i]->tileConfig(configName);
+        }
+    }
+    virtual void tileMeta(int x, int y, int tx, int ty) {
+        for(int i=0; i<fWriters.count(); ++i) {
+            fWriters[i]->tileMeta(x, y, tx, ty);
+        }
+    }
+    virtual void addTileFlag(PictureResultsWriter::TileFlags flag) {
+        for(int i=0; i<fWriters.count(); ++i) {
+            fWriters[i]->addTileFlag(flag);
+        }
+    }
+    virtual void tileData(
+            TimerData* data, 
+            const char format[], 
+            const TimerData::Result result,
+            uint32_t timerTypes,
+            int numInnerLoops = 1) {
+        for(int i=0; i<fWriters.count(); ++i) {
+            fWriters[i]->tileData(data, format, result, timerTypes, 
+                                 numInnerLoops);
+        }
+    }
+   virtual void end() {
+        for(int i=0; i<fWriters.count(); ++i) {
+            fWriters[i]->end();
+        }
+   }
+private:
+    SkTArray<PictureResultsWriter*> fWriters;
+};
+
+/**
+ * Writes to SkBenchLogger to mimic original behavior
+ */
+class PictureResultsLoggerWriter : public PictureResultsWriter {    
+private:
+    void logProgress(const char str[]) {
+        if(fLogger != NULL) {
+            fLogger->logProgress(str);
+        }
+    }
+public:
+    PictureResultsLoggerWriter(SkBenchLogger* log)
+          : fLogger(log), currentLine() {}
+    virtual void bench(const char name[], int32_t x, int32_t y) {
+        SkString result;
+        result.printf("running bench [%i %i] %s ", x, y, name);
+        this->logProgress(result.c_str());
+    }
+    virtual void tileConfig(SkString configName) {
+        currentLine = configName;
+    }
+    virtual void tileMeta(int x, int y, int tx, int ty) {
+        currentLine.appendf(": tile [%i,%i] out of [%i,%i]", x, y, tx, ty);
+    }
+    virtual void addTileFlag(PictureResultsWriter::TileFlags flag) {
+        if(flag == PictureResultsWriter::kPurging) {
+            currentLine.append(" <withPurging>");
+        } else if(flag == PictureResultsWriter::kAvg) {
+            currentLine.append(" <averaged>");
+        }
+    }
+    virtual void tileData(
+            TimerData* data, 
+            const char format[], 
+            const TimerData::Result result,
+            uint32_t timerTypes,
+            int numInnerLoops = 1) {
+        SkString results = data->getResult(format, result,
+                currentLine.c_str(), timerTypes, numInnerLoops);
+        results.append("\n");
+        this->logProgress(results.c_str());
+    }
+    virtual void end() {}
+private:
+    SkBenchLogger* fLogger;
+    SkString currentLine;
+};
+
+#ifdef SK_BUILD_JSON_WRITER
+/**
+ * This PictureResultsWriter collects data in a JSON node
+ *
+ * The format is something like
+ * {
+ *      benches: [
+ *          {
+ *              name: "Name_of_test"
+ *              tilesets: [
+ *                  {
+ *                      name: "Name of the configuration"
+ *                      tiles: [
+ *                          {
+ *                              flags: {
+ *                                  purging: true //Flags for the current tile
+ *                                              // are put here
+ *                              }
+ *                              data: {
+ *                                  wsecs: [....] //Actual data ends up here
+ *                              }
+ *                          }
+ *                      ]
+ *                  }
+ *              ]
+ *          }
+ *      ]
+ * }*/
+
+class PictureJSONResultsWriter : public PictureResultsWriter {
+public:
+    PictureJSONResultsWriter(const char filename[])
+        : fFilename(filename),
+          fRoot(), 
+          fCurrentBench(NULL),
+          fCurrentTileSet(NULL),
+          fCurrentTile(NULL) {}
+
+    virtual void bench(const char name[], int32_t x, int32_t y) {
+        SkString sk_name(name);
+        sk_name.append("_");
+        sk_name.appendS32(x);
+        sk_name.append("_");
+        sk_name.appendS32(y);
+        Json::Value* bench_node = SkFindNamedNode(&fRoot["benches"], sk_name.c_str());
+        fCurrentBench = &(*bench_node)["tileSets"];
+    }
+    virtual void tileConfig(SkString configName) {
+        SkASSERT(fCurrentBench != NULL);
+        fCurrentTileSet = SkFindNamedNode(fCurrentBench, configName.c_str());
+        fCurrentTile = &(*fCurrentTileSet)["tiles"][0];
+    }
+    virtual void tileMeta(int x, int y, int tx, int ty) {
+        SkASSERT(fCurrentTileSet != NULL);
+        (*fCurrentTileSet)["tx"] = tx;
+        (*fCurrentTileSet)["ty"] = ty;
+        fCurrentTile = &(*fCurrentTileSet)["tiles"][x+tx*y];
+    }
+    virtual void addTileFlag(PictureResultsWriter::TileFlags flag) {
+        SkASSERT(fCurrentTile != NULL);
+        if(flag == PictureResultsWriter::kPurging) {
+            (*fCurrentTile)["flags"]["purging"] = true;
+        } else if(flag == PictureResultsWriter::kAvg) {
+            (*fCurrentTile)["flags"]["averaged"] = true;
+        }
+    }
+    virtual void tileData(
+            TimerData* data, 
+            const char format[], 
+            const TimerData::Result result,
+            uint32_t timerTypes,
+            int numInnerLoops = 1) {
+        SkASSERT(fCurrentTile != NULL);
+        (*fCurrentTile)["data"] = data->getJSON(timerTypes, result, numInnerLoops);
+    }
+    virtual void end() {
+       SkFILEWStream stream(fFilename.c_str());
+       stream.writeText(Json::FastWriter().write(fRoot).c_str());
+       stream.flush();
+    }
+private:
+    SkString fFilename;
+    Json::Value fRoot;
+    Json::Value *fCurrentBench;
+    Json::Value *fCurrentTileSet;
+    Json::Value *fCurrentTile;
+};
+#endif // SK_BUILD_JSON_WRITER
+
+#endif
diff --git a/tools/bench_pictures_main.cpp b/tools/bench_pictures_main.cpp
index 5ff519c..b79dfed 100644
--- a/tools/bench_pictures_main.cpp
+++ b/tools/bench_pictures_main.cpp
@@ -12,6 +12,7 @@
 #include "PictureRenderingFlags.h"
 #include "SkBenchLogger.h"
 #include "SkCommandLineFlags.h"
+#include "SkData.h"
 #include "SkDiscardableMemoryPool.h"
 #include "SkGraphics.h"
 #include "SkImageDecoder.h"
@@ -20,8 +21,11 @@
 #include "SkPicture.h"
 #include "SkStream.h"
 #include "picture_utils.h"
+#include "PictureResultsWriter.h"
 
 SkBenchLogger gLogger;
+PictureResultsLoggerWriter gLogWriter(&gLogger);
+PictureResultsMultiWriter gWriter;
 
 // Flags used by this file, in alphabetical order.
 DEFINE_bool(countRAM, false, "Count the RAM used for bitmap pixels in each skp file");
@@ -35,6 +39,9 @@
         "Specific flags are listed above.");
 DEFINE_string(logFile, "", "Destination for writing log output, in addition to stdout.");
 DEFINE_bool(logPerIter, false, "Log each repeat timer instead of mean.");
+#ifdef SK_BUILD_JSON_WRITER
+DEFINE_string(jsonLog, "", "Destination for writing JSON data.");
+#endif
 DEFINE_bool(min, false, "Print the minimum times (instead of average).");
 DECLARE_int32(multi);
 DECLARE_string(readPath);
@@ -187,10 +194,7 @@
     SkString filename;
     sk_tools::get_basename(&filename, inputPath);
 
-    SkString result;
-    result.printf("running bench [%i %i] %s ", picture->width(), picture->height(),
-                  filename.c_str());
-    gLogger.logProgress(result);
+    gWriter.bench(filename.c_str(), picture->width(), picture->height());
 
     benchmark.run(picture);
 
@@ -360,7 +364,7 @@
     }
     benchmark->setRenderer(renderer);
     benchmark->setRepeats(FLAGS_repeat);
-    benchmark->setLogger(&gLogger);
+    benchmark->setWriter(&gWriter);
 }
 
 static int process_input(const char* input,
@@ -417,6 +421,16 @@
         }
     }
 
+#ifdef SK_BUILD_JSON_WRITER
+    SkAutoTDelete<PictureJSONResultsWriter> jsonWriter;
+    if (FLAGS_jsonLog.count() == 1) {
+        jsonWriter.reset(SkNEW(PictureJSONResultsWriter(FLAGS_jsonLog[0])));
+        gWriter.add(jsonWriter.get());
+    }
+
+#endif
+    gWriter.add(&gLogWriter);
+
 
 #if SK_ENABLE_INST_COUNT
     gPrintInstCount = true;
@@ -444,6 +458,7 @@
                  (double) gTotalCacheHits / (gTotalCacheHits + gTotalCacheMisses));
     }
 #endif
+    gWriter.end();
     return 0;
 }