diff options
-rw-r--r-- | gn/utils.gni | 1 | ||||
-rw-r--r-- | src/utils/SkJSONWriter.h | 329 | ||||
-rw-r--r-- | tools/trace/SkChromeTracingTracer.cpp | 134 | ||||
-rw-r--r-- | tools/trace/SkChromeTracingTracer.h | 5 |
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; |