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