Line data Source code
1 : /* Copyright (C) 2023 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
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.
8 : *
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.
13 : *
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/>.
16 : */
17 :
18 : /*
19 : * GPG3-style hierarchical profiler
20 : */
21 :
22 : #include "precompiled.h"
23 :
24 : #include "Profile.h"
25 : #include "ProfileViewer.h"
26 : #include "ThreadUtil.h"
27 :
28 : #include "lib/timer.h"
29 :
30 : #include <numeric>
31 :
32 : ///////////////////////////////////////////////////////////////////////////////////////////////
33 : // CProfileNodeTable
34 :
35 :
36 :
37 : /**
38 : * Class CProfileNodeTable: Implement ProfileViewer's AbstractProfileTable
39 : * interface in order to display profiling data in-game.
40 : */
41 0 : class CProfileNodeTable : public AbstractProfileTable
42 : {
43 : public:
44 : CProfileNodeTable(CProfileNode* n);
45 :
46 : // Implementation of AbstractProfileTable interface
47 : CStr GetName() override;
48 : CStr GetTitle() override;
49 : size_t GetNumberRows() override;
50 : const std::vector<ProfileColumn>& GetColumns() override;
51 :
52 : CStr GetCellText(size_t row, size_t col) override;
53 : AbstractProfileTable* GetChild(size_t row) override;
54 : bool IsHighlightRow(size_t row) override;
55 :
56 : private:
57 : /**
58 : * struct ColumnDescription: The only purpose of this helper structure
59 : * is to provide the global constructor that sets up the column
60 : * description.
61 : */
62 1 : struct ColumnDescription
63 : {
64 : std::vector<ProfileColumn> columns;
65 :
66 1 : ColumnDescription()
67 1 : {
68 1 : columns.push_back(ProfileColumn("Name", 230));
69 1 : columns.push_back(ProfileColumn("calls/frame", 80));
70 1 : columns.push_back(ProfileColumn("msec/frame", 80));
71 1 : columns.push_back(ProfileColumn("calls/turn", 80));
72 1 : columns.push_back(ProfileColumn("msec/turn", 80));
73 1 : }
74 : };
75 :
76 : /// The node represented by this table
77 : CProfileNode* node;
78 :
79 : /// Columns description (shared by all instances)
80 : static ColumnDescription columnDescription;
81 : };
82 :
83 1 : CProfileNodeTable::ColumnDescription CProfileNodeTable::columnDescription;
84 :
85 :
86 : // Constructor/Destructor
87 0 : CProfileNodeTable::CProfileNodeTable(CProfileNode* n)
88 : {
89 0 : node = n;
90 0 : }
91 :
92 : // Short name (= name of profile node)
93 0 : CStr CProfileNodeTable::GetName()
94 : {
95 0 : return node->GetName();
96 : }
97 :
98 : // Title (= explanatory text plus time totals)
99 0 : CStr CProfileNodeTable::GetTitle()
100 : {
101 : char buf[512];
102 0 : sprintf_s(buf, ARRAY_SIZE(buf), "Profiling Information for: %s (Time in node: %.3f msec/frame)", node->GetName(), node->GetFrameTime() * 1000.0f );
103 0 : return buf;
104 : }
105 :
106 : // Total number of children
107 0 : size_t CProfileNodeTable::GetNumberRows()
108 : {
109 0 : return node->GetChildren()->size() + node->GetScriptChildren()->size() + 1;
110 : }
111 :
112 : // Column description
113 0 : const std::vector<ProfileColumn>& CProfileNodeTable::GetColumns()
114 : {
115 0 : return columnDescription.columns;
116 : }
117 :
118 : // Retrieve cell text
119 0 : CStr CProfileNodeTable::GetCellText(size_t row, size_t col)
120 : {
121 : CProfileNode* child;
122 0 : size_t nrchildren = node->GetChildren()->size();
123 0 : size_t nrscriptchildren = node->GetScriptChildren()->size();
124 0 : char buf[256] = "?";
125 :
126 0 : if (row < nrchildren)
127 0 : child = (*node->GetChildren())[row];
128 0 : else if (row < nrchildren + nrscriptchildren)
129 0 : child = (*node->GetScriptChildren())[row - nrchildren];
130 0 : else if (row > nrchildren + nrscriptchildren)
131 0 : return "!bad row!";
132 : else
133 : {
134 : // "unlogged" row
135 0 : if (col == 0)
136 0 : return "unlogged";
137 0 : else if (col == 1)
138 0 : return "";
139 0 : else if (col == 4)
140 0 : return "";
141 :
142 0 : double unlogged_time_frame = node->GetFrameTime();
143 0 : double unlogged_time_turn = node->GetTurnTime();
144 0 : CProfileNode::const_profile_iterator it;
145 :
146 0 : for (it = node->GetChildren()->begin(); it != node->GetChildren()->end(); ++it)
147 : {
148 0 : unlogged_time_frame -= (*it)->GetFrameTime();
149 0 : unlogged_time_turn -= (*it)->GetTurnTime();
150 : }
151 0 : for (it = node->GetScriptChildren()->begin(); it != node->GetScriptChildren()->end(); ++it)
152 : {
153 0 : unlogged_time_frame -= (*it)->GetFrameTime();
154 0 : unlogged_time_turn -= (*it)->GetTurnTime();
155 : }
156 :
157 : // The root node can't easily count per-turn values (since Turn isn't called until
158 : // halfway though a frame), so just reset them the zero to prevent weird displays
159 0 : if (!node->GetParent())
160 : {
161 0 : unlogged_time_turn = 0.0;
162 : }
163 :
164 0 : if (col == 2)
165 0 : sprintf_s(buf, ARRAY_SIZE(buf), "%.3f", unlogged_time_frame * 1000.0f);
166 0 : else if (col == 4)
167 0 : sprintf_s(buf, ARRAY_SIZE(buf), "%.3f", unlogged_time_turn * 1000.f);
168 :
169 0 : return CStr(buf);
170 : }
171 :
172 0 : switch(col)
173 : {
174 0 : default:
175 : case 0:
176 0 : return child->GetName();
177 :
178 0 : case 1:
179 0 : sprintf_s(buf, ARRAY_SIZE(buf), "%.1f", child->GetFrameCalls());
180 0 : break;
181 0 : case 2:
182 0 : sprintf_s(buf, ARRAY_SIZE(buf), "%.3f", child->GetFrameTime() * 1000.0f);
183 0 : break;
184 0 : case 3:
185 0 : sprintf_s(buf, ARRAY_SIZE(buf), "%.1f", child->GetTurnCalls());
186 0 : break;
187 0 : case 4:
188 0 : sprintf_s(buf, ARRAY_SIZE(buf), "%.3f", child->GetTurnTime() * 1000.0f);
189 0 : break;
190 : }
191 0 : return CStr(buf);
192 : }
193 :
194 : // Return a pointer to the child table if the child node is expandable
195 0 : AbstractProfileTable* CProfileNodeTable::GetChild(size_t row)
196 : {
197 : CProfileNode* child;
198 0 : size_t nrchildren = node->GetChildren()->size();
199 0 : size_t nrscriptchildren = node->GetScriptChildren()->size();
200 :
201 0 : if (row < nrchildren)
202 0 : child = (*node->GetChildren())[row];
203 0 : else if (row < nrchildren + nrscriptchildren)
204 0 : child = (*node->GetScriptChildren())[row - nrchildren];
205 : else
206 0 : return 0;
207 :
208 0 : if (child->CanExpand())
209 0 : return child->display_table;
210 :
211 0 : return 0;
212 : }
213 :
214 : // Highlight all script nodes
215 0 : bool CProfileNodeTable::IsHighlightRow(size_t row)
216 : {
217 0 : size_t nrchildren = node->GetChildren()->size();
218 0 : size_t nrscriptchildren = node->GetScriptChildren()->size();
219 :
220 0 : return (row >= nrchildren && row < (nrchildren + nrscriptchildren));
221 : }
222 :
223 : ///////////////////////////////////////////////////////////////////////////////////////////////
224 : // CProfileNode implementation
225 :
226 :
227 : // Note: As with the GPG profiler, name is assumed to be a pointer to a constant string; only pointer equality is checked.
228 0 : CProfileNode::CProfileNode( const char* _name, CProfileNode* _parent )
229 : {
230 0 : name = _name;
231 0 : recursion = 0;
232 :
233 0 : Reset();
234 :
235 0 : parent = _parent;
236 :
237 0 : display_table = new CProfileNodeTable(this);
238 0 : }
239 :
240 0 : CProfileNode::~CProfileNode()
241 : {
242 0 : profile_iterator it;
243 0 : for( it = children.begin(); it != children.end(); ++it )
244 0 : delete( *it );
245 0 : for( it = script_children.begin(); it != script_children.end(); ++it )
246 0 : delete( *it );
247 :
248 0 : delete display_table;
249 0 : }
250 :
251 : template<typename T>
252 0 : static double average(const T& collection)
253 : {
254 0 : if (collection.empty())
255 0 : return 0.0;
256 0 : return std::accumulate(collection.begin(), collection.end(), 0.0) / collection.size();
257 : }
258 :
259 0 : double CProfileNode::GetFrameCalls() const
260 : {
261 0 : return average(calls_per_frame);
262 : }
263 :
264 0 : double CProfileNode::GetFrameTime() const
265 : {
266 0 : return average(time_per_frame);
267 : }
268 :
269 0 : double CProfileNode::GetTurnCalls() const
270 : {
271 0 : return average(calls_per_turn);
272 : }
273 :
274 0 : double CProfileNode::GetTurnTime() const
275 : {
276 0 : return average(time_per_turn);
277 : }
278 :
279 0 : const CProfileNode* CProfileNode::GetChild( const char* childName ) const
280 : {
281 0 : const_profile_iterator it;
282 0 : for( it = children.begin(); it != children.end(); ++it )
283 0 : if( (*it)->name == childName )
284 0 : return( *it );
285 :
286 0 : return( NULL );
287 : }
288 :
289 0 : const CProfileNode* CProfileNode::GetScriptChild( const char* childName ) const
290 : {
291 0 : const_profile_iterator it;
292 0 : for( it = script_children.begin(); it != script_children.end(); ++it )
293 0 : if( (*it)->name == childName )
294 0 : return( *it );
295 :
296 0 : return( NULL );
297 : }
298 :
299 0 : CProfileNode* CProfileNode::GetChild( const char* childName )
300 : {
301 0 : profile_iterator it;
302 0 : for( it = children.begin(); it != children.end(); ++it )
303 0 : if( (*it)->name == childName )
304 0 : return( *it );
305 :
306 0 : CProfileNode* newNode = new CProfileNode( childName, this );
307 0 : children.push_back( newNode );
308 0 : return( newNode );
309 : }
310 :
311 0 : CProfileNode* CProfileNode::GetScriptChild( const char* childName )
312 : {
313 0 : profile_iterator it;
314 0 : for( it = script_children.begin(); it != script_children.end(); ++it )
315 0 : if( (*it)->name == childName )
316 0 : return( *it );
317 :
318 0 : CProfileNode* newNode = new CProfileNode( childName, this );
319 0 : script_children.push_back( newNode );
320 0 : return( newNode );
321 : }
322 :
323 0 : bool CProfileNode::CanExpand()
324 : {
325 0 : return( !( children.empty() && script_children.empty() ) );
326 : }
327 :
328 0 : void CProfileNode::Reset()
329 : {
330 0 : calls_per_frame.clear();
331 0 : calls_per_turn.clear();
332 0 : calls_frame_current = 0;
333 0 : calls_turn_current = 0;
334 :
335 0 : time_per_frame.clear();
336 0 : time_per_turn.clear();
337 0 : time_frame_current = 0.0;
338 0 : time_turn_current = 0.0;
339 :
340 0 : profile_iterator it;
341 0 : for (it = children.begin(); it != children.end(); ++it)
342 0 : (*it)->Reset();
343 0 : for (it = script_children.begin(); it != script_children.end(); ++it)
344 0 : (*it)->Reset();
345 0 : }
346 :
347 0 : void CProfileNode::Frame()
348 : {
349 0 : calls_per_frame.push_back(calls_frame_current);
350 0 : time_per_frame.push_back(time_frame_current);
351 :
352 0 : calls_frame_current = 0;
353 0 : time_frame_current = 0.0;
354 :
355 0 : profile_iterator it;
356 0 : for (it = children.begin(); it != children.end(); ++it)
357 0 : (*it)->Frame();
358 0 : for (it = script_children.begin(); it != script_children.end(); ++it)
359 0 : (*it)->Frame();
360 0 : }
361 :
362 0 : void CProfileNode::Turn()
363 : {
364 0 : calls_per_turn.push_back(calls_turn_current);
365 0 : time_per_turn.push_back(time_turn_current);
366 :
367 0 : calls_turn_current = 0;
368 0 : time_turn_current = 0.0;
369 :
370 0 : profile_iterator it;
371 0 : for (it = children.begin(); it != children.end(); ++it)
372 0 : (*it)->Turn();
373 0 : for (it = script_children.begin(); it != script_children.end(); ++it)
374 0 : (*it)->Turn();
375 0 : }
376 :
377 0 : void CProfileNode::Call()
378 : {
379 0 : calls_frame_current++;
380 0 : calls_turn_current++;
381 0 : if (recursion++ == 0)
382 : {
383 0 : start = timer_Time();
384 : }
385 0 : }
386 :
387 0 : bool CProfileNode::Return()
388 : {
389 0 : if (--recursion != 0)
390 0 : return false;
391 :
392 0 : double now = timer_Time();
393 0 : time_frame_current += (now - start);
394 0 : time_turn_current += (now - start);
395 0 : return true;
396 : }
397 :
398 0 : CProfileManager::CProfileManager() :
399 0 : root(NULL), current(NULL), needs_structural_reset(false)
400 : {
401 0 : PerformStructuralReset();
402 0 : }
403 :
404 0 : CProfileManager::~CProfileManager()
405 : {
406 0 : delete root;
407 0 : }
408 :
409 0 : void CProfileManager::Start( const char* name )
410 : {
411 0 : if( name != current->GetName() )
412 0 : current = current->GetChild( name );
413 0 : current->Call();
414 0 : }
415 :
416 0 : void CProfileManager::StartScript( const char* name )
417 : {
418 0 : if( name != current->GetName() )
419 0 : current = current->GetScriptChild( name );
420 0 : current->Call();
421 0 : }
422 :
423 0 : void CProfileManager::Stop()
424 : {
425 0 : if (current->Return())
426 0 : current = current->GetParent();
427 0 : }
428 :
429 0 : void CProfileManager::Reset()
430 : {
431 0 : root->Reset();
432 0 : }
433 :
434 0 : void CProfileManager::Frame()
435 : {
436 0 : root->time_frame_current += (timer_Time() - root->start);
437 :
438 0 : root->Frame();
439 :
440 0 : if (needs_structural_reset)
441 : {
442 0 : PerformStructuralReset();
443 0 : needs_structural_reset = false;
444 : }
445 :
446 0 : root->start = timer_Time();
447 0 : }
448 :
449 0 : void CProfileManager::Turn()
450 : {
451 0 : root->Turn();
452 0 : }
453 :
454 0 : void CProfileManager::StructuralReset()
455 : {
456 : // We can't immediately perform the reset, because we're probably already
457 : // nested inside the profile tree and it will get very confused if we delete
458 : // the tree when we're not currently at the root.
459 : // So just set a flag to perform the reset at the end of the frame.
460 :
461 0 : needs_structural_reset = true;
462 0 : }
463 :
464 0 : void CProfileManager::PerformStructuralReset()
465 : {
466 0 : delete root;
467 0 : root = new CProfileNode("root", NULL);
468 0 : root->Call();
469 0 : current = root;
470 0 : g_ProfileViewer.AddRootTable(root->display_table, true);
471 0 : }
472 :
473 3163 : CProfileSample::CProfileSample(const char* name)
474 : {
475 3163 : if (CProfileManager::IsInitialised())
476 : {
477 : // The profiler is only safe to use on the main thread
478 0 : if(Threading::IsMainThread())
479 0 : g_Profiler.Start(name);
480 : }
481 3163 : }
482 :
483 3163 : CProfileSample::~CProfileSample()
484 : {
485 3163 : if (CProfileManager::IsInitialised())
486 0 : if(Threading::IsMainThread())
487 0 : g_Profiler.Stop();
488 3166 : }
|