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