SpiecsEngine
 
Loading...
Searching...
No Matches
Instrumentor.h
Go to the documentation of this file.
1#pragma once
2
3#include <Core/Core.h>
4
5#include <algorithm>
6#include <chrono>
7#include <fstream>
8#include <iomanip>
9#include <string>
10#include <thread>
11#include <mutex>
12#include <sstream>
13
14namespace SpicesTest {
15
16 using FloatingPointMicroseconds = std::chrono::duration<double, std::micro>;
17
19 {
20 std::string Name;
21
22 FloatingPointMicroseconds Start;
23 std::chrono::microseconds ElapsedTime;
24 std::thread::id ThreadID;
25 };
26
28 {
29 std::string Name;
30 };
31
33 {
34 public:
35 Instrumentor(const Instrumentor&) = delete;
37
38 void BeginSession(const std::string& name, const std::string& filepath = "results.json")
39 {
40 std::lock_guard lock(m_Mutex);
42 {
43 // If there is already a current session, then close it before beginning new one.
44 // Subsequent profiling output meant for the original session will end up in the
45 // newly opened session instead. That's better than having badly formatted
46 // profiling output.
47 //if (Spices::Log::GetCoreLogger()) // Edge case: BeginSession() might be before Log::Init()
48 //{
49 // SPICES_CORE_ERROR("Instrumentor::BeginSession('{0}') when session '{1}' already open.", name, m_CurrentSession->Name);
50 //}
52 }
53 m_OutputStream.open(filepath);
54
55 if (m_OutputStream.is_open())
56 {
59 }
60 else
61 {
62 //if (Spices::Log::GetCoreLogger()) // Edge case: BeginSession() might be before Log::Init()
63 //{
64 // SPICES_CORE_ERROR("Instrumentor could not open results file '{0}'.", filepath);
65 //}
66 }
67 }
68
70 {
71 std::lock_guard lock(m_Mutex);
73 }
74
75 void WriteProfile(const ProfileResult& result)
76 {
77 std::stringstream json;
78
79 json << std::setprecision(3) << std::fixed;
80 json << ",{";
81 json << "\"cat\":\"function\",";
82 json << "\"dur\":" << (result.ElapsedTime.count()) << ',';
83 json << "\"name\":\"" << result.Name << "\",";
84 json << "\"ph\":\"X\",";
85 json << "\"pid\":0,";
86 json << "\"tid\":" << result.ThreadID << ",";
87 json << "\"ts\":" << result.Start.count();
88 json << "}";
89
90 std::lock_guard lock(m_Mutex);
92 {
93 m_OutputStream << json.str();
94 m_OutputStream.flush();
95 }
96 }
97
98 static Instrumentor& Get()
99 {
100 static Instrumentor instance;
101 return instance;
102 }
103 private:
105 : m_CurrentSession(nullptr)
106 {
107 }
108
110 {
112 }
113
115 {
116 m_OutputStream << "{\"otherData\": {},\"traceEvents\":[{}";
117 m_OutputStream.flush();
118 }
119
121 {
122 m_OutputStream << "]}";
123 m_OutputStream.flush();
124 }
125
126 // Note: you must already own lock on m_Mutex before
127 // calling InternalEndSession()
129 {
131 {
133 m_OutputStream.close();
134 delete m_CurrentSession;
135 m_CurrentSession = nullptr;
136 }
137 }
138 private:
139 std::mutex m_Mutex;
141 std::ofstream m_OutputStream;
142 };
143
145 {
146 public:
147 InstrumentationTimer(const char* name)
148 : m_Name(name), m_Stopped(false)
149 {
150 m_StartTimepoint = std::chrono::steady_clock::now();
151 }
152
154 {
155 if (!m_Stopped)
156 Stop();
157 }
158
159 void Stop()
160 {
161 auto endTimepoint = std::chrono::steady_clock::now();
162 auto highResStart = FloatingPointMicroseconds{ m_StartTimepoint.time_since_epoch() };
163 auto elapsedTime = std::chrono::time_point_cast<std::chrono::microseconds>(endTimepoint).time_since_epoch() - std::chrono::time_point_cast<std::chrono::microseconds>(m_StartTimepoint).time_since_epoch();
164
165 Instrumentor::Get().WriteProfile({ m_Name, highResStart, elapsedTime, std::this_thread::get_id() });
166
167 m_Stopped = true;
168 }
169 private:
170 const char* m_Name;
173 };
174
176
177 template <size_t N>
179 {
180 char Data[N];
181 };
182
183 template <size_t N, size_t K>
184 constexpr auto CleanupOutputString(const char(&expr)[N], const char(&remove)[K])
185 {
186 ChangeResult<N> result = {};
187
188 size_t srcIndex = 0;
189 size_t dstIndex = 0;
190 while (srcIndex < N)
191 {
192 size_t matchIndex = 0;
193 while (matchIndex < K - 1 && srcIndex + matchIndex < N - 1 && expr[srcIndex + matchIndex] == remove[matchIndex])
194 matchIndex++;
195 if (matchIndex == K - 1)
196 srcIndex += matchIndex;
197 result.Data[dstIndex++] = expr[srcIndex] == '"' ? '\'' : expr[srcIndex];
198 srcIndex++;
199 }
200 return result;
201 }
202 }
203}
204
205#define SPICES_PROFILE 1
207// Resolve which function signature macro will be used. Note that this only
208// is resolved when the (pre)compiler starts, so the syntax highlighting
209// could mark the wrong one in your editor!
210#if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || (defined(__ICC) && (__ICC >= 600)) || defined(__ghs__)
211#define SPICES_FUNC_SIG __PRETTY_FUNCTION__
212#elif defined(__DMC__) && (__DMC__ >= 0x810)
213#define SPICES_FUNC_SIG __PRETTY_FUNCTION__
214#elif (defined(__FUNCSIG__) || (_MSC_VER))
215#define SPICES_FUNC_SIG __FUNCSIG__
216#elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || (defined(__IBMCPP__) && (__IBMCPP__ >= 500))
217#define SPICES_FUNC_SIG __FUNCTION__
218#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550)
219#define SPICES_FUNC_SIG __FUNC__
220#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)
221#define SPICES_FUNC_SIG __func__
222#elif defined(__cplusplus) && (__cplusplus >= 201103)
223#define SPICES_FUNC_SIG __func__
224#else
225#define SPICES_FUNC_SIG "SPICES_FUNC_SIG unknown!"
226#endif
227
228#define SPICESTEST_PROFILE_BEGIN_SESSION(name, filepath) ::SpicesTest::Instrumentor::Get().BeginSession(name, filepath)
229#define SPICESTEST_PROFILE_END_SESSION() ::SpicesTest::Instrumentor::Get().EndSession()
230#define SPICESTEST_PROFILE_SCOPE_LINE2(name, line) constexpr auto fixedName##line = ::SpicesTest::InstrumentorUtils::CleanupOutputString(name, "__cdecl ");
231 ::SpicesTest::InstrumentationTimer timer##line(fixedName##line.Data)
232#define SPICESTEST_PROFILE_SCOPE_LINE(name, line) SPICESTEST_PROFILE_SCOPE_LINE2(name, line)
233#define SPICESTEST_PROFILE_SCOPE(name) SPICESTEST_PROFILE_SCOPE_LINE(name, __LINE__)
234#define SPICESTEST_PROFILE_FUNCTION() SPICESTEST_PROFILE_SCOPE(SPICES_FUNC_SIG)
235
236#else
237
238#define SPICESTEST_PROFILE_BEGIN_SESSION(name, filepath)
239#define SPICESTEST_PROFILE_END_SESSION()
240#define SPICESTEST_PROFILE_SCOPE(name)
241#define SPICESTEST_PROFILE_FUNCTION()
242
243#endif
#define SPICES_FUNC_SIG
#define SPICESTEST_PROFILE_SCOPE_LINE2(name, line)
#define SPICESTEST_PROFILE_SCOPE(name)
#define SPICES_PROFILE
#define SPICESTEST_PROFILE_END_SESSION()
#define SPICESTEST_PROFILE_BEGIN_SESSION(name, filepath)
#define SPICESTEST_PROFILE_SCOPE_LINE(name, line)
InstrumentationTimer(const char *name)
std::chrono::time_point< std::chrono::steady_clock > m_StartTimepoint
Instrumentor(Instrumentor &&)=delete
static Instrumentor & Get()
void BeginSession(const std::string &name, const std::string &filepath="results.json")
void WriteProfile(const ProfileResult &result)
InstrumentationSession * m_CurrentSession
std::ofstream m_OutputStream
Instrumentor(const Instrumentor &)=delete
int main(int argc, char **argv)
The Entry of SpicesTest.
Definition main.cpp:76
constexpr auto CleanupOutputString(const char(&expr)[N], const char(&remove)[K])
std::thread::id ThreadID
std::chrono::microseconds ElapsedTime
FloatingPointMicroseconds Start