1 /* Copyright (C) 2022 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
19 * GPG3-style hierarchical profiler
22 #include "precompiled.h"
25 #include "ProfileViewer.h"
26 #include "ThreadUtil.h"
28 #include "lib/timer.h"
32 ///////////////////////////////////////////////////////////////////////////////////////////////
38 * Class CProfileNodeTable: Implement ProfileViewer's AbstractProfileTable
39 * interface in order to display profiling data in-game.
41 class CProfileNodeTable
: public AbstractProfileTable
44 CProfileNodeTable(CProfileNode
* n
);
45 virtual ~CProfileNodeTable();
47 // Implementation of AbstractProfileTable interface
48 virtual CStr
GetName();
49 virtual CStr
GetTitle();
50 virtual size_t GetNumberRows();
51 virtual const std::vector
<ProfileColumn
>& GetColumns();
53 virtual CStr
GetCellText(size_t row
, size_t col
);
54 virtual AbstractProfileTable
* GetChild(size_t row
);
55 virtual bool IsHighlightRow(size_t row
);
59 * struct ColumnDescription: The only purpose of this helper structure
60 * is to provide the global constructor that sets up the column
63 struct ColumnDescription
65 std::vector
<ProfileColumn
> columns
;
69 columns
.push_back(ProfileColumn("Name", 230));
70 columns
.push_back(ProfileColumn("calls/frame", 80));
71 columns
.push_back(ProfileColumn("msec/frame", 80));
72 columns
.push_back(ProfileColumn("calls/turn", 80));
73 columns
.push_back(ProfileColumn("msec/turn", 80));
77 /// The node represented by this table
80 /// Columns description (shared by all instances)
81 static ColumnDescription columnDescription
;
84 CProfileNodeTable::ColumnDescription
CProfileNodeTable::columnDescription
;
87 // Constructor/Destructor
88 CProfileNodeTable::CProfileNodeTable(CProfileNode
* n
)
93 CProfileNodeTable::~CProfileNodeTable()
97 // Short name (= name of profile node)
98 CStr
CProfileNodeTable::GetName()
100 return node
->GetName();
103 // Title (= explanatory text plus time totals)
104 CStr
CProfileNodeTable::GetTitle()
107 sprintf_s(buf
, ARRAY_SIZE(buf
), "Profiling Information for: %s (Time in node: %.3f msec/frame)", node
->GetName(), node
->GetFrameTime() * 1000.0f
);
111 // Total number of children
112 size_t CProfileNodeTable::GetNumberRows()
114 return node
->GetChildren()->size() + node
->GetScriptChildren()->size() + 1;
117 // Column description
118 const std::vector
<ProfileColumn
>& CProfileNodeTable::GetColumns()
120 return columnDescription
.columns
;
123 // Retrieve cell text
124 CStr
CProfileNodeTable::GetCellText(size_t row
, size_t col
)
127 size_t nrchildren
= node
->GetChildren()->size();
128 size_t nrscriptchildren
= node
->GetScriptChildren()->size();
131 if (row
< nrchildren
)
132 child
= (*node
->GetChildren())[row
];
133 else if (row
< nrchildren
+ nrscriptchildren
)
134 child
= (*node
->GetScriptChildren())[row
- nrchildren
];
135 else if (row
> nrchildren
+ nrscriptchildren
)
147 double unlogged_time_frame
= node
->GetFrameTime();
148 double unlogged_time_turn
= node
->GetTurnTime();
149 CProfileNode::const_profile_iterator it
;
151 for (it
= node
->GetChildren()->begin(); it
!= node
->GetChildren()->end(); ++it
)
153 unlogged_time_frame
-= (*it
)->GetFrameTime();
154 unlogged_time_turn
-= (*it
)->GetTurnTime();
156 for (it
= node
->GetScriptChildren()->begin(); it
!= node
->GetScriptChildren()->end(); ++it
)
158 unlogged_time_frame
-= (*it
)->GetFrameTime();
159 unlogged_time_turn
-= (*it
)->GetTurnTime();
162 // The root node can't easily count per-turn values (since Turn isn't called until
163 // halfway though a frame), so just reset them the zero to prevent weird displays
164 if (!node
->GetParent())
166 unlogged_time_turn
= 0.0;
170 sprintf_s(buf
, ARRAY_SIZE(buf
), "%.3f", unlogged_time_frame
* 1000.0f
);
172 sprintf_s(buf
, ARRAY_SIZE(buf
), "%.3f", unlogged_time_turn
* 1000.f
);
181 return child
->GetName();
184 sprintf_s(buf
, ARRAY_SIZE(buf
), "%.1f", child
->GetFrameCalls());
187 sprintf_s(buf
, ARRAY_SIZE(buf
), "%.3f", child
->GetFrameTime() * 1000.0f
);
190 sprintf_s(buf
, ARRAY_SIZE(buf
), "%.1f", child
->GetTurnCalls());
193 sprintf_s(buf
, ARRAY_SIZE(buf
), "%.3f", child
->GetTurnTime() * 1000.0f
);
199 // Return a pointer to the child table if the child node is expandable
200 AbstractProfileTable
* CProfileNodeTable::GetChild(size_t row
)
203 size_t nrchildren
= node
->GetChildren()->size();
204 size_t nrscriptchildren
= node
->GetScriptChildren()->size();
206 if (row
< nrchildren
)
207 child
= (*node
->GetChildren())[row
];
208 else if (row
< nrchildren
+ nrscriptchildren
)
209 child
= (*node
->GetScriptChildren())[row
- nrchildren
];
213 if (child
->CanExpand())
214 return child
->display_table
;
219 // Highlight all script nodes
220 bool CProfileNodeTable::IsHighlightRow(size_t row
)
222 size_t nrchildren
= node
->GetChildren()->size();
223 size_t nrscriptchildren
= node
->GetScriptChildren()->size();
225 return (row
>= nrchildren
&& row
< (nrchildren
+ nrscriptchildren
));
228 ///////////////////////////////////////////////////////////////////////////////////////////////
229 // CProfileNode implementation
232 // Note: As with the GPG profiler, name is assumed to be a pointer to a constant string; only pointer equality is checked.
233 CProfileNode::CProfileNode( const char* _name
, CProfileNode
* _parent
)
242 display_table
= new CProfileNodeTable(this);
245 CProfileNode::~CProfileNode()
248 for( it
= children
.begin(); it
!= children
.end(); ++it
)
250 for( it
= script_children
.begin(); it
!= script_children
.end(); ++it
)
253 delete display_table
;
257 static double average(const T
& collection
)
259 if (collection
.empty())
261 return std::accumulate(collection
.begin(), collection
.end(), 0.0) / collection
.size();
264 double CProfileNode::GetFrameCalls() const
266 return average(calls_per_frame
);
269 double CProfileNode::GetFrameTime() const
271 return average(time_per_frame
);
274 double CProfileNode::GetTurnCalls() const
276 return average(calls_per_turn
);
279 double CProfileNode::GetTurnTime() const
281 return average(time_per_turn
);
284 const CProfileNode
* CProfileNode::GetChild( const char* childName
) const
286 const_profile_iterator it
;
287 for( it
= children
.begin(); it
!= children
.end(); ++it
)
288 if( (*it
)->name
== childName
)
294 const CProfileNode
* CProfileNode::GetScriptChild( const char* childName
) const
296 const_profile_iterator it
;
297 for( it
= script_children
.begin(); it
!= script_children
.end(); ++it
)
298 if( (*it
)->name
== childName
)
304 CProfileNode
* CProfileNode::GetChild( const char* childName
)
307 for( it
= children
.begin(); it
!= children
.end(); ++it
)
308 if( (*it
)->name
== childName
)
311 CProfileNode
* newNode
= new CProfileNode( childName
, this );
312 children
.push_back( newNode
);
316 CProfileNode
* CProfileNode::GetScriptChild( const char* childName
)
319 for( it
= script_children
.begin(); it
!= script_children
.end(); ++it
)
320 if( (*it
)->name
== childName
)
323 CProfileNode
* newNode
= new CProfileNode( childName
, this );
324 script_children
.push_back( newNode
);
328 bool CProfileNode::CanExpand()
330 return( !( children
.empty() && script_children
.empty() ) );
333 void CProfileNode::Reset()
335 calls_per_frame
.clear();
336 calls_per_turn
.clear();
337 calls_frame_current
= 0;
338 calls_turn_current
= 0;
340 time_per_frame
.clear();
341 time_per_turn
.clear();
342 time_frame_current
= 0.0;
343 time_turn_current
= 0.0;
346 for (it
= children
.begin(); it
!= children
.end(); ++it
)
348 for (it
= script_children
.begin(); it
!= script_children
.end(); ++it
)
352 void CProfileNode::Frame()
354 calls_per_frame
.push_back(calls_frame_current
);
355 time_per_frame
.push_back(time_frame_current
);
357 calls_frame_current
= 0;
358 time_frame_current
= 0.0;
361 for (it
= children
.begin(); it
!= children
.end(); ++it
)
363 for (it
= script_children
.begin(); it
!= script_children
.end(); ++it
)
367 void CProfileNode::Turn()
369 calls_per_turn
.push_back(calls_turn_current
);
370 time_per_turn
.push_back(time_turn_current
);
372 calls_turn_current
= 0;
373 time_turn_current
= 0.0;
376 for (it
= children
.begin(); it
!= children
.end(); ++it
)
378 for (it
= script_children
.begin(); it
!= script_children
.end(); ++it
)
382 void CProfileNode::Call()
384 calls_frame_current
++;
385 calls_turn_current
++;
386 if (recursion
++ == 0)
388 start
= timer_Time();
392 bool CProfileNode::Return()
394 if (--recursion
!= 0)
397 double now
= timer_Time();
398 time_frame_current
+= (now
- start
);
399 time_turn_current
+= (now
- start
);
403 CProfileManager::CProfileManager() :
404 root(NULL
), current(NULL
), needs_structural_reset(false)
406 PerformStructuralReset();
409 CProfileManager::~CProfileManager()
414 void CProfileManager::Start( const char* name
)
416 if( name
!= current
->GetName() )
417 current
= current
->GetChild( name
);
421 void CProfileManager::StartScript( const char* name
)
423 if( name
!= current
->GetName() )
424 current
= current
->GetScriptChild( name
);
428 void CProfileManager::Stop()
430 if (current
->Return())
431 current
= current
->GetParent();
434 void CProfileManager::Reset()
439 void CProfileManager::Frame()
441 root
->time_frame_current
+= (timer_Time() - root
->start
);
445 if (needs_structural_reset
)
447 PerformStructuralReset();
448 needs_structural_reset
= false;
451 root
->start
= timer_Time();
454 void CProfileManager::Turn()
459 void CProfileManager::StructuralReset()
461 // We can't immediately perform the reset, because we're probably already
462 // nested inside the profile tree and it will get very confused if we delete
463 // the tree when we're not currently at the root.
464 // So just set a flag to perform the reset at the end of the frame.
466 needs_structural_reset
= true;
469 void CProfileManager::PerformStructuralReset()
472 root
= new CProfileNode("root", NULL
);
475 g_ProfileViewer
.AddRootTable(root
->display_table
, true);
478 CProfileSample::CProfileSample(const char* name
)
480 if (CProfileManager::IsInitialised())
482 // The profiler is only safe to use on the main thread
483 if(Threading::IsMainThread())
484 g_Profiler
.Start(name
);
488 CProfileSample::~CProfileSample()
490 if (CProfileManager::IsInitialised())
491 if(Threading::IsMainThread())