SLProject  4.2.000
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
Instrumentor.h
Go to the documentation of this file.
1 /**
2  * \file Utils/lib-utils/source/Instrumentor.h
3  * \authors Cherno, adaptations by Marcus Hudritsch
4  * \brief Basic instrumentation profiler for writing performance measures
5  * \details It can be used in Chrome://tracing app.
6  * Based on https://gist.github.com/TheCherno
7  * Changes: Compared to Cherno's original I added in memory storage
8  * which is faster and the addProfil function is now thread safe.
9  * \date June 2020
10  * \copyright http://opensource.org/licenses/GPL-3.0
11  * \remarks Please use clangformat to format the code. See more code style on
12  * https://github.com/cpvrlab/SLProject4/wiki/SLProject-Coding-Style
13 */
14 
15 #ifndef INSTRUMENTOR_H
16 #define INSTRUMENTOR_H
17 
18 #include <string>
19 #include <chrono>
20 #include <algorithm>
21 #include <fstream>
22 #include <thread>
23 #include <mutex>
24 
25 /* Set PROFILING to 1 to enable profiling or to 0 for disabling profiling
26  * Just add PROFILE_FUNCTION(); at the beginning of a function that you want to
27  * profile. See the Instrumentor class below on how to display the profiling data.
28  */
29 #define PROFILING 0
30 
31 //-----------------------------------------------------------------------------
32 #if PROFILING
33 # define BEGIN_PROFILING_SESSION(name, storeInMemory, outputPath) Instrumentor::get().beginSession(name, storeInMemory, outputPath)
34 # define END_PROFILING_SESSION Instrumentor::get().endSession()
35 # define PROFILE_SCOPE(name) InstrumentationTimer timer##__LINE__(name)
36 # define PROFILE_FUNCTION() PROFILE_SCOPE(__FUNCTION__)
37 #else
38 # define BEGIN_PROFILING_SESSION(name, storeInMemory, outputPath)
39 # define END_PROFILING_SESSION
40 # define PROFILE_SCOPE(name)
41 # define PROFILE_FUNCTION()
42 #endif
43 //-----------------------------------------------------------------------------
45 {
46  const char* name; //!< pointer to char has constant length
47  long long start; //!< start time point
48  long long end; //!< end time point
49  uint32_t threadID; //!< thread ID
50 };
51 //-----------------------------------------------------------------------------
53 {
54  std::string name;
55 };
56 //-----------------------------------------------------------------------------
57 //! Basic instrumentation profiler for Google Chrome tracing format
58 /*!
59  Usage: include this header file somewhere in your code.
60  In your most outer function (e.g. main) you have to begin profiling session with:
61  Instrumentor::get().beginSession("Session Name"); If you pass storeInMemory=true
62  the profileResults will be stored in memory instead of being written into the
63  file stream which is pretty slow. Of course the in memory storage can quickly
64  use a lot of memory depending how fine grained your profiling is.
65  In app-demo this is done in slCreateApp.
66 
67  In between you can add either PROFILE_FUNCTION(); at the beginning of any routine
68  or PROFILE_SCOPE(scopeName) at the beginning of any scope you want to measure.
69 
70  At the end of your most outer function (e.g. main) you end the session with:
71  Instrumentor::get().endSession();
72 
73  After the endSession you can drag the Profiling-Results.json file into the
74  chrome://tracing page of the Google Chrome browser.
75  In app-demo this is done in SLInterface::slTerminate.
76 */
78 {
79 public:
81  //.........................................................................
82  void beginSession(const std::string& name,
83  const bool storeInMemory = false,
84  const std::string& filePath = "Profiling-Results.json")
85  {
86  _storeInMemory = storeInMemory;
89 
90  if (!_storeInMemory)
91  {
93  writeHeader();
94  }
95  }
96  //.........................................................................
97  void endSession()
98  {
99  if (_storeInMemory)
100  {
101  _outputStream.open(_filePath);
102  writeHeader();
103 
104  for (auto result : _profileResults)
105  writeProfile(result);
106  }
107 
108  // end the file
109  writeFooter();
110  _outputStream.close();
111 
112  delete _currentSession;
113  _currentSession = nullptr;
114  _profileCount = 0;
115  }
116  //.........................................................................
117  /*! addProfile should be as fast as possible for not influencing the
118  profiling by the profiler itself. In addition it must be thread safe.*/
119  void addProfile(const ProfileResult& result)
120  {
121  std::lock_guard<std::mutex> lock(_mutex);
122 
123  if (_storeInMemory)
124  {
125  _profileResults.emplace_back(result);
126  }
127  else
128  {
129  writeProfile(result);
130  }
131  }
132  //.........................................................................
133  void writeProfile(const ProfileResult& result)
134  {
135  if (_profileCount++ > 0)
136  _outputStream << ",";
137 
138  std::string name = result.name;
139  std::replace(name.begin(), name.end(), '"', '\'');
140 
141  _outputStream << "{";
142  _outputStream << "\"cat\":\"function\",";
143  _outputStream << "\"dur\":" << (result.end - result.start) << ',';
144  _outputStream << "\"name\":\"" << name << "\",";
145  _outputStream << "\"ph\":\"X\",";
146  _outputStream << "\"pid\":0,";
147  _outputStream << "\"tid\":" << result.threadID << ",";
148  _outputStream << "\"ts\":" << result.start;
149  _outputStream << "}";
150 
151  // We constantly flush in case of file writing during profiling.
152  if (!_storeInMemory)
153  _outputStream.flush();
154  }
155  //.........................................................................
156  void writeHeader()
157  {
158  _outputStream << "{\"otherData\": {},\"traceEvents\":[";
159  _outputStream.flush();
160  }
161  //.........................................................................
162  void writeFooter()
163  {
164  _outputStream << "]}";
165  _outputStream.flush();
166  }
167  //.........................................................................
168  static Instrumentor& get()
169  {
170  static Instrumentor instance;
171  return instance;
172  }
173  //.........................................................................
174  std::string filePath() { return _filePath; }
175  //.........................................................................
176 
177 private:
179  std::string _filePath;
180  std::ofstream _outputStream;
182  std::mutex _mutex;
183  bool _storeInMemory = false;
184  std::vector<ProfileResult> _profileResults;
185 };
186 //-----------------------------------------------------------------------------
187 typedef std::chrono::time_point<std::chrono::high_resolution_clock> HighResTimePoint;
188 //-----------------------------------------------------------------------------
190 {
191 public:
192  InstrumentationTimer(const char* name) : _name(name), _isStopped(false)
193  {
194  _startTimepoint = std::chrono::high_resolution_clock::now();
195  }
196  //.........................................................................
198  {
199  if (!_isStopped)
200  stop();
201  }
202  //.........................................................................
203  void stop()
204  {
205  auto endTimepoint = std::chrono::high_resolution_clock::now();
206 
207  long long start = std::chrono::time_point_cast<std::chrono::microseconds>(_startTimepoint).time_since_epoch().count();
208  long long end = std::chrono::time_point_cast<std::chrono::microseconds>(endTimepoint).time_since_epoch().count();
209 
210  uint32_t threadID = (uint32_t)std::hash<std::thread::id>{}(std::this_thread::get_id());
211 
212  Instrumentor::get().addProfile({_name, start, end, threadID});
213 
214  _isStopped = true;
215  }
216  //.........................................................................
217 
218 private:
219  const char* _name; //!< function or scope name as char pointer (not std::string)
220  HighResTimePoint _startTimepoint; //!< start timepoint
221  bool _isStopped; //!< flag if timer got stopped
222 };
223 //-----------------------------------------------------------------------------
224 #endif INSTRUMENTER_H
std::chrono::high_resolution_clock::time_point HighResTimePoint
Definition: HighResTimer.h:23
std::chrono::time_point< std::chrono::high_resolution_clock > HighResTimePoint
Definition: Instrumentor.h:187
bool _isStopped
flag if timer got stopped
Definition: Instrumentor.h:221
HighResTimePoint _startTimepoint
start timepoint
Definition: Instrumentor.h:220
const char * _name
function or scope name as char pointer (not std::string)
Definition: Instrumentor.h:219
InstrumentationTimer(const char *name)
Definition: Instrumentor.h:192
Basic instrumentation profiler for Google Chrome tracing format.
Definition: Instrumentor.h:78
void endSession()
Definition: Instrumentor.h:97
void writeFooter()
Definition: Instrumentor.h:162
std::ofstream _outputStream
Definition: Instrumentor.h:180
std::string filePath()
Definition: Instrumentor.h:174
void addProfile(const ProfileResult &result)
Definition: Instrumentor.h:119
void beginSession(const std::string &name, const bool storeInMemory=false, const std::string &filePath="Profiling-Results.json")
Definition: Instrumentor.h:82
std::vector< ProfileResult > _profileResults
Definition: Instrumentor.h:184
std::string _filePath
Definition: Instrumentor.h:179
static Instrumentor & get()
Definition: Instrumentor.h:168
void writeProfile(const ProfileResult &result)
Definition: Instrumentor.h:133
bool _storeInMemory
Definition: Instrumentor.h:183
void writeHeader()
Definition: Instrumentor.h:156
std::mutex _mutex
Definition: Instrumentor.h:182
InstrumentationSession * _currentSession
Definition: Instrumentor.h:178
long long end
end time point
Definition: Instrumentor.h:48
long long start
start time point
Definition: Instrumentor.h:47
uint32_t threadID
thread ID
Definition: Instrumentor.h:49
const char * name
pointer to char has constant length
Definition: Instrumentor.h:46