1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #ifndef PXR_BASE_TF_DEBUG_H
25 #define PXR_BASE_TF_DEBUG_H
26 
27 /// \file tf/debug.h
28 /// \ingroup group_tf_DebuggingOutput
29 /// Conditional debugging output class and macros.
30 
31 #include "pxr/pxr.h"
32 #include "pxr/base/tf/api.h"
33 #include "pxr/base/tf/tf.h"
34 #include "pxr/base/tf/enum.h"
35 #include "pxr/base/tf/preprocessorUtilsLite.h"
36 #include "pxr/base/tf/registryManager.h"
37 #include "pxr/base/tf/stopwatch.h"
38 #include "pxr/base/arch/attributes.h"
39 #include "pxr/base/arch/hints.h"
40 
41 #include <atomic>
42 #include <cstdio>
43 #include <string>
44 #include <vector>
45 
46 PXR_NAMESPACE_OPEN_SCOPE
47 
48 class Tf_DebugSymbolRegistry;
49 
50 /// \addtogroup group_tf_DebuggingOutput
51 ///@{
52 
53 /// \class TfDebug
54 ///
55 /// Enum-based debugging messages.
56 ///
57 /// The \c TfDebug class encapsulates a simple enum-based conditional
58 /// debugging message system.  It is meant as a tool for developers, and
59 /// \e NOT as a means of issuing diagnostic messages to end-users. (This is
60 /// not strictly true. The TfDebug class is extremely useful and has many
61 /// properties that make its use attractive for issuing messages to end-users.
62 /// However, for this purpose, please use the \c TF_INFO macro which more
63 /// clearly indicates its intent.)
64 ///
65 /// The features of \c TfDebug are:
66 ///   \li Debugging messages/calls for an entire enum group can be
67 ///       compiled out-of-existence.
68 ///   \li The cost of checking if a specific message should be printed
69 ///       at runtime (assuming the enum group of the message has not been
70 ///       compile-time disabled) is a single inline array lookup,
71 ///       with a compile-time index into a global array.
72 ///
73 /// The use of the facility is simple:
74 /// \code
75 ///   // header file
76 ///   #include "pxr/base/tf/debug.h"
77 ///   TF_DEBUG_CODES(MY_E1, MY_E2, MY_E3);
78 ///
79 ///   // source file
80 ///   TF_DEBUG(MY_E2).Msg("something about e2\n");
81 ///
82 ///   TF_DEBUG(MY_E3).Msg("val = %d\n", value);
83 /// \endcode
84 ///
85 /// The code in the header file declares the debug symbols to use.  Under
86 /// the hood, this creates an enum with the values given in the argument to
87 /// TF_DEBUG_CODES, along with a first and last sentinel values and passes
88 /// that to TF_DEBUG_RANGE.
89 ///
90 /// If you need to obtain the enum type name, use decltype(SOME_ENUM_VALUE).
91 ///
92 /// In the source file, the indicated debugging messages are printed
93 /// only if the debugging symbols are enabled.  Effectively, the construct
94 /// \code
95 ///     TF_DEBUG(MY_E1).Msg(msgExpr)
96 /// \endcode
97 /// is translated to
98 /// \code
99 ///     if (symbol-MY_E1-is-enabled)
100 ///         output(msgExpr)
101 /// \endcode
102 ///
103 /// The implications are that \c msgExpr is only evaluated if symbol \c MY_E1
104 /// symbol is enabled.
105 ///
106 /// To totally disable TF_DEBUG output for a set of codes at compile time,
107 /// declare the codes using
108 /// TF_CONDITIONALLY_COMPILE_TIME_ENABLED_DEBUG_CODES(condition, ...) where
109 /// ... is all the debug codes.  If 'condition' is false at compile time then
110 /// all TF_DEBUG().Msg()s for these codes are elminated at compile time, so they
111 /// have zero cost.
112 ///
113 /// Most commonly debug symbols are inactive by default, but can be turned
114 /// on either by an environment variable \c TF_DEBUG, or interactively once
115 /// a program has started.
116 ///
117 /// \code
118 ///     TfDebug::DisableAll<MyDebugCodes>();     // disable everything
119 ///
120 ///     TfDebug::Enable(MY_E1);                  // enable just MY_E1
121 /// \endcode
122 ///
123 /// Description strings may be associated with debug codes as follows:
124 /// \code
125 ///   // source file xyz/debugCodes.cpp
126 ///
127 ///   #include "proj/my/debugCodes.h"
128 ///   #include "pxr/base/tf/debug.h"
129 ///   #include "pxr/base/tf/registryManager.h"
130 ///
131 ///   TF_REGISTRY_FUNCTION(TfDebug) {
132 ///       TF_DEBUG_ENVIRONMENT_SYMBOL(MY_E1, "loading of blah-blah files");
133 ///       TF_DEBUG_ENVIRONMENT_SYMBOL(MY_E2, "parsing of mdl code");
134 ///       // etc.
135 ///   }
136 /// \endcode
137 ///
138 ///
139 class TfDebug {
140     enum _NodeState { _NodeUninitialized, _NodeDisabled, _NodeEnabled };
141 
142 public:
143     /// Mark debugging as enabled for enum value \c val.
144     ///
145     /// The default state for all debugging symbols is disabled. Note that the
146     /// template parameter is deduced from \c val:
147     /// \code
148     ///     TfDebug::Enable(MY_E3);
149     /// \endcode
150     template <class T>
Enable(T val)151     static void Enable(T val) {
152         _SetNode(_GetNode(val), Tf_DebugGetEnumName(val), true);
153     }
154 
155     /// Mark debugging as disabled for enum value \c val.
156     template <class T>
Disable(T val)157     static void Disable(T val) {
158         _SetNode(_GetNode(val), Tf_DebugGetEnumName(val), false);
159     }
160 
161     /// Mark debugging as enabled for all enum values of type \c T.
162     ///
163     /// Note that the template parameter must be explicitly supplied:
164     /// \code
165     ///     TfDebug::EnableAll<MyDebugCodes>()
166     /// \endcode
167     template <class T>
EnableAll()168     static void EnableAll() {
169         const int n = _Traits<T>::NumCodes;
170         for (int i = 0; i != n; ++i) {
171             T code = static_cast<T>(i);
172             _SetNode(_GetNode(code), Tf_DebugGetEnumName(code), true);
173         }
174     }
175 
176     /// Mark debugging as disabled for all enum values of type \c T.
177     template <class T>
DisableAll()178     static void DisableAll() {
179         const int n = _Traits<T>::NumCodes;
180         for (int i = 0; i != n; ++i) {
181             T code = static_cast<T>(i);
182             _SetNode(_GetNode(code), Tf_DebugGetEnumName(code), false);
183         }
184     }
185 
186     /// True if debugging is enabled for the enum value \c val.
187     ///
188     /// Note that not only must the specific enum value \c val be marked as
189     /// enabled, but the enum type \c T must be globally enabled; this is
190     /// controlled by the first argument to the
191     /// \c TF_CONDITIONALLY_COMPILE_TIME_ENABLED_DEBUG_CODES() macro.
192     template <class T>
IsEnabled(T val)193     static bool IsEnabled(T val) {
194         static_assert(_Traits<T>::IsDeclared,
195                       "Must declare debug codes with TF_DEBUG_CODES()");
196         if (_Traits<T>::CompileTimeEnabled) {
197             _Node &node = _GetNode(val);
198             _NodeState curState = node.state.load();
199             if (ARCH_UNLIKELY(curState == _NodeUninitialized)) {
200                 _InitializeNode(_GetNode(val), Tf_DebugGetEnumName(val));
201                 curState = node.state.load();
202             }
203             return curState == _NodeEnabled;
204         }
205         return false;
206     }
207 
208     /// True if debugging can be activated at run-time, whether or not it is
209     /// currently enabled.
210     template <class T>
IsCompileTimeEnabled()211     static bool IsCompileTimeEnabled() {
212         static_assert(_Traits<T>::IsDeclared,
213                       "Must declare debug codes with TF_DEBUG_CODES()");
214         return _Traits<T>::CompileTimeEnabled;
215     }
216 
217     /// Return the number of debugging symbols of this type.
218     ///
219     /// Returns the number of different enums in the range.
220     template <class T>
GetNumDebugCodes()221     static size_t GetNumDebugCodes() {
222         static_assert(_Traits<T>::IsDeclared,
223                       "Must declare debug codes with TF_DEBUG_CODES()");
224         return _Traits<T>::NumCodes;
225     }
226 
227 #if !defined(doxygen)
228     struct Helper {
229         static TF_API void Msg(const std::string& msg);
230         static TF_API void Msg(const char* msg, ...) ARCH_PRINTF_FUNCTION(1,2);
231     };
232 #endif
233 
234     template <bool B>
235     struct ScopeHelper {
ScopeHelperScopeHelper236         ScopeHelper(bool enabled, const char* name) {
237             if ((active = enabled)) {
238                 str = name;
239                 TfDebug::_ScopedOutput(true, str);
240             }
241             else
242                 str = NULL;
243         }
244 
~ScopeHelperScopeHelper245         ~ScopeHelper() {
246             if (active)
247                 TfDebug::_ScopedOutput(false, str);
248         }
249 
250         bool active;
251         const char* str;
252     };
253 
254     template <bool B>
255     struct TimedScopeHelper {
256         TimedScopeHelper(bool enabled, const char* fmt, ...)
257             ARCH_PRINTF_FUNCTION(3, 4);
258         ~TimedScopeHelper();
259 
260         bool active;
261         std::string str;
262         TfStopwatch stopwatch;
263     };
264 
265     /// Set registered debug symbols matching \p pattern to \p value.
266     ///
267     /// All registered debug symbols matching \p pattern are set to \p value.
268     /// The only matching is an exact match with \p pattern, or if \p pattern
269     /// ends with an '*' as is otherwise a prefix of a debug symbols.  The
270     /// names of all debug symbols set by this call are returned as a vector.
271     TF_API
272     static std::vector<std::string> SetDebugSymbolsByName(
273         const std::string& pattern, bool value);
274 
275     /// True if the specified debug symbol is set.
276     TF_API
277     static bool IsDebugSymbolNameEnabled(const std::string& name);
278 
279     /// Get a description of all debug symbols and their purpose.
280     ///
281     /// A single string describing all registered debug symbols along with
282     /// short descriptions is returned.
283     TF_API
284     static std::string GetDebugSymbolDescriptions();
285 
286     /// Get a listing of all debug symbols.
287     TF_API
288     static std::vector<std::string> GetDebugSymbolNames();
289 
290     /// Get a description for the specified debug symbol.
291     ///
292     /// A short description of the debug symbol is returned. This is the same
293     /// description string that is embedded in the return value of
294     /// GetDebugSymbolDescriptions.
295     TF_API
296     static std::string GetDebugSymbolDescription(const std::string& name);
297 
298     /// Direct debug output to \a either stdout or stderr.
299     ///
300     /// Note that \a file MUST be either stdout or stderr.  If not, issue an
301     /// error and do nothing.  Debug output is issued to stdout by default.
302     /// If the environment variable TF_DEBUG_OUTPUT_FILE is set to 'stderr',
303     /// then output is issued to stderr by default.
304     TF_API
305     static void SetOutputFile(FILE *file);
306 
307     struct _Node;
308 
309     // Public, to be used in TF_DEBUG_ENVIRONMENT_SYMBOL() macro,
310     // but not meant to be used otherwise.
311     template <class T>
_RegisterDebugSymbol(T enumVal,char const * name,char const * descrip)312     static void _RegisterDebugSymbol(
313         T enumVal, char const *name, char const *descrip) {
314         static_assert(_Traits<T>::IsDeclared,
315                       "Must declare debug codes with TF_DEBUG_CODES()");
316         const int index = static_cast<int>(enumVal);
317         const int numCodes = _Traits<T>::NumCodes;
318         if (ARCH_UNLIKELY(index < 0 || index >= numCodes)) {
319             _ComplainAboutInvalidSymbol(name);
320             return;
321         }
322         _RegisterDebugSymbolImpl(&_GetNode(enumVal), name, descrip);
323     }
324 
325     TF_API
326     static void _RegisterDebugSymbolImpl(_Node *addr, char const *enumName,
327                                          char const *descrip);
328 
329     // Unfortunately, we need to make both _Traits and _Node, below
330     // public because of their use in macros.
331     // Please treat both as a private data structures!
332 
333     template <class T>
334     struct _Traits {
335         static constexpr bool IsDeclared = false;
336     };
337 
338     // Note: this structure gets initialized statically zero
339     // (_NodeUninitialized) statically.
340     struct _Node {
341         mutable std::atomic<_NodeState> state;
342     };
343 
344 private:
345 
346     template <class T>
347     struct _Data {
348         static _Node nodes[_Traits<T>::NumCodes];
349     };
350 
351     template <class T>
_GetNode(T val)352     static _Node &_GetNode(T val) {
353         return _Data<T>::nodes[static_cast<int>(val)];
354     }
355 
356     friend class Tf_DebugSymbolRegistry;
357 
358     TF_API
359     static void _InitializeNode(_Node &node, char const *name);
360 
361     TF_API
362     static void _ComplainAboutInvalidSymbol(char const *name);
363 
364     TF_API
365     static void _SetNode(_Node &node, char const *name, bool state);
366 
367     TF_API
368     static void _ScopedOutput(bool start, char const *str);
369 };
370 
371 template <class T>
372 TfDebug::_Node TfDebug::_Data<T>::nodes[];
373 
374 template <>
375 struct TfDebug::TimedScopeHelper<false> {
376     TimedScopeHelper(bool, const char*, ...)
377         ARCH_PRINTF_FUNCTION(3, 4) {
378     }
379 };
380 
381 /// Define debugging symbols
382 ///
383 /// This is a simple macro that takes care of declaring debug codes. Use it as
384 /// follows:
385 /// \code
386 /// TF_DEBUG_CODES(
387 ///   MY_E1,
388 ///   MY_E2
389 /// );
390 /// \endcode
391 ///
392 /// \hideinitializer
393 #define TF_DEBUG_CODES(...)                                                  \
394     TF_CONDITIONALLY_COMPILE_TIME_ENABLED_DEBUG_CODES(true, __VA_ARGS__)
395 
396 /// Define debugging symbols
397 ///
398 /// This is a simple macro that takes care of declaring debug codes, subject to
399 /// a compile-time condition that enables or disables them completely. Use it as
400 /// follows:
401 /// \code
402 /// TF_CONDITIONALLY_COMPILE_TIME_ENABLED_DEBUG_CODES(
403 ///   <Enabled State: a compile-time value convertible to bool>
404 ///   MY_E1,
405 ///   MY_E2
406 /// );
407 /// \endcode
408 ///
409 /// If the Enabled State is true, this is equivalent to the TF_DEBUG_CODES()
410 /// macro.  If it is false, then these debug codes are disabled at compile time
411 /// and generated code pays no cost for them.
412 ///
413 /// \hideinitializer
414 #define TF_CONDITIONALLY_COMPILE_TIME_ENABLED_DEBUG_CODES(condition, ...)      \
415     enum _TF_DEBUG_ENUM_NAME(__VA_ARGS__) {                                    \
416         __VA_ARGS__ ,                                                          \
417         TF_PP_CAT( _TF_DEBUG_ENUM_NAME(__VA_ARGS__), __PAST_END)               \
418     };                                                                         \
419     template <>                                                                \
420     struct TfDebug::_Traits<_TF_DEBUG_ENUM_NAME(__VA_ARGS__)> {                \
421         static constexpr bool IsDeclared = true;                               \
422         static constexpr int NumCodes =                                        \
423             TF_PP_CAT(_TF_DEBUG_ENUM_NAME(__VA_ARGS__), __PAST_END);           \
424         static constexpr bool CompileTimeEnabled = (condition);                \
425     };                                                                         \
426     inline char const *                                                        \
427     Tf_DebugGetEnumName(_TF_DEBUG_ENUM_NAME(__VA_ARGS__) val) {                \
428         constexpr char const *CStrings[] = {                                   \
429             TF_PP_FOR_EACH(_TF_DEBUG_MAKE_STRING, __VA_ARGS__)                 \
430         };                                                                     \
431         return CStrings[static_cast<int>(val)];                                \
432     };
433 
434 #define _TF_DEBUG_MAKE_STRING(x) #x,
435 
436 // In the _TF_DEBUG_ENUM_NAME macro below we pass 'dummy' to
437 // _TF_DEBUG_FIRST_CODE as the second argument to ensure that we always
438 // have more than one argument as expected by _TF_DEBUG_FIRST_CODE.
439 #define _TF_DEBUG_ENUM_NAME(...)                                             \
440     TF_PP_CAT(_TF_DEBUG_FIRST_CODE(__VA_ARGS__, dummy), __DebugCodes)
441 
442 #define _TF_DEBUG_FIRST_CODE(first, ...) first
443 
444 /// Evaluate and print debugging message \c msg if \c enumVal is enabled for
445 /// debugging.
446 ///
447 /// This macro is a newer, more convenient form of the \c TF_DEBUG() macro.
448 /// Writing
449 /// \code
450 ///      TF_DEBUG_MSG(enumVal, msg, ...);
451 /// \endcode
452 /// is equivalent to
453 /// \code
454 ///     TF_DEBUG(enumVal).Msg(msg, ...);
455 /// \endcode
456 ///
457 /// The TF_DEBUG_MSG() macro allows either an std::string argument or
458 /// a printf-like format string followed by a variable number of arguments:
459 /// \code
460 ///     TF_DEBUG_MSG(enumVal, "opening file %s\n", file.c_str());
461 ///
462 ///     TF_DEBUG_MSG(enumVal, "opening file " + file);
463 /// \endcode
464 ///
465 /// \hideinitializer
466 #define TF_DEBUG_MSG(enumVal, ...)                                                \
467     if (!TfDebug::IsEnabled(enumVal)) /* empty */ ; else TfDebug::Helper().Msg(__VA_ARGS__)
468 
469 /// Evaluate and print debugging message \c msg if \c enumVal is enabled for
470 /// debugging.
471 ///
472 /// The \c TF_DEBUG() macro is used as follows:
473 /// \code
474 ///     TF_DEBUG(enumVal).Msg("opening file %s, count = %d\n",
475 ///                           file.c_str(), count);
476 /// \endcode
477 ///
478 /// If \c enumVal is of enumerated type \c enumType, and \c enumType
479 /// has been enabled for debugging (see \c TF_DEBUG_CODES()), and
480 /// the specific value \c enumVal has been enabled for debugging by a call
481 /// to \c TfDebug::Enable(), then the arguments in the \c Msg() call are
482 /// evaluated and printed.  The argument to \c Msg() may either be a
483 /// \c const \c char* and a variable number of arguments, using standard
484 /// printf-formatting rules, or a \c std::string variable:
485 /// \code
486 ///     TF_DEBUG(enumVal).Msg("opening file " + file + "\n");
487 /// \endcode
488 ///
489 /// Note that the arguments to \c Msg() are unevaluated when the value
490 /// \c enumVal is not enabled for debugging, so \c Msg() must be free
491 /// of side-effects; however, when \c enumVal is not enabled, there is
492 /// no expense incurred in computing the arguments to \c Msg().  Note
493 /// that if the entire enum type corresponding to \c enumVal is
494 /// disabled (a compile-time determination) then the code for the \e
495 /// entire \c TF_DEBUG().Msg() statement will typically not even be
496 /// generated!
497 ///
498 /// \sa TF_DEBUG_MSG()
499 ///
500 /// \hideinitializer
501 #define TF_DEBUG(enumVal)                                               \
502     if (!TfDebug::IsEnabled(enumVal)) /* empty */ ; else TfDebug::Helper()
503 
504 /// Evaluate and print diagnostic messages intended for end-users.
505 ///
506 /// The TF_INFO(x) macro is cosmetic; it actually just calls the TF_DEBUG
507 /// macro (see above).  This macro should be used if its output is intended to
508 /// be seen by end-users.
509 ///
510 /// \hideinitializer
511 #define TF_INFO(x) TF_DEBUG(x)
512 
513 /// Print description and time spent in scope upon beginning and exiting it if
514 /// \p enumVal is enabled for debugging.
515 ///
516 /// The \c TF_DEBUG_TIMED_SCOPE() macro is used as follows:
517 /// \code
518 /// void Attribute::Compute()
519 /// {
520 ///     TF_DEBUG_TIMED_SCOPE(ATTR_COMPUTE, "Computing %s", name.c_str());
521 ///     ...
522 /// }
523 /// \endcode
524 ///
525 /// When the \c TF_DEBUG_TIMED_SCOPE macro is invoked, a timer is started and
526 /// the supplied description is printed. When the enclosing scope is exited
527 /// (in the example, when Attribute::Compute() finishes) the timer is stopped
528 /// and the scope description and measured time are printed. This allows for
529 /// very fine-grained timing of operations.
530 ///
531 /// Note that if the entire enum type corresponding to \p enumVal is disabled
532 /// (a compile-time determination) then the presence of a
533 /// \c TF_DEBUG_TIMED_SCOPE() macro should not produce any extra generated
534 /// code (in an optimized build).  If the enum type is enabled, but the
535 /// particular value \p enumVal is disabled, the cost of the macro should be
536 /// quite minimal; still, it would be best not to embed the macro in functions
537 /// that are called in very tight loops, in final released code.
538 ///
539 /// \hideinitializer
540 #define TF_DEBUG_TIMED_SCOPE(enumVal, ...)                              \
541     TfDebug::TimedScopeHelper<                                          \
542         TfDebug::_Traits<                                               \
543             std::decay<decltype(enumVal)>::type>::CompileTimeEnabled>   \
544     TF_PP_CAT(local__TfScopeDebugSwObject, __LINE__)(                   \
545         TfDebug::IsEnabled(enumVal), __VA_ARGS__)
546 
547 /// Register description strings with enum symbols for debugging.
548 ///
549 /// This call should be used in source files, not header files, and should
550 /// This macro should usually appear within a
551 /// \c TF_REGISTRY_FUNCTION(TfDebug,...) call.  The first argument should be
552 /// the literal name of the enum symbol, while the second argument should be a
553 /// (short) description of what debugging will be enabled if the symbol is
554 /// activated.  The enum being registered must be one which is contained in
555 /// some TF_DEBUG_CODES() call. For example:
556 /// \code
557 ///   TF_REGISTRY_FUNCTION(TfDebug) {
558 ///       TF_DEBUG_ENVIRONMENT_SYMBOL(MY_E1, "loading of blah-blah files");
559 ///       TF_DEBUG_ENVIRONMENT_SYMBOL(MY_E2, "parsing of mdl code");
560 ///       // etc.
561 ///   }
562 /// \endcode
563 ///
564 /// \hideinitializer
565 #define TF_DEBUG_ENVIRONMENT_SYMBOL(VAL, descrip)                              \
566     if (TfDebug::_Traits<                                                      \
567         std::decay<decltype(VAL)>::type>::CompileTimeEnabled) {                \
568         TF_ADD_ENUM_NAME(VAL);                                                 \
569         TfDebug::_RegisterDebugSymbol(VAL, #VAL, descrip);                     \
570     }
571 
572 ///@}
573 
574 PXR_NAMESPACE_CLOSE_SCOPE
575 
576 #endif
577