Files
OpenEngine/open_engine/include/open_engine/instrumentor.hpp
2026-03-10 07:42:42 +01:00

178 lines
5.3 KiB
C++

#ifndef INSTRUMENTOR_HPP
#define INSTRUMENTOR_HPP
#include "open_engine/logging.hpp"
#include <iomanip>
#include <mutex>
#include <sstream>
#include <string>
#include <chrono>
#include <algorithm>
#include <fstream>
#include <thread>
namespace OpenEngine {
using FloatingPointMicroseconds = std::chrono::duration<double, std::micro>;
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<std::chrono::microseconds>(end_timepoint).time_since_epoch()
- std::chrono::time_point_cast<std::chrono::microseconds>(start_timepoint).time_since_epoch();
uint32_t thread_id = std::hash<std::thread::id>{}(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<std::chrono::high_resolution_clock> 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