aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gn/utils.gni1
-rw-r--r--src/utils/SkJSONWriter.h329
-rw-r--r--tools/trace/SkChromeTracingTracer.cpp134
-rw-r--r--tools/trace/SkChromeTracingTracer.h5
4 files changed, 405 insertions, 64 deletions
diff --git a/gn/utils.gni b/gn/utils.gni
index 14bec37def..faab5d9e1b 100644
--- a/gn/utils.gni
+++ b/gn/utils.gni
@@ -45,6 +45,7 @@ skia_utils_sources = [
"$_src/utils/SkInsetConvexPolygon.cpp",
"$_src/utils/SkInsetConvexPolygon.h",
"$_src/utils/SkInterpolator.cpp",
+ "$_src/utils/SkJSONWriter.h",
"$_src/utils/SkMatrix22.cpp",
"$_src/utils/SkMatrix22.h",
"$_src/utils/SkMultiPictureDocument.cpp",
diff --git a/src/utils/SkJSONWriter.h b/src/utils/SkJSONWriter.h
new file mode 100644
index 0000000000..9ac28ebc47
--- /dev/null
+++ b/src/utils/SkJSONWriter.h
@@ -0,0 +1,329 @@
+/*
+ * 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 SkJSONWriter_DEFINED
+#define SkJSONWriter_DEFINED
+
+#include "SkStream.h"
+
+#include <inttypes.h>
+
+/**
+ * Lightweight class for writing properly structured JSON data. No random-access, everything must
+ * be generated in-order. The resulting JSON is written directly to the SkWStream supplied at
+ * construction time. Output is buffered, so writing to disk (via an SkFILEWStream) is ideal.
+ *
+ * There is a basic state machine to ensure that JSON is structured correctly, and to allow for
+ * (optional) pretty formatting.
+ *
+ * This class adheres to the RFC-4627 usage of JSON (not ECMA-404). In other words, all JSON
+ * created with this class must have a top-level object or array. Free-floating values of other
+ * types are not considered valid.
+ *
+ * Note that all error checking is in the form of asserts - invalid usage in a non-debug build
+ * will simply produce invalid JSON.
+ */
+class SkJSONWriter : SkNoncopyable {
+public:
+ enum class Mode {
+ /**
+ * Output the minimal amount of text. No additional whitespace (including newlines) is
+ * generated. The resulting JSON is suitable for fast parsing and machine consumption.
+ */
+ kFast,
+
+ /**
+ * Output human-readable JSON, with indented objects and arrays, and one value per line.
+ * Slightly slower than kFast, and produces data that is somewhat larger.
+ */
+ kPretty
+ };
+
+ /**
+ * Construct a JSON writer that will serialize all the generated JSON to 'stream'.
+ */
+ SkJSONWriter(SkWStream* stream, Mode mode = Mode::kFast)
+ : fBlock(new char[kBlockSize])
+ , fWrite(fBlock)
+ , fBlockEnd(fBlock + kBlockSize)
+ , fStream(stream)
+ , fMode(mode)
+ , fState(State::kStart) {
+ fScopeStack.push_back(Scope::kNone);
+ }
+
+ ~SkJSONWriter() {
+ this->flush();
+ delete[] fBlock;
+ SkASSERT(fScopeStack.count() == 1);
+ }
+
+ /**
+ * Force all buffered output to be flushed to the underlying stream.
+ */
+ void flush() {
+ if (fWrite != fBlock) {
+ fStream->write(fBlock, fWrite - fBlock);
+ fWrite = fBlock;
+ }
+ }
+
+ /**
+ * Append the name (key) portion of an object member. Must be called between beginObject() and
+ * endObject(). If you have both the name and value of an object member, you can simply call
+ * the two argument versions of the other append functions.
+ */
+ void appendName(const char* name) {
+ if (!name) {
+ return;
+ }
+ SkASSERT(Scope::kObject == this->scope());
+ SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState);
+ if (State::kObjectValue == fState) {
+ this->write(",", 1);
+ }
+ this->newline();
+ this->write("\"", 1);
+ this->write(name, strlen(name));
+ this->write("\":", 2);
+ fState = State::kObjectName;
+ }
+
+ /**
+ * Adds a new object. A name must be supplied when called between beginObject() and
+ * endObject(). Calls to beginObject() must be balanced by corresponding calls to endObject().
+ */
+ void beginObject(const char* name = nullptr) {
+ this->appendName(name);
+ this->beginValue(true);
+ this->write("{", 1);
+ fScopeStack.push_back(Scope::kObject);
+ fState = State::kObjectBegin;
+ }
+
+ /**
+ * Ends an object that was previously started with beginObject().
+ */
+ void endObject() {
+ SkASSERT(Scope::kObject == this->scope());
+ SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState);
+ bool emptyObject = State::kObjectBegin == fState;
+ this->popScope();
+ if (!emptyObject) {
+ this->newline();
+ }
+ this->write("}", 1);
+ }
+
+ /**
+ * Adds a new array. A name must be supplied when called between beginObject() and
+ * endObject(). Calls to beginArray() must be balanced by corresponding calls to endArray().
+ */
+ void beginArray(const char* name = nullptr) {
+ this->appendName(name);
+ this->beginValue(true);
+ this->write("[", 1);
+ fScopeStack.push_back(Scope::kArray);
+ fState = State::kArrayBegin;
+ }
+
+ /**
+ * Ends an array that was previous started with beginArray().
+ */
+ void endArray() {
+ SkASSERT(Scope::kArray == this->scope());
+ SkASSERT(State::kArrayBegin == fState || State::kArrayValue == fState);
+ bool emptyArray = State::kArrayBegin == fState;
+ this->popScope();
+ if (!emptyArray) {
+ this->newline();
+ }
+ this->write("]", 1);
+ }
+
+ /**
+ * Functions for adding values of various types. The single argument versions add un-named
+ * values, so must be called either
+ * - Between beginArray() and endArray() -or-
+ * - Between beginObject() and endObject(), after calling appendName()
+ */
+ void appendString(const char* value) {
+ this->beginValue();
+ this->write("\"", 1);
+ if (value) {
+ while (*value) {
+ switch (*value) {
+ case '"': this->write("\\\"", 2); break;
+ case '\\': this->write("\\\\", 2); break;
+ case '\b': this->write("\\b", 2); break;
+ case '\f': this->write("\\f", 2); break;
+ case '\n': this->write("\\n", 2); break;
+ case '\r': this->write("\\r", 2); break;
+ case '\t': this->write("\\t", 2); break;
+ default: this->write(value, 1); break;
+ }
+ value++;
+ }
+ }
+ this->write("\"", 1);
+ }
+
+ void appendPointer(const void* value) { this->beginValue(); this->appendf("\"%p\"", value); }
+ void appendBool(bool value) {
+ this->beginValue();
+ if (value) {
+ this->write("true", 4);
+ } else {
+ this->write("false", 5);
+ }
+ }
+ void appendS32(int32_t value) { this->beginValue(); this->appendf("%d", value); }
+ void appendS64(int64_t value) { this->beginValue(); this->appendf("%" PRId64, value); }
+ void appendU32(uint32_t value) { this->beginValue(); this->appendf("%u", value); }
+ void appendU64(uint64_t value) { this->beginValue(); this->appendf("%" PRIu64, value); }
+ void appendFloat(float value) { this->beginValue(); this->appendf("%f", value);; }
+ void appendDouble(double value) { this->beginValue(); this->appendf("%f", value); }
+ void appendHexU32(uint32_t value) { this->beginValue(); this->appendf("\"0x%x\"", value); }
+ void appendHexU64(uint64_t value) { this->beginValue(); this->appendf("\"0x%" PRIx64 "\"", value); }
+
+#define DEFINE_NAMED_APPEND(function, type) \
+ void function(const char* name, type value) { this->appendName(name); this->function(value); }
+
+ /**
+ * Functions for adding named values of various types. These add a name field, so must be
+ * called between beginObject() and endObject().
+ */
+ DEFINE_NAMED_APPEND(appendString, const char *)
+ DEFINE_NAMED_APPEND(appendPointer, const void *)
+ DEFINE_NAMED_APPEND(appendBool, bool)
+ DEFINE_NAMED_APPEND(appendS32, int32_t)
+ DEFINE_NAMED_APPEND(appendS64, int64_t)
+ DEFINE_NAMED_APPEND(appendU32, uint32_t)
+ DEFINE_NAMED_APPEND(appendU64, uint64_t)
+ DEFINE_NAMED_APPEND(appendFloat, float)
+ DEFINE_NAMED_APPEND(appendDouble, double)
+ DEFINE_NAMED_APPEND(appendHexU32, uint32_t)
+ DEFINE_NAMED_APPEND(appendHexU64, uint64_t)
+
+#undef DEFINE_NAMED_APPEND
+
+private:
+ enum {
+ // Using a 32k scratch block gives big performance wins, but we diminishing returns going
+ // any larger. Even with a 1MB block, time to write a large (~300 MB) JSON file only drops
+ // another ~10%.
+ kBlockSize = 32 * 1024,
+ };
+
+ enum class Scope {
+ kNone,
+ kObject,
+ kArray
+ };
+
+ enum class State {
+ kStart,
+ kEnd,
+ kObjectBegin,
+ kObjectName,
+ kObjectValue,
+ kArrayBegin,
+ kArrayValue,
+ };
+
+ void appendf(const char* fmt, ...) {
+ const int kBufferSize = 1024;
+ char buffer[kBufferSize];
+ va_list argp;
+ va_start(argp, fmt);
+#ifdef SK_BUILD_FOR_WIN
+ int length = _vsnprintf_s(buffer, kBufferSize, _TRUNCATE, fmt, argp);
+#else
+ int length = vsnprintf(buffer, kBufferSize, fmt, argp);
+#endif
+ SkASSERT(length >= 0 && length < kBufferSize);
+ va_end(argp);
+ this->write(buffer, length);
+ }
+
+ void beginValue(bool structure = false) {
+ SkASSERT(State::kObjectName == fState ||
+ State::kArrayBegin == fState ||
+ State::kArrayValue == fState ||
+ (structure && State::kStart == fState));
+ if (State::kArrayValue == fState) {
+ this->write(",", 1);
+ }
+ if (Scope::kArray == this->scope()) {
+ this->newline();
+ } else if (Scope::kObject == this->scope() && Mode::kPretty == fMode) {
+ this->write(" ", 1);
+ }
+ // We haven't added the value yet, but all (non-structure) callers emit something
+ // immediately, so transition state, to simplify the calling code.
+ if (!structure) {
+ fState = Scope::kArray == this->scope() ? State::kArrayValue : State::kObjectValue;
+ }
+ }
+
+ void newline() {
+ if (Mode::kPretty == fMode) {
+ this->write("\n", 1);
+ for (int i = 0; i < fScopeStack.count() - 1; ++i) {
+ this->write(" ", 3);
+ }
+ }
+ }
+
+ void write(const char* buf, size_t length) {
+ if (static_cast<size_t>(fBlockEnd - fWrite) < length) {
+ // Don't worry about splitting writes that overflow our block.
+ this->flush();
+ }
+ if (length > kBlockSize) {
+ // Send particularly large writes straight through to the stream (unbuffered).
+ fStream->write(buf, length);
+ } else {
+ memcpy(fWrite, buf, length);
+ fWrite += length;
+ }
+ }
+
+ Scope scope() const {
+ SkASSERT(!fScopeStack.empty());
+ return fScopeStack.back();
+ }
+
+ void popScope() {
+ fScopeStack.pop_back();
+ switch (this->scope()) {
+ case Scope::kNone:
+ fState = State::kEnd;
+ break;
+ case Scope::kObject:
+ fState = State::kObjectValue;
+ break;
+ case Scope::kArray:
+ fState = State::kArrayValue;
+ break;
+ default:
+ SkDEBUGFAIL("Invalid scope");
+ break;
+ }
+ }
+
+ char* fBlock;
+ char* fWrite;
+ char* fBlockEnd;
+
+ SkWStream* fStream;
+ Mode fMode;
+ State fState;
+ SkSTArray<16, Scope, true> fScopeStack;
+};
+
+#endif
diff --git a/tools/trace/SkChromeTracingTracer.cpp b/tools/trace/SkChromeTracingTracer.cpp
index fd9e8d1c3d..ced4f74fa9 100644
--- a/tools/trace/SkChromeTracingTracer.cpp
+++ b/tools/trace/SkChromeTracingTracer.cpp
@@ -6,6 +6,7 @@
*/
#include "SkChromeTracingTracer.h"
+#include "SkJSONWriter.h"
#include "SkThreadID.h"
#include "SkTraceEvent.h"
#include "SkOSFile.h"
@@ -86,31 +87,38 @@ void SkChromeTracingTracer::updateTraceEventDuration(const uint8_t* categoryEnab
traceEvent->fClockEnd = std::chrono::high_resolution_clock::now().time_since_epoch().count();
}
-static Json::Value trace_value_to_json(uint64_t argValue, uint8_t argType) {
+static void trace_value_to_json(SkJSONWriter* writer, uint64_t argValue, uint8_t argType) {
skia::tracing_internals::TraceValueUnion value;
value.as_uint = argValue;
switch (argType) {
case TRACE_VALUE_TYPE_BOOL:
- return Json::Value(value.as_bool);
+ writer->appendBool(value.as_bool);
+ break;
case TRACE_VALUE_TYPE_UINT:
- return Json::Value(static_cast<Json::UInt64>(value.as_uint));
+ writer->appendU64(value.as_uint);
+ break;
case TRACE_VALUE_TYPE_INT:
- return Json::Value(static_cast<Json::Int64>(value.as_uint));
+ writer->appendS64(value.as_int);
+ break;
case TRACE_VALUE_TYPE_DOUBLE:
- return Json::Value(value.as_double);
+ writer->appendDouble(value.as_double);
+ break;
case TRACE_VALUE_TYPE_POINTER:
- return Json::Value(SkStringPrintf("%p", value.as_pointer).c_str());
+ writer->appendPointer(value.as_pointer);
+ break;
case TRACE_VALUE_TYPE_STRING:
case TRACE_VALUE_TYPE_COPY_STRING:
- return Json::Value(value.as_string);
+ writer->appendString(value.as_string);
+ break;
default:
- return Json::Value("<unknown type>");
+ writer->appendString("<unknown type>");
+ break;
}
}
-Json::Value SkChromeTracingTracer::traceEventToJson(const TraceEvent& traceEvent,
- BaseTypeResolver* baseTypeResolver) {
+void SkChromeTracingTracer::traceEventToJson(SkJSONWriter* writer, const TraceEvent& traceEvent,
+ BaseTypeResolver* baseTypeResolver) {
// We track the original (creation time) "name" of each currently live object, so we can
// automatically insert "base_name" fields in object snapshot events.
if (TRACE_EVENT_PHASE_CREATE_OBJECT == traceEvent.fPhase) {
@@ -121,89 +129,91 @@ Json::Value SkChromeTracingTracer::traceEventToJson(const TraceEvent& traceEvent
baseTypeResolver->remove(traceEvent.fID);
}
- Json::Value json;
+ writer->beginObject();
+
char phaseString[2] = { traceEvent.fPhase, 0 };
- json["ph"] = phaseString;
- json["name"] = traceEvent.fName;
- json["cat"] = traceEvent.fCategory;
+ writer->appendString("ph", phaseString);
+ writer->appendString("name", traceEvent.fName);
+ writer->appendString("cat", traceEvent.fCategory);
if (0 != traceEvent.fID) {
// IDs are (almost) always pointers
- json["id"] = SkStringPrintf("%p", traceEvent.fID).c_str();
+ writer->appendPointer("id", reinterpret_cast<void*>(traceEvent.fID));
}
// Convert nanoseconds to microseconds (standard time unit for tracing JSON files)
- json["ts"] = static_cast<double>(traceEvent.fClockBegin) * 1E-3;
+ writer->appendDouble("ts", static_cast<double>(traceEvent.fClockBegin) * 1E-3);
if (0 != traceEvent.fClockEnd) {
- json["dur"] = static_cast<double>(traceEvent.fClockEnd - traceEvent.fClockBegin) * 1E-3;
+ double dur = static_cast<double>(traceEvent.fClockEnd - traceEvent.fClockBegin) * 1E-3;
+ writer->appendDouble("dur", dur);
}
- json["tid"] = static_cast<Json::Int64>(traceEvent.fThreadID);
+ writer->appendS64("tid", traceEvent.fThreadID);
// Trace events *must* include a process ID, but for internal tools this isn't particularly
// important (and certainly not worth adding a cross-platform API to get it).
- json["pid"] = 0;
+ writer->appendS32("pid", 0);
if (traceEvent.fNumArgs) {
- Json::Value args;
+ writer->beginObject("args");
+
+ bool addedSnapshot = false;
+ if (TRACE_EVENT_PHASE_SNAPSHOT_OBJECT == traceEvent.fPhase &&
+ baseTypeResolver->find(traceEvent.fID) &&
+ 0 != strcmp(*baseTypeResolver->find(traceEvent.fID), traceEvent.fName)) {
+ // Special handling for snapshots where the name differs from creation.
+ writer->beginObject("snapshot");
+ writer->appendString("base_type", *baseTypeResolver->find(traceEvent.fID));
+ addedSnapshot = true;
+ }
+
for (int i = 0; i < traceEvent.fNumArgs; ++i) {
- Json::Value argValue = trace_value_to_json(traceEvent.fArgValues[i],
- traceEvent.fArgTypes[i]);
+ // TODO: Skip '#'
+ writer->appendName(traceEvent.fArgNames[i]);
+
if (traceEvent.fArgNames[i] && '#' == traceEvent.fArgNames[i][0]) {
- // Interpret #foo as an ID reference
- Json::Value idRef;
- idRef["id_ref"] = argValue;
- args[traceEvent.fArgNames[i] + 1] = idRef;
+ writer->beginObject();
+ writer->appendName("id_ref");
+ trace_value_to_json(writer, traceEvent.fArgValues[i], traceEvent.fArgTypes[i]);
+ writer->endObject();
} else {
- args[traceEvent.fArgNames[i]] = argValue;
+ trace_value_to_json(writer, traceEvent.fArgValues[i], traceEvent.fArgTypes[i]);
}
}
- if (TRACE_EVENT_PHASE_SNAPSHOT_OBJECT == traceEvent.fPhase &&
- baseTypeResolver->find(traceEvent.fID) &&
- 0 != strcmp(*baseTypeResolver->find(traceEvent.fID), traceEvent.fName)) {
- // Special handling for snapshots where the name differs from creation.
- // We start with args = { "snapshot": "Object info" }
-
- // Inject base_type. args = { "snapshot": "Object Info", "base_type": "BaseFoo" }
- args["base_type"] = *baseTypeResolver->find(traceEvent.fID);
-
- // Wrap this up in a new dict, again keyed by "snapshot". The inner "snapshot" is now
- // arbitrary, but we don't have a better name (and the outer key *must* be snapshot).
- // snapshot = { "snapshot": { "snapshot": "Object Info", "base_type": "BaseFoo" } }
- Json::Value snapshot;
- snapshot["snapshot"] = args;
-
- // Now insert that whole thing as the event's args.
- // { "name": "DerivedFoo", "id": "0x12345678, ...
- // "args": { "snapshot": { "snapshot": "Object Info", "base_type": "BaseFoo" } }
- // }, ...
- json["args"] = snapshot;
- } else {
- json["args"] = args;
+
+ if (addedSnapshot) {
+ writer->endObject();
}
+
+ writer->endObject();
}
- return json;
+
+ writer->endObject();
}
void SkChromeTracingTracer::flush() {
SkAutoMutexAcquire lock(fMutex);
- Json::Value root(Json::arrayValue);
+ SkString dirname = SkOSPath::Dirname(fFilename.c_str());
+ if (!dirname.isEmpty() && !sk_exists(dirname.c_str(), kWrite_SkFILE_Flag)) {
+ if (!sk_mkdir(dirname.c_str())) {
+ SkDebugf("Failed to create directory.");
+ }
+ }
+
+ SkFILEWStream fileStream(fFilename.c_str());
+ SkJSONWriter writer(&fileStream, SkJSONWriter::Mode::kFast);
+ writer.beginArray();
+
BaseTypeResolver baseTypeResolver;
for (int i = 0; i < fBlocks.count(); ++i) {
for (int j = 0; j < kEventsPerBlock; ++j) {
- root.append(this->traceEventToJson(fBlocks[i][j], &baseTypeResolver));
+ this->traceEventToJson(&writer, fBlocks[i][j], &baseTypeResolver);
}
}
for (int i = 0; i < fEventsInCurBlock; ++i) {
- root.append(this->traceEventToJson(fCurBlock[i], &baseTypeResolver));
+ this->traceEventToJson(&writer, fCurBlock[i], &baseTypeResolver);
}
- SkString dirname = SkOSPath::Dirname(fFilename.c_str());
- if (!dirname.isEmpty() && !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::FastWriter().write(root).c_str());
- stream.flush();
+ writer.endArray();
+ writer.flush();
+ fileStream.flush();
}
diff --git a/tools/trace/SkChromeTracingTracer.h b/tools/trace/SkChromeTracingTracer.h
index 987031641b..c6411bb69f 100644
--- a/tools/trace/SkChromeTracingTracer.h
+++ b/tools/trace/SkChromeTracingTracer.h
@@ -10,11 +10,12 @@
#include "SkEventTracer.h"
#include "SkEventTracingPriv.h"
-#include "SkJSONCPP.h"
#include "SkSpinlock.h"
#include "SkString.h"
#include "SkTHash.h"
+class SkJSONWriter;
+
/**
* A SkEventTracer implementation that logs events to JSON for viewing with chrome://tracing.
*/
@@ -80,7 +81,7 @@ private:
typedef SkTHashMap<uint64_t, const char*> BaseTypeResolver;
TraceEvent* appendEvent(const TraceEvent&);
- Json::Value traceEventToJson(const TraceEvent&, BaseTypeResolver* baseTypeResolver);
+ void traceEventToJson(SkJSONWriter*, const TraceEvent&, BaseTypeResolver* baseTypeResolver);
SkString fFilename;
SkSpinlock fMutex;