Index: ps/trunk/source/ps/Profile.cpp =================================================================== --- ps/trunk/source/ps/Profile.cpp (revision 26804) +++ ps/trunk/source/ps/Profile.cpp (revision 26805) @@ -1,773 +1,493 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * GPG3-style hierarchical profiler */ #include "precompiled.h" #include "Profile.h" #include "ProfileViewer.h" #include "ThreadUtil.h" #include "lib/timer.h" -#if OS_WIN && !defined(NDEBUG) -# define USE_CRT_SET_ALLOC_HOOK -#endif - -#if defined(__GLIBC__) && !defined(NDEBUG) -//# define USE_GLIBC_MALLOC_HOOK -# define USE_GLIBC_MALLOC_OVERRIDE -# include -# include -# include "lib/sysdep/cpu.h" -#endif - #include /////////////////////////////////////////////////////////////////////////////////////////////// // CProfileNodeTable /** * Class CProfileNodeTable: Implement ProfileViewer's AbstractProfileTable * interface in order to display profiling data in-game. */ class CProfileNodeTable : public AbstractProfileTable { public: CProfileNodeTable(CProfileNode* n); virtual ~CProfileNodeTable(); // Implementation of AbstractProfileTable interface virtual CStr GetName(); virtual CStr GetTitle(); virtual size_t GetNumberRows(); virtual const std::vector& GetColumns(); virtual CStr GetCellText(size_t row, size_t col); virtual AbstractProfileTable* GetChild(size_t row); virtual bool IsHighlightRow(size_t row); private: /** * struct ColumnDescription: The only purpose of this helper structure * is to provide the global constructor that sets up the column * description. */ struct ColumnDescription { std::vector columns; ColumnDescription() { columns.push_back(ProfileColumn("Name", 230)); columns.push_back(ProfileColumn("calls/frame", 80)); columns.push_back(ProfileColumn("msec/frame", 80)); - columns.push_back(ProfileColumn("mallocs/frame", 120)); columns.push_back(ProfileColumn("calls/turn", 80)); columns.push_back(ProfileColumn("msec/turn", 80)); - columns.push_back(ProfileColumn("mallocs/turn", 80)); } }; /// The node represented by this table CProfileNode* node; /// Columns description (shared by all instances) static ColumnDescription columnDescription; }; CProfileNodeTable::ColumnDescription CProfileNodeTable::columnDescription; // Constructor/Destructor CProfileNodeTable::CProfileNodeTable(CProfileNode* n) { node = n; } CProfileNodeTable::~CProfileNodeTable() { } // Short name (= name of profile node) CStr CProfileNodeTable::GetName() { return node->GetName(); } // Title (= explanatory text plus time totals) CStr CProfileNodeTable::GetTitle() { char buf[512]; sprintf_s(buf, ARRAY_SIZE(buf), "Profiling Information for: %s (Time in node: %.3f msec/frame)", node->GetName(), node->GetFrameTime() * 1000.0f ); return buf; } // Total number of children size_t CProfileNodeTable::GetNumberRows() { return node->GetChildren()->size() + node->GetScriptChildren()->size() + 1; } // Column description const std::vector& CProfileNodeTable::GetColumns() { return columnDescription.columns; } // Retrieve cell text CStr CProfileNodeTable::GetCellText(size_t row, size_t col) { CProfileNode* child; size_t nrchildren = node->GetChildren()->size(); size_t nrscriptchildren = node->GetScriptChildren()->size(); char buf[256] = "?"; if (row < nrchildren) child = (*node->GetChildren())[row]; else if (row < nrchildren + nrscriptchildren) child = (*node->GetScriptChildren())[row - nrchildren]; else if (row > nrchildren + nrscriptchildren) return "!bad row!"; else { // "unlogged" row if (col == 0) return "unlogged"; else if (col == 1) return ""; else if (col == 4) return ""; double unlogged_time_frame = node->GetFrameTime(); double unlogged_time_turn = node->GetTurnTime(); - double unlogged_mallocs_frame = node->GetFrameMallocs(); - double unlogged_mallocs_turn = node->GetTurnMallocs(); CProfileNode::const_profile_iterator it; for (it = node->GetChildren()->begin(); it != node->GetChildren()->end(); ++it) { unlogged_time_frame -= (*it)->GetFrameTime(); unlogged_time_turn -= (*it)->GetTurnTime(); - unlogged_mallocs_frame -= (*it)->GetFrameMallocs(); - unlogged_mallocs_turn -= (*it)->GetTurnMallocs(); } for (it = node->GetScriptChildren()->begin(); it != node->GetScriptChildren()->end(); ++it) { unlogged_time_frame -= (*it)->GetFrameTime(); unlogged_time_turn -= (*it)->GetTurnTime(); - unlogged_mallocs_frame -= (*it)->GetFrameMallocs(); - unlogged_mallocs_turn -= (*it)->GetTurnMallocs(); } // The root node can't easily count per-turn values (since Turn isn't called until // halfway though a frame), so just reset them the zero to prevent weird displays if (!node->GetParent()) { unlogged_time_turn = 0.0; - unlogged_mallocs_turn = 0.0; } if (col == 2) sprintf_s(buf, ARRAY_SIZE(buf), "%.3f", unlogged_time_frame * 1000.0f); - else if (col == 3) - sprintf_s(buf, ARRAY_SIZE(buf), "%.1f", unlogged_mallocs_frame); - else if (col == 5) + else if (col == 4) sprintf_s(buf, ARRAY_SIZE(buf), "%.3f", unlogged_time_turn * 1000.f); - else if (col == 6) - sprintf_s(buf, ARRAY_SIZE(buf), "%.1f", unlogged_mallocs_turn); return CStr(buf); } switch(col) { default: case 0: return child->GetName(); case 1: sprintf_s(buf, ARRAY_SIZE(buf), "%.1f", child->GetFrameCalls()); break; case 2: sprintf_s(buf, ARRAY_SIZE(buf), "%.3f", child->GetFrameTime() * 1000.0f); break; case 3: - sprintf_s(buf, ARRAY_SIZE(buf), "%.1f", child->GetFrameMallocs()); - break; - case 4: sprintf_s(buf, ARRAY_SIZE(buf), "%.1f", child->GetTurnCalls()); break; - case 5: + case 4: sprintf_s(buf, ARRAY_SIZE(buf), "%.3f", child->GetTurnTime() * 1000.0f); break; - case 6: - sprintf_s(buf, ARRAY_SIZE(buf), "%.1f", child->GetTurnMallocs()); - break; } return CStr(buf); } // Return a pointer to the child table if the child node is expandable AbstractProfileTable* CProfileNodeTable::GetChild(size_t row) { CProfileNode* child; size_t nrchildren = node->GetChildren()->size(); size_t nrscriptchildren = node->GetScriptChildren()->size(); if (row < nrchildren) child = (*node->GetChildren())[row]; else if (row < nrchildren + nrscriptchildren) child = (*node->GetScriptChildren())[row - nrchildren]; else return 0; if (child->CanExpand()) return child->display_table; return 0; } // Highlight all script nodes bool CProfileNodeTable::IsHighlightRow(size_t row) { size_t nrchildren = node->GetChildren()->size(); size_t nrscriptchildren = node->GetScriptChildren()->size(); return (row >= nrchildren && row < (nrchildren + nrscriptchildren)); } /////////////////////////////////////////////////////////////////////////////////////////////// // CProfileNode implementation // Note: As with the GPG profiler, name is assumed to be a pointer to a constant string; only pointer equality is checked. CProfileNode::CProfileNode( const char* _name, CProfileNode* _parent ) { name = _name; recursion = 0; Reset(); parent = _parent; display_table = new CProfileNodeTable(this); } CProfileNode::~CProfileNode() { profile_iterator it; for( it = children.begin(); it != children.end(); ++it ) delete( *it ); for( it = script_children.begin(); it != script_children.end(); ++it ) delete( *it ); delete display_table; } template static double average(const T& collection) { if (collection.empty()) return 0.0; return std::accumulate(collection.begin(), collection.end(), 0.0) / collection.size(); } double CProfileNode::GetFrameCalls() const { return average(calls_per_frame); } double CProfileNode::GetFrameTime() const { return average(time_per_frame); } double CProfileNode::GetTurnCalls() const { return average(calls_per_turn); } double CProfileNode::GetTurnTime() const { return average(time_per_turn); } -double CProfileNode::GetFrameMallocs() const -{ - return average(mallocs_per_frame); -} - -double CProfileNode::GetTurnMallocs() const -{ - return average(mallocs_per_turn); -} - const CProfileNode* CProfileNode::GetChild( const char* childName ) const { const_profile_iterator it; for( it = children.begin(); it != children.end(); ++it ) if( (*it)->name == childName ) return( *it ); return( NULL ); } const CProfileNode* CProfileNode::GetScriptChild( const char* childName ) const { const_profile_iterator it; for( it = script_children.begin(); it != script_children.end(); ++it ) if( (*it)->name == childName ) return( *it ); return( NULL ); } CProfileNode* CProfileNode::GetChild( const char* childName ) { profile_iterator it; for( it = children.begin(); it != children.end(); ++it ) if( (*it)->name == childName ) return( *it ); CProfileNode* newNode = new CProfileNode( childName, this ); children.push_back( newNode ); return( newNode ); } CProfileNode* CProfileNode::GetScriptChild( const char* childName ) { profile_iterator it; for( it = script_children.begin(); it != script_children.end(); ++it ) if( (*it)->name == childName ) return( *it ); CProfileNode* newNode = new CProfileNode( childName, this ); script_children.push_back( newNode ); return( newNode ); } bool CProfileNode::CanExpand() { return( !( children.empty() && script_children.empty() ) ); } void CProfileNode::Reset() { calls_per_frame.clear(); calls_per_turn.clear(); calls_frame_current = 0; calls_turn_current = 0; time_per_frame.clear(); time_per_turn.clear(); time_frame_current = 0.0; time_turn_current = 0.0; - mallocs_per_frame.clear(); - mallocs_per_turn.clear(); - mallocs_frame_current = 0; - mallocs_turn_current = 0; - profile_iterator it; for (it = children.begin(); it != children.end(); ++it) (*it)->Reset(); for (it = script_children.begin(); it != script_children.end(); ++it) (*it)->Reset(); } void CProfileNode::Frame() { calls_per_frame.push_back(calls_frame_current); time_per_frame.push_back(time_frame_current); - mallocs_per_frame.push_back(mallocs_frame_current); calls_frame_current = 0; time_frame_current = 0.0; - mallocs_frame_current = 0; profile_iterator it; for (it = children.begin(); it != children.end(); ++it) (*it)->Frame(); for (it = script_children.begin(); it != script_children.end(); ++it) (*it)->Frame(); } void CProfileNode::Turn() { calls_per_turn.push_back(calls_turn_current); time_per_turn.push_back(time_turn_current); - mallocs_per_turn.push_back(mallocs_turn_current); calls_turn_current = 0; time_turn_current = 0.0; - mallocs_turn_current = 0; profile_iterator it; for (it = children.begin(); it != children.end(); ++it) (*it)->Turn(); for (it = script_children.begin(); it != script_children.end(); ++it) (*it)->Turn(); } -// TODO: these should probably only count allocations that occur in the thread being profiled -#if defined(USE_CRT_SET_ALLOC_HOOK) - -static long malloc_count = 0; -static _CRT_ALLOC_HOOK prev_hook; - -static int crt_alloc_hook(int allocType, void* userData, size_t size, int blockType, - long requestNumber, const unsigned char* filename, int lineNumber) -{ - if (allocType == _HOOK_ALLOC && Threading::IsMainThread()) - ++malloc_count; - - if (prev_hook) - return prev_hook(allocType, userData, size, blockType, requestNumber, filename, lineNumber); - else - return 1; -} - -static void alloc_hook_initialize() -{ - prev_hook = _CrtSetAllocHook(crt_alloc_hook); -} - -static long get_memory_alloc_count() -{ - return malloc_count; -} - -#elif defined(USE_GLIBC_MALLOC_HOOK) - -// Set up malloc hooks to count allocations - see -// http://www.gnu.org/software/libc/manual/html_node/Hooks-for-Malloc.html -static intptr_t malloc_count = 0; -static void *(*old_malloc_hook) (size_t, const void*); -static std::mutex alloc_hook_mutex; -static void *malloc_hook(size_t size, const void* UNUSED(caller)) -{ - // This doesn't really work across threads. The hooks are global variables, and - // we have to temporarily unhook in order to call the real malloc, and during that - // time period another thread may perform an unhooked (hence uncounted) allocation - // which we will miss. - - // Two threads may execute the hook simultaneously, so we need to do the - // temporary unhooking in a thread-safe way, so for simplicity we just use a mutex. - std::lock_guard lock(alloc_hook_mutex); - ++malloc_count; - __malloc_hook = old_malloc_hook; - void* result = malloc(size); - old_malloc_hook = __malloc_hook; - __malloc_hook = malloc_hook; - return result; -} - -static void alloc_hook_initialize() -{ - std::lock_guard lock(alloc_hook_mutex); - old_malloc_hook = __malloc_hook; - __malloc_hook = malloc_hook; - // (we don't want to bother hooking realloc and memalign, because if they allocate - // new memory then they'll be caught by the malloc hook anyway) -} -/* -It would be nice to do: - __attribute__ ((visibility ("default"))) void (*__malloc_initialize_hook)() = malloc_initialize_hook; -except that doesn't seem to work in practice, since something (?) resets the -hook to NULL some time while loading the game, after we've set it here - so -we just call malloc_initialize_hook once inside CProfileManager::Frame instead -and hope nobody deletes our hook after that. -*/ -static long get_memory_alloc_count() -{ - return malloc_count; -} - -#elif defined(USE_GLIBC_MALLOC_OVERRIDE) - -static intptr_t alloc_count = 0; - -// We override the malloc/realloc/calloc/free functions and then use dlsym to -// defer the actual allocation to the real libc implementation. -// The dlsym call will (in glibc 2.9/2.10) call calloc once (to allocate an error -// message structure), so we have a bootstrapping problem when trying to -// get the first called function via dlsym. So we kludge it by returning a statically-allocated -// buffer for the very first call to calloc after we've called dlsym. -// This is quite hacky but it seems to just about work in practice... - -// TODO: KNOWN ISSUE: Use after free and infinite recursion -// We assign the glibc free function to libc_free in our malloc/calloc function (with dlsym). -// We did that in the free function before, but had to change it to work around the first problem described below. -// It's not a good solution because some of the problems described here can reappear when the first -// call to malloc/calloc changes and enters the function with a different state. -// -// Dl* functions (dlsym, dlopen etc.) store an error message internally if something fails. -// Calling dlerror returns a pointer to this error message. Calling dlerror a second time or calling dlsym -// causes it to free the internal storage for this error message. -// This behaviour can cause two types of problems: -// -// 1. Infinite recursion due to free call -// Problem occurs if: We use any of the dl* functions in our free function and free gets called with an internal -// error message buffer allocated. -// What happens: Our call to the dl* function causes another free-call insdie glibc which calls our free function -// and can cause infinite recursion. -// -// 2. Use after free -// Problem occurs if: An external library (or any other function) calls a dl* function that stores an internal -// error string, then calls dlerror to receive the message and then calls any of our malloc/calloc/realloc/free fuctions. -// Our function uses one of the dl* functions too. After calling our function, it tries to use the error message pointer -// it got with dlerror before. -// What happens: Our call to the dl* function will free the storage of the message and the pointer in the external library -// becomes invalid. We get undefined behaviour if the extern library uses the error message pointer after that. - - -static bool alloc_bootstrapped = false; -static char alloc_bootstrap_buffer[32]; // sufficient for x86_64 -static bool alloc_has_called_dlsym = false; -static void (*libc_free)(void*) = NULL; -// (We'll only be running a single thread at this point so no need for locking these variables) - -//#define ALLOC_DEBUG - -void* malloc(size_t sz) -{ - cpu_AtomicAdd(&alloc_count, 1); - - static void *(*libc_malloc)(size_t); - if (libc_malloc == NULL) - { - alloc_has_called_dlsym = true; - libc_malloc = (void *(*)(size_t)) dlsym(RTLD_NEXT, "malloc"); - } - void* ret = libc_malloc(sz); -#ifdef ALLOC_DEBUG - printf("### malloc(%d) = %p\n", sz, ret); -#endif - - if (libc_free == NULL) - libc_free = (void (*)(void*)) dlsym(RTLD_NEXT, "free"); - - return ret; -} - -void* realloc(void* ptr, size_t sz) -{ - cpu_AtomicAdd(&alloc_count, 1); - - static void *(*libc_realloc)(void*, size_t); - if (libc_realloc == NULL) - { - alloc_has_called_dlsym = true; - libc_realloc = (void *(*)(void*, size_t)) dlsym(RTLD_NEXT, "realloc"); - } - void* ret = libc_realloc(ptr, sz); -#ifdef ALLOC_DEBUG - printf("### realloc(%p, %d) = %p\n", ptr, sz, ret); -#endif - return ret; -} - -void* calloc(size_t nm, size_t sz) -{ - cpu_AtomicAdd(&alloc_count, 1); - - static void *(*libc_calloc)(size_t, size_t); - if (libc_calloc == NULL) - { - if (alloc_has_called_dlsym && !alloc_bootstrapped) - { - ENSURE(nm*sz <= ARRAY_SIZE(alloc_bootstrap_buffer)); -#ifdef ALLOC_DEBUG - printf("### calloc-bs(%d, %d) = %p\n", nm, sz, alloc_bootstrap_buffer); -#endif - alloc_bootstrapped = true; - return alloc_bootstrap_buffer; - } - alloc_has_called_dlsym = true; - libc_calloc = (void *(*)(size_t, size_t)) dlsym(RTLD_NEXT, "calloc"); - } - void* ret = libc_calloc(nm, sz); -#ifdef ALLOC_DEBUG - printf("### calloc(%d, %d) = %p\n", nm, sz, ret); -#endif - - if (libc_free == NULL) - libc_free = (void (*)(void*)) dlsym(RTLD_NEXT, "free"); - - return ret; -} - -void free(void* ptr) -{ - // Might be triggered if free is called before any calloc/malloc calls or if the dlsym call inside - // our calloc/malloc function causes a free call. Read the known issue comment block a few lines above. - ENSURE (libc_free != NULL); - - libc_free(ptr); -#ifdef ALLOC_DEBUG - printf("### free(%p)\n", ptr); -#endif -} - -static void alloc_hook_initialize() -{ -} - -static long get_memory_alloc_count() -{ - return alloc_count; -} - -#else - -static void alloc_hook_initialize() -{ -} -static long get_memory_alloc_count() -{ - // TODO: don't show this column of data when we don't have sensible values - // to display. - return 0; -} -#endif - void CProfileNode::Call() { calls_frame_current++; calls_turn_current++; if (recursion++ == 0) { start = timer_Time(); - start_mallocs = get_memory_alloc_count(); } } bool CProfileNode::Return() { if (--recursion != 0) return false; double now = timer_Time(); - long allocs = get_memory_alloc_count(); time_frame_current += (now - start); time_turn_current += (now - start); - mallocs_frame_current += (allocs - start_mallocs); - mallocs_turn_current += (allocs - start_mallocs); return true; } CProfileManager::CProfileManager() : root(NULL), current(NULL), needs_structural_reset(false) { PerformStructuralReset(); } CProfileManager::~CProfileManager() { delete root; } void CProfileManager::Start( const char* name ) { if( name != current->GetName() ) current = current->GetChild( name ); current->Call(); } void CProfileManager::StartScript( const char* name ) { if( name != current->GetName() ) current = current->GetScriptChild( name ); current->Call(); } void CProfileManager::Stop() { if (current->Return()) current = current->GetParent(); } void CProfileManager::Reset() { root->Reset(); } void CProfileManager::Frame() { - ONCE(alloc_hook_initialize()); - root->time_frame_current += (timer_Time() - root->start); - root->mallocs_frame_current += (get_memory_alloc_count() - root->start_mallocs); root->Frame(); if (needs_structural_reset) { PerformStructuralReset(); needs_structural_reset = false; } root->start = timer_Time(); - root->start_mallocs = get_memory_alloc_count(); } void CProfileManager::Turn() { root->Turn(); } void CProfileManager::StructuralReset() { // We can't immediately perform the reset, because we're probably already // nested inside the profile tree and it will get very confused if we delete // the tree when we're not currently at the root. // So just set a flag to perform the reset at the end of the frame. needs_structural_reset = true; } void CProfileManager::PerformStructuralReset() { delete root; root = new CProfileNode("root", NULL); root->Call(); current = root; g_ProfileViewer.AddRootTable(root->display_table, true); } CProfileSample::CProfileSample(const char* name) { if (CProfileManager::IsInitialised()) { // The profiler is only safe to use on the main thread if(Threading::IsMainThread()) g_Profiler.Start(name); } } CProfileSample::~CProfileSample() { if (CProfileManager::IsInitialised()) if(Threading::IsMainThread()) g_Profiler.Stop(); } Index: ps/trunk/source/ps/Profile.h =================================================================== --- ps/trunk/source/ps/Profile.h (revision 26804) +++ ps/trunk/source/ps/Profile.h (revision 26805) @@ -1,178 +1,170 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * GPG3-style hierarchical profiler */ #ifndef INCLUDED_PROFILE #define INCLUDED_PROFILE #include #include "lib/adts/ring_buf.h" #include "lib/posix/posix_pthread.h" #include "ps/Profiler2.h" #include "ps/Singleton.h" #define PROFILE_AMORTIZE_FRAMES 30 #define PROFILE_AMORTIZE_TURNS 5 class CProfileManager; class CProfileNodeTable; // To profile scripts usefully, we use a call hook that's called on every enter/exit, // and need to find the function name. But most functions are anonymous so we make do // with filename plus line number instead. // Computing the names is fairly expensive, and we need to return an interned char* // for the profiler to hold a copy of, so we use boost::flyweight to construct interned // strings per call location. // // TODO: Check again how much the overhead for getting filename and line really is and if // it has increased with the new approach after the SpiderMonkey 31 upgrade. class CProfileNode { NONCOPYABLE(CProfileNode); public: typedef std::vector::iterator profile_iterator; typedef std::vector::const_iterator const_profile_iterator; CProfileNode( const char* name, CProfileNode* parent ); ~CProfileNode(); const char* GetName() const { return name; } double GetFrameCalls() const; double GetFrameTime() const; double GetTurnCalls() const; double GetTurnTime() const; - double GetFrameMallocs() const; - double GetTurnMallocs() const; const CProfileNode* GetChild( const char* name ) const; const CProfileNode* GetScriptChild( const char* name ) const; const std::vector* GetChildren() const { return( &children ); } const std::vector* GetScriptChildren() const { return( &script_children ); } bool CanExpand(); CProfileNode* GetChild( const char* name ); CProfileNode* GetScriptChild( const char* name ); CProfileNode* GetParent() const { return( parent ); } // Resets timing information for this node and all its children void Reset(); // Resets frame timings for this node and all its children void Frame(); // Resets turn timings for this node and all its children void Turn(); // Enters the node void Call(); // Leaves the node. Returns true if the node has actually been left bool Return(); private: friend class CProfileManager; friend class CProfileNodeTable; const char* name; int calls_frame_current; int calls_turn_current; RingBuf calls_per_frame; RingBuf calls_per_turn; double time_frame_current; double time_turn_current; RingBuf time_per_frame; RingBuf time_per_turn; - long mallocs_frame_current; - long mallocs_turn_current; - RingBuf mallocs_per_frame; - RingBuf mallocs_per_turn; - double start; - long start_mallocs; int recursion; CProfileNode* parent; std::vector children; std::vector script_children; CProfileNodeTable* display_table; }; class CProfileManager : public Singleton { public: CProfileManager(); ~CProfileManager(); // Begins timing for a named subsection void Start( const char* name ); void StartScript( const char* name ); // Ends timing for the current subsection void Stop(); // Resets all timing information void Reset(); // Resets frame timing information void Frame(); // Resets turn timing information // (Must not be called before Frame) void Turn(); // Resets absolutely everything, at the end of this frame void StructuralReset(); inline const CProfileNode* GetCurrent() { return( current ); } inline const CProfileNode* GetRoot() { return( root ); } private: CProfileNode* root; CProfileNode* current; bool needs_structural_reset; void PerformStructuralReset(); }; #define g_Profiler CProfileManager::GetSingleton() class CProfileSample { public: CProfileSample(const char* name); ~CProfileSample(); }; // Put a PROFILE("xyz") block at the start of all code to be profiled. // Profile blocks last until the end of the containing scope. #define PROFILE(name) CProfileSample __profile(name) // Cheat a bit to make things slightly easier on the user #define PROFILE_START(name) { CProfileSample __profile(name) #define PROFILE_END(name) } // Do both old and new profilers simultaneously (1+2=3), for convenience. #define PROFILE3(name) PROFILE(name); PROFILE2(name) // Also do GPU #define PROFILE3_GPU(name) PROFILE(name); PROFILE2(name); PROFILE2_GPU(name) #endif // INCLUDED_PROFILE