| // 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 | |
| // 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 "marl/trace.h" | |
| #include "marl/defer.h" | |
| #include "marl/scheduler.h" | |
| #include "marl/thread.h" | |
| #if MARL_TRACE_ENABLED | |
| #include <atomic> | |
| #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 marl { | |
| 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 marl | |
| #endif // MARL_TRACE_ENABLED |