1 // This file is part of the "Irrlicht Engine".
\r
2 // For conditions of distribution and use, see copyright notice in irrlicht.h
\r
3 // Written by Michael Zeilfelder
\r
5 #ifndef __I_PROFILER_H_INCLUDED__
\r
6 #define __I_PROFILER_H_INCLUDED__
\r
8 #include "IrrCompileConfig.h"
\r
9 #include "irrString.h"
\r
10 #include "irrArray.h"
\r
12 #include <limits.h> // for INT_MAX (we should have a S32_MAX...)
\r
19 //! Used to store the profile data (and also used for profile group data).
\r
22 friend class IProfiler;
\r
30 bool operator<(const SProfileData& pd) const
\r
35 bool operator==(const SProfileData& pd) const
\r
40 u32 getGroupIndex() const
\r
45 const core::stringw& getName() const
\r
50 //! Each time profiling for this data is stopped it increases the counter by 1.
\r
51 u32 getCallsCounter() const
\r
56 //! Longest time a profile call for this id took from start until it was stopped again.
\r
57 u32 getLongestTime() const
\r
62 //! Time spend between start/stop
\r
63 u32 getTimeSum() const
\r
70 // just to be used for searching as it does no initialization besides id
\r
71 SProfileData(u32 id) : Id(id) {}
\r
78 LastTimeStarted = 0;
\r
79 StartStopCounter = 0;
\r
86 s32 StartStopCounter; // 0 means stopped > 0 means it runs.
\r
91 u32 LastTimeStarted;
\r
94 //! Code-profiler. Please check the example in the Irrlicht examples folder about how to use it.
\r
95 // Implementer notes:
\r
96 // The design is all about allowing to use the central start/stop mechanism with minimal time overhead.
\r
97 // This is why the class works without a virtual functions interface contrary to the usual Irrlicht design.
\r
98 // And also why it works with id's instead of strings in the start/stop functions even if it makes using
\r
99 // the class slightly harder.
\r
100 // The class comes without reference-counting because the profiler instance is never released (TBD).
\r
104 //! Constructor. You could use this to create a new profiler, but usually getProfiler() is used to access the global instance.
\r
105 IProfiler() : Timer(0), NextAutoId(INT_MAX)
\r
108 virtual ~IProfiler()
\r
111 //! Add an id with given name and group which can be used for profiling with start/stop
\r
112 /** After calling this once you can start/stop profiling for the given id.
\r
113 \param id: Should be >= 0 as negative id's are reserved for Irrlicht. Also very large numbers (near INT_MAX) might
\r
114 have been added automatically by the other add function.
\r
115 \param name: Name for displaying profile data.
\r
116 \param groupName: Each id belongs into a group - this helps on displaying profile data. */
\r
117 inline void add(s32 id, const core::stringw &name, const core::stringw &groupName);
\r
119 //! Add an automatically generated for the given name and group which can be used for profiling with start/stop.
\r
120 /** After calling this once you can start/stop profiling with the returned id.
\r
121 \param name: Name for displaying profile data.
\r
122 \param groupName: Each id belongs into a group - this helps on displaying profile data.
\r
123 \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. */
\r
124 inline s32 add(const core::stringw &name, const core::stringw &groupName);
\r
126 //! Return the number of profile data blocks. There is one for each id.
\r
127 u32 getProfileDataCount() const
\r
129 return ProfileDatas.size();
\r
132 //! Search for the index of the profile data by name
\r
133 /** \param result Receives the resulting data index when one was found.
\r
134 \param name String with name to search for
\r
135 \return true when found, false when not found */
\r
136 inline bool findDataIndex(u32 & result, const core::stringw &name) const;
\r
138 //! Get the profile data
\r
139 /** \param index A value between 0 and getProfileDataCount()-1. Indices can change when new id's are added.*/
\r
140 const SProfileData& getProfileDataByIndex(u32 index) const
\r
142 return ProfileDatas[index];
\r
145 //! Get the profile data
\r
146 /** \param id Same value as used in ::add
\r
147 \return Profile data for the given id or 0 when it does not exist. */
\r
148 inline const SProfileData* getProfileDataById(u32 id);
\r
150 //! Get the number of profile groups. Will be at least 1.
\r
151 /** NOTE: The first groups is always L"overview" which is an overview for all existing groups */
\r
152 inline u32 getGroupCount() const
\r
154 return ProfileGroups.size();
\r
157 //! Get profile data for a group.
\r
158 /** NOTE: The first groups is always L"overview" which is an overview for all existing groups */
\r
159 inline const SProfileData& getGroupData(u32 index) const
\r
161 return ProfileGroups[index];
\r
164 //! Find the group index by the group-name
\r
165 /** \param result Receives the resulting group index when one was found.
\r
166 \param name String with name to search for
\r
167 \return true when found, false when not found */
\r
168 inline bool findGroupIndex(u32 & result, const core::stringw &name) const;
\r
171 //! Start profile-timing for the given id
\r
172 /** This increases an internal run-counter for the given id. It will profile as long as that counter is > 0.
\r
173 NOTE: you have to add the id first with one of the ::add functions
\r
175 inline void start(s32 id);
\r
177 //! Stop profile-timing for the given id
\r
178 /** This increases an internal run-counter for the given id. If it reaches 0 the time since start is recorded.
\r
179 You should have the same amount of start and stop calls. If stop is called more often than start
\r
180 then the additional stop calls will be ignored (counter never goes below 0)
\r
182 inline void stop(s32 id);
\r
184 //! Reset profile data for the given id
\r
185 inline void resetDataById(s32 id);
\r
187 //! Reset profile data for the given index
\r
188 inline void resetDataByIndex(u32 index);
\r
190 //! Reset profile data for a whole group
\r
191 inline void resetGroup(u32 index);
\r
193 //! Reset all profile data
\r
194 /** NOTE: This is not deleting id's or groups, just resetting all timers to 0. */
\r
195 inline void resetAll();
\r
197 //! Write all profile-data into a string
\r
198 /** \param result Receives the result string.
\r
199 \param includeOverview When true a group-overview is attached first
\r
200 \param suppressUncalled When true elements which got never called are not printed */
\r
201 virtual void printAll(core::stringw &result, bool includeOverview=false,bool suppressUncalled=true) const = 0;
\r
203 //! Write the profile data of one group into a string
\r
204 /** \param result Receives the result string.
\r
205 \param groupIndex_ */
\r
206 virtual void printGroup(core::stringw &result, u32 groupIndex, bool suppressUncalled) const = 0;
\r
210 inline u32 addGroup(const core::stringw &name);
\r
212 // I would prefer using os::Timer, but os.h is not in the public interface so far.
\r
213 // Timer must be initialized by the implementation.
\r
215 core::array<SProfileData> ProfileDatas;
\r
216 core::array<SProfileData> ProfileGroups;
\r
219 s32 NextAutoId; // for giving out id's automatically
\r
222 //! Access the Irrlicht profiler object.
\r
223 /** Profiler is always accessible, except in destruction of global objects.
\r
224 If you want to get internal profiling information about the engine itself
\r
225 you will have to re-compile the engine with _IRR_COMPILE_WITH_PROFILING_ enabled.
\r
226 But you can use the profiler for profiling your own projects without that. */
\r
227 IRRLICHT_API IProfiler& IRRCALLCONV getProfiler();
\r
229 //! Class where the objects profile their own life-time.
\r
230 /** This is a comfort wrapper around the IProfiler start/stop mechanism which is easier to use
\r
231 when you want to profile a scope. You only have to create an object and it will profile it's own lifetime
\r
232 for the given id. */
\r
233 class CProfileScope
\r
236 //! Construct with an known id.
\r
237 /** This is the fastest scope constructor, but the id must have been added before.
\r
238 \param id Any id which you did add to the profiler before. */
\r
239 CProfileScope(s32 id)
\r
240 : Id(id), Profiler(getProfiler())
\r
242 Profiler.start(Id);
\r
245 //! Object will create the given name, groupName combination for the id if it doesn't exist already
\r
246 /** \param id: Should be >= 0 as negative id's are reserved for Irrlicht. Also very large numbers (near INT_MAX) might
\r
247 have been created already by the automatic add function of ::IProfiler.
\r
248 \param name: Name for displaying profile data.
\r
249 \param groupName: Each id belongs into a group - this helps on displaying profile data. */
\r
250 CProfileScope(s32 id, const core::stringw &name, const core::stringw &groupName)
\r
251 : Id(id), Profiler(getProfiler())
\r
253 Profiler.add(Id, name, groupName);
\r
254 Profiler.start(Id);
\r
257 //! Object will create an id for the given name, groupName combination if they don't exist already
\r
258 /** Slowest scope constructor, but usually still fine unless speed is very critical.
\r
259 \param name: Name for displaying profile data.
\r
260 \param groupName: Each id belongs into a group - this helps on displaying profile data. */
\r
261 CProfileScope(const core::stringw &name, const core::stringw &groupName)
\r
262 : Profiler(getProfiler())
\r
264 Id = Profiler.add(name, groupName);
\r
265 Profiler.start(Id);
\r
275 IProfiler& Profiler;
\r
279 // IMPLEMENTATION for in-line stuff
\r
281 void IProfiler::start(s32 id)
\r
283 s32 idx = ProfileDatas.binary_search(SProfileData(id));
\r
284 if ( idx >= 0 && Timer )
\r
286 ++ProfileDatas[idx].StartStopCounter;
\r
287 if (ProfileDatas[idx].StartStopCounter == 1 )
\r
288 ProfileDatas[idx].LastTimeStarted = Timer->getRealTime();
\r
292 void IProfiler::stop(s32 id)
\r
296 u32 timeNow = Timer->getRealTime();
\r
297 s32 idx = ProfileDatas.binary_search(SProfileData(id));
\r
300 SProfileData &data = ProfileDatas[idx];
\r
301 --ProfileDatas[idx].StartStopCounter;
\r
302 if ( data.LastTimeStarted != 0 && ProfileDatas[idx].StartStopCounter == 0)
\r
304 // update data for this id
\r
306 u32 diffTime = timeNow - data.LastTimeStarted;
\r
307 data.TimeSum += diffTime;
\r
308 if ( diffTime > data.LongestTime )
\r
309 data.LongestTime = diffTime;
\r
310 data.LastTimeStarted = 0;
\r
312 // update data of it's group
\r
313 SProfileData & group = ProfileGroups[data.GroupIndex];
\r
314 ++group.CountCalls;
\r
315 group.TimeSum += diffTime;
\r
316 if ( diffTime > group.LongestTime )
\r
317 group.LongestTime = diffTime;
\r
318 group.LastTimeStarted = 0;
\r
320 else if ( ProfileDatas[idx].StartStopCounter < 0 )
\r
322 // ignore additional stop calls
\r
323 ProfileDatas[idx].StartStopCounter = 0;
\r
329 s32 IProfiler::add(const core::stringw &name, const core::stringw &groupName)
\r
332 if ( findDataIndex(index, name) )
\r
334 add( ProfileDatas[index].Id, name, groupName );
\r
335 return ProfileDatas[index].Id;
\r
339 s32 id = NextAutoId;
\r
341 add( id, name, groupName );
\r
346 void IProfiler::add(s32 id, const core::stringw &name, const core::stringw &groupName)
\r
349 if ( !findGroupIndex(groupIdx, groupName) )
\r
351 groupIdx = addGroup(groupName);
\r
354 SProfileData data(id);
\r
355 s32 idx = ProfileDatas.binary_search(data);
\r
359 data.GroupIndex = groupIdx;
\r
362 ProfileDatas.push_back(data);
\r
363 ProfileDatas.sort();
\r
367 // only reset on group changes, otherwise we want to keep the data or coding CProfileScope would become tricky.
\r
368 if ( groupIdx != ProfileDatas[idx].GroupIndex )
\r
370 resetDataByIndex((u32)idx);
\r
371 ProfileDatas[idx].GroupIndex = groupIdx;
\r
373 ProfileDatas[idx].Name = name;
\r
377 u32 IProfiler::addGroup(const core::stringw &name)
\r
379 SProfileData group;
\r
380 group.Id = -1; // Id for groups doesn't matter so far
\r
382 ProfileGroups.push_back(group);
\r
383 return ProfileGroups.size()-1;
\r
386 bool IProfiler::findDataIndex(u32 & result, const core::stringw &name) const
\r
388 for ( u32 i=0; i < ProfileDatas.size(); ++i )
\r
390 if ( ProfileDatas[i].Name == name )
\r
400 const SProfileData* IProfiler::getProfileDataById(u32 id)
\r
402 SProfileData data(id);
\r
403 s32 idx = ProfileDatas.binary_search(data);
\r
405 return &ProfileDatas[idx];
\r
409 bool IProfiler::findGroupIndex(u32 & result, const core::stringw &name) const
\r
411 for ( u32 i=0; i < ProfileGroups.size(); ++i )
\r
413 if ( ProfileGroups[i].Name == name )
\r
423 void IProfiler::resetDataById(s32 id)
\r
425 s32 idx = ProfileDatas.binary_search(SProfileData(id));
\r
428 resetDataByIndex((u32)idx);
\r
432 void IProfiler::resetDataByIndex(u32 index)
\r
434 SProfileData &data = ProfileDatas[index];
\r
436 SProfileData & group = ProfileGroups[data.GroupIndex];
\r
437 group.CountCalls -= data.CountCalls;
\r
438 group.TimeSum -= data.TimeSum;
\r
443 //! Reset profile data for a whole group
\r
444 void IProfiler::resetGroup(u32 index)
\r
446 for ( u32 i=0; i<ProfileDatas.size(); ++i )
\r
448 if ( ProfileDatas[i].GroupIndex == index )
\r
449 ProfileDatas[i].reset();
\r
451 if ( index < ProfileGroups.size() )
\r
452 ProfileGroups[index].reset();
\r
455 void IProfiler::resetAll()
\r
457 for ( u32 i=0; i<ProfileDatas.size(); ++i )
\r
459 ProfileDatas[i].reset();
\r
462 for ( u32 i=0; i<ProfileGroups.size(); ++i )
\r
464 ProfileGroups[i].reset();
\r
468 //! For internal engine use:
\r
469 //! Code inside IRR_PROFILE is only executed when _IRR_COMPILE_WITH_PROFILING_ is set
\r
470 //! This allows disabling all profiler code completely by changing that define.
\r
471 //! It's generally useful to wrap profiler-calls in application code with a similar macro.
\r
472 #ifdef _IRR_COMPILE_WITH_PROFILING_
\r
473 #define IRR_PROFILE(X) X
\r
475 #define IRR_PROFILE(X)
\r
476 #endif // IRR_PROFILE
\r
480 #endif // __I_PROFILER_H_INCLUDED__
\r