// // Copyright 2018 Pixar // // Licensed under the Apache License, Version 2.0 (the "Apache License") // with the following modification; you may not use this file except in // compliance with the Apache License and the following modification to it: // Section 6. Trademarks. is deleted and replaced with: // // 6. Trademarks. This License does not grant permission to use the trade // names, trademarks, service marks, or product names of the Licensor // and its affiliates, except as required to comply with Section 4(c) of // the License and to reproduce the content of the NOTICE file. // // You may obtain a copy of the Apache License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the Apache License with the above modification is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the Apache License for the specific // language governing permissions and limitations under the Apache License. // #include "pxr/base/trace/jsonSerialization.h" #include "pxr/pxr.h" #include "pxr/base/js/json.h" #include "pxr/base/js/utils.h" #include "pxr/base/tf/stringUtils.h" #include "pxr/base/trace/eventData.h" #include "pxr/base/trace/eventTreeBuilder.h" PXR_NAMESPACE_OPEN_SCOPE //////////////////////////////////////////////////////////////////////////////// /// JS utility functions //////////////////////////////////////////////////////////////////////////////// template static typename std::enable_if< !std::is_same::value && !std::is_same::value && !std::is_same::value, boost::optional >::type _JsGet(const boost::optional& js) { if (js && js->Is()) { return js->Get(); } return boost::none; } template static typename std::enable_if< std::is_same::value || std::is_same::value || std::is_same::value, const T* >::type _JsGet(const boost::optional& js) { if (js && js->Is()) { return &js->Get(); } return nullptr; } template ::value || std::is_same::value || std::is_same::value, const T*, boost::optional >::type> ReturnType _JsGetValue(const JsObject& js, const std::string& key) { return _JsGet(JsFindValue(js, key)); } // Chrome stores timestamps in microseconds whild Trace stores them in ticks. static TraceEvent::TimeStamp _MicrosecondsToTicks(double us) { return static_cast( us*1000.0 / ArchGetNanosecondsPerTick()); } static double _TicksToMicroSeconds(TraceEvent::TimeStamp t) { return ArchTicksToNanoseconds(t)/1000.0; } // TraceEvent::EventType is stored as a string in JSON. static const char* _EventTypeToString(TraceEvent::EventType t) { switch(t) { case TraceEvent::EventType::Begin: return "Begin"; case TraceEvent::EventType::End: return "End"; case TraceEvent::EventType::CounterDelta: return "CounterDelta"; case TraceEvent::EventType::CounterValue: return "CounterValue"; case TraceEvent::EventType::Timespan: return "Timespan"; case TraceEvent::EventType::ScopeData: return "Data"; case TraceEvent::EventType::Marker: return "Marker"; case TraceEvent::EventType::Unknown: return "Unknown"; } return "Unknown"; } static TraceEvent::EventType _EventTypeFromString(const std::string& s) { if (s == "Begin") { return TraceEvent::EventType::Begin; } else if (s == "End") { return TraceEvent::EventType::End; } else if (s == "CounterDelta") { return TraceEvent::EventType::CounterDelta; } else if (s == "CounterValue") { return TraceEvent::EventType::CounterValue; } else if (s == "Timespan") { return TraceEvent::EventType::Timespan; } else if (s == "Data") { return TraceEvent::EventType::ScopeData; } else if (s == "Mark") { return TraceEvent::EventType::Marker; } return TraceEvent::EventType::Unknown; } // Helper struct to hold data needed to reconstruct an event list. // Since events are read from json out of order, they are placed in // unorderedEvents first. Later they are sorted and added to the eventList. struct EventListConstructionData { TraceEventList eventList; std::vector unorderedEvents; }; using ChromeThreadId = std::string; using ChromeConstructionMap = std::map; // Writes a JSON representatoin of a Trace event. This format is a "raw" format // that does not match the Chrome format. static void _WriteTraceEventToJSON(JsWriter& js, const TfToken& key, const TraceEvent& e) { switch (e.GetType()) { case TraceEvent::EventType::Begin: case TraceEvent::EventType::End: js.WriteObject( "key", key.GetString(), "category", static_cast(e.GetCategory()), "type", _EventTypeToString(e.GetType()), "ts", _TicksToMicroSeconds(e.GetTimeStamp()) ); break; case TraceEvent::EventType::CounterDelta: case TraceEvent::EventType::CounterValue: js.WriteObject( "key", key.GetString(), "category", static_cast(e.GetCategory()), "type", _EventTypeToString(e.GetType()), "ts", _TicksToMicroSeconds(e.GetTimeStamp()), "value", e.GetCounterValue()); break; case TraceEvent::EventType::ScopeData: js.WriteObject( "key", key.GetString(), "category", static_cast(e.GetCategory()), "type", _EventTypeToString(e.GetType()), "ts", _TicksToMicroSeconds(e.GetTimeStamp()), "data", [&e](JsWriter& js) { e.GetData().WriteJson(js); }); break; case TraceEvent::EventType::Timespan: js.WriteObject( "key", key.GetString(), "category", static_cast(e.GetCategory()), "type", _EventTypeToString(e.GetType()), "start", _TicksToMicroSeconds(e.GetStartTimeStamp()), "end", _TicksToMicroSeconds(e.GetEndTimeStamp())); break; case TraceEvent::EventType::Marker: js.WriteObject( "key", key.GetString(), "category", static_cast(e.GetCategory()), "type", _EventTypeToString(e.GetType()), "ts", _TicksToMicroSeconds(e.GetTimeStamp()) ); break; case TraceEvent::EventType::Unknown: break; } } // Reads a "raw" format JSON object and adds it to the eventListData if it can. void _TraceEventFromJSON( const JsValue& jsValue, EventListConstructionData& eventListData) { if (!jsValue.IsObject()) { return; } TraceEventList& list = eventListData.eventList; std::vector& unorderedEvents = eventListData.unorderedEvents; const JsObject& js = jsValue.GetJsObject(); const std::string* keyStr = _JsGetValue(js, "key"); boost::optional category = _JsGetValue(js, "category"); const std::string* typeStr = _JsGetValue(js, "type"); boost::optional tsMicroSeconds = _JsGetValue(js, "ts"); boost::optional ts; if (tsMicroSeconds) { ts = _MicrosecondsToTicks(*tsMicroSeconds); } if (keyStr && category && typeStr) { TraceEvent::EventType type = _EventTypeFromString(*typeStr); switch (type) { case TraceEvent::EventType::Unknown: break; case TraceEvent::EventType::Begin: if (ts) { unorderedEvents.emplace_back( TraceEvent::Begin, list.CacheKey(*keyStr), *ts, *category); } break; case TraceEvent::EventType::End: if (ts) { unorderedEvents.emplace_back( TraceEvent::End, list.CacheKey(*keyStr), *ts, *category); } break; case TraceEvent::EventType::Marker: if (ts) { unorderedEvents.emplace_back( TraceEvent::Marker, list.CacheKey(*keyStr), *ts, *category); } break; case TraceEvent::EventType::Timespan: { boost::optional start = _JsGetValue(js, "start"); boost::optional end = _JsGetValue(js, "end"); if (start && end) { unorderedEvents.emplace_back( TraceEvent::Timespan, list.CacheKey(*keyStr), *start, *end, *category); } } break; case TraceEvent::EventType::CounterDelta: { boost::optional value = _JsGetValue(js, "value"); if (ts && value) { TraceEvent event(TraceEvent::CounterDelta, list.CacheKey(*keyStr), *value, *category); event.SetTimeStamp(*ts); unorderedEvents.emplace_back(std::move(event));; } } break; case TraceEvent::EventType::CounterValue: { boost::optional value = _JsGetValue(js, "value"); if (ts && value) { TraceEvent event(TraceEvent::CounterValue, list.CacheKey(*keyStr), *value, *category); event.SetTimeStamp(*ts); unorderedEvents.emplace_back(std::move(event));; } } break; case TraceEvent::EventType::ScopeData: if (ts) { if (boost::optional dataValue = JsFindValue(js, "data")) { if (dataValue->Is()) { TraceEvent event( TraceEvent::Data, list.CacheKey(*keyStr), dataValue->Get(), *category); event.SetTimeStamp(*ts); unorderedEvents.emplace_back(std::move(event));; } else if (dataValue->Is()) { TraceEvent event( TraceEvent::Data, list.CacheKey(*keyStr), dataValue->Get(), *category); event.SetTimeStamp(*ts); unorderedEvents.emplace_back(std::move(event));; } else if (dataValue->Is()) { TraceEvent event( TraceEvent::Data, list.CacheKey(*keyStr), dataValue->Get(), *category); event.SetTimeStamp(*ts); unorderedEvents.emplace_back(std::move(event));; } else if (dataValue->Is()) { TraceEvent event( TraceEvent::Data, list.CacheKey(*keyStr), dataValue->Get(), *category); event.SetTimeStamp(*ts); unorderedEvents.emplace_back(std::move(event));; } else if (dataValue->Is()) { TraceEvent event( TraceEvent::Data, list.CacheKey(*keyStr), list.StoreData(dataValue->GetString().c_str()), *category); event.SetTimeStamp(*ts); unorderedEvents.emplace_back(std::move(event));; } } } break; } } } namespace { // This class writes a JSON array of JSON objects per thread in the collection // which has Counter events and Data events. This data is need in addition to // the Chrome Format JSON to fully reconstruct a TraceCollection. class _WriteCollectionEventsToJson : public TraceCollection::Visitor { public: void CreateThreadsObject(JsWriter& js) const { JsArray threads; js.WriteArray(_eventsPerThread, [](JsWriter& js, ThreadToEventMap::const_reference p) { js.WriteObject( "thread", p.first, "events", [&p] (JsWriter& js) { js.WriteArray(p.second, [](JsWriter& js, const EventPair& e) { _WriteTraceEventToJSON(js, e.first, *e.second); } ); } ); }); } virtual bool AcceptsCategory(TraceCategoryId categoryId) override { return true; } virtual void OnEvent( const TraceThreadId& threadId, const TfToken& key, const TraceEvent& event) override { // Only convert Counter and Data events. The other types will be in the // chrome format. switch (event.GetType()) { case TraceEvent::EventType::ScopeData: case TraceEvent::EventType::CounterDelta: case TraceEvent::EventType::CounterValue: _eventsPerThread[threadId.ToString()].emplace_back(key, &event); break; case TraceEvent::EventType::Begin: case TraceEvent::EventType::End: case TraceEvent::EventType::Timespan: case TraceEvent::EventType::Marker: case TraceEvent::EventType::Unknown: break; } } virtual void OnBeginCollection() override {} virtual void OnEndCollection() override {} virtual void OnBeginThread(const TraceThreadId& threadId) override {} virtual void OnEndThread(const TraceThreadId& threadId) override {} private: using EventPair = std::pair; using ThreadToEventMap = std::map>; ThreadToEventMap _eventsPerThread; }; } static void _WriteTraceEventsToJson( JsWriter& js, const std::vector>& collections) { using CollectionPtr = std::shared_ptr; // Convert Counter and Data events to JSON. _WriteCollectionEventsToJson eventsToJson; for (const CollectionPtr& collection : collections) { if (collection) { collection->Iterate(eventsToJson); } } js.WriteObject( "threadEvents", [&eventsToJson] (JsWriter& js) { eventsToJson.CreateThreadsObject(js); } ); } bool Trace_JSONSerialization::WriteCollectionsToJSON( JsWriter& js, const std::vector>& collections) { auto extraDataWriter = [&collections](JsWriter& js) { js.WriteKey("libTraceData"); _WriteTraceEventsToJson(js, collections); }; using CollectionPtr = std::shared_ptr; TraceEventTreeRefPtr graph = TraceEventTree::New(); for (const CollectionPtr& collection : collections) { if (collection) { graph->Add(*collection); } } graph->WriteChromeTraceObject(js,extraDataWriter); return true; } // This function converts Chrome trace events into TraceEvents and adds them to // output. static void _ImportChromeEvents( const JsArray& traceEvents, ChromeConstructionMap& output) { std::map tidToNames; for (const JsValue& event : traceEvents) { if (const JsObject* eventObj = _JsGet(event)) { const std::string* tid = _JsGetValue(*eventObj, "tid"); // tid field might be an integer if (!tid) { boost::optional utid = _JsGetValue(*eventObj, "tid"); if (utid) { auto it = tidToNames.find(*utid); if (it == tidToNames.end()) { it = tidToNames.insert( std::make_pair( *utid, TfStringPrintf("%" PRId64, *utid))).first; } tid = &it->second; } } boost::optional ts = _JsGetValue(*eventObj, "ts"); // ts field might be an integer if (!ts) { boost::optional uts = _JsGetValue(*eventObj, "ts"); if (uts) { ts = *uts; } } const std::string* name = _JsGetValue(*eventObj, "name"); const std::string* ph = _JsGetValue(*eventObj, "ph"); boost::optional catId = _JsGetValue(*eventObj, "libTraceCatId"); if (tid && ts && name && ph) { if (!catId) { catId = 0; } if (*ph == "B") { TraceKey key = output[*tid].eventList.CacheKey(*name); output[*tid].unorderedEvents.emplace_back( TraceEvent::Begin, key, _MicrosecondsToTicks(*ts), *catId); } else if (*ph == "E") { TraceKey key = output[*tid].eventList.CacheKey(*name); output[*tid].unorderedEvents.emplace_back( TraceEvent::End, key, _MicrosecondsToTicks(*ts), *catId); } else if (*ph == "R" || *ph == "I" || *ph == "i") { TraceKey key = output[*tid].eventList.CacheKey(*name); output[*tid].unorderedEvents.emplace_back( TraceEvent::Marker, key, _MicrosecondsToTicks(*ts), *catId); } else if (*ph == "X") { // dur field might be a double or an int. boost::optional dur = _JsGetValue(*eventObj, "dur"); if (!dur) { boost::optional udur = _JsGetValue(*eventObj, "dur"); if (udur) { dur = *udur; } } // if dur field was not found check for the tdur field. if (!dur) { // tdur field might be a double or an int. dur = _JsGetValue(*eventObj, "tdur"); boost::optional utdur = _JsGetValue(*eventObj, "tdur"); if (utdur) { dur = *utdur; } } if (dur) { TraceKey key = output[*tid].eventList.CacheKey(*name); output[*tid].unorderedEvents.emplace_back( TraceEvent::Timespan, key, _MicrosecondsToTicks(*ts), _MicrosecondsToTicks(*ts) + _MicrosecondsToTicks(*dur), *catId); } } } } } } // Creates a TraceEventList from EventListConstructionData. static std::unique_ptr _ConstructEventList(EventListConstructionData& data) { TF_AXIOM(data.eventList.IsEmpty()); // TraceEventLists are sorted by timestamp. std::sort(data.unorderedEvents.begin(), data.unorderedEvents.end(), [] (const TraceEvent& lhs, const TraceEvent& rhs) -> bool { TraceEvent::TimeStamp l_time = lhs.GetTimeStamp(); TraceEvent::TimeStamp r_time = rhs.GetTimeStamp(); return l_time < r_time; }); // Add the events to the eventList. // TODO: make a constructor that takes an event vector so we don't have to // make copies? for (TraceEvent& e : data.unorderedEvents) { data.eventList.EmplaceBack(std::move(e)); } data.unorderedEvents.clear(); return std::unique_ptr( new TraceEventList(std::move(data.eventList))); } std::unique_ptr Trace_JSONSerialization::CollectionFromJSON(const JsValue& jsValue) { const JsObject* traceObj = _JsGet(jsValue); const JsArray* chromeEvents = 0; if (traceObj) { chromeEvents = _JsGetValue(*traceObj, "traceEvents"); } else { chromeEvents = _JsGet(jsValue); } const JsObject* traceDataObj = traceObj ? _JsGetValue(*traceObj, "libTraceData") : nullptr; ChromeConstructionMap constMap; // Add events from the chrome trace format. if (chromeEvents) { _ImportChromeEvents(*chromeEvents, constMap); } // Add events from the libTrace specific json. if (traceDataObj) { if (const JsArray* threadEvents = _JsGetValue(*traceDataObj, "threadEvents")) { for (const JsValue& v : *threadEvents) { if (const JsObject* threadObj = _JsGet(v)) { const ChromeThreadId* threadId = _JsGetValue(*threadObj, "thread"); const JsArray* eventArray = _JsGetValue(*threadObj, "events"); if (threadId && eventArray) { for (const JsValue& eventValue : *eventArray) { _TraceEventFromJSON( eventValue, constMap[*threadId]); } } } } } } // Create the event lists and collection. if (!constMap.empty()) { std::unique_ptr collection(new TraceCollection()); for (ChromeConstructionMap::value_type& c : constMap) { collection->AddToCollection( TraceThreadId(c.first), _ConstructEventList(c.second)); } return collection; } return nullptr; } PXR_NAMESPACE_CLOSE_SCOPE