• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include <sys/syscall.h>
17 
18 #include "libpandabase/macros.h"
19 #include "os/thread.h"
20 #include "runtime/tooling/sampler/sampling_profiler.h"
21 #include "runtime/include/managed_thread.h"
22 #include "runtime/thread_manager.h"
23 #include "runtime/tooling/sampler/stack_walker_base.h"
24 #include "runtime/tooling/pt_thread_info.h"
25 #include "runtime/signal_handler.h"
26 #include "runtime/coroutines/coroutine.h"
27 
28 namespace panda::tooling::sampler {
29 
30 static std::atomic<int> g_sCurrentHandlersCounter = 0;
31 
32 /* static */
33 Sampler *Sampler::instance_ = nullptr;
34 
35 static std::atomic<size_t> g_sLostSamples = 0;
36 static std::atomic<size_t> g_sLostSegvSamples = 0;
37 static std::atomic<size_t> g_sLostInvalidSamples = 0;
38 static std::atomic<size_t> g_sLostNotFindSamples = 0;
39 static std::atomic<size_t> g_sTotalSamples = 0;
40 
41 class ScopedThreadSampling {
42 public:
ScopedThreadSampling(ThreadSamplingInfo * samplingInfo)43     explicit ScopedThreadSampling(ThreadSamplingInfo *samplingInfo) : samplingInfo_(samplingInfo)
44     {
45         ASSERT(samplingInfo_ != nullptr);
46         ASSERT(samplingInfo_->IsThreadSampling() == false);
47         samplingInfo_->SetThreadSampling(true);
48     }
49 
~ScopedThreadSampling()50     ~ScopedThreadSampling()
51     {
52         ASSERT(samplingInfo_->IsThreadSampling() == true);
53         samplingInfo_->SetThreadSampling(false);
54     }
55 
56 private:
57     ThreadSamplingInfo *samplingInfo_;
58 
59     NO_COPY_SEMANTIC(ScopedThreadSampling);
60     NO_MOVE_SEMANTIC(ScopedThreadSampling);
61 };
62 
63 class ScopedHandlersCounting {
64 public:
ScopedHandlersCounting()65     explicit ScopedHandlersCounting()
66     {
67         ++g_sCurrentHandlersCounter;
68     }
69 
~ScopedHandlersCounting()70     ~ScopedHandlersCounting()
71     {
72         --g_sCurrentHandlersCounter;
73     }
74 
75     NO_COPY_SEMANTIC(ScopedHandlersCounting);
76     NO_MOVE_SEMANTIC(ScopedHandlersCounting);
77 };
78 
79 /* static */
Create()80 Sampler *Sampler::Create()
81 {
82     /*
83      * Sampler can be created only once and managed by one thread
84      * Runtime::Tools owns it ptr after it's created
85      */
86     ASSERT(instance_ == nullptr);
87     instance_ = new Sampler;
88 
89     /**
90      * As soon as the sampler is created, we subscribe to the events
91      * This is done so that start and stop do not depend on the runtime
92      * Internal issue #13780
93      */
94     ASSERT(Runtime::GetCurrent() != nullptr);
95 
96     Runtime::GetCurrent()->GetNotificationManager()->AddListener(instance_,
97                                                                  RuntimeNotificationManager::Event::THREAD_EVENTS);
98     Runtime::GetCurrent()->GetNotificationManager()->AddListener(instance_,
99                                                                  RuntimeNotificationManager::Event::LOAD_MODULE);
100     /**
101      * Collect threads and modules which were created before sampler start
102      * If we collect them before add listeners then new thread can be created (or new module can be loaded)
103      * so we will lose this thread (or module)
104      */
105     instance_->CollectThreads();
106     instance_->CollectModules();
107 
108     return Sampler::instance_;
109 }
110 
111 /* static */
Destroy(Sampler * sampler)112 void Sampler::Destroy(Sampler *sampler)
113 {
114     ASSERT(instance_ != nullptr);
115     ASSERT(instance_ == sampler);
116     ASSERT(!sampler->isActive_);
117 
118     LOG(INFO, PROFILER) << "Total samples: " << g_sTotalSamples << "\nLost samples: " << g_sLostSamples;
119     LOG(INFO, PROFILER) << "Lost samples(Invalid method ptr): " << g_sLostInvalidSamples
120                         << "\nLost samples(Invalid pf ptr): " << g_sLostNotFindSamples;
121     LOG(INFO, PROFILER) << "Lost samples(SIGSEGV occured): " << g_sLostSegvSamples;
122 
123     Runtime::GetCurrent()->GetNotificationManager()->RemoveListener(instance_,
124                                                                     RuntimeNotificationManager::Event::THREAD_EVENTS);
125     Runtime::GetCurrent()->GetNotificationManager()->RemoveListener(instance_,
126                                                                     RuntimeNotificationManager::Event::LOAD_MODULE);
127 
128     instance_->ClearManagedThreadSet();
129     instance_->ClearLoadedPfs();
130 
131     delete sampler;
132     instance_ = nullptr;
133 }
134 
Sampler()135 Sampler::Sampler() : runtime_(Runtime::GetCurrent()), sampleInterval_(DEFAULT_SAMPLE_INTERVAL_US)
136 {
137     ASSERT_NATIVE_CODE();
138 }
139 
AddThreadHandle(ManagedThread * thread)140 void Sampler::AddThreadHandle(ManagedThread *thread)
141 {
142     os::memory::LockHolder holder(managedThreadsLock_);
143     managedThreads_.insert(thread->GetId());
144 }
145 
EraseThreadHandle(ManagedThread * thread)146 void Sampler::EraseThreadHandle(ManagedThread *thread)
147 {
148     os::memory::LockHolder holder(managedThreadsLock_);
149     managedThreads_.erase(thread->GetId());
150 }
151 
ThreadStart(ManagedThread * managedThread)152 void Sampler::ThreadStart(ManagedThread *managedThread)
153 {
154     AddThreadHandle(managedThread);
155 }
156 
ThreadEnd(ManagedThread * managedThread)157 void Sampler::ThreadEnd(ManagedThread *managedThread)
158 {
159     EraseThreadHandle(managedThread);
160 }
161 
LoadModule(std::string_view name)162 void Sampler::LoadModule(std::string_view name)
163 {
164     auto callback = [this, name](const panda_file::File &pf) {
165         if (pf.GetFilename() == name) {
166             auto ptrId = reinterpret_cast<uintptr_t>(&pf);
167             FileInfo pfModule;
168             pfModule.ptr = ptrId;
169             pfModule.pathname = pf.GetFullFileName();
170             pfModule.checksum = pf.GetHeader()->checksum;
171             if (!loadedPfsQueue_.FindValue(ptrId)) {
172                 loadedPfsQueue_.Push(pfModule);
173             }
174             os::memory::LockHolder holder(loadedPfsLock_);
175             this->loadedPfs_.push_back(pfModule);
176             return false;
177         }
178         return true;
179     };
180     runtime_->GetClassLinker()->EnumeratePandaFiles(callback, false);
181 }
182 
Start(const char * filename)183 bool Sampler::Start(const char *filename)
184 {
185     if (isActive_) {
186         LOG(ERROR, PROFILER) << "Attemp to start sampling profiler while it's already started";
187         return false;
188     }
189 
190     if (UNLIKELY(!communicator_.Init())) {
191         LOG(ERROR, PROFILER) << "Failed to create pipes for sampling listener. Profiler cannot be started";
192         return false;
193     }
194 
195     isActive_ = true;
196     // Creating std::string instead of sending pointer to avoid UB stack-use-after-scope
197     listenerThread_ = std::make_unique<std::thread>(&Sampler::ListenerThreadEntry, this, std::string(filename));
198     listenerTid_ = listenerThread_->native_handle();
199 
200     // All prepairing actions should be done before this thread is started
201     samplerThread_ = std::make_unique<std::thread>(&Sampler::SamplerThreadEntry, this);
202     samplerTid_ = samplerThread_->native_handle();
203 
204     return true;
205 }
206 
Stop()207 void Sampler::Stop()
208 {
209     if (!isActive_) {
210         LOG(ERROR, PROFILER) << "Attemp to stop sampling profiler, but it was not started";
211         return;
212     }
213     if (!samplerThread_->joinable()) {
214         LOG(FATAL, PROFILER) << "Sampling profiler thread unexpectedly disappeared";
215         UNREACHABLE();
216     }
217     if (!listenerThread_->joinable()) {
218         LOG(FATAL, PROFILER) << "Listener profiler thread unexpectedly disappeared";
219         UNREACHABLE();
220     }
221 
222     isActive_ = false;
223     samplerThread_->join();
224     listenerThread_->join();
225 
226     // After threads are stopped we can clear all sampler info
227     samplerThread_.reset();
228     listenerThread_.reset();
229     samplerTid_ = 0;
230     listenerTid_ = 0;
231 }
232 
WriteLoadedPandaFiles(StreamWriter * writerPtr)233 void Sampler::WriteLoadedPandaFiles(StreamWriter *writerPtr)
234 {
235     os::memory::LockHolder holder(loadedPfsLock_);
236     if (LIKELY(loadedPfs_.empty())) {
237         return;
238     }
239     for (const auto &module : loadedPfs_) {
240         if (!writerPtr->IsModuleWritten(module)) {
241             writerPtr->WriteModule(module);
242         }
243     }
244     loadedPfs_.clear();
245 }
246 
CollectThreads()247 void Sampler::CollectThreads()
248 {
249     auto tManager = runtime_->GetPandaVM()->GetThreadManager();
250     if (UNLIKELY(tManager == nullptr)) {
251         // NOTE(m.strizhak): make it for languages without thread_manager
252         LOG(FATAL, PROFILER) << "Thread manager is nullptr";
253         UNREACHABLE();
254     }
255 
256     tManager->EnumerateThreads(
257         [this](ManagedThread *thread) {
258             AddThreadHandle(thread);
259             return true;
260         },
261         static_cast<unsigned int>(EnumerationFlag::ALL), static_cast<unsigned int>(EnumerationFlag::VM_THREAD));
262 }
263 
CollectModules()264 void Sampler::CollectModules()
265 {
266     auto callback = [this](const panda_file::File &pf) {
267         auto ptrId = reinterpret_cast<uintptr_t>(&pf);
268         FileInfo pfModule;
269 
270         pfModule.ptr = ptrId;
271         pfModule.pathname = pf.GetFullFileName();
272         pfModule.checksum = pf.GetHeader()->checksum;
273 
274         if (!loadedPfsQueue_.FindValue(ptrId)) {
275             loadedPfsQueue_.Push(pfModule);
276         }
277 
278         os::memory::LockHolder holder(loadedPfsLock_);
279         this->loadedPfs_.push_back(pfModule);
280 
281         return true;
282     };
283     runtime_->GetClassLinker()->EnumeratePandaFiles(callback, false);
284 }
285 
SigProfSamplingProfilerHandler(int signum,siginfo_t * siginfo,void * ptr)286 void SigProfSamplingProfilerHandler([[maybe_unused]] int signum, [[maybe_unused]] siginfo_t *siginfo,
287                                     [[maybe_unused]] void *ptr)
288 {
289     if (g_sCurrentHandlersCounter == 0) {
290         // Sampling ended if S_CURRENT_HANDLERS_COUNTER is 0. Thread started executing handler for signal
291         // that was sent before end, so thread is late now and we should return from handler
292         return;
293     }
294     auto scopedHandlersCounting = ScopedHandlersCounting();
295 
296     ManagedThread *mthread = ManagedThread::GetCurrent();
297     ASSERT(mthread != nullptr);
298 
299     // Checking that code is being executed
300     auto framePtr = reinterpret_cast<CFrame::SlotType *>(mthread->GetCurrentFrame());
301     if (framePtr == nullptr) {
302         return;
303     }
304 
305     auto frame = mthread->GetCurrentFrame();
306     bool isCompiled = mthread->IsCurrentFrameCompiled();
307     auto threadStatus = mthread->GetStatus();
308 
309     bool isCoroutineRunning = false;
310     if (Coroutine::ThreadIsCoroutine(mthread)) {
311         isCoroutineRunning = Coroutine::CastFromThread(mthread)->GetCoroutineStatus() == Coroutine::Status::RUNNING;
312     }
313 
314     Method *topMethod = frame->GetMethod();
315 
316     g_sTotalSamples++;
317 
318     const LockFreeQueue &pfsQueue = Sampler::GetSampleQueuePF();
319 
320     SampleInfo sample {};
321     // Volatile because we don't need to optimize this variable to be able to use setjmp without clobbering
322     // Optimized variables may end up with incorrect value as a consequence of a longjmp() operation
323     volatile size_t stackCounter = 0;
324 
325     ScopedThreadSampling scopedThreadSampling(mthread->GetPtThreadInfo()->GetSamplingInfo());
326 
327     // NOLINTNEXTLINE(cert-err52-cpp)
328     if (setjmp(mthread->GetPtThreadInfo()->GetSamplingInfo()->GetSigSegvJmpEnv()) != 0) {
329         // This code executed after longjmp()
330         // In case of SIGSEGV we lose the sample
331         g_sLostSamples++;
332         g_sLostSegvSamples++;
333         return;
334     }
335 
336     if (threadStatus == ThreadStatus::RUNNING) {
337         sample.threadInfo.threadStatus = SampleInfo::ThreadStatus::RUNNING;
338     } else if (threadStatus == ThreadStatus::NATIVE && isCoroutineRunning) {
339         sample.threadInfo.threadStatus = SampleInfo::ThreadStatus::RUNNING;
340     } else {
341         sample.threadInfo.threadStatus = SampleInfo::ThreadStatus::SUSPENDED;
342     }
343 
344     if (StackWalkerBase::IsMethodInBoundaryFrame(topMethod)) {
345         bool isFrameBoundary = true;
346         while (isFrameBoundary) {
347             Method *method = frame->GetMethod();
348             Frame *prev = frame->GetPrevFrame();
349 
350             if (StackWalkerBase::IsMethodInI2CFrame(method)) {
351                 sample.stackInfo.managedStack[stackCounter].pandaFilePtr = helpers::ToUnderlying(FrameKind::BRIDGE);
352                 sample.stackInfo.managedStack[stackCounter].fileId = helpers::ToUnderlying(FrameKind::BRIDGE);
353                 ++stackCounter;
354 
355                 frame = prev;
356                 isCompiled = false;
357             } else if (StackWalkerBase::IsMethodInC2IFrame(method)) {
358                 sample.stackInfo.managedStack[stackCounter].pandaFilePtr = helpers::ToUnderlying(FrameKind::BRIDGE);
359                 sample.stackInfo.managedStack[stackCounter].fileId = helpers::ToUnderlying(FrameKind::BRIDGE);
360                 ++stackCounter;
361 
362                 frame = prev;
363                 isCompiled = true;
364             } else if (StackWalkerBase::IsMethodInBPFrame(method)) {
365                 g_sLostSamples++;
366                 return;
367             } else {
368                 isFrameBoundary = false;
369             }
370         }
371     } else if (isCompiled) {
372         auto signalContext = SignalContext(ptr);
373         auto pc = signalContext.GetPC();
374         auto fp = signalContext.GetFP();
375         bool pcInCompiled = InAllocatedCodeRange(pc);
376         CFrame cframe(frame);
377         bool isNative = cframe.IsNative();
378         if (!isNative && fp == nullptr) {
379             sample.stackInfo.managedStack[stackCounter].pandaFilePtr = helpers::ToUnderlying(FrameKind::BRIDGE);
380             sample.stackInfo.managedStack[stackCounter].fileId = helpers::ToUnderlying(FrameKind::BRIDGE);
381             ++stackCounter;
382 
383             // fp is not set yet, so cframe not finished, currently in bridge, previous frame iframe
384             isCompiled = false;
385         } else if (!isNative && fp != nullptr) {
386             auto pfId = reinterpret_cast<uintptr_t>(frame->GetMethod()->GetPandaFile());
387             if (pcInCompiled) {
388                 // Currently in compiled method so get it from fp
389                 frame = reinterpret_cast<Frame *>(fp);
390             } else if (!pcInCompiled && pfsQueue.FindValue(pfId)) {
391                 sample.stackInfo.managedStack[stackCounter].pandaFilePtr = helpers::ToUnderlying(FrameKind::BRIDGE);
392                 sample.stackInfo.managedStack[stackCounter].fileId = helpers::ToUnderlying(FrameKind::BRIDGE);
393                 ++stackCounter;
394 
395                 // pc not in jitted code, so fp is not up-to-date, currently not in cfame
396                 isCompiled = false;
397             }
398         }
399     }
400 
401     auto stackWalker = StackWalkerBase(frame, isCompiled);
402 
403     while (stackWalker.HasFrame()) {
404         auto method = stackWalker.GetMethod();
405 
406         if (method == nullptr || IsInvalidPointer(reinterpret_cast<uintptr_t>(method))) {
407             g_sLostSamples++;
408             g_sLostInvalidSamples++;
409             return;
410         }
411 
412         auto pf = method->GetPandaFile();
413         auto pfId = reinterpret_cast<uintptr_t>(pf);
414         if (!pfsQueue.FindValue(pfId)) {
415             g_sLostSamples++;
416             g_sLostNotFindSamples++;
417             return;
418         }
419 
420         sample.stackInfo.managedStack[stackCounter].pandaFilePtr = pfId;
421         sample.stackInfo.managedStack[stackCounter].fileId = method->GetFileId().GetOffset();
422 
423         ++stackCounter;
424         stackWalker.NextFrame();
425 
426         if (stackCounter == SampleInfo::StackInfo::MAX_STACK_DEPTH) {
427             // According to the limitations we should drop all frames that is higher than MAX_STACK_DEPTH
428             break;
429         }
430     }
431     if (stackCounter == 0) {
432         return;
433     }
434     sample.stackInfo.managedStackSize = stackCounter;
435     sample.threadInfo.threadId = os::thread::GetCurrentThreadId();
436 
437     const ThreadCommunicator &communicator = Sampler::GetSampleCommunicator();
438     communicator.SendSample(sample);
439 }
440 
SamplerThreadEntry()441 void Sampler::SamplerThreadEntry()
442 {
443     struct sigaction action {};
444     action.sa_sigaction = &SigProfSamplingProfilerHandler;
445     action.sa_flags = SA_SIGINFO | SA_ONSTACK;
446     // Clear signal set
447     sigemptyset(&action.sa_mask);
448     // Ignore incoming sigprof if handler isn't completed
449     sigaddset(&action.sa_mask, SIGPROF);
450 
451     struct sigaction oldAction {};
452 
453     if (sigaction(SIGPROF, &action, &oldAction) == -1) {
454         LOG(FATAL, PROFILER) << "Sigaction failed, can't start profiling";
455         UNREACHABLE();
456     }
457 
458     // We keep handler assigned to SigProfSamplingProfilerHandler after sampling end because
459     // otherwice deadlock can happen if signal will be slow and reach thread after handler resignation
460     if (oldAction.sa_sigaction != nullptr && oldAction.sa_sigaction != SigProfSamplingProfilerHandler) {
461         LOG(FATAL, PROFILER) << "SIGPROF signal handler was overriden in sampling profiler";
462         UNREACHABLE();
463     }
464     ++g_sCurrentHandlersCounter;
465 
466     auto pid = getpid();
467     // Atomic with relaxed order reason: data race with is_active_
468     while (isActive_.load(std::memory_order_relaxed)) {
469         {
470             os::memory::LockHolder holder(managedThreadsLock_);
471             for (const auto &threadId : managedThreads_) {
472                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
473                 if (syscall(SYS_tgkill, pid, threadId, SIGPROF) != 0) {
474                     LOG(ERROR, PROFILER) << "Can't send signal to thread";
475                 }
476             }
477         }
478         os::thread::NativeSleepUS(sampleInterval_);
479     }
480 
481     // Sending last sample on finish to avoid of deadlock in listener
482     SampleInfo lastSample;
483     lastSample.stackInfo.managedStackSize = 0;
484     communicator_.SendSample(lastSample);
485 
486     --g_sCurrentHandlersCounter;
487 
488     const unsigned int timeToSleepMs = 100;
489     do {
490         os::thread::NativeSleep(timeToSleepMs);
491     } while (g_sCurrentHandlersCounter != 0);
492 }
493 
494 // Passing std:string copy instead of reference, 'cause another thread owns this object
495 // NOLINTNEXTLINE(performance-unnecessary-value-param)
ListenerThreadEntry(std::string outputFile)496 void Sampler::ListenerThreadEntry(std::string outputFile)
497 {
498     auto writerPtr = std::make_unique<StreamWriter>(outputFile.c_str());
499     // Writing panda files that were loaded before sampler was created
500     WriteLoadedPandaFiles(writerPtr.get());
501 
502     SampleInfo bufferSample;
503     // Atomic with relaxed order reason: data race with is_active_
504     while (isActive_.load(std::memory_order_relaxed)) {
505         WriteLoadedPandaFiles(writerPtr.get());
506         communicator_.ReadSample(&bufferSample);
507         if (LIKELY(bufferSample.stackInfo.managedStackSize != 0)) {
508             writerPtr->WriteSample(bufferSample);
509         }
510     }
511     // Writing all remaining samples
512     while (!communicator_.IsPipeEmpty()) {
513         WriteLoadedPandaFiles(writerPtr.get());
514         communicator_.ReadSample(&bufferSample);
515         if (LIKELY(bufferSample.stackInfo.managedStackSize != 0)) {
516             writerPtr->WriteSample(bufferSample);
517         }
518     }
519 }
520 
521 }  // namespace panda::tooling::sampler
522