IProfiler.h 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. // This file is part of the "Irrlicht Engine".
  2. // For conditions of distribution and use, see copyright notice in irrlicht.h
  3. // Written by Michael Zeilfelder
  4. #ifndef IRR_I_PROFILER_H_INCLUDED
  5. #define IRR_I_PROFILER_H_INCLUDED
  6. #include "IrrCompileConfig.h"
  7. #include "irrString.h"
  8. #include "irrArray.h"
  9. #include "ITimer.h"
  10. #include <limits.h> // for INT_MAX (we should have a S32_MAX...)
  11. namespace irr
  12. {
  13. class ITimer;
  14. //! Used to store the profile data (and also used for profile group data).
  15. struct SProfileData
  16. {
  17. friend class IProfiler;
  18. SProfileData()
  19. {
  20. GroupIndex = 0;
  21. reset();
  22. }
  23. bool operator<(const SProfileData& pd) const
  24. {
  25. return Id < pd.Id;
  26. }
  27. bool operator==(const SProfileData& pd) const
  28. {
  29. return Id == pd.Id;
  30. }
  31. u32 getGroupIndex() const
  32. {
  33. return GroupIndex;
  34. }
  35. const core::stringw& getName() const
  36. {
  37. return Name;
  38. }
  39. //! Each time profiling for this data is stopped it increases the counter by 1.
  40. u32 getCallsCounter() const
  41. {
  42. return CountCalls;
  43. }
  44. //! Longest time a profile call for this id took from start until it was stopped again.
  45. u32 getLongestTime() const
  46. {
  47. return LongestTime;
  48. }
  49. //! Time spend between start/stop
  50. u32 getTimeSum() const
  51. {
  52. return TimeSum;
  53. }
  54. private:
  55. // just to be used for searching as it does no initialization besides id
  56. SProfileData(u32 id) : Id(id) {}
  57. void reset()
  58. {
  59. CountCalls = 0;
  60. LongestTime = 0;
  61. TimeSum = 0;
  62. LastTimeStarted = 0;
  63. StartStopCounter = 0;
  64. }
  65. s32 Id;
  66. u32 GroupIndex;
  67. core::stringw Name;
  68. s32 StartStopCounter; // 0 means stopped > 0 means it runs.
  69. u32 CountCalls;
  70. u32 LongestTime;
  71. u32 TimeSum;
  72. u32 LastTimeStarted;
  73. };
  74. //! Code-profiler. Please check the example in the Irrlicht examples folder about how to use it.
  75. // Implementer notes:
  76. // The design is all about allowing to use the central start/stop mechanism with minimal time overhead.
  77. // This is why the class works without a virtual functions interface contrary to the usual Irrlicht design.
  78. // And also why it works with id's instead of strings in the start/stop functions even if it makes using
  79. // the class slightly harder.
  80. // The class comes without reference-counting because the profiler instance is never released (TBD).
  81. class IProfiler
  82. {
  83. public:
  84. //! Constructor. You could use this to create a new profiler, but usually getProfiler() is used to access the global instance.
  85. IProfiler() : Timer(0), NextAutoId(INT_MAX)
  86. {}
  87. virtual ~IProfiler()
  88. {}
  89. //! Add an id with given name and group which can be used for profiling with start/stop
  90. /** After calling this once you can start/stop profiling for the given id.
  91. \param id: Should be >= 0 as negative id's are reserved for Irrlicht. Also very large numbers (near INT_MAX) might
  92. have been added automatically by the other add function.
  93. \param name: Name for displaying profile data.
  94. \param groupName: Each id belongs into a group - this helps on displaying profile data. */
  95. inline void add(s32 id, const core::stringw &name, const core::stringw &groupName);
  96. //! Add an automatically generated for the given name and group which can be used for profiling with start/stop.
  97. /** After calling this once you can start/stop profiling with the returned id.
  98. \param name: Name for displaying profile data.
  99. \param groupName: Each id belongs into a group - this helps on displaying profile data.
  100. \return Automatic id's start at INT_MAX and count down for each new id. If the name already has an id then that id will be returned. */
  101. inline s32 add(const core::stringw &name, const core::stringw &groupName);
  102. //! Return the number of profile data blocks. There is one for each id.
  103. u32 getProfileDataCount() const
  104. {
  105. return ProfileDatas.size();
  106. }
  107. //! Search for the index of the profile data by name
  108. /** \param result Receives the resulting data index when one was found.
  109. \param name String with name to search for
  110. \return true when found, false when not found */
  111. inline bool findDataIndex(u32 & result, const core::stringw &name) const;
  112. //! Get the profile data
  113. /** \param index A value between 0 and getProfileDataCount()-1. Indices can change when new id's are added.*/
  114. const SProfileData& getProfileDataByIndex(u32 index) const
  115. {
  116. return ProfileDatas[index];
  117. }
  118. //! Get the profile data
  119. /** \param id Same value as used in ::add
  120. \return Profile data for the given id or 0 when it does not exist. */
  121. inline const SProfileData* getProfileDataById(u32 id);
  122. //! Get the number of profile groups. Will be at least 1.
  123. /** NOTE: The first groups is always L"overview" which is an overview for all existing groups */
  124. inline u32 getGroupCount() const
  125. {
  126. return ProfileGroups.size();
  127. }
  128. //! Get profile data for a group.
  129. /** NOTE: The first groups is always L"overview" which is an overview for all existing groups */
  130. inline const SProfileData& getGroupData(u32 index) const
  131. {
  132. return ProfileGroups[index];
  133. }
  134. //! Find the group index by the group-name
  135. /** \param result Receives the resulting group index when one was found.
  136. \param name String with name to search for
  137. \return true when found, false when not found */
  138. inline bool findGroupIndex(u32 & result, const core::stringw &name) const;
  139. //! Start profile-timing for the given id
  140. /** This increases an internal run-counter for the given id. It will profile as long as that counter is > 0.
  141. NOTE: you have to add the id first with one of the ::add functions
  142. */
  143. inline void start(s32 id);
  144. //! Stop profile-timing for the given id
  145. /** This increases an internal run-counter for the given id. If it reaches 0 the time since start is recorded.
  146. You should have the same amount of start and stop calls. If stop is called more often than start
  147. then the additional stop calls will be ignored (counter never goes below 0)
  148. */
  149. inline void stop(s32 id);
  150. //! Reset profile data for the given id
  151. inline void resetDataById(s32 id);
  152. //! Reset profile data for the given index
  153. inline void resetDataByIndex(u32 index);
  154. //! Reset profile data for a whole group
  155. inline void resetGroup(u32 index);
  156. //! Reset all profile data
  157. /** NOTE: This is not deleting id's or groups, just resetting all timers to 0. */
  158. inline void resetAll();
  159. //! Write all profile-data into a string
  160. /** \param result Receives the result string.
  161. \param includeOverview When true a group-overview is attached first
  162. \param suppressUncalled When true elements which got never called are not printed */
  163. virtual void printAll(core::stringw &result, bool includeOverview=false,bool suppressUncalled=true) const = 0;
  164. //! Write the profile data of one group into a string
  165. /** \param result Receives the result string.
  166. \param groupIndex_ */
  167. virtual void printGroup(core::stringw &result, u32 groupIndex, bool suppressUncalled) const = 0;
  168. protected:
  169. inline u32 addGroup(const core::stringw &name);
  170. // I would prefer using os::Timer, but os.h is not in the public interface so far.
  171. // Timer must be initialized by the implementation.
  172. ITimer * Timer;
  173. core::array<SProfileData> ProfileDatas;
  174. core::array<SProfileData> ProfileGroups;
  175. private:
  176. s32 NextAutoId; // for giving out id's automatically
  177. };
  178. //! Access the Irrlicht profiler object.
  179. /** Profiler is always accessible, except in destruction of global objects.
  180. If you want to get internal profiling information about the engine itself
  181. you will have to re-compile the engine with _IRR_COMPILE_WITH_PROFILING_ enabled.
  182. But you can use the profiler for profiling your own projects without that. */
  183. IRRLICHT_API IProfiler& IRRCALLCONV getProfiler();
  184. //! Class where the objects profile their own life-time.
  185. /** This is a comfort wrapper around the IProfiler start/stop mechanism which is easier to use
  186. when you want to profile a scope. You only have to create an object and it will profile it's own lifetime
  187. for the given id. */
  188. class CProfileScope
  189. {
  190. public:
  191. //! Construct with an known id.
  192. /** This is the fastest scope constructor, but the id must have been added before.
  193. \param id Any id which you did add to the profiler before. */
  194. CProfileScope(s32 id)
  195. : Id(id), Profiler(getProfiler())
  196. {
  197. Profiler.start(Id);
  198. }
  199. //! Object will create the given name, groupName combination for the id if it doesn't exist already
  200. /** \param id: Should be >= 0 as negative id's are reserved for Irrlicht. Also very large numbers (near INT_MAX) might
  201. have been created already by the automatic add function of ::IProfiler.
  202. \param name: Name for displaying profile data.
  203. \param groupName: Each id belongs into a group - this helps on displaying profile data. */
  204. CProfileScope(s32 id, const core::stringw &name, const core::stringw &groupName)
  205. : Id(id), Profiler(getProfiler())
  206. {
  207. Profiler.add(Id, name, groupName);
  208. Profiler.start(Id);
  209. }
  210. //! Object will create an id for the given name, groupName combination if they don't exist already
  211. /** Slowest scope constructor, but usually still fine unless speed is very critical.
  212. \param name: Name for displaying profile data.
  213. \param groupName: Each id belongs into a group - this helps on displaying profile data. */
  214. CProfileScope(const core::stringw &name, const core::stringw &groupName)
  215. : Profiler(getProfiler())
  216. {
  217. Id = Profiler.add(name, groupName);
  218. Profiler.start(Id);
  219. }
  220. ~CProfileScope()
  221. {
  222. Profiler.stop(Id);
  223. }
  224. protected:
  225. s32 Id;
  226. IProfiler& Profiler;
  227. };
  228. // IMPLEMENTATION for in-line stuff
  229. void IProfiler::start(s32 id)
  230. {
  231. const s32 idx = ProfileDatas.binary_search(SProfileData(id));
  232. if ( idx >= 0 && Timer )
  233. {
  234. ++ProfileDatas[idx].StartStopCounter;
  235. if (ProfileDatas[idx].StartStopCounter == 1 )
  236. ProfileDatas[idx].LastTimeStarted = Timer->getRealTime();
  237. }
  238. }
  239. void IProfiler::stop(s32 id)
  240. {
  241. if ( Timer )
  242. {
  243. const u32 timeNow = Timer->getRealTime();
  244. const s32 idx = ProfileDatas.binary_search(SProfileData(id));
  245. if ( idx >= 0 )
  246. {
  247. SProfileData &data = ProfileDatas[idx];
  248. --ProfileDatas[idx].StartStopCounter;
  249. if ( data.LastTimeStarted != 0 && ProfileDatas[idx].StartStopCounter == 0)
  250. {
  251. // update data for this id
  252. ++data.CountCalls;
  253. const u32 diffTime = timeNow - data.LastTimeStarted;
  254. data.TimeSum += diffTime;
  255. if ( diffTime > data.LongestTime )
  256. data.LongestTime = diffTime;
  257. data.LastTimeStarted = 0;
  258. // update data of it's group
  259. SProfileData & group = ProfileGroups[data.GroupIndex];
  260. ++group.CountCalls;
  261. group.TimeSum += diffTime;
  262. if ( diffTime > group.LongestTime )
  263. group.LongestTime = diffTime;
  264. group.LastTimeStarted = 0;
  265. }
  266. else if ( ProfileDatas[idx].StartStopCounter < 0 )
  267. {
  268. // ignore additional stop calls
  269. ProfileDatas[idx].StartStopCounter = 0;
  270. }
  271. }
  272. }
  273. }
  274. s32 IProfiler::add(const core::stringw &name, const core::stringw &groupName)
  275. {
  276. u32 index;
  277. if ( findDataIndex(index, name) )
  278. {
  279. add( ProfileDatas[index].Id, name, groupName );
  280. return ProfileDatas[index].Id;
  281. }
  282. else
  283. {
  284. const s32 id = NextAutoId;
  285. --NextAutoId;
  286. add( id, name, groupName );
  287. return id;
  288. }
  289. }
  290. void IProfiler::add(s32 id, const core::stringw &name, const core::stringw &groupName)
  291. {
  292. u32 groupIdx;
  293. if ( !findGroupIndex(groupIdx, groupName) )
  294. {
  295. groupIdx = addGroup(groupName);
  296. }
  297. SProfileData data(id);
  298. const s32 idx = ProfileDatas.binary_search(data);
  299. if ( idx < 0 )
  300. {
  301. data.reset();
  302. data.GroupIndex = groupIdx;
  303. data.Name = name;
  304. ProfileDatas.push_back(data);
  305. ProfileDatas.sort();
  306. }
  307. else
  308. {
  309. // only reset on group changes, otherwise we want to keep the data or coding CProfileScope would become tricky.
  310. if ( groupIdx != ProfileDatas[idx].GroupIndex )
  311. {
  312. resetDataByIndex((u32)idx);
  313. ProfileDatas[idx].GroupIndex = groupIdx;
  314. }
  315. ProfileDatas[idx].Name = name;
  316. }
  317. }
  318. u32 IProfiler::addGroup(const core::stringw &name)
  319. {
  320. SProfileData group;
  321. group.Id = -1; // Id for groups doesn't matter so far
  322. group.Name = name;
  323. ProfileGroups.push_back(group);
  324. return ProfileGroups.size()-1;
  325. }
  326. bool IProfiler::findDataIndex(u32 & result, const core::stringw &name) const
  327. {
  328. for ( u32 i=0; i < ProfileDatas.size(); ++i )
  329. {
  330. if ( ProfileDatas[i].Name == name )
  331. {
  332. result = i;
  333. return true;
  334. }
  335. }
  336. return false;
  337. }
  338. const SProfileData* IProfiler::getProfileDataById(u32 id)
  339. {
  340. SProfileData data(id);
  341. const s32 idx = ProfileDatas.binary_search(data);
  342. if ( idx >= 0 )
  343. return &ProfileDatas[idx];
  344. return NULL;
  345. }
  346. bool IProfiler::findGroupIndex(u32 & result, const core::stringw &name) const
  347. {
  348. for ( u32 i=0; i < ProfileGroups.size(); ++i )
  349. {
  350. if ( ProfileGroups[i].Name == name )
  351. {
  352. result = i;
  353. return true;
  354. }
  355. }
  356. return false;
  357. }
  358. void IProfiler::resetDataById(s32 id)
  359. {
  360. const s32 idx = ProfileDatas.binary_search(SProfileData(id));
  361. if ( idx >= 0 )
  362. {
  363. resetDataByIndex((u32)idx);
  364. }
  365. }
  366. void IProfiler::resetDataByIndex(u32 index)
  367. {
  368. SProfileData &data = ProfileDatas[index];
  369. SProfileData & group = ProfileGroups[data.GroupIndex];
  370. group.CountCalls -= data.CountCalls;
  371. group.TimeSum -= data.TimeSum;
  372. data.reset();
  373. }
  374. //! Reset profile data for a whole group
  375. void IProfiler::resetGroup(u32 index)
  376. {
  377. for ( u32 i=0; i<ProfileDatas.size(); ++i )
  378. {
  379. if ( ProfileDatas[i].GroupIndex == index )
  380. ProfileDatas[i].reset();
  381. }
  382. if ( index < ProfileGroups.size() )
  383. ProfileGroups[index].reset();
  384. }
  385. void IProfiler::resetAll()
  386. {
  387. for ( u32 i=0; i<ProfileDatas.size(); ++i )
  388. {
  389. ProfileDatas[i].reset();
  390. }
  391. for ( u32 i=0; i<ProfileGroups.size(); ++i )
  392. {
  393. ProfileGroups[i].reset();
  394. }
  395. }
  396. //! For internal engine use:
  397. //! Code inside IRR_PROFILE is only executed when _IRR_COMPILE_WITH_PROFILING_ is set
  398. //! This allows disabling all profiler code completely by changing that define.
  399. //! It's generally useful to wrap profiler-calls in application code with a similar macro.
  400. #ifdef _IRR_COMPILE_WITH_PROFILING_
  401. #define IRR_PROFILE(X) X
  402. #else
  403. #define IRR_PROFILE(X)
  404. #endif // IRR_PROFILE
  405. } // namespace irr
  406. #endif // IRR_I_PROFILER_H_INCLUDED