// Copyright 2020 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "perfetto-tracing-only.h" #include "trace_packet.pbzero.h" #include "counter_descriptor.pbzero.h" #include "track_descriptor.pbzero.h" #include "track_event.pbzero.h" #include "interned_data.pbzero.h" #include "perfetto/base/time.h" #include "perfetto/protozero/message_handle.h" #include "perfetto/protozero/proto_utils.h" #include "perfetto/protozero/root_message.h" #include "perfetto/protozero/scattered_stream_writer.h" #include #include #include #include #include #include #include #include #include namespace virtualdeviceperfetto { static FILE* sDefaultFileHandle = nullptr; static VirtualDeviceTraceConfig sTraceConfig = { .initialized = false, .tracingDisabled = true, .packetsWritten = 0, .sequenceIdWritten = 0, .currentInterningId = 1, .currentThreadId = 1, .hostFilename = nullptr, .guestFilename = nullptr, .combinedFilename = nullptr, .hostStartTime = 0, .guestStartTime = 0, .guestTimeDiff = 0, .perThreadStorageMb = 1, }; #define TRACE_STACK_DEPTH_MAX 16 class TraceContext; struct SavedTraceInfo { uint8_t* data; size_t allocSize; size_t written; bool first; }; class TraceStorage { public: void add(TraceContext* context) { std::lock_guard lock(mContextsLock); mContexts.insert(context); } void remove(TraceContext* context) { std::lock_guard lock(mContextsLock); mContexts.erase(context); } void onTracingEnabled() { // do stuff } void onTracingDisabled() { saveTracesToDisk(); } // When a thread exited early before tracing was disabled void saveTrace(const SavedTraceInfo& trace) { std::lock_guard lock(mContextsLock); saveTraceLocked(trace); } private: void saveTraceLocked(const SavedTraceInfo& trace) { if (trace.data) mSavedTraces.push_back(trace); } void saveTracesToDisk(); std::mutex mContextsLock; // protects |mContexts| std::unordered_set mContexts; std::vector mSavedTraces; }; static TraceStorage sTraceStorage; class TraceContext : public protozero::ScatteredStreamWriter::Delegate { public: TraceContext() : mWriter(this) { sTraceStorage.add(this); } SavedTraceInfo save(bool partialReset = false) { ScopedTracingLock lock(&mTracingLock); return saveLocked(partialReset); } virtual ~TraceContext() { sTraceStorage.remove(this); if (mTraceBuffer) { sTraceStorage.saveTrace(save()); } } virtual protozero::ContiguousMemoryRange GetNewBuffer() { if (mWritingPacket) { mPacket.Finalize(); } finishAndRefresh(); if (mWritingPacket) { beginPacket(); size_t writtenThisTime = size_t(((uintptr_t)mWriter.write_ptr()) - (uintptr_t)mTraceBuffer); return protozero::ContiguousMemoryRange{mWriter.write_ptr(), mWriter.write_ptr() + (mTraceBufferSize - writtenThisTime) }; } else { return protozero::ContiguousMemoryRange{mTraceBuffer, mTraceBuffer + mTraceBufferSize}; } } #ifdef __cplusplus # define CC_LIKELY( exp ) (__builtin_expect( !!(exp), true )) # define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), false )) #else # define CC_LIKELY( exp ) (__builtin_expect( !!(exp), 1 )) # define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), 0 )) #endif static const uint32_t kSequenceId = 1; static constexpr char kTrackNamePrefix[] = "emu-"; static constexpr char kCounterNamePrefix[] = "-count-"; void beginTrace(const char* name) { if (CC_LIKELY(sTraceConfig.tracingDisabled)) return; if (CC_UNLIKELY(mStackDepth == TRACE_STACK_DEPTH_MAX)) return; ScopedTracingLock lock(&mTracingLock); ensureThreadInfo(); bool needEmitEventIntern = false; mCurrentEventNameIid[mStackDepth] = internEvent(name, &needEmitEventIntern); if (CC_UNLIKELY(needEmitEventIntern)) { beginPacket(); mPacket.set_trusted_packet_sequence_id(kSequenceId); mPacket.set_sequence_flags(2 /* incremental */); auto interned_data = mPacket.set_interned_data(); auto eventname = interned_data->add_event_names(); eventname->set_iid(mCurrentEventNameIid[mStackDepth]); eventname->set_name(name); endPacket(); } // Finally do the actual thing beginPacket(); mPacket.set_trusted_packet_sequence_id(kSequenceId); mPacket.set_sequence_flags(2 /* incremental */); mPacket.set_timestamp(getTimestamp()); auto trackevent = mPacket.set_track_event(); trackevent->set_track_uuid(mThreadId); // thread id trackevent->add_category_iids(mCurrentCategoryIid[mStackDepth]); trackevent->set_name_iid(mCurrentEventNameIid[mStackDepth]); trackevent->set_type(::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN); endPacket(); ++mStackDepth; } inline void ensureThreadInfo() __attribute__((always_inline)) { // Write trusted sequence id if this is the first packet. if (CC_UNLIKELY(1 == __atomic_add_fetch(&sTraceConfig.packetsWritten, 1, __ATOMIC_SEQ_CST))) { mFirst = true; sTraceConfig.sequenceIdWritten = true; beginPacket(); mPacket.set_trusted_packet_sequence_id(1); mPacket.set_sequence_flags(1); endPacket(); } else if (!CC_LIKELY(sTraceConfig.sequenceIdWritten)) { // Not the first packet, but some other thread is writing the sequence id at the moment, wait for it. while (!sTraceConfig.sequenceIdWritten); } // TODO: Allow changing category static const char kCategory[] = "gfxstream"; bool needEmitCategoryIntern = false; mCurrentCategoryIid[mStackDepth] = internCategory(kCategory, &needEmitCategoryIntern); if (CC_UNLIKELY(needEmitCategoryIntern)) { beginPacket(); mPacket.set_trusted_packet_sequence_id(kSequenceId); mPacket.set_sequence_flags(2 /* incremental */); auto interned_data = mPacket.set_interned_data(); auto category = interned_data->add_event_categories(); category->set_iid(mCurrentCategoryIid[mStackDepth]); category->set_name(kCategory); endPacket(); } if (CC_UNLIKELY(mNeedToSetThreadId)) { mThreadId = __atomic_add_fetch(&sTraceConfig.currentThreadId, 1, __ATOMIC_RELAXED); mNeedToSetThreadId = false; fprintf(stderr, "%s: found thread id: %u\n", __func__, mThreadId); beginPacket(); mPacket.set_trusted_packet_sequence_id(kSequenceId); mPacket.set_sequence_flags(2 /* incremental */); auto desc = mPacket.set_track_descriptor(); desc->set_uuid(mThreadId); desc->set_name(getTrackNameFromThreadId(mThreadId)); endPacket(); } } void endTrace() { if (CC_LIKELY(sTraceConfig.tracingDisabled)) return; if (CC_UNLIKELY(mStackDepth == TRACE_STACK_DEPTH_MAX)) return; if (CC_UNLIKELY(mStackDepth == 0)) return; --mStackDepth; ScopedTracingLock lock(&mTracingLock); // Finally do the actual thing beginPacket(); mPacket.set_trusted_packet_sequence_id(kSequenceId); mPacket.set_sequence_flags(2 /* incremental */); mPacket.set_timestamp(getTimestamp()); auto trackevent = mPacket.set_track_event(); trackevent->add_category_iids(mCurrentCategoryIid[mStackDepth]); trackevent->set_track_uuid(mThreadId); // thread id trackevent->set_name_iid(mCurrentEventNameIid[mStackDepth]); trackevent->set_type(::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END); endPacket(); } void traceCounter(const char* name, int64_t val) { if (CC_LIKELY(sTraceConfig.tracingDisabled)) return; ScopedTracingLock lock(&mTracingLock); ensureThreadInfo(); bool first; uint32_t counterId; uint64_t counterTrackUuid = getOrCreateCounterTrackUuid(name, &counterId, &first); if (CC_UNLIKELY(first)) { fprintf(stderr, "%s: thread id: %u has a counter: %u. uuid: 0x%llx\n", __func__, mThreadId, counterId, (unsigned long long)(counterTrackUuid)); beginPacket(); auto desc = mPacket.set_track_descriptor(); desc->set_uuid(counterTrackUuid); desc->set_name(getTrackNameFromThreadIdAndCounterName(mThreadId, name)); desc->set_counter(); endPacket(); } // Do the actual counter track event beginPacket(); mPacket.set_trusted_packet_sequence_id(kSequenceId); mPacket.set_sequence_flags(2 /* incremental */); mPacket.set_timestamp(getTimestamp()); auto trackevent = mPacket.set_track_event(); trackevent->set_track_uuid(counterTrackUuid); trackevent->set_type(::perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER); trackevent->set_counter_value(val); endPacket(); } void waitFinish() { ScopedTracingLock lock(&mTracingLock); } private: class ScopedTracingLock { public: ScopedTracingLock(std::atomic_flag* flag) : mFlag(flag) { while (mFlag->test_and_set(std::memory_order_acquire)) { // spin } } ~ScopedTracingLock() { mFlag->clear(std::memory_order_release); } private: std::atomic_flag* mFlag; }; SavedTraceInfo saveLocked(bool partialReset) { // Invalidates mTraceBuffer, transfers ownership of it. SavedTraceInfo info = { mTraceBuffer, mTraceBufferSize, size_t(((uintptr_t)mWriter.write_ptr()) - (uintptr_t)mTraceBuffer), mFirst, }; resetLocked(partialReset); return info; } void resetLocked(bool partialReset) { mTraceBuffer = nullptr; mTraceBufferSize = 0; mFirst = false; if (partialReset) return; mNeedToSetThreadId = true; mThreadId = 0; mNeedToConfigureGuestTime = true; mCurrentCounterId = 1; mTimeDiff = 0; mStackDepth = 0; mEventCategoryInterningIds.clear(); mEventNameInterningIds.clear(); mCounterNameToTrackUuids.clear(); mPacket.Reset(&mWriter); } void finishAndRefresh() { if (mTraceBuffer) { sTraceStorage.saveTrace(saveLocked(true /* partial reset */)); } allocTraceBuffer(); }; void allocTraceBuffer() { // Freed after ownership is transferred to Trace Storage mTraceBufferSize = sTraceConfig.perThreadStorageMb * 1048576; mTraceBuffer = (uint8_t*)malloc(mTraceBufferSize); mWriter.Reset(protozero::ContiguousMemoryRange{mTraceBuffer, mTraceBuffer + mTraceBufferSize}); } inline uint64_t getTimestamp() { uint64_t t = (uint64_t)(::perfetto::base::GetWallTimeNs().count()); t += sTraceConfig.guestTimeDiff; return t; } template static inline uint8_t* writeVarInt(T value, uint8_t* target) { // If value is <= 0 we must first sign extend to int64_t (see [1]). // Finally we always cast to an unsigned value to to avoid arithmetic // (sign expanding) shifts in the while loop. // [1]: "If you use int32 or int64 as the type for a negative number, the // resulting varint is always ten bytes long". // - developers.google.com/protocol-buffers/docs/encoding // So for each input type we do the following casts: // uintX_t -> uintX_t -> uintX_t // int8_t -> int64_t -> uint64_t // int16_t -> int64_t -> uint64_t // int32_t -> int64_t -> uint64_t // int64_t -> int64_t -> uint64_t using MaybeExtendedType = typename std::conditional::value, T, int64_t>::type; using UnsignedType = typename std::make_unsigned::type; MaybeExtendedType extended_value = static_cast(value); UnsignedType unsigned_value = static_cast(extended_value); while (unsigned_value >= 0x80) { *target++ = static_cast(unsigned_value) | 0x80; unsigned_value >>= 7; } *target = static_cast(unsigned_value); return target + 1; } void beginPacket() { if (CC_UNLIKELY(mTraceBuffer == nullptr)) { allocTraceBuffer(); } // Make sure there's enough space for the preamble and size field, and to hold a track event. static const size_t kTrackEventPadding = 200; // conservatively 200 bytes static const size_t kPacketHeaderSize = 4; size_t neededSpace = kPacketHeaderSize + 4 + kTrackEventPadding; if (mWriter.bytes_available() < neededSpace) { finishAndRefresh(); } mPacket.Reset(&mWriter); // Write the preamble constexpr uint32_t tag = protozero::proto_utils::MakeTagLengthDelimited(1 /* trace packet id */); uint8_t tagScratch[10]; auto scratchNext = writeVarInt(tag, tagScratch); mWriter.WriteBytes(tagScratch, scratchNext - tagScratch); // Reserve the size field uint8_t* header = mWriter.ReserveBytes(kPacketHeaderSize); memset(header, 0, kPacketHeaderSize); mPacket.set_size_field(header); mWritingPacket = true; } void endPacket() { mWritingPacket = false; mPacket.Finalize(); } uint32_t internCategory(const char* str, bool* firstTime) { auto it = mEventCategoryInterningIds.find(str); if (it != mEventCategoryInterningIds.end()) { *firstTime = false; return it->second; } uint32_t res = sTraceConfig.currentInterningId; mEventCategoryInterningIds[str] = res; __atomic_add_fetch(&sTraceConfig.currentInterningId, 1, __ATOMIC_RELAXED); *firstTime = true; return res; } uint32_t internEvent(const char* str, bool* firstTime) { auto it = mEventNameInterningIds.find(str); if (it != mEventNameInterningIds.end()) { *firstTime = false; return it->second; } uint32_t res = sTraceConfig.currentInterningId; mEventNameInterningIds[str] = res; __atomic_add_fetch(&sTraceConfig.currentInterningId, 1, __ATOMIC_RELAXED); *firstTime = true; return res; } static std::string getTrackNameFromThreadId(uint32_t threadId) { std::stringstream ss; ss << kTrackNamePrefix << threadId; return ss.str(); } static std::string getTrackNameFromThreadIdAndCounterName(uint32_t threadId, const char* counterName) { std::stringstream ss; ss << kTrackNamePrefix << threadId << kCounterNamePrefix << counterName; return ss.str(); } uint64_t getOrCreateCounterTrackUuid(const char* name, uint32_t* counterId, bool* firstTime) { auto it = mCounterNameToTrackUuids.find(name); uint64_t res; if (CC_UNLIKELY(it == mCounterNameToTrackUuids.end())) { // The counter track uuid is the thread id | shifted counter id. res = (((uint64_t)mCurrentCounterId) << 32) | mThreadId; mCounterNameToTrackUuids[name] = res; *counterId = mCurrentCounterId; *firstTime = true; // Increment counter id for this thread. ++mCurrentCounterId; } else { res = it->second; *counterId = res >> 32; *firstTime = false; } return res; } uint8_t* mTraceBuffer = nullptr; size_t mTraceBufferSize = 0; bool mFirst = false; bool mWritingPacket = false; bool mNeedToSetThreadId = true; uint32_t mThreadId = 0; bool mNeedToConfigureGuestTime = true; uint32_t mCurrentCounterId = 1; uint64_t mTimeDiff = 0; std::atomic_flag mTracingLock; uint32_t mStackDepth = 0; uint32_t mCurrentCategoryIid[TRACE_STACK_DEPTH_MAX]; uint32_t mCurrentEventNameIid[TRACE_STACK_DEPTH_MAX]; protozero::RootMessage<::perfetto::protos::pbzero::TracePacket> mPacket; protozero::ScatteredStreamWriter mWriter; std::unordered_map mEventCategoryInterningIds; std::unordered_map mEventNameInterningIds; std::unordered_map mCounterNameToTrackUuids; }; void asyncTraceSaveFunc() { fprintf(stderr, "%s: Saving combined trace async...\n", __func__); static const int kWaitSecondsPerIteration = 1; static const int kMaxIters = 20; static const int kMinItersForGuestFileSize = 2; const char* hostFilename = sTraceConfig.hostFilename; const char* guestFilename = sTraceConfig.guestFilename; const char* combinedFilename = sTraceConfig.combinedFilename; std::streampos currGuestSize = 0; int numGoodGuestFileSizeIters = 0; bool good = false; for (int i = 0; i < kMaxIters; ++i) { fprintf(stderr, "%s: Waiting for 1 second...\n", __func__); std::this_thread::sleep_for(std::chrono::seconds(kWaitSecondsPerIteration)); fprintf(stderr, "%s: Querying file size of guest trace...\n", __func__); std::ifstream guestFile(guestFilename, std::ios::in | std::ios::binary | std::ios::ate); std::streampos size = guestFile.tellg(); if (!size) { fprintf(stderr, "%s: No size, try again\n", __func__); continue; } if (size != currGuestSize) { fprintf(stderr, "%s: Sized changed (%llu to %llu), try again\n", __func__, (unsigned long long)currGuestSize, (unsigned long long)size); currGuestSize = size; continue; } ++numGoodGuestFileSizeIters; if (numGoodGuestFileSizeIters == kMinItersForGuestFileSize) { fprintf(stderr, "%s: size is stable, continue saving\n", __func__); good = true; break; } } if (!good) { fprintf(stderr, "%s: Timed out when waiting for guest file to stabilize, skipping combined trace saving.\n", __func__); return; } std::ifstream hostFile(hostFilename, std::ios_base::binary); std::ifstream guestFile(guestFilename, std::ios_base::binary); std::ofstream combinedFile(combinedFilename, std::ios::out | std::ios_base::binary); combinedFile << guestFile.rdbuf() << hostFile.rdbuf(); fprintf(stderr, "%s: Wrote combined trace (%s)\n", __func__, combinedFilename); } void TraceStorage::saveTracesToDisk() { fprintf(stderr, "%s: Tracing ended================================================================================\n", __func__); fprintf(stderr, "%s: Saving trace to disk. Configuration:\n", __func__); fprintf(stderr, "%s: host filename: %s\n", __func__, sTraceConfig.hostFilename); fprintf(stderr, "%s: guest filename: %s\n", __func__, sTraceConfig.guestFilename); fprintf(stderr, "%s: combined filename: %s\n", __func__, sTraceConfig.combinedFilename); fprintf(stderr, "%s: Saving host trace first...\n", __func__); std::lock_guard lock(mContextsLock); for (auto context: mContexts) { saveTraceLocked(context->save()); }; std::ofstream hostOut; hostOut.open(sTraceConfig.hostFilename, std::ios::out | std::ios::binary); for (const auto& info : mSavedTraces) { if (info.first) { hostOut.write((const char*)(info.data), info.written); } } for (const auto& info : mSavedTraces) { if (!info.first) { hostOut.write((const char*)(info.data), info.written); } free(info.data); } hostOut.close(); mSavedTraces.clear(); fprintf(stderr, "%s: Saving host trace first...(done)\n", __func__); if (!sTraceConfig.guestFilename || !sTraceConfig.combinedFilename) { fprintf(stderr, "%s: skipping guest combined trace, " "either guest file name (%p) not specified or " "combined file name (%p) not specified\n", __func__, sTraceConfig.guestFilename, sTraceConfig.combinedFilename); return; } std::thread saveThread(asyncTraceSaveFunc); saveThread.detach(); } static thread_local TraceContext sThreadLocalTraceContext; PERFETTO_TRACING_ONLY_EXPORT void setTraceConfig(std::function f) { f(sTraceConfig); } PERFETTO_TRACING_ONLY_EXPORT VirtualDeviceTraceConfig queryTraceConfig() { return sTraceConfig; } PERFETTO_TRACING_ONLY_EXPORT void initialize(const bool** tracingDisabledPtr) { *tracingDisabledPtr = &sTraceConfig.tracingDisabled; } bool useFilenameByEnv(const char* s) { return s && ("" != std::string(s)); } PERFETTO_TRACING_ONLY_EXPORT void enableTracing() { const char* hostFilenameByEnv = std::getenv("VPERFETTO_HOST_FILE"); const char* guestFilenameByEnv = std::getenv("VPERFETTO_GUEST_FILE"); const char* combinedFilenameByEnv = std::getenv("VPERFETTO_COMBINED_FILE"); if (useFilenameByEnv(hostFilenameByEnv)) { sTraceConfig.hostFilename = hostFilenameByEnv; } if (useFilenameByEnv(guestFilenameByEnv)) { sTraceConfig.guestFilename = guestFilenameByEnv; } if (useFilenameByEnv(combinedFilenameByEnv)) { sTraceConfig.combinedFilename = combinedFilenameByEnv; } // Don't enable tracing if host filename is null if (!sTraceConfig.hostFilename) return; // Don't enable it twice if (!sTraceConfig.tracingDisabled) return; fprintf(stderr, "%s: Tracing begins================================================================================\n", __func__); fprintf(stderr, "%s: Configuration:\n", __func__); fprintf(stderr, "%s: host filename: %s (possibly set via $VPERFETTO_HOST_FILE)\n", __func__, sTraceConfig.hostFilename); fprintf(stderr, "%s: guest filename: %s (possibly set via $VPERFETTO_GUEST_FILE)\n", __func__, sTraceConfig.guestFilename); fprintf(stderr, "%s: combined filename: %s (possibly set via $VPERFETTO_COMBINED_FILE)\n", __func__, sTraceConfig.combinedFilename); fprintf(stderr, "%s: guest time diff to add to host time: %llu\n", __func__, (unsigned long long)sTraceConfig.guestTimeDiff); sTraceConfig.packetsWritten = 0; sTraceConfig.sequenceIdWritten = 0; sTraceConfig.currentInterningId = 1; sTraceConfig.currentThreadId = 1; sTraceStorage.onTracingEnabled(); sTraceConfig.tracingDisabled = 0; } PERFETTO_TRACING_ONLY_EXPORT void disableTracing() { // Don't enable or disable tracing if host filename is null if (!sTraceConfig.hostFilename) return; uint32_t tracingWasDisabled = sTraceConfig.tracingDisabled; sTraceConfig.tracingDisabled = 1; if (!tracingWasDisabled) { sTraceStorage.onTracingDisabled(); } sTraceConfig.packetsWritten = 0; sTraceConfig.sequenceIdWritten = 0; sTraceConfig.currentInterningId = 1; sTraceConfig.currentThreadId = 1; sTraceConfig.guestTimeDiff = 0; } PERFETTO_TRACING_ONLY_EXPORT void beginTrace(const char* name) { if (CC_LIKELY(sTraceConfig.tracingDisabled)) return; sThreadLocalTraceContext.beginTrace(name); } PERFETTO_TRACING_ONLY_EXPORT void endTrace() { if (CC_LIKELY(sTraceConfig.tracingDisabled)) return; sThreadLocalTraceContext.endTrace(); } PERFETTO_TRACING_ONLY_EXPORT void traceCounter(const char* name, int64_t val) { if (CC_LIKELY(sTraceConfig.tracingDisabled)) return; sThreadLocalTraceContext.traceCounter(name, val); } PERFETTO_TRACING_ONLY_EXPORT void setGuestTime(uint64_t t) { virtualdeviceperfetto::setTraceConfig([t](virtualdeviceperfetto::VirtualDeviceTraceConfig& config) { // can only be set before tracing if (!config.tracingDisabled) { return; } config.guestStartTime = t; config.hostStartTime = (uint64_t)(::perfetto::base::GetWallTimeNs().count()); config.guestTimeDiff = config.guestStartTime - config.hostStartTime; }); } } // namespace virtualdeviceperfetto