33#include < contrib/libs/tcmalloc/tcmalloc/malloc_extension.h>
44
55#include < ydb/library/actors/prof/tag.h>
6- #include < library/cpp/cache/cache .h>
6+ #include < ydb/core/mon/mon .h>
77
8+ #include < library/cpp/cache/cache.h>
89#if defined(USE_DWARF_BACKTRACE)
910# include < library/cpp/dwarf_backtrace/backtrace.h>
1011#endif
11-
1212#include < library/cpp/html/pcdata/pcdata.h>
1313#include < library/cpp/monlib/service/pages/templates.h>
1414
15- #include < ydb/core/mon/mon.h>
16-
1715#include < util/stream/format.h>
1816
17+ #include < thread>
18+
1919using namespace NActors ;
2020
2121namespace NKikimr {
@@ -478,6 +478,91 @@ class TTcMallocState : public IAllocState {
478478};
479479
480480
481+ void HandleTcMallocSoftLimit ();
482+
483+ class TTcMallocLimitHandler : public TSingletonTraits <TTcMallocLimitHandler> {
484+ public:
485+ Y_DECLARE_SINGLETON_FRIEND ();
486+
487+ ~TTcMallocLimitHandler () {
488+ if (Thread_.joinable ()) {
489+ {
490+ std::unique_lock<std::mutex> lock (Mutex_);
491+ JustQuit_ = true ;
492+ }
493+ Fire ();
494+ Thread_.join ();
495+ }
496+ }
497+
498+ void SetOutputStream (IOutputStream& out) {
499+ Out_ = &out;
500+ }
501+
502+ void Fire () {
503+ std::unique_lock<std::mutex> lock (Mutex_);
504+ Fired_ = true ;
505+ CV_.notify_all ();
506+ }
507+
508+ private:
509+ TTcMallocLimitHandler () {
510+ tcmalloc::MallocExtension::EnableForkSupport ();
511+ tcmalloc::MallocExtension::SetSoftMemoryLimitHandler (&HandleTcMallocSoftLimit);
512+ Thread_ = std::thread (&TTcMallocLimitHandler::Handle, this );
513+ }
514+
515+ private:
516+ std::mutex Mutex_;
517+ bool Fired_ = false ; // protected by Mutex_
518+ bool JustQuit_ = false ; // protected by Mutex_
519+ std::condition_variable CV_; // protected by Mutex_
520+
521+ IOutputStream* Out_ = &Cerr;
522+ std::thread Thread_;
523+
524+ void Handle () {
525+ std::unique_lock<std::mutex> lock (Mutex_);
526+ CV_.wait (lock, [&] {
527+ return Fired_;
528+ });
529+
530+ if (JustQuit_) {
531+ return ;
532+ }
533+
534+ *Out_ << tcmalloc::MallocExtension::GetStats () << Endl;
535+
536+ if (auto childPid = fork (); childPid == 0 ) {
537+ kill (getppid (), SIGSTOP);
538+
539+ *Out_ << " Child: " << getpid () << " , parent process stopped: " << getppid () << Endl;
540+
541+ try {
542+ auto profile = tcmalloc::MallocExtension::SnapshotCurrent (tcmalloc::ProfileType::kHeap );
543+ TAllocationAnalyzer analyzer (std::move (profile));
544+ TAllocationStats allocationStats;
545+ analyzer.Prepare (&allocationStats);
546+ analyzer.Dump (*Out_, 256 , 1024 , true , true );
547+ } catch (...) {
548+ kill (getppid (), SIGCONT);
549+ throw ;
550+ }
551+
552+ kill (getppid (), SIGCONT);
553+ } else if (childPid < 0 ) {
554+ *Out_ << " Failed to dump current heap: fork failed" << Endl;
555+ }
556+
557+ // TODO: probably should wait for child, but we're going to OOM anyway.
558+ }
559+ };
560+
561+ void HandleTcMallocSoftLimit () {
562+ Singleton<TTcMallocLimitHandler>()->Fire ();
563+ }
564+
565+
481566class TTcMallocMonitor : public IAllocMonitor {
482567 TDynamicCountersPtr CounterGroup;
483568
@@ -694,6 +779,11 @@ class TTcMallocMonitor : public IAllocMonitor {
694779
695780 CountHistogram = CounterGroup->GetHistogram (" tcmalloc.sampled_count" ,
696781 NMonitoring::ExponentialHistogram (TAllocationStats::MaxSizeIndex, 2 , 1 ), false );
782+
783+ #ifdef PROFILE_MEMORY_ALLOCATIONS
784+ // Setup tcmalloc soft limit handling
785+ Singleton<TTcMallocLimitHandler>();
786+ #endif
697787 }
698788
699789 void RegisterPages (TMon* mon, TActorSystem* actorSystem, TActorId actorId) override {
@@ -807,6 +897,7 @@ class TTcMallocProfiler : public IProfilerLogic {
807897 }
808898};
809899
900+ // Public functions
810901
811902std::unique_ptr<IAllocStats> CreateTcMallocStats (TDynamicCountersPtr group) {
812903 return std::make_unique<TTcMallocStats>(std::move (group));
@@ -824,4 +915,4 @@ std::unique_ptr<IProfilerLogic> CreateTcMallocProfiler() {
824915 return std::make_unique<TTcMallocProfiler>();
825916}
826917
827- }
918+ } // namespace NKikimr
0 commit comments