Yarn: Add Trace class
When enabled, it writes out a chrome trace file populated with events from the YARN_* event macros.
Bug: b/139010488
Change-Id: Ifae27b2bcf08f767e9886664b1a998162a617108
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/34813
Tested-by: Ben Clayton <bclayton@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
diff --git a/src/Yarn/Scheduler.cpp b/src/Yarn/Scheduler.cpp
index dab3628..25b432e 100644
--- a/src/Yarn/Scheduler.cpp
+++ b/src/Yarn/Scheduler.cpp
@@ -19,11 +19,21 @@
#include "Debug.hpp"
#include "Defer.hpp"
#include "Thread.hpp"
+#include "Trace.hpp"
#if defined(_WIN32)
#include <intrin.h> // __nop()
#endif
+// Enable to trace scheduler events.
+#define ENABLE_TRACE_EVENTS 0
+
+#if ENABLE_TRACE_EVENTS
+#define TRACE(...) YARN_SCOPED_EVENT(__VA_ARGS__)
+#else
+#define TRACE(...)
+#endif
+
namespace
{
@@ -228,6 +238,7 @@
void Scheduler::Fiber::yield()
{
+ YARN_SCOPED_EVENT("YIELD");
worker->yield(this);
}
@@ -398,6 +409,7 @@
{
case Mode::MultiThreaded:
{
+ YARN_NAME_THREAD("Thread<%.2d> Fiber<%.2d>", int(id), Fiber::current()->id);
{
std::unique_lock<std::mutex> lock(work.mutex);
work.added.wait(lock, [this] { return work.num > 0 || shutdown; });
@@ -441,6 +453,7 @@
void Scheduler::Worker::spinForWork()
{
+ TRACE("SPIN");
Task stolen;
constexpr auto duration = std::chrono::milliseconds(1);
diff --git a/src/Yarn/Thread.cpp b/src/Yarn/Thread.cpp
index 83c56b2..e415453 100644
--- a/src/Yarn/Thread.cpp
+++ b/src/Yarn/Thread.cpp
@@ -14,6 +14,8 @@
#include "Thread.hpp"
+#include "Trace.hpp"
+
#include <cstdarg>
#if defined(_WIN32)
@@ -51,6 +53,7 @@
wchar_t wname[1024];
mbstowcs(wname, name, 1024);
setThreadDescription(GetCurrentThread(), wname);
+ YARN_NAME_THREAD("%s", name);
}
unsigned int Thread::numLogicalCPUs()
@@ -88,6 +91,8 @@
#else
pthread_setname_np(pthread_self(), name);
#endif
+
+ YARN_NAME_THREAD("%s", name);
}
unsigned int Thread::numLogicalCPUs()
diff --git a/src/Yarn/Trace.cpp b/src/Yarn/Trace.cpp
new file mode 100644
index 0000000..0b00e14
--- /dev/null
+++ b/src/Yarn/Trace.cpp
@@ -0,0 +1,248 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// 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.
+
+// The Trace API produces a trace event file that can be consumed with Chrome's
+// about:tracing viewer.
+// Documentation can be found at:
+// https://www.chromium.org/developers/how-tos/trace-event-profiling-tool
+// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit
+
+#include "Trace.hpp"
+
+#include "Defer.hpp"
+#include "Scheduler.hpp"
+#include "Thread.hpp"
+
+#if YARN_TRACE_ENABLED
+
+#include <atomic>
+#include <unordered_set>
+#include <fstream>
+
+namespace
+{
+
+// Chrome traces can choke or error on very large trace files.
+// Limit the number of events created to this number.
+static constexpr int MaxEvents = 100000;
+
+uint64_t threadFiberID(uint32_t threadID, uint32_t fiberID)
+{
+ return static_cast<uint64_t>(threadID) * 31 + static_cast<uint64_t>(fiberID);
+}
+
+} // anonymous namespace
+
+namespace yarn
+{
+
+Trace* Trace::get()
+{
+ static Trace trace;
+ return &trace;
+}
+
+Trace::Trace()
+{
+ nameThread("main");
+ thread = std::thread([&] {
+ Thread::setName("Trace worker");
+
+ auto out = std::fstream("chrome.trace", std::ios_base::out);
+
+ out << "[" << std::endl;
+ defer(out << std::endl << "]" << std::endl);
+
+ auto first = true;
+ for (int i = 0; i < MaxEvents; i++)
+ {
+ auto event = take();
+ if (event->type() == Event::Type::Shutdown)
+ {
+ return;
+ }
+ if (!first) { out << "," << std::endl; };
+ first = false;
+ out << "{" << std::endl;
+ event->write(out);
+ out << "}";
+ }
+
+ stopped = true;
+
+ while(take()->type() != Event::Type::Shutdown) {}
+ });
+}
+
+Trace::~Trace()
+{
+ put(new Shutdown());
+ thread.join();
+}
+
+void Trace::nameThread(const char* fmt, ...)
+{
+ if (stopped) { return; }
+ auto event = new NameThreadEvent();
+
+ va_list vararg;
+ va_start(vararg, fmt);
+ vsnprintf(event->name, Trace::MaxEventNameLength, fmt, vararg);
+ va_end(vararg);
+
+ put(event);
+}
+
+void Trace::beginEvent(const char* fmt, ...)
+{
+ if (stopped) { return; }
+ auto event = new BeginEvent();
+
+ va_list vararg;
+ va_start(vararg, fmt);
+ vsnprintf(event->name, Trace::MaxEventNameLength, fmt, vararg);
+ va_end(vararg);
+
+ event->timestamp = timestamp();
+ put(event);
+}
+
+void Trace::endEvent()
+{
+ if (stopped) { return; }
+ auto event = new EndEvent();
+ event->timestamp = timestamp();
+ put(event);
+}
+
+void Trace::beginAsyncEvent(uint32_t id, const char* fmt, ...)
+{
+ if (stopped) { return; }
+ auto event = new AsyncStartEvent();
+
+ va_list vararg;
+ va_start(vararg, fmt);
+ vsnprintf(event->name, Trace::MaxEventNameLength, fmt, vararg);
+ va_end(vararg);
+
+ event->timestamp = timestamp();
+ event->id = id;
+ put(event);
+}
+
+void Trace::endAsyncEvent(uint32_t id, const char* fmt, ...)
+{
+ if (stopped) { return; }
+ auto event = new AsyncEndEvent();
+
+ va_list vararg;
+ va_start(vararg, fmt);
+ vsnprintf(event->name, Trace::MaxEventNameLength, fmt, vararg);
+ va_end(vararg);
+
+ event->timestamp = timestamp();
+ event->id = id;
+ put(event);
+}
+
+uint64_t Trace::timestamp()
+{
+ auto now = std::chrono::high_resolution_clock::now();
+ auto diff = std::chrono::duration_cast<std::chrono::microseconds>(now - createdAt);
+ return static_cast<uint64_t>(diff.count());
+}
+
+void Trace::put(Event* event)
+{
+ auto idx = eventQueueWriteIdx++ % eventQueues.size();
+ auto &queue = eventQueues[idx];
+ std::unique_lock<std::mutex> lock(queue.mutex);
+ auto notify = queue.data.size() == 0;
+ queue.data.push(std::unique_ptr<Event>(event));
+ lock.unlock();
+ if (notify) { queue.condition.notify_one(); }
+}
+
+std::unique_ptr<Trace::Event> Trace::take()
+{
+ auto idx = eventQueueReadIdx++ % eventQueues.size();
+ auto &queue = eventQueues[idx];
+ std::unique_lock<std::mutex> lock(queue.mutex);
+ queue.condition.wait(lock, [&queue] { return queue.data.size() > 0; });
+ auto event = std::move(queue.data.front());
+ queue.data.pop();
+ return event;
+}
+
+#define QUOTE(x) "\"" << x << "\""
+#define INDENT " "
+
+Trace::Event::Event() :
+ threadID(std::hash<std::thread::id>()(std::this_thread::get_id()))
+{
+ if (auto fiber = Scheduler::Fiber::current())
+ {
+ fiberID = fiber->id;
+ }
+}
+
+void Trace::Event::write(std::ostream &out) const
+{
+ out << INDENT << QUOTE("name") << ": " << QUOTE(name) << "," << std::endl;
+ if (categories != nullptr)
+ {
+ out << INDENT << QUOTE("cat") << ": " << "\"";
+ auto first = true;
+ for (auto category = *categories; category != nullptr; category++)
+ {
+ if (!first) { out << ","; }
+ out << category;
+ }
+ out << "\"," << std::endl;
+ }
+ if (fiberID != 0)
+ {
+ out << INDENT << QUOTE("args") << ": " << "{" << std::endl
+ << INDENT << INDENT << QUOTE("fiber") << ": " << fiberID << std::endl
+ << INDENT << "}," << std::endl;
+ }
+ if (threadID != 0)
+ {
+ out << INDENT << QUOTE("tid") << ": " << threadFiberID(threadID, fiberID) << "," << std::endl;
+ }
+ out << INDENT << QUOTE("ph") << ": " << QUOTE(static_cast<char>(type())) << "," << std::endl
+ << INDENT << QUOTE("pid") << ": " << processID << "," << std::endl
+ << INDENT << QUOTE("ts") << ": " << timestamp << std::endl;
+}
+
+void Trace::NameThreadEvent::write(std::ostream &out) const
+{
+ out << INDENT << QUOTE("name") << ": " << QUOTE("thread_name") << "," << std::endl
+ << INDENT << QUOTE("ph") << ": " << QUOTE("M") << "," << std::endl
+ << INDENT << QUOTE("pid") << ": " << processID << "," << std::endl
+ << INDENT << QUOTE("tid") << ": " << threadFiberID(threadID, fiberID) << "," << std::endl
+ << INDENT << QUOTE("args") << ": {" << QUOTE("name") << ": " << QUOTE(name) << "}" << std::endl;
+}
+
+void Trace::AsyncEvent::write(std::ostream &out) const
+{
+ out << INDENT << QUOTE("id") << ": " << QUOTE(id) << "," << std::endl
+ << INDENT << QUOTE("cat") << ": " << QUOTE("async") << "," << std::endl;
+ Event::write(out);
+}
+
+
+} // namespace yarn
+
+#endif // YARN_TRACE_ENABLED
\ No newline at end of file
diff --git a/src/Yarn/Trace.hpp b/src/Yarn/Trace.hpp
new file mode 100644
index 0000000..3034ccf
--- /dev/null
+++ b/src/Yarn/Trace.hpp
@@ -0,0 +1,226 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// 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.
+
+// The Trace API produces a trace event file that can be consumed with Chrome's
+// chrome://tracing viewer.
+// Documentation can be found at:
+// https://www.chromium.org/developers/how-tos/trace-event-profiling-tool
+// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit
+
+#define YARN_TRACE_ENABLED 0
+
+#if YARN_TRACE_ENABLED
+
+#include <array>
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <cstdarg>
+#include <cstring>
+#include <mutex>
+#include <ostream>
+#include <queue>
+#include <thread>
+
+namespace yarn
+{
+
+// Trace writes a trace event file into the current working directory that can
+// be consumed with Chrome's chrome://tracing viewer.
+// Use the YARN_* macros below instead of using this class directly.
+class Trace
+{
+public:
+ static constexpr size_t MaxEventNameLength = 64;
+
+ static Trace* get();
+
+ void nameThread(const char* fmt, ...);
+ void beginEvent(const char* fmt, ...);
+ void endEvent();
+ void beginAsyncEvent(uint32_t id, const char* fmt, ...);
+ void endAsyncEvent(uint32_t id, const char* fmt, ...);
+
+ class ScopedEvent
+ {
+ public:
+ inline ScopedEvent(const char* fmt, ...);
+ inline ~ScopedEvent();
+ private:
+ Trace * const trace;
+ };
+
+ class ScopedAsyncEvent
+ {
+ public:
+ inline ScopedAsyncEvent(uint32_t id, const char* fmt, ...);
+ inline ~ScopedAsyncEvent();
+ private:
+ Trace * const trace;
+ const uint32_t id;
+ std::string name;
+ };
+
+private:
+ Trace();
+ ~Trace();
+ Trace(const Trace&) = delete;
+ Trace& operator = (const Trace&) = delete;
+
+ struct Event
+ {
+ enum class Type : uint8_t
+ {
+ Begin = 'B',
+ End = 'E',
+ Complete = 'X',
+ Instant = 'i',
+ Counter = 'C',
+ AsyncStart = 'b',
+ AsyncInstant = 'n',
+ AsyncEnd = 'e',
+ FlowStart = 's',
+ FlowStep = 't',
+ FlowEnd = 'f',
+ Sample = 'P',
+ ObjectCreated = 'N',
+ ObjectSnapshot = 'O',
+ ObjectDestroyed = 'D',
+ Metadata = 'M',
+ GlobalMemoryDump = 'V',
+ ProcessMemoryDump = 'v',
+ Mark = 'R',
+ ClockSync = 'c',
+ ContextEnter = '(',
+ ContextLeave = ')',
+
+ // Internal types
+ Shutdown = 'S',
+ };
+
+ Event();
+ virtual ~Event() = default;
+ virtual Type type() const = 0;
+ virtual void write(std::ostream &out) const;
+
+ char name[MaxEventNameLength] = {};
+ const char **categories = nullptr;
+ uint64_t timestamp = 0; // in microseconds
+ uint32_t processID = 0;
+ uint32_t threadID;
+ uint32_t fiberID;
+ };
+
+ struct BeginEvent : public Event { Type type() const override { return Type::Begin; } };
+ struct EndEvent : public Event { Type type() const override { return Type::End; } };
+ struct MetadataEvent : public Event { Type type() const override { return Type::Metadata; } };
+ struct Shutdown : public Event { Type type() const override { return Type::Shutdown; } };
+
+ struct AsyncEvent : public Event
+ {
+ void write(std::ostream &out) const override;
+ uint32_t id;
+ };
+
+ struct AsyncStartEvent : public AsyncEvent { Type type() const override { return Type::AsyncStart; } };
+ struct AsyncEndEvent : public AsyncEvent { Type type() const override { return Type::AsyncEnd; } };
+
+ struct NameThreadEvent : public MetadataEvent
+ {
+ void write(std::ostream &out) const override;
+ };
+
+ uint64_t timestamp(); // in microseconds
+
+ void put(Event*);
+ std::unique_ptr<Event> take();
+
+ struct EventQueue
+ {
+ std::queue< std::unique_ptr<Event> > data; // guarded by mutes
+ std::condition_variable condition;
+ std::mutex mutex;
+ };
+ std::array<EventQueue, 1> eventQueues; // TODO: Increasing this from 1 can cause events to go out of order. Investigate, fix.
+ std::atomic<unsigned int> eventQueueWriteIdx = { 0 };
+ unsigned int eventQueueReadIdx = 0;
+ std::chrono::time_point<std::chrono::high_resolution_clock> createdAt = std::chrono::high_resolution_clock::now();
+ std::thread thread;
+ std::atomic<bool> stopped = { false };
+};
+
+Trace::ScopedEvent::ScopedEvent(const char* fmt, ...) : trace(Trace::get())
+{
+ if (trace != nullptr)
+ {
+ char name[Trace::MaxEventNameLength];
+ va_list vararg;
+ va_start(vararg, fmt);
+ vsnprintf(name, Trace::MaxEventNameLength, fmt, vararg);
+ va_end(vararg);
+
+ trace->beginEvent(name);
+ }
+}
+
+Trace::ScopedEvent::~ScopedEvent()
+{
+ if (trace != nullptr)
+ {
+ trace->endEvent();
+ }
+}
+
+Trace::ScopedAsyncEvent::ScopedAsyncEvent(uint32_t id, const char* fmt, ...) : trace(Trace::get()), id(id)
+{
+ if (trace != nullptr)
+ {
+ char buf[Trace::MaxEventNameLength];
+ va_list vararg;
+ va_start(vararg, fmt);
+ vsnprintf(buf, Trace::MaxEventNameLength, fmt, vararg);
+ va_end(vararg);
+ name = buf;
+
+ trace->beginAsyncEvent(id, "%s", buf);
+ }
+}
+
+Trace::ScopedAsyncEvent::~ScopedAsyncEvent()
+{
+ if (trace != nullptr)
+ {
+ trace->endAsyncEvent(id, "%s", name.c_str());
+ }
+}
+
+} // namespace yarn
+
+#define YARN_CONCAT_(a, b) a ## b
+#define YARN_CONCAT(a, b) YARN_CONCAT_(a,b)
+#define YARN_SCOPED_EVENT(...) yarn::Trace::ScopedEvent YARN_CONCAT(scoped_event, __LINE__)(__VA_ARGS__);
+#define YARN_BEGIN_ASYNC_EVENT(id, ...) do { if (auto t = yarn::Trace::get()) { t->beginAsyncEvent(id, __VA_ARGS__); } } while(false);
+#define YARN_END_ASYNC_EVENT(id, ...) do { if (auto t = yarn::Trace::get()) { t->endAsyncEvent(id, __VA_ARGS__); } } while(false);
+#define YARN_SCOPED_ASYNC_EVENT(id, ...) yarn::Trace::ScopedAsyncEvent YARN_CONCAT(defer_, __LINE__)(id, __VA_ARGS__);
+#define YARN_NAME_THREAD(...) do { if (auto t = yarn::Trace::get()) { t->nameThread(__VA_ARGS__); } } while(false);
+
+#else // YARN_TRACE_ENABLED
+
+#define YARN_SCOPED_EVENT(...)
+#define YARN_BEGIN_ASYNC_EVENT(id, ...)
+#define YARN_END_ASYNC_EVENT(id, ...)
+#define YARN_SCOPED_ASYNC_EVENT(id, ...)
+#define YARN_NAME_THREAD(...)
+
+#endif // YARN_TRACE_ENABLED