SLProject  4.2.000
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
Profiler.cpp
Go to the documentation of this file.
1 /**
2  * \file Profiler.cpp
3  * \authors Marino von Wattenwyl
4  * \date December 2021
5  * \copyright http://opensource.org/licenses/GPL-3.0
6  * \remarks Please use clangformat to format the code. See more code style on
7  * https://github.com/cpvrlab/SLProject4/wiki/SLProject-Coding-Style
8 */
9 
10 #include <Profiler.h>
11 #include <utility>
12 #include <algorithm>
13 #include <cstdint>
14 #include <cstring>
15 #include <iostream>
16 #include <ByteOrder.h>
17 
18 //-----------------------------------------------------------------------------
19 /*!
20  * Starts a profiling session by saving the session start timestamp so it
21  * can later be subtracted from the individual result timestamps to get the
22  * time points relative to the start of the session.
23  * \param filePath The path where the trace file should be written to
24  */
25 void Profiler::beginSession(std::string filePath)
26 {
27  _filePath = std::move(filePath);
28 
29  auto startPoint = std::chrono::high_resolution_clock::now();
30  _sessionStart = std::chrono::time_point_cast<std::chrono::microseconds>(startPoint).time_since_epoch().count();
31 }
32 //-----------------------------------------------------------------------------
33 /*! Ends the profiling session and writes the result to a trace file.
34  * A trace file (.slt) has the following layout:
35  * Number of scopes: int32
36  * Scope 1 name: (length: int32, name: non-null-terminated char array)
37  * Scope 2 name: (length: int32, name: non-null-terminated char array)
38  * ...
39  * Number of threads: int32
40  * Thread 1 name: (length: int32, name: non-null-terminated char array)
41  * Number of scopes entered in thread 1: int32
42  * Scope 1 in thread 1 (name index: int32, depth: int32, start time: int64, end time: int64)
43  * Scope 2 in thread 1 (name index: int32, depth: int32, start time: int64, end time: int64)
44  * ...
45  * Thread 2 name: (length: int32, name: non-null-terminated char array)
46  * Number of scopes entered in thread 2: int32
47  * Scope 1 in thread 2 (name index: int32, depth: int32, start time: int64, end time: int64)
48  * Scope 2 in thread 2 (name index: int32, depth: int32, start time: int64, end time: int64)
49  * ...
50  * ...
51  *
52  * All data is written in the big-endian format because that's the endianness that
53  * Java uses to read the data later on in the trace viewer.
54  * This means that the function has to check the endianness of the system
55  * and convert all integers to big endian if we're on a little-endian system.
56  */
58 {
59  std::ofstream fileStream(_filePath, std::ios::binary);
60 
61  ////////////////////////////////////////
62  // Collect scope names and thread IDs //
63  ////////////////////////////////////////
64 
65  std::vector<const char*> scopeNames;
66  std::vector<uint32_t> threadIds;
67 
68  for (ProfilingResult& result : _results)
69  {
70  if (std::find(scopeNames.begin(), scopeNames.end(), result.name) == scopeNames.end())
71  scopeNames.push_back(result.name);
72 
73  if (std::find(threadIds.begin(), threadIds.end(), result.threadId) == threadIds.end())
74  threadIds.push_back(result.threadId);
75  }
76 
77  /////////////////////////
78  // Write scope section //
79  /////////////////////////
80 
81  // Write the number of scope names
82  auto numScopeNames = (uint32_t)scopeNames.size();
83  ByteOrder::writeBigEndian32(numScopeNames, fileStream);
84 
85  // Write each scope name
86  for (const char* scopeName : scopeNames)
87  {
88  writeString(scopeName, fileStream);
89  }
90 
91  /////////////////////////
92  // Write trace section //
93  /////////////////////////
94 
95  // Write number of threads
96  auto numThreads = (uint32_t)threadIds.size();
97  ByteOrder::writeBigEndian32(numThreads, fileStream);
98 
99  for (uint32_t threadId : threadIds)
100  {
101  // Write thread name
102  writeString(_threadNames[threadId].c_str(), fileStream);
103 
104  // Count and write number of scopes in thread
105  uint32_t numScopes = 0;
106  for (ProfilingResult& result : _results)
107  {
108  if (result.threadId == threadId) numScopes++;
109  }
110  ByteOrder::writeBigEndian32(numScopes, fileStream);
111 
112  // Write results of thread
113  for (ProfilingResult& result : _results)
114  {
115  if (result.threadId != threadId) continue;
116 
117  auto nameIndex = (uint32_t)(std::find(scopeNames.begin(), scopeNames.end(), result.name) - scopeNames.begin());
118  auto depth = result.depth;
119  auto start = result.start - _sessionStart;
120  auto end = result.end - _sessionStart;
121 
122  ByteOrder::writeBigEndian32(nameIndex, fileStream);
123  ByteOrder::writeBigEndian32(depth, fileStream);
124  ByteOrder::writeBigEndian64(start, fileStream);
125  ByteOrder::writeBigEndian64(end, fileStream);
126  }
127  }
128 }
129 //-----------------------------------------------------------------------------
130 /*!
131  * Stores a result thread-safely in a vector so it can be written to a trace
132  * file at the end of the session.
133  * \param result
134  */
136 {
137  _mutex.lock();
138  _results.push_back(result);
139  _mutex.unlock();
140 }
141 //-----------------------------------------------------------------------------
142 /*!
143  * Associates the thread in which the function was called with the name provided.
144  * This function must be called at the start of every profiled thread.
145  * It is sensibly also thread-safe.
146  * \param name
147  */
148 void Profiler::profileThread(const std::string& name)
149 {
150  _mutex.lock();
151 
152  for (uint32_t i = 0; i < _threadNames.size(); i++)
153  {
154  if (_threadNames[i] == name)
155  {
157  _mutex.unlock();
158  return;
159  }
160  }
161 
162  uint32_t threadId = (uint32_t)_threadNames.size();
163  _threadNames.push_back(name);
164  ProfilerTimer::threadId = threadId;
165 
166  _mutex.unlock();
167 }
168 //-----------------------------------------------------------------------------
169 //! Writes the length (32-bit) and the string (non-null-terminated) itself to the file stream
170 void Profiler::writeString(const char* s, std::ofstream& stream)
171 {
172  ByteOrder::writeBigEndian32((uint32_t)std::strlen(s), stream);
173  stream << s;
174 }
175 //-----------------------------------------------------------------------------
176 thread_local uint32_t ProfilerTimer::threadId = INVALID_THREAD_ID;
177 thread_local uint32_t ProfilerTimer::threadDepth = 0;
178 //-----------------------------------------------------------------------------
179 /*!
180  * Constructor for ProfilerTimer that saves the current time as the start
181  * time, the thread-local depth as the scope depth and increases the
182  * thread-local depth since we have just entered a scope.
183  * PROFILE_THREAD must be called in the current thread before this function
184  * or else the current thread can't be identified and the application exits.
185  * \param name Name of the scope
186  */
188 {
189  // If the thread ID is INVALID_THREAD_ID, PROFILE_THREAD hasn't been called
190  // We don't know the current thread in this case, so we simply skip
192  {
193  _running = false;
194  std::cout << ("Warning: Attempted to profile scope in non-profiled thread\nScope name: " + std::string(name) + "\n").c_str();
195  return;
196  }
197 
198  _name = name;
199  _startPoint = std::chrono::high_resolution_clock::now();
201  _running = true;
202 
203  threadDepth++;
204 }
205 //-----------------------------------------------------------------------------
206 /*!
207  * Destructor for ProfilerTimer that creates a ProfilingResult with
208  * the scope name, the depth, the start time, the current
209  * time as the end time and the current thread ID. The ProfilingResult is then
210  * registered with the Profiler and the thread-local depth is decreased since
211  * we have just exited a scope.
212  */
214 {
215  if (!_running) return;
216  _running = false;
217 
218  auto endTimePoint = std::chrono::high_resolution_clock::now();
219  uint64_t start = std::chrono::time_point_cast<std::chrono::microseconds>(_startPoint).time_since_epoch().count();
220  uint64_t end = std::chrono::time_point_cast<std::chrono::microseconds>(endTimePoint).time_since_epoch().count();
221 
222  ProfilingResult result{_name, _depth, start, end, threadId};
224  threadDepth--;
225 }
226 //-----------------------------------------------------------------------------
SLScene * s
Definition: SLScene.h:31
void endSession()
Definition: Profiler.cpp:57
static Profiler & instance()
Definition: Profiler.h:70
std::string _filePath
Future path of the trace file.
Definition: Profiler.h:87
std::vector< std::string > _threadNames
List of thread names (the thread ID is the index)
Definition: Profiler.h:90
std::vector< ProfilingResult > _results
List of profiling results (of all threads)
Definition: Profiler.h:89
void profileThread(const std::string &name)
Definition: Profiler.cpp:148
uint64_t _sessionStart
Start timestamp of the session in microseconds.
Definition: Profiler.h:88
std::string filePath()
Definition: Profiler.h:77
static void writeString(const char *s, std::ofstream &stream)
Writes the length (32-bit) and the string (non-null-terminated) itself to the file stream.
Definition: Profiler.cpp:170
void recordResult(ProfilingResult result)
Definition: Profiler.cpp:135
std::mutex _mutex
Mutex for accessing profiling results and thread names.
Definition: Profiler.h:91
void beginSession(std::string filePath)
Definition: Profiler.cpp:25
const char * _name
Definition: Profiler.h:114
std::chrono::time_point< std::chrono::high_resolution_clock > _startPoint
Definition: Profiler.h:116
ProfilerTimer(const char *name)
Definition: Profiler.cpp:187
static thread_local uint32_t threadId
Definition: Profiler.h:111
static thread_local uint32_t threadDepth
Definition: Profiler.h:112
uint32_t _depth
Definition: Profiler.h:115
static constexpr uint32_t INVALID_THREAD_ID
Definition: Profiler.h:110
bool _running
Definition: Profiler.h:117
The SLScene class represents the top level instance holding the scene structure.
Definition: SLScene.h:47
void writeBigEndian32(uint32_t number, std::ostream &stream)
Definition: ByteOrder.cpp:118
void writeBigEndian64(uint64_t number, std::ostream &stream)
Definition: ByteOrder.cpp:129