1 /**
2 * Copyright (c) 2021-2025 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 #include <atomic>
18
19 #include "coroutines/coroutine_stats.h"
20 #include "libpandabase/macros.h"
21 #include "os/thread.h"
22 #include "runtime/tooling/sampler/sampling_profiler.h"
23 #include "runtime/include/managed_thread.h"
24 #include "runtime/thread_manager.h"
25 #include "runtime/tooling/sampler/stack_walker_base.h"
26 #include "runtime/tooling/pt_thread_info.h"
27 #include "runtime/signal_handler.h"
28 #include "runtime/coroutines/coroutine.h"
29
30 namespace ark::tooling::sampler {
31
32 static std::atomic<int> g_sCurrentHandlersCounter = 0;
33
34 /* static */
35 Sampler *Sampler::instance_ = nullptr;
36
37 static std::atomic<size_t> g_sLostSamples = 0;
38 static std::atomic<size_t> g_sLostSegvSamples = 0;
39 static std::atomic<size_t> g_sLostInvalidSamples = 0;
40 static std::atomic<size_t> g_sLostNotFindSamples = 0;
41 static std::atomic<size_t> g_sTotalSamples = 0;
42
43 class ScopedThreadSampling {
44 public:
ScopedThreadSampling(ThreadSamplingInfo * samplingInfo)45 explicit ScopedThreadSampling(ThreadSamplingInfo *samplingInfo) : samplingInfo_(samplingInfo)
46 {
47 ASSERT(samplingInfo_ != nullptr);
48 ASSERT(samplingInfo_->IsThreadSampling() == false);
49 samplingInfo_->SetThreadSampling(true);
50 }
51
~ScopedThreadSampling()52 ~ScopedThreadSampling()
53 {
54 ASSERT(samplingInfo_->IsThreadSampling() == true);
55 samplingInfo_->SetThreadSampling(false);
56 }
57
58 private:
59 ThreadSamplingInfo *samplingInfo_;
60
61 NO_COPY_SEMANTIC(ScopedThreadSampling);
62 NO_MOVE_SEMANTIC(ScopedThreadSampling);
63 };
64
65 class ScopedHandlersCounting {
66 public:
ScopedHandlersCounting()67 explicit ScopedHandlersCounting()
68 {
69 ++g_sCurrentHandlersCounter;
70 }
71
~ScopedHandlersCounting()72 ~ScopedHandlersCounting()
73 {
74 --g_sCurrentHandlersCounter;
75 }
76
77 NO_COPY_SEMANTIC(ScopedHandlersCounting);
78 NO_MOVE_SEMANTIC(ScopedHandlersCounting);
79 };
80
81 /* static */
Create()82 Sampler *Sampler::Create()
83 {
84 /*
85 * Sampler can be created only once and managed by one thread
86 * Runtime::Tools owns it ptr after it's created
87 */
88 ASSERT(instance_ == nullptr);
89 instance_ = new Sampler;
90
91 /**
92 * As soon as the sampler is created, we subscribe to the events
93 * This is done so that start and stop do not depend on the runtime
94 * Internal issue #13780
95 */
96 ASSERT(Runtime::GetCurrent() != nullptr);
97
98 Runtime::GetCurrent()->GetNotificationManager()->AddListener(instance_,
99 RuntimeNotificationManager::Event::THREAD_EVENTS);
100 Runtime::GetCurrent()->GetNotificationManager()->AddListener(instance_,
101 RuntimeNotificationManager::Event::LOAD_MODULE);
102 /**
103 * Collect threads and modules which were created before sampler start
104 * If we collect them before add listeners then new thread can be created (or new module can be loaded)
105 * so we will lose this thread (or module)
106 */
107 instance_->CollectThreads();
108 instance_->CollectModules();
109
110 return Sampler::instance_;
111 }
112
LogProfilerStats()113 static void LogProfilerStats()
114 {
115 LOG(INFO, PROFILER) << "Total samples: " << g_sTotalSamples;
116 LOG(INFO, PROFILER) << "Lost samples: " << g_sLostSamples;
117 LOG(INFO, PROFILER) << "Lost samples(Invalid method ptr): " << g_sLostInvalidSamples;
118 LOG(INFO, PROFILER) << "Lost samples(Invalid pf ptr): " << g_sLostNotFindSamples;
119 LOG(INFO, PROFILER) << "Lost samples(SIGSEGV occured): " << g_sLostSegvSamples;
120 }
121
122 /* static */
Destroy(Sampler * sampler)123 void Sampler::Destroy(Sampler *sampler)
124 {
125 ASSERT(instance_ != nullptr);
126 ASSERT(instance_ == sampler);
127
128 // Atomic with acquire order reason: To ensure start/stop load correctly
129 ASSERT(!sampler->isActive_.load(std::memory_order_acquire));
130
131 Runtime::GetCurrent()->GetNotificationManager()->RemoveListener(instance_,
132 RuntimeNotificationManager::Event::THREAD_EVENTS);
133 Runtime::GetCurrent()->GetNotificationManager()->RemoveListener(instance_,
134 RuntimeNotificationManager::Event::LOAD_MODULE);
135
136 instance_->ClearManagedThreadSet();
137 instance_->ClearLoadedPfs();
138
139 delete sampler;
140 instance_ = nullptr;
141
142 LOG(INFO, PROFILER) << "Sampling profiler destroyed";
143 LogProfilerStats();
144 }
145
Sampler()146 Sampler::Sampler() : runtime_(Runtime::GetCurrent()), sampleInterval_(DEFAULT_SAMPLE_INTERVAL_US)
147 {
148 ASSERT_NATIVE_CODE();
149 }
150
AddThreadHandle(ManagedThread * thread)151 void Sampler::AddThreadHandle(ManagedThread *thread)
152 {
153 os::memory::LockHolder holder(managedThreadsLock_);
154 managedThreads_.insert(thread->GetId());
155 }
156
EraseThreadHandle(ManagedThread * thread)157 void Sampler::EraseThreadHandle(ManagedThread *thread)
158 {
159 os::memory::LockHolder holder(managedThreadsLock_);
160 managedThreads_.erase(thread->GetId());
161 }
162
ThreadStart(ManagedThread * managedThread)163 void Sampler::ThreadStart(ManagedThread *managedThread)
164 {
165 AddThreadHandle(managedThread);
166 }
167
ThreadEnd(ManagedThread * managedThread)168 void Sampler::ThreadEnd(ManagedThread *managedThread)
169 {
170 EraseThreadHandle(managedThread);
171 }
172
LoadModule(std::string_view name)173 void Sampler::LoadModule(std::string_view name)
174 {
175 auto callback = [this, name](const panda_file::File &pf) {
176 if (pf.GetFilename() == name) {
177 auto ptrId = reinterpret_cast<uintptr_t>(&pf);
178 FileInfo pfModule;
179 pfModule.ptr = ptrId;
180 pfModule.pathname = pf.GetFullFileName();
181 pfModule.checksum = pf.GetHeader()->checksum;
182 if (!loadedPfsQueue_.FindValue(ptrId)) {
183 loadedPfsQueue_.Push(pfModule);
184 }
185 os::memory::LockHolder holder(loadedPfsLock_);
186 this->loadedPfs_.push_back(pfModule);
187 return false;
188 }
189 return true;
190 };
191 runtime_->GetClassLinker()->EnumeratePandaFiles(callback, false);
192 }
193
Start(std::unique_ptr<StreamWriter> && writer)194 bool Sampler::Start(std::unique_ptr<StreamWriter> &&writer)
195 {
196 // Atomic with acquire order reason: To ensure start/stop load correctly
197 if (isActive_.load(std::memory_order_acquire)) {
198 LOG(ERROR, PROFILER) << "Attemp to start sampling profiler while it's already started";
199 return false;
200 }
201
202 if (UNLIKELY(!communicator_.Init())) {
203 LOG(ERROR, PROFILER) << "Failed to create pipes for sampling listener. Profiler cannot be started";
204 return false;
205 }
206
207 // Atomic with release order reason: To ensure start store correctly
208 isActive_.store(true, std::memory_order_release);
209
210 listenerThread_ = std::make_unique<std::thread>(&Sampler::ListenerThreadEntry, this, std::move(writer));
211 listenerTid_ = listenerThread_->native_handle();
212
213 // All prepairing actions should be done before this thread is started
214 samplerThread_ = std::make_unique<std::thread>(&Sampler::SamplerThreadEntry, this);
215 samplerTid_ = samplerThread_->native_handle();
216 LOG(INFO, PROFILER) << "Sampling profiler started";
217 return true;
218 }
219
Stop()220 bool Sampler::Stop()
221 {
222 // Atomic with acquire order reason: To ensure start/stop load correctly
223 if (!isActive_.load(std::memory_order_acquire)) {
224 LOG(ERROR, PROFILER) << "Attemp to stop sampling profiler, but it was not started";
225 return false;
226 }
227 if (!samplerThread_->joinable()) {
228 LOG(FATAL, PROFILER) << "Sampling profiler thread unexpectedly disappeared";
229 UNREACHABLE();
230 }
231 if (!listenerThread_->joinable()) {
232 LOG(FATAL, PROFILER) << "Listener profiler thread unexpectedly disappeared";
233 UNREACHABLE();
234 }
235
236 // Atomic with release order reason: To ensure stop store correctly
237 isActive_.store(false, std::memory_order_release);
238 samplerThread_->join();
239 listenerThread_->join();
240
241 // After threads are stopped we can clear all sampler info
242 samplerThread_.reset();
243 listenerThread_.reset();
244 samplerTid_ = 0;
245 listenerTid_ = 0;
246
247 LOG(INFO, PROFILER) << "Sampling profiler stopped";
248 LogProfilerStats();
249 return true;
250 }
251
WriteLoadedPandaFiles(StreamWriter * writerPtr)252 void Sampler::WriteLoadedPandaFiles(StreamWriter *writerPtr)
253 {
254 os::memory::LockHolder holder(loadedPfsLock_);
255 if (LIKELY(loadedPfs_.empty())) {
256 return;
257 }
258 for (const auto &module : loadedPfs_) {
259 if (!writerPtr->IsModuleWritten(module)) {
260 writerPtr->WriteModule(module);
261 }
262 }
263 loadedPfs_.clear();
264 }
265
CollectThreads()266 void Sampler::CollectThreads()
267 {
268 auto tManager = runtime_->GetPandaVM()->GetThreadManager();
269 if (UNLIKELY(tManager == nullptr)) {
270 // NOTE(m.strizhak): make it for languages without thread_manager
271 LOG(FATAL, PROFILER) << "Thread manager is nullptr";
272 UNREACHABLE();
273 }
274
275 tManager->EnumerateThreads(
276 [this](ManagedThread *thread) {
277 AddThreadHandle(thread);
278 return true;
279 },
280 static_cast<unsigned int>(EnumerationFlag::ALL), static_cast<unsigned int>(EnumerationFlag::VM_THREAD));
281 }
282
CollectModules()283 void Sampler::CollectModules()
284 {
285 auto callback = [this](const panda_file::File &pf) {
286 auto ptrId = reinterpret_cast<uintptr_t>(&pf);
287 FileInfo pfModule;
288
289 pfModule.ptr = ptrId;
290 pfModule.pathname = pf.GetFullFileName();
291 pfModule.checksum = pf.GetHeader()->checksum;
292
293 if (!loadedPfsQueue_.FindValue(ptrId)) {
294 loadedPfsQueue_.Push(pfModule);
295 }
296
297 os::memory::LockHolder holder(loadedPfsLock_);
298 this->loadedPfs_.push_back(pfModule);
299
300 return true;
301 };
302 runtime_->GetClassLinker()->EnumeratePandaFiles(callback, false);
303 }
304
GetThreadStatus(ManagedThread * mthread)305 static SampleInfo::ThreadStatus GetThreadStatus(ManagedThread *mthread)
306 {
307 ASSERT(mthread != nullptr);
308
309 auto threadStatus = mthread->GetStatus();
310 if (threadStatus == ThreadStatus::RUNNING) {
311 return SampleInfo::ThreadStatus::RUNNING;
312 }
313
314 bool isCoroutineRunning = false;
315 if (Coroutine::ThreadIsCoroutine(mthread)) {
316 isCoroutineRunning = Coroutine::CastFromThread(mthread)->GetCoroutineStatus() == Coroutine::Status::RUNNING;
317 }
318 if (threadStatus == ThreadStatus::NATIVE && isCoroutineRunning) {
319 return SampleInfo::ThreadStatus::RUNNING;
320 }
321
322 return SampleInfo::ThreadStatus::SUSPENDED;
323 }
324
325 struct SamplerFrameInfo {
326 Frame *frame;
327 bool isCompiled;
328 };
329
330 /**
331 * @brief Collects samples from boundary frames.
332 * @returns true if bypass frame was found, false otherwise.
333 */
CollectBoundaryFrames(SamplerFrameInfo & frameInfo,SampleInfo & sample,size_t & stackCounter)334 static bool CollectBoundaryFrames(SamplerFrameInfo &frameInfo, SampleInfo &sample, size_t &stackCounter)
335 {
336 ASSERT(frameInfo.frame != nullptr);
337
338 bool isFrameBoundary = true;
339 while (isFrameBoundary) {
340 auto *prevFrame = frameInfo.frame->GetPrevFrame();
341 const auto *method = frameInfo.frame->GetMethod();
342 if (StackWalkerBase::IsMethodInI2CFrame(method)) {
343 sample.stackInfo.managedStack[stackCounter].pandaFilePtr = helpers::ToUnderlying(FrameKind::BRIDGE);
344 sample.stackInfo.managedStack[stackCounter].fileId = helpers::ToUnderlying(FrameKind::BRIDGE);
345 ++stackCounter;
346
347 frameInfo.frame = prevFrame;
348 frameInfo.isCompiled = false;
349 } else if (StackWalkerBase::IsMethodInC2IFrame(method)) {
350 sample.stackInfo.managedStack[stackCounter].pandaFilePtr = helpers::ToUnderlying(FrameKind::BRIDGE);
351 sample.stackInfo.managedStack[stackCounter].fileId = helpers::ToUnderlying(FrameKind::BRIDGE);
352 ++stackCounter;
353
354 frameInfo.frame = prevFrame;
355 frameInfo.isCompiled = true;
356 } else if (StackWalkerBase::IsMethodInBPFrame(method)) {
357 g_sLostSamples++;
358 return true;
359 } else {
360 isFrameBoundary = false;
361 }
362 }
363 return false;
364 }
365
ProcessCompiledTopFrame(SamplerFrameInfo & frameInfo,SampleInfo & sample,size_t & stackCounter,void * signalContextPtr)366 static void ProcessCompiledTopFrame(SamplerFrameInfo &frameInfo, SampleInfo &sample, size_t &stackCounter,
367 void *signalContextPtr)
368 {
369 CFrame cframe(frameInfo.frame);
370 if (cframe.IsNative()) {
371 return;
372 }
373
374 auto signalContext = SignalContext(signalContextPtr);
375 auto fp = signalContext.GetFP();
376 if (fp == nullptr) {
377 sample.stackInfo.managedStack[stackCounter].pandaFilePtr = helpers::ToUnderlying(FrameKind::BRIDGE);
378 sample.stackInfo.managedStack[stackCounter].fileId = helpers::ToUnderlying(FrameKind::BRIDGE);
379 ++stackCounter;
380
381 // fp is not set yet, so cframe not finished, currently in bridge, previous frame iframe
382 frameInfo.isCompiled = false;
383 return;
384 }
385
386 auto pc = signalContext.GetPC();
387 bool pcInCompiledCode = InAllocatedCodeRange(pc);
388 if (pcInCompiledCode) {
389 // Currently in compiled method so get it from fp
390 frameInfo.frame = reinterpret_cast<Frame *>(fp);
391 } else {
392 const LockFreeQueue &pfsQueue = Sampler::GetSampleQueuePF();
393 auto pfId = reinterpret_cast<uintptr_t>(frameInfo.frame->GetMethod()->GetPandaFile());
394 if (pfsQueue.FindValue(pfId)) {
395 sample.stackInfo.managedStack[stackCounter].pandaFilePtr = helpers::ToUnderlying(FrameKind::BRIDGE);
396 sample.stackInfo.managedStack[stackCounter].fileId = helpers::ToUnderlying(FrameKind::BRIDGE);
397 ++stackCounter;
398
399 // pc not in jitted code, so fp is not up-to-date, currently not in cfame
400 frameInfo.isCompiled = false;
401 }
402 }
403 }
404
405 /**
406 * @brief Walk stack frames and collect samples.
407 * @returns true if invalid frame was encountered, false otherwise.
408 */
CollectFrames(SamplerFrameInfo & frameInfo,SampleInfo & sample,size_t & stackCounter)409 static bool CollectFrames(SamplerFrameInfo &frameInfo, SampleInfo &sample, size_t &stackCounter)
410 {
411 const LockFreeQueue &pfsQueue = Sampler::GetSampleQueuePF();
412 auto stackWalker = StackWalkerBase(frameInfo.frame, frameInfo.isCompiled);
413 while (stackWalker.HasFrame()) {
414 auto *method = stackWalker.GetMethod();
415 if (method == nullptr || IsInvalidPointer(reinterpret_cast<uintptr_t>(method))) {
416 g_sLostSamples++;
417 g_sLostInvalidSamples++;
418 return true;
419 }
420
421 auto *pf = method->GetPandaFile();
422 auto pfId = reinterpret_cast<uintptr_t>(pf);
423 if (!pfsQueue.FindValue(pfId)) {
424 g_sLostSamples++;
425 g_sLostNotFindSamples++;
426 return true;
427 }
428
429 sample.stackInfo.managedStack[stackCounter].pandaFilePtr = pfId;
430 sample.stackInfo.managedStack[stackCounter].fileId = method->GetFileId().GetOffset();
431 sample.stackInfo.managedStack[stackCounter].bcOffset = stackWalker.GetBytecodePc();
432 ++stackCounter;
433 stackWalker.NextFrame();
434
435 if (stackCounter == SampleInfo::StackInfo::MAX_STACK_DEPTH) {
436 // According to the limitations we should drop all frames that is higher than MAX_STACK_DEPTH
437 break;
438 }
439 }
440 sample.timeStamp = Sampler::GetMicrosecondsTimeStamp();
441 return false;
442 }
443
SigProfSamplingProfilerHandler(int signum,siginfo_t * siginfo,void * ptr)444 void SigProfSamplingProfilerHandler([[maybe_unused]] int signum, [[maybe_unused]] siginfo_t *siginfo,
445 [[maybe_unused]] void *ptr)
446 {
447 if (g_sCurrentHandlersCounter == 0) {
448 // Sampling ended if S_CURRENT_HANDLERS_COUNTER is 0. Thread started executing handler for signal
449 // that was sent before end, so thread is late now and we should return from handler
450 return;
451 }
452 auto scopedHandlersCounting = ScopedHandlersCounting();
453
454 Coroutine *coro = Coroutine::GetCurrent();
455 if (coro != nullptr && coro->GetCoroutineStatus() != Coroutine::Status::RUNNING) {
456 return;
457 }
458
459 ManagedThread *mthread = coro == nullptr ? ManagedThread::GetCurrent() : coro;
460 ASSERT(mthread != nullptr);
461
462 // Checking that code is being executed
463 auto *framePtr = reinterpret_cast<CFrame::SlotType *>(mthread->GetCurrentFrame());
464 if (framePtr == nullptr) {
465 return;
466 }
467
468 g_sTotalSamples++;
469
470 // Note that optimized variables may end up with incorrect value as a consequence of a longjmp() operation
471 // - see "local variable clobbering and setjmp".
472 // Variables below are not volatile because they are not used after longjmp() is done.
473 SamplerFrameInfo frameInfo {mthread->GetCurrentFrame(), mthread->IsCurrentFrameCompiled()};
474
475 SampleInfo sample {};
476 // `mthread` is passed as non-const argument into `GetThreadStatus`, so call it before `setjmp`
477 // in order to bypass "variable might be clobbered by ‘longjmp’" compiler warning.
478 sample.threadInfo.threadStatus = GetThreadStatus(mthread);
479 sample.threadInfo.threadId = coro == nullptr ? os::thread::GetCurrentThreadId() : coro->GetCoroutineId();
480 size_t stackCounter = 0;
481
482 ScopedThreadSampling scopedThreadSampling(mthread->GetPtThreadInfo()->GetSamplingInfo());
483
484 auto &sigSegvJmpBuf = mthread->GetPtThreadInfo()->GetSamplingInfo()->GetSigSegvJmpEnv();
485 // NOLINTNEXTLINE(cert-err52-cpp)
486 if (setjmp(sigSegvJmpBuf) != 0) {
487 // This code executed after longjmp()
488 // In case of SIGSEGV we lose the sample
489 g_sLostSamples++;
490 g_sLostSegvSamples++;
491 return;
492 }
493
494 if (StackWalkerBase::IsMethodInBoundaryFrame(frameInfo.frame->GetMethod())) {
495 auto foundBypassFrame = CollectBoundaryFrames(frameInfo, sample, stackCounter);
496 if (foundBypassFrame) {
497 return;
498 }
499 } else if (frameInfo.isCompiled) {
500 ProcessCompiledTopFrame(frameInfo, sample, stackCounter, ptr);
501 }
502
503 auto lostSample = CollectFrames(frameInfo, sample, stackCounter);
504 if (lostSample) {
505 return;
506 }
507
508 if (stackCounter == 0) {
509 return;
510 }
511 sample.stackInfo.managedStackSize = stackCounter;
512
513 const ThreadCommunicator &communicator = Sampler::GetSampleCommunicator();
514 communicator.SendSample(sample);
515 }
516
SamplerThreadEntry()517 void Sampler::SamplerThreadEntry()
518 {
519 struct sigaction action {};
520 action.sa_sigaction = &SigProfSamplingProfilerHandler;
521 action.sa_flags = SA_SIGINFO | SA_ONSTACK;
522 // Clear signal set
523 sigemptyset(&action.sa_mask);
524 // Ignore incoming sigprof if handler isn't completed
525 sigaddset(&action.sa_mask, SIGPROF);
526
527 struct sigaction oldAction {};
528
529 if (sigaction(SIGPROF, &action, &oldAction) == -1) {
530 LOG(FATAL, PROFILER) << "Sigaction failed, can't start profiling";
531 UNREACHABLE();
532 }
533
534 // We keep handler assigned to SigProfSamplingProfilerHandler after sampling end because
535 // otherwice deadlock can happen if signal will be slow and reach thread after handler resignation
536 if (oldAction.sa_sigaction != nullptr && oldAction.sa_sigaction != SigProfSamplingProfilerHandler) {
537 LOG(FATAL, PROFILER) << "SIGPROF signal handler was overriden in sampling profiler";
538 UNREACHABLE();
539 }
540 ++g_sCurrentHandlersCounter;
541
542 auto pid = getpid();
543 // Atomic with acquire order reason: To ensure start/stop load correctly
544 while (isActive_.load(std::memory_order_acquire)) {
545 {
546 os::memory::LockHolder holder(managedThreadsLock_);
547 for (const auto &threadId : managedThreads_) {
548 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
549 if (syscall(SYS_tgkill, pid, threadId, SIGPROF) != 0) {
550 LOG(ERROR, PROFILER) << "Can't send signal to thread";
551 }
552 }
553 }
554 os::thread::NativeSleepUS(sampleInterval_);
555 }
556
557 // Sending last sample on finish to avoid of deadlock in listener
558 SampleInfo lastSample;
559 lastSample.stackInfo.managedStackSize = 0;
560 communicator_.SendSample(lastSample);
561
562 --g_sCurrentHandlersCounter;
563
564 const unsigned int timeToSleepMs = 100;
565 do {
566 os::thread::NativeSleep(timeToSleepMs);
567 } while (g_sCurrentHandlersCounter != 0);
568 }
569
570 // NOLINTNEXTLINE(performance-unnecessary-value-param)
ListenerThreadEntry(std::unique_ptr<StreamWriter> writerPtr)571 void Sampler::ListenerThreadEntry(std::unique_ptr<StreamWriter> writerPtr)
572 {
573 // Writing panda files that were loaded before sampler was created
574 WriteLoadedPandaFiles(writerPtr.get());
575
576 SampleInfo bufferSample;
577 // Atomic with acquire order reason: To ensure start/stop load correctly
578 while (isActive_.load(std::memory_order_acquire)) {
579 WriteLoadedPandaFiles(writerPtr.get());
580 communicator_.ReadSample(&bufferSample);
581 if (LIKELY(bufferSample.stackInfo.managedStackSize != 0)) {
582 writerPtr->WriteSample(bufferSample);
583 }
584 }
585 // Writing all remaining samples
586 while (!communicator_.IsPipeEmpty()) {
587 WriteLoadedPandaFiles(writerPtr.get());
588 communicator_.ReadSample(&bufferSample);
589 if (LIKELY(bufferSample.stackInfo.managedStackSize != 0)) {
590 writerPtr->WriteSample(bufferSample);
591 }
592 }
593 }
594
GetMicrosecondsTimeStamp()595 uint64_t Sampler::GetMicrosecondsTimeStamp()
596 {
597 static constexpr int USEC_PER_SEC = 1000 * 1000;
598 static constexpr int NSEC_PER_USEC = 1000;
599 struct timespec time {};
600 clock_gettime(CLOCK_MONOTONIC, &time);
601 return time.tv_sec * USEC_PER_SEC + time.tv_nsec / NSEC_PER_USEC;
602 }
603
604 } // namespace ark::tooling::sampler
605