| // Copyright 2019 The Marl Authors. |
| // |
| // 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 |
| // |
| // https://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 |
| |
| #ifndef marl_trace_h |
| #define marl_trace_h |
| |
| #define MARL_TRACE_ENABLED 0 |
| |
| #if MARL_TRACE_ENABLED |
| |
| #include <array> |
| #include <atomic> |
| #include <chrono> |
| #include <condition_variable> |
| #include <cstdarg> |
| #include <cstring> |
| #include <mutex> |
| #include <ostream> |
| #include <queue> |
| #include <thread> |
| |
| namespace marl { |
| |
| // Trace writes a trace event file into the current working directory that can |
| // be consumed with Chrome's chrome://tracing viewer. |
| // Use the MARL_* 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; |
| }; |
| // TODO: Increasing this from 1 can cause events to go out of order. |
| // Investigate, fix. |
| std::array<EventQueue, 1> eventQueues; |
| 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 marl |
| |
| #define MARL_CONCAT_(a, b) a##b |
| #define MARL_CONCAT(a, b) MARL_CONCAT_(a, b) |
| #define MARL_SCOPED_EVENT(...) \ |
| marl::Trace::ScopedEvent MARL_CONCAT(scoped_event, __LINE__)(__VA_ARGS__); |
| #define MARL_BEGIN_ASYNC_EVENT(id, ...) \ |
| do { \ |
| if (auto t = marl::Trace::get()) { \ |
| t->beginAsyncEvent(id, __VA_ARGS__); \ |
| } \ |
| } while (false); |
| #define MARL_END_ASYNC_EVENT(id, ...) \ |
| do { \ |
| if (auto t = marl::Trace::get()) { \ |
| t->endAsyncEvent(id, __VA_ARGS__); \ |
| } \ |
| } while (false); |
| #define MARL_SCOPED_ASYNC_EVENT(id, ...) \ |
| marl::Trace::ScopedAsyncEvent MARL_CONCAT(defer_, __LINE__)(id, __VA_ARGS__); |
| #define MARL_NAME_THREAD(...) \ |
| do { \ |
| if (auto t = marl::Trace::get()) { \ |
| t->nameThread(__VA_ARGS__); \ |
| } \ |
| } while (false); |
| |
| #else // MARL_TRACE_ENABLED |
| |
| #define MARL_SCOPED_EVENT(...) |
| #define MARL_BEGIN_ASYNC_EVENT(id, ...) |
| #define MARL_END_ASYNC_EVENT(id, ...) |
| #define MARL_SCOPED_ASYNC_EVENT(id, ...) |
| #define MARL_NAME_THREAD(...) |
| |
| #endif // MARL_TRACE_ENABLED |
| |
| #endif // marl_trace_h |