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