First stab at JSON event tracer

Not yet thread safe (so it forces threading off).
Builds JSON on the fly, so overhead is certainly bad.
Plan to fix all of that, but this at least "works".

There is now one tracing flag: 'trace'.
- 'debugf' installs the SkDebugf tracer.
- 'atrace' installs the Android ATrace tracer.
- Any other value is interpreted as a filename, and
  produces a JSON file for chrome://tracing.

All three modes work in DM, nanobench, and Viewer.

Bug: skia:
Change-Id: I3fbc22382b99418a508c670be2770195c0a1c364
Reviewed-on: https://skia-review.googlesource.com/24781
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 70b8cb5..975d560 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1030,8 +1030,12 @@
       "tools/sk_tool_utils.cpp",
       "tools/sk_tool_utils_font.cpp",
       "tools/timer/Timer.cpp",
+      "tools/trace/SkChromeTracingTracer.cpp",
+      "tools/trace/SkChromeTracingTracer.h",
       "tools/trace/SkDebugfTracer.cpp",
       "tools/trace/SkDebugfTracer.h",
+      "tools/trace/SkEventTracingPriv.cpp",
+      "tools/trace/SkEventTracingPriv.h",
     ]
     libs = []
     if (is_ios) {
diff --git a/bench/nanobench.cpp b/bench/nanobench.cpp
index 7148bc2..05c475a 100644
--- a/bench/nanobench.cpp
+++ b/bench/nanobench.cpp
@@ -33,6 +33,7 @@
 #include "SkCommonFlagsPathRenderer.h"
 #include "SkData.h"
 #include "SkDebugfTracer.h"
+#include "SkEventTracingPriv.h"
 #include "SkGraphics.h"
 #include "SkLeanWindows.h"
 #include "SkOSFile.h"
@@ -1104,9 +1105,9 @@
 
 int main(int argc, char** argv) {
     SkCommandLineFlags::Parse(argc, argv);
-    if (FLAGS_trace) {
-        SkEventTracer::SetInstance(new SkDebugfTracer);
-    }
+
+    initializeEventTracingForTools(&FLAGS_threads);
+
 #if defined(SK_BUILD_FOR_IOS)
     cd_Documents();
 #endif
diff --git a/dm/DM.cpp b/dm/DM.cpp
index 06ad765..6310ed7 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -11,6 +11,7 @@
 #include "Resources.h"
 #include "SkBBHFactory.h"
 #include "SkChecksum.h"
+#include "SkChromeTracingTracer.h"
 #include "SkCodec.h"
 #include "SkColorPriv.h"
 #include "SkColorSpace.h"
@@ -20,7 +21,7 @@
 #include "SkCommonFlagsPathRenderer.h"
 #include "SkData.h"
 #include "SkDebugfTracer.h"
-#include "SkEventTracer.h"
+#include "SkEventTracingPriv.h"
 #include "SkFontMgr.h"
 #include "SkGraphics.h"
 #include "SkHalf.h"
@@ -1272,10 +1273,10 @@
 
 int main(int argc, char** argv) {
     SkCommandLineFlags::Parse(argc, argv);
-    if (FLAGS_trace) {
-        SkAssertResult(SkEventTracer::SetInstance(new SkDebugfTracer()));
-    }
-    #if defined(SK_BUILD_FOR_IOS)
+
+    initializeEventTracingForTools(&FLAGS_threads);
+
+#if defined(SK_BUILD_FOR_IOS)
     cd_Documents();
 #endif
     setbuf(stdout, nullptr);
@@ -1386,6 +1387,7 @@
 
     SkGraphics::PurgeAllCaches();
     info("Finished!\n");
+
     return 0;
 }
 
diff --git a/tools/flags/SkCommonFlags.cpp b/tools/flags/SkCommonFlags.cpp
index 35fc0cf..0148bcd 100644
--- a/tools/flags/SkCommonFlags.cpp
+++ b/tools/flags/SkCommonFlags.cpp
@@ -72,8 +72,6 @@
                                     "whether it's concave or convex, we consider a path complicated"
                                     "if its number of points is comparable to its resolution.");
 
-DEFINE_bool(trace, false, "Show trace events using SkDebugf.");
-
 bool CollectImages(SkCommandLineFlags::StringArray images, SkTArray<SkString>* output) {
     SkASSERT(output);
 
diff --git a/tools/flags/SkCommonFlags.h b/tools/flags/SkCommonFlags.h
index 71ff8c3..92ac141 100644
--- a/tools/flags/SkCommonFlags.h
+++ b/tools/flags/SkCommonFlags.h
@@ -34,7 +34,6 @@
 DECLARE_bool(pre_log);
 DECLARE_bool(analyticAA);
 DECLARE_bool(forceAnalyticAA);
-DECLARE_bool(trace)
 
 DECLARE_string(key);
 DECLARE_string(properties);
diff --git a/tools/trace/SkChromeTracingTracer.cpp b/tools/trace/SkChromeTracingTracer.cpp
new file mode 100644
index 0000000..aa71ff0
--- /dev/null
+++ b/tools/trace/SkChromeTracingTracer.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkChromeTracingTracer.h"
+#include "SkThreadID.h"
+#include "SkTraceEvent.h"
+#include "SkOSFile.h"
+#include "SkOSPath.h"
+#include "SkStream.h"
+
+#include <chrono>
+
+SkEventTracer::Handle SkChromeTracingTracer::addTraceEvent(char phase,
+                                                           const uint8_t* categoryEnabledFlag,
+                                                           const char* name,
+                                                           uint64_t id,
+                                                           int numArgs,
+                                                           const char** argNames,
+                                                           const uint8_t* argTypes,
+                                                           const uint64_t* argValues,
+                                                           uint8_t flags) {
+    Json::Value traceEvent;
+    char phaseString[2] = { phase, 0 };
+    traceEvent["ph"] = phaseString;
+    traceEvent["name"] = name;
+    traceEvent["cat"] = "skia"; // TODO
+    auto now = std::chrono::high_resolution_clock::now();
+    std::chrono::duration<double, std::nano> ns = now.time_since_epoch();
+    traceEvent["ts"] = ns.count() * 1E-3;
+    traceEvent["tid"] = static_cast<Json::Int64>(SkGetThreadID());
+    traceEvent["pid"] = 1; // TODO
+
+    if (numArgs) {
+        Json::Value args;
+        for (int i = 0; i < numArgs; ++i) {
+            switch (argTypes[i]) {
+                case TRACE_VALUE_TYPE_BOOL:
+                    args[argNames[i]] = (*reinterpret_cast<bool*>(argValues[i]) ? true : false);
+                    break;
+                case TRACE_VALUE_TYPE_UINT:
+                    args[argNames[i]] = static_cast<uint32_t>(argValues[i]);
+                    break;
+                case TRACE_VALUE_TYPE_INT:
+                    args[argNames[i]] = static_cast<int32_t>(argValues[i]);
+                    break;
+                case TRACE_VALUE_TYPE_DOUBLE:
+                    args[argNames[i]] = *SkTCast<const double*>(&argValues[i]);
+                    break;
+                case TRACE_VALUE_TYPE_POINTER:
+                    args[argNames[i]] = reinterpret_cast<void*>(argValues[i]);
+                    break;
+                case TRACE_VALUE_TYPE_STRING:
+                case TRACE_VALUE_TYPE_COPY_STRING:
+                    args[argNames[i]] = reinterpret_cast<const char*>(argValues[i]);
+                    break;
+                default:
+                    args[argNames[i]] = "<unknown type>";
+                    break;
+            }
+        }
+        traceEvent["args"] = args;
+    }
+    Json::Value& newValue(fRoot.append(traceEvent));
+    return reinterpret_cast<Handle>(&newValue);
+}
+
+void SkChromeTracingTracer::updateTraceEventDuration(const uint8_t* categoryEnabledFlag,
+                                                     const char* name,
+                                                     SkEventTracer::Handle handle) {
+    Json::Value* traceEvent = reinterpret_cast<Json::Value*>(handle);
+    auto now = std::chrono::high_resolution_clock::now();
+    std::chrono::duration<double, std::nano> ns = now.time_since_epoch();
+    auto us = ns.count() * 1E-3;
+    (*traceEvent)["dur"] = us - (*traceEvent)["ts"].asDouble();
+}
+
+const uint8_t* SkChromeTracingTracer::getCategoryGroupEnabled(const char* name) {
+    static uint8_t yes = SkEventTracer::kEnabledForRecording_CategoryGroupEnabledFlags;
+    return &yes;
+}
+
+void SkChromeTracingTracer::flush() {
+    SkString dirname = SkOSPath::Dirname(fFilename.c_str());
+    if (!sk_exists(dirname.c_str(), kWrite_SkFILE_Flag)) {
+        if (!sk_mkdir(dirname.c_str())) {
+            SkDebugf("Failed to create directory.");
+        }
+    }
+    SkFILEWStream stream(fFilename.c_str());
+    stream.writeText(Json::StyledWriter().write(fRoot).c_str());
+    stream.flush();
+}
diff --git a/tools/trace/SkChromeTracingTracer.h b/tools/trace/SkChromeTracingTracer.h
new file mode 100644
index 0000000..da34205
--- /dev/null
+++ b/tools/trace/SkChromeTracingTracer.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkChromeTracingTracer_DEFINED
+#define SkChromeTracingTracer_DEFINED
+
+#include "SkEventTracer.h"
+#include "SkJSONCPP.h"
+#include "SkString.h"
+
+/**
+ * A SkEventTracer implementation that logs events to JSON for viewing with chrome://tracing.
+ */
+class SkChromeTracingTracer : public SkEventTracer {
+public:
+    SkChromeTracingTracer(const char* filename) : fRoot(Json::arrayValue), fFilename(filename) {}
+    ~SkChromeTracingTracer() override { this->flush(); }
+
+    SkEventTracer::Handle addTraceEvent(char phase,
+                                        const uint8_t* categoryEnabledFlag,
+                                        const char* name,
+                                        uint64_t id,
+                                        int numArgs,
+                                        const char** argNames,
+                                        const uint8_t* argTypes,
+                                        const uint64_t* argValues,
+                                        uint8_t flags) override;
+
+    void updateTraceEventDuration(const uint8_t* categoryEnabledFlag,
+                                  const char* name,
+                                  SkEventTracer::Handle handle) override;
+
+    const uint8_t* getCategoryGroupEnabled(const char* name) override;
+
+    const char* getCategoryGroupName(const uint8_t* categoryEnabledFlag) override {
+        static const char* category = "category?";
+        return category;
+    }
+
+private:
+    void flush();
+
+    Json::Value fRoot;
+    SkString fFilename;
+};
+
+#endif
diff --git a/tools/trace/SkEventTracingPriv.cpp b/tools/trace/SkEventTracingPriv.cpp
new file mode 100644
index 0000000..217511f
--- /dev/null
+++ b/tools/trace/SkEventTracingPriv.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkEventTracingPriv.h"
+
+#include "SkATrace.h"
+#include "SkCommandLineFlags.h"
+#include "SkEventTracer.h"
+#include "SkChromeTracingTracer.h"
+#include "SkDebugfTracer.h"
+
+DEFINE_string(trace, "",
+              "Log trace events in one of several modes:\n"
+              "  debugf     : Show events using SkDebugf\n"
+              "  atrace     : Send events to Android ATrace\n"
+              "  <filename> : Any other string is interpreted as a filename. Writes\n"
+              "               trace events to specified file as JSON, for viewing\n"
+              "               with chrome://tracing");
+
+void initializeEventTracingForTools(int32_t* threadsFlag) {
+    if (FLAGS_trace.isEmpty()) {
+        return;
+    }
+
+    const char* traceFlag = FLAGS_trace[0];
+    SkEventTracer* eventTracer = nullptr;
+    if (0 == strcmp(traceFlag, "atrace")) {
+        eventTracer = new SkATrace();
+    } else if (0 == strcmp(traceFlag, "debugf")) {
+        eventTracer = new SkDebugfTracer();
+    } else {
+        if (threadsFlag && *threadsFlag != 0) {
+            SkDebugf("JSON tracing is not yet thread-safe, disabling threading.\n");
+            *threadsFlag = 0;
+        }
+        eventTracer = new SkChromeTracingTracer(traceFlag);
+    }
+
+    SkAssertResult(SkEventTracer::SetInstance(eventTracer));
+}
diff --git a/tools/trace/SkEventTracingPriv.h b/tools/trace/SkEventTracingPriv.h
new file mode 100644
index 0000000..330f3f8
--- /dev/null
+++ b/tools/trace/SkEventTracingPriv.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkEventTracingPriv_DEFINED
+#define SkEventTracingPriv_DEFINED
+
+#include "SkTypes.h"
+
+/**
+ * Construct and install an SkEventTracer, based on the 'trace' command line argument.
+ *
+ * @param threadsFlag Pointer to the FLAGS_threads variable (or nullptr). This is used to disable
+ *                    threading when tracing to JSON. (Remove this param when JSON tracer is thread
+ *                    safe).
+ */
+void initializeEventTracingForTools(int32_t* threadsFlag);
+
+#endif
diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp
index 6753b6b..6e20cbb 100644
--- a/tools/viewer/Viewer.cpp
+++ b/tools/viewer/Viewer.cpp
@@ -21,6 +21,7 @@
 #include "SkCommandLineFlags.h"
 #include "SkCommonFlagsPathRenderer.h"
 #include "SkDashPathEffect.h"
+#include "SkEventTracingPriv.h"
 #include "SkGraphics.h"
 #include "SkImagePriv.h"
 #include "SkMetaData.h"
@@ -157,12 +158,11 @@
 
 static DEFINE_string2(backend, b, "sw", "Backend to use. Allowed values are " BACKENDS_STR ".");
 
-static DEFINE_bool(atrace, false, "Enable support for using ATrace. ATrace is only supported on Android.");
-
 DEFINE_int32(msaa, 0, "Number of subpixel samples. 0 for no HW antialiasing.");
 DEFINE_pathrenderer_flag;
 
 DEFINE_bool(instancedRendering, false, "Enable instanced rendering on GPU backends.");
+DECLARE_int32(threads)
 
 const char *kBackendTypeStrings[sk_app::Window::kBackendTypeCount] = {
     "OpenGL",
@@ -255,7 +255,6 @@
     , fZoomLevel(0.0f)
     , fGestureDevice(GestureDevice::kNone)
 {
-    static SkTaskGroup::Enabler kTaskGroupEnabler;
     SkGraphics::Init();
 
     static SkOnce initPathRendererNames;
@@ -285,9 +284,8 @@
     SetResourcePath("/data/local/tmp/resources");
 #endif
 
-    if (FLAGS_atrace) {
-        SkAssertResult(SkEventTracer::SetInstance(new SkATrace()));
-    }
+    initializeEventTracingForTools(&FLAGS_threads);
+    static SkTaskGroup::Enabler kTaskGroupEnabler(FLAGS_threads);
 
     fBackendType = get_backend_type(FLAGS_backend[0]);
     fWindow = Window::CreateNativeWindow(platformData);