From 69fd008199989c5a5a96f992dcaa4089b63f490f Mon Sep 17 00:00:00 2001 From: Brian Osman Date: Wed, 9 Aug 2017 09:25:39 -0400 Subject: Added SkJSONWriter This is a stand-alone helper class for writing properly structured JSON to an SkWStream. It currently solves two problems (although this CL only uses it in one context): 1) Performance. Writing out JSON this way is about 10x faster than using JSONCPP. For the large amounts of data generated by the tracing system, that's a big win. 2) Makes it easy to emit structured JSON from code that's not fully centralized. We'd like to spit out JSON that describes a GrContext, GrGpu, GrCaps, etc... Doing that with simple string manipulation is complex, and spreads this logic over all those functions. Using JSONCPP adds yet another (large) third party library dependency (that we only build into our own tools right now). This went through several revisions. I originally planned it as a stateful SkString wrapper, so the user could just build their JSON as a string. That's O(N^2), though, because SkString grows by a (small) constant amount. Even using a better growth strategy still means needing RAM for all the resulting text, which is usually pointless. This version has a constant memory cost, so writing huge amounts of JSON to disk (tracing a long DM run can emit 100's of MBs) doesn't stress resources. Bug: skia: Change-Id: Ia716524b246db0f97d332da60d2ce9903069e748 Reviewed-on: https://skia-review.googlesource.com/31204 Commit-Queue: Brian Osman Reviewed-by: Mike Klein --- tools/trace/SkChromeTracingTracer.cpp | 134 ++++++++++++++++++---------------- tools/trace/SkChromeTracingTracer.h | 5 +- 2 files changed, 75 insertions(+), 64 deletions(-) (limited to 'tools/trace') 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(value.as_uint)); + writer->appendU64(value.as_uint); + break; case TRACE_VALUE_TYPE_INT: - return Json::Value(static_cast(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(""); + writer->appendString(""); + 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(traceEvent.fID)); } // Convert nanoseconds to microseconds (standard time unit for tracing JSON files) - json["ts"] = static_cast(traceEvent.fClockBegin) * 1E-3; + writer->appendDouble("ts", static_cast(traceEvent.fClockBegin) * 1E-3); if (0 != traceEvent.fClockEnd) { - json["dur"] = static_cast(traceEvent.fClockEnd - traceEvent.fClockBegin) * 1E-3; + double dur = static_cast(traceEvent.fClockEnd - traceEvent.fClockBegin) * 1E-3; + writer->appendDouble("dur", dur); } - json["tid"] = static_cast(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 BaseTypeResolver; TraceEvent* appendEvent(const TraceEvent&); - Json::Value traceEventToJson(const TraceEvent&, BaseTypeResolver* baseTypeResolver); + void traceEventToJson(SkJSONWriter*, const TraceEvent&, BaseTypeResolver* baseTypeResolver); SkString fFilename; SkSpinlock fMutex; -- cgit v1.2.3