1 /**************************************************************************\
2 * Copyright (c) Kongsberg Oil & Gas Technologies AS
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * Neither the name of the copyright holder nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32
33
34 /*!
35 \page profiling_intro Scene Graph Profiling
36
37 <h2>Enabling profiling in Coin</h2>
38
39 To enable profiling in Coin, use the environment variable \ref
40 COIN_PROFILER. When profiling is enabled, Coin will gather
41 profiling data during every scene graph traversal by any action.
42
43 <h2>Enabling the default profiling display</h2>
44
45 To get some profiling data shown on the screen, you also need to use
46 the \ref COIN_PROFILER_OVERLAY environment variable.
47
48 This will give you the default profiling graphics, which
49 shows a top-list of node timings categorized by node types, a
50 scrolling graph of action traversal timings, and a scene graph
51 navigator for closer scene graph inspection.
52
53 <h2>Read the profiling data</h2>
54
55 The SoProfilerStats node can be used to fetch the profiling data in the
56 scene graph. If it is positioned anywhere in the scene graph, the
57 fields of the node will be updated every time SoGLRenderAction is
58 applied to the scene graph, with profiling data gathered from every
59 traversal through the scene graph since the last SoGLRenderAction, up
60 to the point where SoProfilerStats is located. Depending of how you
61 wish to use the data, either attach sensors to the fields, or connect
62 the the fields on other coin nodes to the fields on SoProfilerStats.
63 */
64
65
66 /**************************************************************************/
67
68 #ifdef HAVE_CONFIG_H
69 #include "config.h"
70 #endif // HAVE_CONFIG_H
71
72 #include <Inventor/annex/Profiler/SoProfiler.h>
73 #include "profiler/SoProfilerP.h"
74
75 #include <string>
76 #include <vector>
77
78 #include <Inventor/errors/SoDebugError.h>
79 #include <Inventor/SoType.h>
80 #include <Inventor/actions/SoActions.h>
81 #include <Inventor/nodekits/SoNodeKit.h>
82
83 #include <Inventor/annex/Profiler/elements/SoProfilerElement.h>
84 #include <Inventor/annex/Profiler/nodes/SoProfilerStats.h>
85 #include <Inventor/annex/Profiler/engines/SoProfilerTopEngine.h>
86 #include <Inventor/annex/Profiler/utils/SoProfilingReportGenerator.h>
87 #ifdef HAVE_NODEKITS
88 #include <Inventor/annex/Profiler/nodekits/SoNodeVisualize.h>
89 #include <Inventor/annex/Profiler/nodekits/SoProfilerOverlayKit.h>
90 #include <Inventor/annex/Profiler/nodekits/SoProfilerTopKit.h>
91 #include <Inventor/annex/Profiler/nodekits/SoScrollingGraphKit.h>
92 #include <Inventor/annex/Profiler/nodekits/SoProfilerVisualizeKit.h>
93 #endif // HAVE_NODEKITS
94
95 #include "tidbitsp.h"
96 #include "misc/SoDBP.h"
97
98 // *************************************************************************
99
100 /*!
101 \class SoProfiler SoProfiler.h Profiler/SoProfiler.h
102 \brief Main static class for initializing the scene graph profiling subsystem.
103
104 \ingroup profiler
105 */
106
107 namespace {
108
109 namespace profiler {
110 static SbBool initialized = FALSE;
111
112 static SbBool enabled = FALSE;
113
114 namespace rendering {
115 static SbBool syncgl = FALSE;
116 static float redraw_rate = -1.0f;
117 };
118
119 namespace overlay {
120 static SbBool active = FALSE;
121 };
122
123 namespace console {
124 static SbBool active = FALSE;
125 static SbBool clear = FALSE;
126 static SbBool header = FALSE;
127 static int lines = 20;
128 static SoProfilingReportGenerator::DataCategorization category =
129 SoProfilingReportGenerator::NODES;
130 static SoType actiontype = SoType::badType();
131 static SbBool onstdout = FALSE;
132 static SbBool onstderr = FALSE;
133 };
134
135 };
136
137 void
tokenize(const std::string & input,const std::string & delimiters,std::vector<std::string> & tokens,int count=-1)138 tokenize(const std::string & input, const std::string & delimiters, std::vector<std::string> & tokens, int count = -1)
139 {
140 std::string::size_type last_pos = 0, pos = 0;
141 while (TRUE) {
142 --count;
143 pos = input.find_first_of(delimiters, last_pos);
144 if ((pos == std::string::npos) || (count == 0)) {
145 tokens.push_back(input.substr(last_pos));
146 break;
147 } else {
148 tokens.push_back(input.substr(last_pos, pos - last_pos));
149 last_pos = pos + 1;
150 }
151 }
152 }
153
154 } // namespace
155
156
157 /*!
158 Initializes the Coin scene graph profiling subsystem.
159 */
160
161 void
init(void)162 SoProfiler::init(void)
163 {
164 if (profiler::initialized) return;
165
166 SoProfilerStats::initClass();
167 SoProfilerTopEngine::initClass();
168
169 #ifdef HAVE_NODEKITS
170 SoNodeKit::init();
171 SoProfilerOverlayKit::initClass();
172 SoProfilerVisualizeKit::initClass();
173 SoProfilerTopKit::initClass();
174 SoScrollingGraphKit::initClass();
175 SoNodeVisualize::initClass();
176 #endif // HAVE_NODEKITS
177
178 SoProfilingReportGenerator::init();
179
180 profiler::enabled = TRUE;
181
182 //SoProfilerP::setActionType(SoRayPickAction::getClassTypeId());
183 SoProfilerP::parseCoinProfilerOverlayVariable();
184
185 profiler::initialized = TRUE;
186 }
187
188 /*!
189 Returns whether profiling info is shown in an overlay fashion on
190 the GL canvas or not.
191 */
192 SbBool
isOverlayActive(void)193 SoProfiler::isOverlayActive(void)
194 {
195 return SoProfiler::isEnabled() && profiler::overlay::active;
196 }
197
198 /*!
199 Returns whether profiling info is shown on the console or not.
200 */
201 SbBool
isConsoleActive(void)202 SoProfiler::isConsoleActive(void)
203 {
204 return SoProfiler::isEnabled() && profiler::console::active;
205 }
206
207 /*!
208 Enable/disable the profiling subsystem at runtime.
209 */
210 void
enable(SbBool enable)211 SoProfiler::enable(SbBool enable)
212 {
213 if (!profiler::initialized) {
214 assert(!"SoProfiler module not initialized");
215 SoDebugError::post("SoProfiler::enable", "module not initialized");
216 return;
217 }
218 profiler::enabled = enable;
219 }
220
221 /*!
222 Returns whether profiling is enabled or not.
223 */
224
225 SbBool
isEnabled(void)226 SoProfiler::isEnabled(void)
227 {
228 return profiler::enabled;
229 }
230
231 SbBool
shouldContinuousRender(void)232 SoProfilerP::shouldContinuousRender(void)
233 {
234 return profiler::rendering::redraw_rate != -1.0f;
235 }
236
237 float
getContinuousRenderDelay(void)238 SoProfilerP::getContinuousRenderDelay(void)
239 {
240 return profiler::rendering::redraw_rate;
241 }
242
243 SbBool
shouldSyncGL(void)244 SoProfilerP::shouldSyncGL(void)
245 {
246 return profiler::rendering::syncgl;
247 }
248
249 SbBool
shouldClearConsole(void)250 SoProfilerP::shouldClearConsole(void)
251 {
252 return profiler::console::clear;
253 }
254
255 SbBool
shouldOutputHeaderOnConsole(void)256 SoProfilerP::shouldOutputHeaderOnConsole(void)
257 {
258 return profiler::console::header;
259 }
260
261 void
setActionType(SoType actiontype)262 SoProfilerP::setActionType(SoType actiontype)
263 {
264 #define IF_ACTION(actionname) \
265 if (actiontype.isDerivedFrom(actionname::getClassTypeId())) { \
266 SO_ENABLE(actionname, SoProfilerElement); \
267 profiler::console::actiontype = actiontype; \
268 }
269
270 IF_ACTION(SoGLRenderAction)
271 else IF_ACTION(SoPickAction)
272 else IF_ACTION(SoCallbackAction)
273 else IF_ACTION(SoGetBoundingBoxAction)
274 else IF_ACTION(SoGetMatrixAction)
275 else IF_ACTION(SoGetPrimitiveCountAction)
276 else IF_ACTION(SoHandleEventAction)
277 else IF_ACTION(SoToVRMLAction)
278 else IF_ACTION(SoAudioRenderAction)
279 else IF_ACTION(SoSimplifyAction)
280 else {
281 SoDebugError::postInfo("SoProfilerP::setActionType",
282 "profiling action of type '%s' is not supported",
283 actiontype.getName().getString());
284 }
285 #undef IF_ACTION
286 }
287
288 SoType
getActionType(void)289 SoProfilerP::getActionType(void)
290 {
291 if (profiler::console::actiontype == SoType::badType()) {
292 profiler::console::actiontype = SoGLRenderAction::getClassTypeId();
293 }
294 return profiler::console::actiontype;
295 }
296
297 void
parseCoinProfilerVariable(void)298 SoProfilerP::parseCoinProfilerVariable(void)
299 {
300 // variable COIN_PROFILER
301 // - on
302 // - syncgl - implies on
303 // - [nocaching - implies on] // todo
304
305 const char * env = coin_getenv(SoDBP::EnvVars::COIN_PROFILER);
306 if (env == NULL) return;
307 std::vector<std::string> parameters;
308 tokenize(env, ":", parameters);
309 if ((parameters.size() == 1) &&
310 (parameters[0].find_first_not_of("+-0123456789 \t") == std::string::npos)) {
311 // just have a numeral value (or nothing) - old semantics
312 profiler::enabled = atoi(parameters[0].data()) > 0 ? TRUE : FALSE;
313 }
314 else if (parameters.size() > 0) {
315 std::vector<std::string>::iterator it = parameters.begin();
316 while (it != parameters.end()) {
317 if ((*it).compare("on") == 0) {
318 profiler::enabled = TRUE;
319 }
320 else if ((*it).compare("off") == 0) {
321 profiler::enabled = FALSE;
322 }
323 else if ((*it).compare("syncgl") == 0) {
324 profiler::enabled = TRUE;
325 profiler::rendering::syncgl = TRUE;
326 }
327 else {
328 SoDebugError::postWarning("SoProfilerP::parseCoinProfilerVariable",
329 "invalid token '%s'", (*it).data());
330 }
331 ++it;
332 }
333 }
334 }
335
336 void
parseCoinProfilerOverlayVariable(void)337 SoProfilerP::parseCoinProfilerOverlayVariable(void)
338 {
339 const char * env = coin_getenv(SoDBP::EnvVars::COIN_PROFILER_OVERLAY);
340 if (env == NULL) return;
341 std::vector<std::string> parameters;
342 tokenize(env, ":", parameters);
343
344 if (parameters.size() == 1 && atoi(parameters[0].data()) > 0) {
345 // old behaviour, default setup
346 profiler::overlay::active = TRUE;
347 // SoDebugError::postInfo("SoProfiler::initialize", "default old behaviour parsing");
348 }
349 else if (parameters.size() > 0) {
350 // SoDebugError::postInfo("SoProfiler::initialize", "new tokenized parsing");
351
352 for (std::vector<std::string>::iterator it = parameters.begin(); it != parameters.end(); ++it) {
353 if (it == parameters.begin()) {
354 profiler::overlay::active = TRUE;
355 }
356
357 std::vector<std::string> param, subargs;
358 tokenize(*it, "=", param, 2);
359 if (param.size() > 1) {
360 tokenize(param[1], ",", subargs);
361 }
362
363 // configure if the profiling system should continuously
364 // schedule redraws to get more "live" data on the overlay
365 if (param[0].compare("autoredraw") == 0) {
366 if (param.size() == 1) {
367 // no argument ->continuous redraws
368 profiler::rendering::redraw_rate = 0.0f;
369 } else {
370 // argument decides redraw-delay-rate
371 profiler::rendering::redraw_rate = static_cast<float>(atof(param[1].data()));
372 }
373 if (profiler::rendering::redraw_rate < 0.0f) {
374 profiler::rendering::redraw_rate = -1.0f; // -1 exact means no redraws
375 }
376 }
377
378 else if (param[0].compare("stdout") == 0) {
379 profiler::overlay::active = FALSE;
380 profiler::console::active = TRUE;
381 profiler::console::onstdout = TRUE;
382 }
383
384 else if (param[0].compare("stderr") == 0) {
385 profiler::overlay::active = FALSE;
386 profiler::console::active = TRUE;
387 profiler::console::onstderr = TRUE;
388 }
389
390 else if (param[0].compare("clear") == 0 && profiler::console::active) {
391 profiler::console::clear = TRUE;
392 }
393
394 else if (param[0].compare("header") == 0 && profiler::console::active) {
395 profiler::console::header = TRUE;
396 }
397
398 else if (param[0].compare("lines") == 0) {
399 if (subargs.size() > 0) {
400 profiler::console::lines = atoi(subargs[0].data());
401 if (profiler::console::lines < 0 || profiler::console::lines > 512) {
402 SoDebugError::postWarning("SoProfiler",
403 "Number of lines out of range. Seting 20.",
404 profiler::console::lines);
405 profiler::console::lines = 20;
406 }
407 } else {
408 SoDebugError::postWarning("SoProfiler",
409 "'lines' takes a numeric argument.");
410 }
411 }
412
413 else if (param[0].compare("action") == 0) {
414 if (subargs.size() > 0) {
415 SoType actiontype = SoType::fromName(subargs[0].data());
416 if (actiontype.isDerivedFrom(SoAction::getClassTypeId())) {
417 SoProfilerP::setActionType(actiontype);
418 } else {
419 SoDebugError::postWarning("SoProfiler",
420 "Classname '%s' does not specify an action type.",
421 subargs[0].data());
422 }
423 } else {
424 SoDebugError::postWarning("SoProfiler",
425 "'action' takes a classname as argument.");
426 }
427 }
428
429 else if (param[0].compare("category") == 0) {
430 if (subargs.size() > 0) {
431 if (subargs[0].compare("nodes") == 0) {
432 profiler::console::category =
433 SoProfilingReportGenerator::NODES;
434 } else if (subargs[0].compare("types") == 0) {
435 profiler::console::category =
436 SoProfilingReportGenerator::TYPES;
437 } else if (subargs[0].compare("names") == 0) {
438 profiler::console::category =
439 SoProfilingReportGenerator::NAMES;
440 } else {
441 SoDebugError::postWarning("SoProfiler",
442 "'category' argument must be nodes, types, or names - was '%s'.",
443 subargs[0].data());
444 }
445 } else {
446 SoDebugError::postWarning("SoProfiler",
447 "'category' must have argument nodes, types, or names.");
448 }
449 }
450
451 // configure if and how we should display toplists
452 else if (param[0].compare("toplist") == 0) {
453 enum TopListType { NODE_TYPE, NODE_NAME, ACTION_TYPE, INVALID } toplisttype = INVALID;
454 if (subargs.size() > 0 && subargs[0].compare("nodes") == 0) {
455 // top-list based on node type statistics
456 toplisttype = NODE_TYPE;
457 }
458 else if (subargs.size() > 0 && subargs[0].compare("names") == 0) {
459 // top-list based on named node statistics
460 toplisttype = NODE_NAME;
461 }
462 else if (subargs.size() > 0 && subargs[0].compare("actions") == 0) {
463 // top-list based on action type statistics
464 toplisttype = ACTION_TYPE;
465 }
466 else if (subargs.size() > 0) {
467 // default to node type
468 toplisttype = INVALID;
469 }
470 if (toplisttype != INVALID) {
471 std::vector<std::string>::iterator it = subargs.begin();
472 ++it;
473 while (it != subargs.end()) {
474 std::vector<std::string> subarg;
475 tokenize((*it).data(), "=", subarg, 2);
476 if (subarg[0].compare("header") == 0) {
477 // display header on toplist
478 }
479 else if (subarg[0].compare("lines") == 0) {
480 // needs numeric argument
481 if (subarg.size() == 2) {
482 /*int lines = atoi(subarg[1].data());*/
483 } else {
484 // error: toplist.lines needs a numeric argument`
485 }
486 }
487 else if ((subarg[0].compare("action") == 0) && (toplisttype != ACTION_TYPE)) {
488 // need name of action
489 if (subarg.size() == 2) {
490 SoType actiontype = SoType::fromName(subarg[1].data());
491 if (actiontype.isDerivedFrom(SoAction::getClassTypeId())) {
492 SoProfilerP::setActionType(actiontype);
493 } else {
494 SoDebugError::postWarning("SoProfiler",
495 "classname '%s' does not specify an action type",
496 subarg[1].data());
497 // error - must specify action type
498 }
499 } else {
500 // error: toplist.action needs an SoAction-derived type name
501 }
502 }
503 else if ((subarg[0].compare("inclusive") == 0) && (toplisttype != ACTION_TYPE)) {
504 // no argument
505 }
506 else if ((subarg[0].compare("exclusive") == 0) && (toplisttype != ACTION_TYPE)) {
507 // no argument
508 }
509 ++it;
510 }
511 } else {
512 }
513 }
514
515 // configure charts
516 else if (param[0].compare("graph") == 0) {
517 }
518
519 // configure scene graph view
520 else if (param[0].compare("sceneview") == 0) {
521 }
522
523 // fallthrough
524 else {
525 SoDebugError::postWarning("SoProfiler::initialize",
526 "Unknown COIN_PROFILER_OVERLAY parameter '%s'.",
527 param[0].data());
528 }
529 }
530
531 // profiler::overlay::active = TRUE;
532 }
533 else {
534 // env variable is empty - don't activate overlay parts
535 }
536 }
537
538 /*
539 Default implementation for dumping on console instead of overlaying
540 statistics over the 3D graphics.
541 */
542 void
dumpToConsole(const SbProfilingData & data)543 SoProfilerP::dumpToConsole(const SbProfilingData & data)
544 {
545 SoProfilingReportGenerator::ReportCB * callback = NULL;
546 if (profiler::console::onstdout) {
547 callback = SoProfilingReportGenerator::stdoutCB;
548 }
549 else if (profiler::console::onstderr) {
550 callback = SoProfilingReportGenerator::stderrCB;
551 }
552 if (!callback) {
553 return;
554 }
555
556 if (SoProfilerP::shouldClearConsole()) {
557 // send ansi-console clear screen code
558 static const char CLEAR_SEQUENCE[] = "\033c";
559 if (profiler::console::onstdout) {
560 fputs(CLEAR_SEQUENCE,coin_get_stdout());
561 } else if (profiler::console::onstderr) {
562 fputs(CLEAR_SEQUENCE,coin_get_stderr());
563 }
564 }
565
566 SoProfilingReportGenerator::DataCategorization category =
567 profiler::console::category;
568
569 // set up how to sort the toplist
570 SbProfilingReportSortCriteria * sortsettings =
571 SoProfilingReportGenerator::getDefaultReportSortCriteria(category);
572
573 // set up how to print the toplist
574 SbProfilingReportPrintCriteria * printsettings =
575 SoProfilingReportGenerator::getDefaultReportPrintCriteria(category);
576
577 SoProfilingReportGenerator::generate(data,
578 category,
579 sortsettings,
580 printsettings,
581 profiler::console::lines,
582 SoProfilerP::shouldOutputHeaderOnConsole(),
583 callback,
584 NULL);
585
586 SoProfilingReportGenerator::freeCriteria(sortsettings);
587 SoProfilingReportGenerator::freeCriteria(printsettings);
588 }
589