#ifndef INSTRUMENTOR_HPP #define INSTRUMENTOR_HPP #include "open_engine/logging.hpp" #include #include #include #include #include #include #include #include namespace OpenEngine { using FloatingPointMicroseconds = std::chrono::duration; struct ProfileResult { std::string name; FloatingPointMicroseconds start; std::chrono::microseconds elapsed_time; std::thread::id thread_id; }; struct InstrumentationSession { const char* name; }; class Instrumentor { public: ~Instrumentor() { EndSession(); }; void BeginSession(const char* name, const std::string& filepath = "results.json") { std::lock_guard lock(mutex); if (current_session) { if (Logger::GetCoreLogger()) OE_CORE_ERROR("Instrumentor::BeginSession({}), when session {} already exists", name, current_session->name); InternalEndSession(); } output_stream.open(filepath); if(output_stream.is_open()) { current_session = new InstrumentationSession(name); WriteHeader(); } else { if (Logger::GetCoreLogger()) OE_CORE_ERROR("Instrumentor could not open results file: {}", filepath); } }; void EndSession() { std::lock_guard lock(mutex); InternalEndSession(); }; void WriteProfile(const ProfileResult& result) { std::stringstream json; std::string name = result.name; std::replace(name.begin(), name.end(), '"', '\''); json << std::setprecision(3) << std::fixed; json << ",{"; json << "\"cat\":\"function\","; json << "\"dur\":" << (result.elapsed_time.count()) << ','; json << "\"name\":\"" << name << "\","; json << "\"ph\":\"X\","; json << "\"pid\":0,"; json << "\"tid\":" << result.thread_id << ","; json << "\"ts\":" << result.start.count(); json << "}"; std::lock_guard lock(mutex); if (current_session) { output_stream << json.str(); output_stream.flush(); } }; static Instrumentor& Get() { static Instrumentor instance; return instance; }; private: void WriteHeader() { output_stream << "{\"otherData\": {},\"traceEvents\":[{}"; }; void WriteFooter() { output_stream << "]}"; }; void InternalEndSession() { if (current_session) { WriteFooter(); output_stream.close(); delete current_session; current_session = nullptr; } } Instrumentor() { }; private: std::mutex mutex; InstrumentationSession* current_session = nullptr; std::ofstream output_stream; int profile_count = 0; }; class InstrumentationTimer { public: InstrumentationTimer(const char* name) : name(name), stopped(false) { start_timepoint = std::chrono::high_resolution_clock::now(); }; ~InstrumentationTimer() { if (!stopped) Stop(); }; void Stop() { auto end_timepoint = std::chrono::high_resolution_clock::now(); auto start = FloatingPointMicroseconds{start_timepoint.time_since_epoch()}; auto elapsed_time = std::chrono::time_point_cast(end_timepoint).time_since_epoch() - std::chrono::time_point_cast(start_timepoint).time_since_epoch(); uint32_t thread_id = std::hash{}(std::this_thread::get_id()); Instrumentor::Get().WriteProfile({ name, start, elapsed_time, std::this_thread::get_id() }); stopped = true; }; private: const char* name; std::chrono::time_point start_timepoint; bool stopped; }; } #define OE_PROFILE 1 #if OE_PROFILE #define OE_PROFILE_BEGIN_SESSION(name, filepath) ::OpenEngine::Instrumentor::Get().BeginSession(name, filepath) #define OE_PROFILE_END_SESSION() ::OpenEngine::Instrumentor::Get().EndSession() #define OE_PROFILE_SCOPE(name) ::OpenEngine::InstrumentationTimer timer##__LINE__(name); #define OE_PROFILE_FUNCTION() OE_PROFILE_SCOPE(__PRETTY_FUNCTION__) #else #define OE_PROFILE_BEGIN_SESSION(name, filepath) #define OE_PROFILE_END_SESSION() #define OE_PROFILE_SCOPE(name) #define OE_PROFILE_FUNCTION() #endif #endif // INSTRUMENTOR_HPP