Loading...
Searching...
No Matches
mallocTag.h
Go to the documentation of this file.
1//
2// Copyright 2016 Pixar
3//
4// Licensed under the terms set forth in the LICENSE.txt file available at
5// https://openusd.org/license.
6//
7#ifndef PXR_BASE_TF_MALLOC_TAG_H
8#define PXR_BASE_TF_MALLOC_TAG_H
9
10#include "pxr/pxr.h"
11#include "pxr/base/tf/api.h"
14#include "pxr/base/arch/hints.h"
15
16#include <atomic>
17#include <cstdlib>
18#include <cstdint>
19#include <iosfwd>
20#include <new>
21#include <string>
22#include <type_traits>
23#include <utility>
24#include <vector>
25
26PXR_NAMESPACE_OPEN_SCOPE
27
30
31// TfMallocTag collects by interposing the allocator via ArchMallocHook, which
32// requires the old glibc-style hook variables. When
33// PXR_ARCH_SUPPORT_MALLOC_HOOKS is not defined (see pxr/pxr.h), no
34// interposition is possible, so the entire collection implementation is omitted
35// and this API compiles to minimal stubs.
36//
37// Callers need not care which build they are in. The stub build behaves as a
38// full build would where Initialize() fails. Initialize() reports failure,
39// IsInitialized() is always false, the queries report no memory, and tag
40// push/pop compiles away. Only report parsing and printing --
41// CallTree::LoadReport(), Report(), and GetPrettyPrintString() -- remain fully
42// functional, so a report captured elsewhere can still be read.
43
44struct Tf_MallocPathNode;
45
53
54 // Wrapper-type marking a tag name whose characters are immortal, so that
55 // TfMallocTag may key on their address and adopt them without copying.
56 struct _ImmortalName {
57 char const *str;
58 };
59
60public:
61 struct CallStackInfo;
62
69 struct CallTree {
89 struct PathNode {
90 size_t nBytes,
92 size_t nAllocations;
93 std::string siteName;
94 std::vector<PathNode>
96 };
97
106 struct CallSite {
107 std::string name;
108 size_t nBytes;
109 };
110
117
118
121
142 TF_API
144 size_t maxPrintedNodes = 100000) const;
145
150 TF_API
151 void Report(
152 std::ostream &out,
153 const std::string &rootName) const;
154
156 TF_API
157 void Report(
158 std::ostream &out) const;
159
163 TF_API
165 std::istream &in);
166
168
169
171 std::vector<CallSite> callSites;
172
175
177 std::vector<CallStackInfo> capturedCallStacks;
178 };
179
184 {
186 std::vector<uintptr_t> stack;
187
190 size_t size;
191
195 };
196
212 TF_API static bool Initialize(std::string* errMsg = nullptr);
213
222 TF_API static void Shutdown();
223
229 static inline bool IsInitialized() {
230#ifdef PXR_ARCH_SUPPORT_MALLOC_HOOKS
231 return TfMallocTag::_initState
232 .load(std::memory_order_acquire) == _Initialized;
233#else
234 // Constant false, so every caller below -- Auto, Pop,
235 // GetCurrentStackState, StackOverride, PauseControl -- folds away
236 // entirely rather than testing a variable at runtime.
237 return false;
238#endif
239 }
240
245 TF_API static void Clear();
246
256 TF_API static size_t GetTotalBytes();
257
279 TF_API static size_t GetMaxTotalBytes();
280
297 TF_API static bool GetCallTree(CallTree* tree, bool skipRepeated = true);
298
302 TF_API static std::string GetPerfStats(bool includePerThread=false);
303
304private:
305
306 struct _ThreadData;
307
308public:
309
336 class Auto {
337 public:
338 Auto(const Auto &) = delete;
339 Auto& operator=(const Auto &) = delete;
340
341 Auto(Auto &&) = delete;
342 Auto& operator=(Auto &&) = delete;
343
364 template <class Str, class... Strs>
365 explicit Auto(Str &&name1, Strs &&... nameN)
366 : _threadData(
367 TfMallocTag::_Push(_TagName(std::forward<Str>(name1))))
368 , _nTags(0) {
369 if (_threadData) {
370 // Accumulate _nTags; _Begin will not push null or empty tags.
371 _nTags = 1;
372 (..., (_nTags += (TfMallocTag::_Begin(
373 _TagName(std::forward<Strs>(nameN)),
374 _threadData) ? 1 : 0)));
375 }
376 }
377
388 inline void Release() {
389 if (_threadData) {
390 TfMallocTag::_End(_nTags, _threadData);
391 _threadData = nullptr;
392 }
393 }
394
400 inline ~Auto() {
401 Release();
402 }
403
404 private:
405
406 // Is this tag argument a char array whose address we may treat as
407 // immortal -- a string literal, or a static const char array?
408 //
409 // What we can actually test for is a const char array lvalue. It is as
410 // narrow as we can make it and deliberately not "any char array".
411 // Unfortunately this can still accept objects that are not literals --
412 // a `const char buf[N]` on the stack also passes, and no C++17
413 // construct can tell that from a literal. So this is the best we can
414 // do, and remains a contract on callers rather than a check: a name
415 // passed as a const char array must live as long as the program.
416 template <class Str>
417 static constexpr bool _IsImmortalCharArray =
418 std::is_lvalue_reference_v<Str> &&
419 std::is_array_v<std::remove_reference_t<Str>> &&
420 std::is_const_v<
421 std::remove_extent_t<std::remove_reference_t<Str>>>;
422
423 template <class Str>
424 static auto _TagName(Str &&name) {
425 if constexpr (_IsImmortalCharArray<Str &&>) {
426 return _ImmortalName { name };
427 }
428 else {
429 return _TagNameNotArray(std::forward<Str>(name));
430 }
431 }
432 // A TfEternalString's characters are immortal (and content-unique) so
433 // route it exactly as a literal. This exact match beats the
434 // std::string const & overload below, which TfEternalString's implicit
435 // conversion would otherwise reach.
436 static _ImmortalName _TagNameNotArray(TfEternalString s) {
437 return _ImmortalName { s.c_str() };
438 }
439 static char const *_TagNameNotArray(char const *cstr) { return cstr; }
440 static char const *_TagNameNotArray(std::string const &str) {
441 // Borrowed, not owned. `str` may be a temporary in which case this
442 // reference dangles and the c_str() pointer is invalidated at the
443 // end of the full-expression that called us. Both callers above
444 // resolve the tag within that same full-expression, which makes
445 // this safe.
446 return str.c_str();
447 }
448
449 _ThreadData* _threadData;
450 int _nTags;
451
452 friend class TfMallocTag;
453 };
454
455 // An historical compatibility: in prior versions, Auto could accept only
456 // one argument, so Auto2 existed to handle two arguments. Now Auto can
457 // accept any number of arguments, so Auto2 is just an alias for Auto.
458 using Auto2 = Auto;
459
460 // fwd for friendship.
461 class StackOverride;
462
470 public:
471 StackState() = default;
472
473 private:
474 explicit StackState(Tf_MallocPathNode *top) : _top(top) {}
475
476 Tf_MallocPathNode *_top = nullptr;
477
478 friend class TfMallocTag;
479 friend class StackOverride;
480 };
481
495 public:
496 explicit StackOverride(StackState state)
497 : _state(state)
498 , _tls(state._top ? _Push() : nullptr) {}
499
500 // Noncopyable, nonmovable.
501 StackOverride(const StackOverride &) = delete;
502 StackOverride &operator=(const StackOverride &) = delete;
503
505 if (ARCH_UNLIKELY(_tls)) {
506 _Pop();
507 }
508 }
509
510 private:
511 TF_API _ThreadData *_Push() const;
512 TF_API void _Pop() const;
513
514 const StackState _state;
515 _ThreadData *_tls;
516
517 friend class TfMallocTag;
518 };
519
538 class PauseControl {
539 public:
540 PauseControl(PauseControl const &) = delete;
541 PauseControl &operator=(PauseControl const &) = delete;
542
545 PauseControl(PauseControl &&other) noexcept
546 : _td(std::exchange(other._td, nullptr))
547 , _scope(std::exchange(other._scope, _NotPaused)) {}
548
552 if (this != &other) {
553 if (IsPaused()) {
554 Unpause();
555 }
556 _td = std::exchange(other._td, nullptr);
557 _scope = std::exchange(other._scope, _NotPaused);
558 }
559 return *this;
560 }
561
564 Unpause();
565 }
566
570 if (IsPaused()) {
571 return;
572 }
573 _Pause(_ThisThread);
574 }
575
579 if (IsPaused()) {
580 return;
581 }
582 _Pause(_AllThreads);
583 }
584
586 void Unpause() {
587 if (IsPaused()) {
588 _Unpause();
589 }
590 }
591
593 bool IsPaused() const {
594 return _scope != _NotPaused;
595 }
596
597 private:
598 enum _Scope {
599 _NotPaused, _ThisThread, _AllThreads
600 };
601
602 // Only constructible via TfMallocTag factory functions.
603 explicit PauseControl(_Scope desired) {
604 if (desired != _NotPaused) {
605 _Pause(desired);
606 }
607 }
608
609 TF_API
610 void _Pause(_Scope desired);
611
612 TF_API
613 void _Unpause();
614
615 _ThreadData *_td = nullptr;
616 _Scope _scope = _NotPaused;
617
618 friend class TfMallocTag;
619 };
620
623 [[nodiscard]] static PauseControl PauseThisThread() {
624 return PauseControl(PauseControl::_ThisThread);
625 }
626
629 [[nodiscard]] static PauseControl PauseAllThreads() {
630 return PauseControl(PauseControl::_AllThreads);
631 }
632
635 [[nodiscard]] static PauseControl DeferredPause() {
636 return PauseControl(PauseControl::_NotPaused);
637 }
638
639
647 return ARCH_UNLIKELY(TfMallocTag::IsInitialized())
648 ? _GetCurrentStackState() : StackState {};
649 }
650
660 static void Push(const std::string& name) {
661 _Push(name.c_str());
662 }
663
665 static void Push(const char* name) {
666 _Push(name);
667 }
668
670 static void Push(TfEternalString name) {
671 _Push(_ImmortalName { name.c_str() });
672 }
673
683 static void Pop() {
685 _PopChecked();
686 }
687 }
688
706 TF_API static void SetDebugMatchList(const std::string& matchList);
707
729 TF_API static void
730 SetCapturedMallocStacksMatchList(const std::string& matchList);
731
737 TF_API static std::vector<std::vector<uintptr_t> > GetCapturedMallocStacks();
738
739private:
740#ifdef PXR_ARCH_SUPPORT_MALLOC_HOOKS
741 // Atomic initialization state -- valid transitions are from N -> N+1
742 // cyclically, and from _Initializing -> _NotInitialized if initialization
743 // fails.
744 enum _InitState {
745 _NotInitialized,
746 _Initializing,
747 _Initialized,
748 _ShuttingDown
749 };
750#endif
751
752 friend struct _TemporaryDisabler;
753
754 friend struct Tf_MallocGlobalData;
755
756 static inline _ThreadData *_Push(char const *name) {
758 return _Begin(name);
759 }
760 return nullptr;
761 }
762 static inline _ThreadData *_Push(_ImmortalName name) {
764 return _Begin(name);
765 }
766 return nullptr;
767 }
768
769 TF_API static _ThreadData *
770 _Begin(char const *name, _ThreadData *threadData = nullptr);
771
772 TF_API static _ThreadData *
773 _Begin(_ImmortalName name, _ThreadData *threadData = nullptr);
774
775 // The Auto/StackOverride exit path. `threadData` must be non-null and
776 // `nTags` must match the number of pushes that were actually performed;
777 // both hold by construction at the only call site (Auto::Release()).
778 TF_API static void _End(int nTags, _ThreadData *threadData);
779
780 // The manual Pop() path, which tolerates an unbalanced pop. Kept separate
781 // from _End() so that the Auto path pays nothing for the check.
782 TF_API static void _PopChecked();
783
784 TF_API static StackState _GetCurrentStackState();
785
786 static void* _MallocWrapper(size_t, const void*);
787 static void* _ReallocWrapper(void*, size_t, const void*);
788 static void* _MemalignWrapper(size_t, size_t, const void*);
789 static void _FreeWrapper(void*, const void*);
790
791 friend class TfMallocTag::Auto;
792 class Tls;
793 friend class TfMallocTag::Tls;
794#ifdef PXR_ARCH_SUPPORT_MALLOC_HOOKS
795 TF_API static std::atomic<_InitState> _initState;
796#endif
797};
798
801
804
836//
837PXR_NAMESPACE_CLOSE_SCOPE
838
839#define TF_MALLOC_TAG_NEW(...) \
840 /* this is for STL purposes */ \
841 ARCH_ALWAYS_INLINE inline void* operator new(::std::size_t, void* ptr) { \
842 return ptr; \
843 } \
844 \
845 ARCH_ALWAYS_INLINE inline void* operator new(::std::size_t s) { \
846 PXR_NS::TfAutoMallocTag tag(__VA_ARGS__); \
847 return ::operator new(s, std::nothrow); \
848 } \
849 \
850 ARCH_ALWAYS_INLINE inline void* operator new[](::std::size_t s) { \
851 PXR_NS::TfAutoMallocTag tag(__VA_ARGS__); \
852 return ::operator new[](s, std::nothrow); \
853 } \
854 \
855 /* Required due to the placement-new override above. */ \
856 ARCH_ALWAYS_INLINE inline void operator delete(void* ptr, void* place) {} \
857 \
858 ARCH_ALWAYS_INLINE inline void operator delete(void* ptr, size_t) { \
859 ::operator delete(ptr, std::nothrow); \
860 } \
861 \
862 ARCH_ALWAYS_INLINE inline void operator delete[] (void* ptr, size_t) { \
863 ::operator delete[](ptr, std::nothrow); \
864 } \
865
866#endif
An immutable, interned string whose character data is guaranteed to remain valid at a fixed address f...
Scoped (i.e.
Definition mallocTag.h:336
Auto(Str &&name1, Strs &&... nameN)
Push one or more memory tags onto the local-call stack with names name1 ... nameN.
Definition mallocTag.h:365
~Auto()
Pop a memory tag from the local-call stack.
Definition mallocTag.h:400
void Release()
Pop the tag from the stack before it is destructed.
Definition mallocTag.h:388
Debugging-oriented RAII object that pauses malloc tag collection for either the current thread or all...
void Unpause()
Resume collection. This PauseControl must currently be paused.
Definition mallocTag.h:586
void PauseThisThread()
Pause collection on the current thread.
Definition mallocTag.h:569
bool IsPaused() const
Return true if this PauseControl is currently paused.
Definition mallocTag.h:593
PauseControl & operator=(PauseControl &&other) noexcept
Move-assign from other, adopting its pause state and leaving it unpaused.
Definition mallocTag.h:551
~PauseControl()
Unpause if paused.
Definition mallocTag.h:563
PauseControl(PauseControl &&other) noexcept
Move-construct from other, adopting its pause state and leaving it unpaused.
Definition mallocTag.h:545
void PauseAllThreads()
Pause collection on all threads.
Definition mallocTag.h:578
An object that represents a snapshot of a thread's TfMallocTag stack state.
Definition mallocTag.h:469
Top-down memory tagging system.
Definition mallocTag.h:52
static void Push(const char *name)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition mallocTag.h:665
static void Push(TfEternalString name)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition mallocTag.h:670
static TF_API void SetDebugMatchList(const std::string &matchList)
Sets the tags to trap in the debugger.
size_t numAllocations
The number of allocations (always one unless stack frames have been combined to create unique stacks)...
Definition mallocTag.h:194
static StackState GetCurrentStackState()
Capture the current thread's TfMallocTag stack state and return it.
Definition mallocTag.h:646
static TF_API std::vector< std::vector< uintptr_t > > GetCapturedMallocStacks()
Returns the captured malloc stack traces for allocations billed to the malloc tags passed to SetCaptu...
static TF_API void SetCapturedMallocStacksMatchList(const std::string &matchList)
Sets the tags to trace.
static void Push(const std::string &name)
Manually push a tag onto the stack.
Definition mallocTag.h:660
std::vector< uintptr_t > stack
The stack frame pointers.
Definition mallocTag.h:186
size_t size
The amount of allocated memory (accumulated over all allocations sharing this call stack).
Definition mallocTag.h:190
static TF_API bool Initialize(std::string *errMsg=nullptr)
Initialize the memory tagging system.
static PauseControl PauseThisThread()
Return a PauseControl that immediately pauses collection for the calling thread.
Definition mallocTag.h:623
static TF_API size_t GetTotalBytes()
Return total number of allocated bytes.
static TF_API void Shutdown()
Shutdown the memory tagging system.
static PauseControl DeferredPause()
Return a PauseControl in the unpaused state, for later manual control via PauseThisThread() or PauseA...
Definition mallocTag.h:635
static bool IsInitialized()
Return true if the tagging system is active.
Definition mallocTag.h:229
static void Pop()
Manually pop a tag from the stack.
Definition mallocTag.h:683
static TF_API bool GetCallTree(CallTree *tree, bool skipRepeated=true)
Return a snapshot of memory usage.
static TF_API size_t GetMaxTotalBytes()
Return an estimate of the maximum total number of bytes that have ever been allocated at one time.
static PauseControl PauseAllThreads()
Return a PauseControl that immediately pauses collection for all threads.
Definition mallocTag.h:629
static TF_API void Clear()
Clear all recorded allocation events, in-flight events from all threads, and reset the total bytes an...
static TF_API std::string GetPerfStats(bool includePerThread=false)
Return a report of diagnostic information related to the performance of the TfMallocTag tracking inte...
This struct is used to represent a call stack taken for an allocation that was billed under a specifi...
Definition mallocTag.h:184
Stripped down version of diagnostic.h that doesn't define std::string.
Compiler hints.
STL namespace.
Summary data structure for malloc statistics.
Definition mallocTag.h:69
std::vector< PathNode > children
Children nodes.
Definition mallocTag.h:95
TF_API std::string GetPrettyPrintString(PrintSetting setting=BOTH, size_t maxPrintedNodes=100000) const
Return the malloc report string.
size_t nAllocations
The number of allocations for this node.
Definition mallocTag.h:92
std::string siteName
Tag name.
Definition mallocTag.h:93
std::vector< CallStackInfo > capturedCallStacks
The captured malloc stacks.
Definition mallocTag.h:177
size_t nBytes
Mem allocated including descendant nodes.
Definition mallocTag.h:90
TF_API void Report(std::ostream &out) const
Generates a report to the ostream out.
size_t nBytesDirect
Mem allocated excluding descendant nodes.
Definition mallocTag.h:91
std::string name
Tag name.
Definition mallocTag.h:107
PrintSetting
Specify which parts of the report to print.
Definition mallocTag.h:112
@ BOTH
Print both tree and call sites.
Definition mallocTag.h:115
@ TREE
Print the full call tree.
Definition mallocTag.h:113
@ CALLSITES
Print just the call sites > 0.1%.
Definition mallocTag.h:114
PathNode root
Root node of the call-site hierarchy.
Definition mallocTag.h:174
TF_API void Report(std::ostream &out, const std::string &rootName) const
Generates a report to the ostream out.
TF_API bool LoadReport(std::istream &in)
Load the contents of in into the root of the call tree.
std::vector< CallSite > callSites
All call sites.
Definition mallocTag.h:171
Record of the bytes allocated under each different tag.
Definition mallocTag.h:106
Node in the call tree structure.
Definition mallocTag.h:89