From 8f2a5f59e1345b654ce5c256f61f7a046974e788 Mon Sep 17 00:00:00 2001 From: hhl Date: Thu, 11 Apr 2024 20:15:21 +0800 Subject: [PATCH] Main Thread DumpStack And Trace Signed-off-by: hhl --- bundle.json | 5 +- frameworks/native/BUILD.gn | 8 + frameworks/native/main_thread_sampler.cpp | 480 ++++++++++++++++++ frameworks/native/main_thread_sampler.h | 114 +++++ frameworks/native/sample_stack_printer.cpp | 124 +++++ frameworks/native/sample_stack_printer.h | 65 +++ .../native/test/unittest/common/BUILD.gn | 17 + .../common/main_thread_sampler_test.cpp | 160 ++++++ .../common/main_thread_sampler_test.h | 32 ++ frameworks/native/watchdog_inner.cpp | 122 ++++- frameworks/native/watchdog_inner.h | 9 + frameworks/native/watchdog_task.h | 1 + frameworks/native/xcollie_utils.cpp | 93 ++++ frameworks/native/xcollie_utils.h | 17 + 14 files changed, 1245 insertions(+), 2 deletions(-) create mode 100644 frameworks/native/main_thread_sampler.cpp create mode 100644 frameworks/native/main_thread_sampler.h create mode 100644 frameworks/native/sample_stack_printer.cpp create mode 100644 frameworks/native/sample_stack_printer.h create mode 100644 frameworks/native/test/unittest/common/main_thread_sampler_test.cpp create mode 100644 frameworks/native/test/unittest/common/main_thread_sampler_test.h diff --git a/bundle.json b/bundle.json index d481a9c..7ca413e 100644 --- a/bundle.json +++ b/bundle.json @@ -22,14 +22,17 @@ "ram": "", "deps": { "components": [ + "bundle_framework", "hilog", "hisysevent", "c_utils", "eventhandler", "faultloggerd", "ffrt", + "hiview", "ipc", - "init" + "init", + "storage_service" ], "third_party": [] }, diff --git a/frameworks/native/BUILD.gn b/frameworks/native/BUILD.gn index 5607dd4..8a444c1 100644 --- a/frameworks/native/BUILD.gn +++ b/frameworks/native/BUILD.gn @@ -33,17 +33,25 @@ ohos_source_set("libhicollie_source") { "watchdog_task.cpp", "xcollie.cpp", "xcollie_utils.cpp", + "main_thread_sampler.cpp", + "sample_stack_printer.cpp", ] configs = [ ":hicollie_include" ] external_deps = [ + "bundle_framework:appexecfwk_base", + "bundle_framework:appexecfwk_core", "c_utils:utils", "faultloggerd:libbacktrace_local", + "faultloggerd:libunwinder", + "faultloggerd:libasync_stack", "ffrt:libffrt", "hilog:libhilog", "hisysevent:libhisysevent", + "hiview:libucollection_client", "init:libbegetutil", "ipc:ipc_core", + "storage_service:storage_manager_acl" ] defines = [] diff --git a/frameworks/native/main_thread_sampler.cpp b/frameworks/native/main_thread_sampler.cpp new file mode 100644 index 0000000..ec8f550 --- /dev/null +++ b/frameworks/native/main_thread_sampler.cpp @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "main_thread_sampler.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "unwinder.h" +#include "dfx_regs.h" +#include "xcollie_utils.h" +#include "xcollie_define.h" +#include "dfx_elf.h" +#include "dfx_frame_formatter.h" +#include "sample_stack_printer.h" + +namespace OHOS { +namespace HiviewDFX { +struct UnwindInfo { + ThreadUnwindContext* context; + DfxMaps* maps; +}; +// do not log in signal handler +// todo move to LocalThreadUnwinder? +void MainThreadSampler::ThreadSamplerSignalHandler(int sig, siginfo_t* si, void* context) +{ + MainThreadSampler::GetInstance().WriteContext(context); +} + +MainThreadSampler::MainThreadSampler() +{ +} + +MainThreadSampler::~MainThreadSampler() +{ +} + +int MainThreadSampler::FindUnwindTable(uintptr_t pc, UnwindTableInfo& outTableInfo, void *arg) +{ + UnwindInfo* unwindInfo = static_cast(arg); + if (unwindInfo == nullptr) { + printf("invalid FindUnwindTable param\n"); + return -1; + } + + std::shared_ptr map; + if (unwindInfo->maps->FindMapByAddr(pc, map)) { + auto elf = map->GetElf(); + if (elf != nullptr) { + return elf->FindUnwindTableInfo(pc, map, outTableInfo); + } + } + return -1; +} + +int MainThreadSampler::AccessMem(uintptr_t addr, uintptr_t *val, void *arg) +{ + UnwindInfo* unwindInfo = static_cast(arg); + if (unwindInfo == nullptr || addr + sizeof(uintptr_t) < addr) { + printf("invalid AccessMem param\n"); + return -1; + } + + *val = 0; + if (addr < unwindInfo->context->sp || + addr + sizeof(uintptr_t) >= unwindInfo->context->sp + 16*1024) { + return MainThreadSampler::GetInstance().AccessElfMem(addr, val); + } else { + size_t stackOffset = addr - unwindInfo->context->sp; + if (stackOffset >= 16*1024) { + printf("limit stack\n"); + return -1; + } + *val = *(uintptr_t *)&unwindInfo->context->buffer[stackOffset]; + } + return 0; +} + +int MainThreadSampler::GetMapByPc(uintptr_t pc, std::shared_ptr& map, void *arg) +{ + UnwindInfo* unwindInfo = static_cast(arg); + if (unwindInfo == nullptr) { + printf("invalid GetMapByPc param\n"); + return -1; + } + + return unwindInfo->maps->FindMapByAddr(pc, map) ? 0 : -1; +} + +bool MainThreadSampler::Init() +{ + if (init_) { + return true; + } + + if (!InitRecordBuffer()) { + printf("Failed to InitRecordBuffer\n"); + return false; + } + + if (!InitUnwinder()) { + printf("Failed to InitUnwinder\n"); + return false; + } + + if (!InstallSignalHandler()) { + printf("Failed to InstallSignalHandler\n"); + return false; + } + pid_ = getprocpid(); + init_ = true; + return true; +} + +bool MainThreadSampler::InitRecordBuffer() +{ + if (mmapStart_ != MAP_FAILED) { + return true; + } + // create buffer + bufferSize_ = SAMPLER_MAX_BUFFER_SZ * sizeof(struct ThreadUnwindContext); + mmapStart_ = mmap(NULL, bufferSize_, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mmapStart_ == MAP_FAILED) { + XCOLLIE_LOGE("Failed to create buffer for thread sampler!(%d)\n", errno); + return false; + } + + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, mmapStart_, bufferSize_, "sampler_buf"); + return true; +} + +void MainThreadSampler::ReleaseRecordBuffer() +{ + // todo +} + +bool MainThreadSampler::InitUnwinder() +{ + accessors_ = std::make_shared(); + accessors_->AccessReg = nullptr; + accessors_->AccessMem = &MainThreadSampler::AccessMem; + accessors_->GetMapByPc = &MainThreadSampler::GetMapByPc; + accessors_->FindUnwindTable = &MainThreadSampler::FindUnwindTable; + unwinder_ = std::make_shared(accessors_); + // todo use fp to unwind js stack + maps_ = DfxMaps::Create(); + if (!maps_->GetStackRange(mainStackBegin_, mainStackEnd_)) { + printf("Failed to get stack range\n"); + return false; + } + + // todo + // 1. add release method + // 2. add fallback + // 3. add size to constructor + uniqueStackTable_ = std::make_unique(); + if (!uniqueStackTable_->Init()) { + printf("Failed to init unique_table\n"); + return false; + } + return true; +} + +bool MainThreadSampler::InstallSignalHandler() +{ + struct sigaction action {}; + sigfillset(&action.sa_mask); + action.sa_sigaction = MainThreadSampler::ThreadSamplerSignalHandler; + action.sa_flags = SA_RESTART | SA_SIGINFO; + if (sigaction(41, &action, nullptr) != 0) { + XCOLLIE_LOGE("Failed to register signal(%d:%d)", 41, errno); + return false; + } + return true; +} + +int MainThreadSampler::AccessElfMem(uintptr_t addr, uintptr_t *val) +{ + std::shared_ptr map; + if (maps_->FindMapByAddr(addr, map)) { + auto elf = map->GetElf(); + if (elf != nullptr) { + uint64_t foff = addr - map->begin + map->offset - elf->GetBaseOffset(); + if (elf->Read(foff, val, sizeof(uintptr_t))) { + return 0; + } + } + } + return -1; +} + +ThreadUnwindContext* MainThreadSampler::GetReadContext() +{ + ThreadUnwindContext* contextArray = static_cast(mmapStart_); + int32_t index = readIndex_; + if (contextArray[index].requestTime == 0 || contextArray[index].snapshotTime == 0) { + return nullptr; + } + + ThreadUnwindContext* ret = &contextArray[index]; + readIndex_ = (index + 1) % SAMPLER_MAX_BUFFER_SZ; + return ret; +} + +ThreadUnwindContext* MainThreadSampler::GetWriteContext() +{ + ThreadUnwindContext* contextArray = static_cast(mmapStart_); + int32_t index = writeIndex_; + if (contextArray[index].requestTime > 0 && + (contextArray[index].snapshotTime == 0 || contextArray[index].processTime == 0)) { + return nullptr; + } + return &contextArray[index]; +} + +void MainThreadSampler::WriteContext(void* context) +{ + struct timespec begin; + clock_gettime(CLOCK_MONOTONIC, &begin); + + if (!init_) { + printf("MainThreadSampler has not been inited.\n"); + return; + } + + // only impl arm64 + ThreadUnwindContext* contextArray = static_cast(mmapStart_); + int32_t index = writeIndex_; + + // current buffer has not been processed, stop copy + if (contextArray[index].snapshotTime > 0 && contextArray[index].processTime == 0) { + printf("current buffer has not been processed, index:%d\n", index); + return; + } + + contextArray[index].fp = static_cast(context)->uc_mcontext.regs[29]; + contextArray[index].lr = static_cast(context)->uc_mcontext.regs[30]; + contextArray[index].sp = static_cast(context)->uc_mcontext.sp; + contextArray[index].pc = static_cast(context)->uc_mcontext.pc; + if (contextArray[index].sp < mainStackBegin_ || + contextArray[index].sp >= mainStackEnd_) { + printf("invalid sp? begin:%p end:%p sp:%p\n", + (void*)mainStackBegin_, (void*)mainStackEnd_, (void*)contextArray[index].sp); + return; + } + + uintptr_t curStackSz = mainStackEnd_ - contextArray[index].sp; + uintptr_t cpySz = curStackSz > 16*1024 ? 16*1024 : curStackSz; + if (memcpy_s((void*)(contextArray[index].buffer), 16*1024, + (void*)(contextArray[index].sp), cpySz) != 0) { + printf("failed to copy stack?\n"); + return; + } + + // use to predict time consumption + struct timespec end; + clock_gettime(CLOCK_MONOTONIC, &end); + contextArray[index].snapshotTime = end.tv_sec*1000000000L + end.tv_nsec; + writeIndex_ = (index + 1) % SAMPLER_MAX_BUFFER_SZ; + + // todo remove it + mainThreadCount_++; + mainThreadTimeCost_ += end.tv_sec*1000000000L + end.tv_nsec - (begin.tv_sec*1000000000L + begin.tv_nsec); +} + +void MainThreadSampler::SendSampleRequest() +{ + ThreadUnwindContext* ptr = GetWriteContext(); + if (ptr == nullptr) { + return; + } + + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) { + printf("failed to gettime"); + return; + } + + ptr->requestTime = ts.tv_sec*1000000000L + ts.tv_nsec; + siginfo_t si {0}; + si.si_signo = 41; + si.si_errno = 0; + si.si_code = -1; + if (syscall(SYS_rt_tgsigqueueinfo, pid_, pid_, si.si_signo, &si) != 0) { + printf("Failed to queue signal(%d) to %d, errno(%d).\n", si.si_signo, pid_, errno); + return; + } + requestCount_++; +} + +void MainThreadSampler::ProcessStackBuffer() +{ + while (true) { + ThreadUnwindContext* context = GetReadContext(); + if (context == nullptr) { + break; + } + + struct timespec unwindStart; + if (clock_gettime(CLOCK_MONOTONIC, &unwindStart) == -1) { + printf("failed to gettime2\n"); + } + + // do unwind and store pcs + UnwindInfo unwindInfo = { + .context = context, + .maps = maps_.get(), + }; + + // todo, impl arm/x86_64? + static std::shared_ptr regs = std::make_shared(); + regs->SetSp(context->sp); + regs->SetPc(context->pc); + regs->SetFp(context->fp); + // todo ? + regs->SetReg(30, &(context->lr)); + unwinder_->SetRegs(regs); + unwinder_->EnableFillFrames(false); + unwinder_->Unwind(&unwindInfo); + auto pcs = unwinder_->GetPcs(); + + std::lock_guard lock(mutex_); + uint64_t stackId = 0; + auto stackIdPtr = reinterpret_cast(&stackId); + uniqueStackTable_->PutPcsInTable(stackIdPtr, pcs.data(), pcs.size()); + auto it = stackIdTimeMap_.find(stackId); + if (it == stackIdTimeMap_.end()) { + std::vector timestamps; + timestamps.emplace_back(context->snapshotTime); + stackIdTimeMap_[stackId] = timestamps; + } else { + it->second.emplace_back(context->snapshotTime); + } + + // todo encapsulate gettime or just use a boolean to represent snapshot is done + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) { + printf("failed to gettime2\n"); + } + + context->processTime = ts.tv_sec*1000000000L + ts.tv_nsec; + overallTimeCost_ += context->snapshotTime - context->requestTime; + processCount_++; + context->requestTime = 0; + context->snapshotTime = 0; + unwinderCount_++; + unwinderTimeCost_ += context->processTime - (unwindStart.tv_sec*1000000000L + unwindStart.tv_nsec); + } +} + +int32_t MainThreadSampler::Sample(int32_t interval) +{ + sampleCount_++; + SendSampleRequest(); + ProcessStackBuffer(); + return 0; +} + +// decrease order +bool cmp(std::pair& a, + std::pair& b) +{ + return a.second > b.second; +} + +// todo impl tree format printer +bool MainThreadSampler::CollectStack(std::string& stack, bool treeFormat) +{ + std::lock_guard lock(mutex_); + struct timespec collectStart; + if (clock_gettime(CLOCK_MONOTONIC, &collectStart) == -1) { + printf("failed to gettime2\n"); + } + // sort stackId by count + std::vector > sortedStackId; + for (auto it = stackIdTimeMap_.begin(); it != stackIdTimeMap_.end(); it++) { + uint64_t stackId = it->first; + sortedStackId.emplace_back(std::make_pair(std::move(stackId), it->second.size())); + std::sort(sortedStackId.begin(), sortedStackId.end(), cmp); + } + + if (!treeFormat) { + for (auto it = sortedStackId.begin(); it != sortedStackId.end(); it++) { + std::vector pcs; + OHOS::HiviewDFX::StackId stackId; + stackId.value = it->first; + if (uniqueStackTable_->GetPcsByStackId(stackId, pcs)) { + size_t count = it->second; + printf("Current stack happend %d times with following timestamp:\n", (int32_t)count); + auto timestamps = stackIdTimeMap_[it->first]; + for (auto timestamp : timestamps) { + printf("%lld\n", (unsigned long long)timestamp); + } + + std::vector frames; + unwinder_->GetFramesByPcs(frames, pcs, maps_); + for (auto& frame : frames) { + unwinder_->FillFrame(frame); + auto frameStr = DfxFrameFormatter::GetFrameStr(frame); + printf("%s\n", frameStr.c_str()); + } + } else { + printf("Failed to find stack by id?\n"); + } + } + } else { + auto printer = std::make_unique(unwinder_, maps_); + for (auto it = sortedStackId.begin(); it != sortedStackId.end(); it++) { + std::vector pcs; + OHOS::HiviewDFX::StackId stackId; + stackId.value = it->first; + if (uniqueStackTable_->GetPcsByStackId(stackId, pcs)) { + printer->Insert(pcs, it->second); + } + } + + stack = printer->Print(); + printf("%s\n", stack.c_str()); + } + stackIdTimeMap_.clear(); + + struct timespec collectEnd; + if (clock_gettime(CLOCK_MONOTONIC, &collectEnd) == -1) { + printf("failed to gettime2\n"); + } + uint64_t elapse = (collectEnd.tv_sec*1000000000L + collectEnd.tv_nsec) - + (collectStart.tv_sec*1000000000L + collectStart.tv_nsec); + printf("Sample count:%llu\nRequest count:%llu\nSnapshot count:%llu\nAverage mainThread block time:%llu ns\n", + (unsigned long long)sampleCount_, + (unsigned long long)requestCount_, + (unsigned long long)mainThreadCount_, + (unsigned long long)mainThreadTimeCost_/mainThreadCount_); + printf("Average process time:%llu ns\n", + (unsigned long long)overallTimeCost_/processCount_); + printf("Average unwinder time:%llu ns\n", + (unsigned long long)unwinderTimeCost_/unwinderCount_); + printf("FormatStack time:%llu ns\n", (unsigned long long)elapse); + sampleCount_ = 0; + requestCount_ = 0; + mainThreadCount_ = 0; + mainThreadTimeCost_ = 0; + mainThreadCount_ = 0; + overallTimeCost_ = 0; + processCount_ = 0; + unwinderCount_ = 0; + unwinderTimeCost_ = 0; + return false; +} + +void MainThreadSampler::InvalidateCache() +{ + // todo drop unique table and request buffer if it is not in using +} +} // end of namespace HiviewDFX +} // end of namespace OHOS diff --git a/frameworks/native/main_thread_sampler.h b/frameworks/native/main_thread_sampler.h new file mode 100644 index 0000000..22a3278 --- /dev/null +++ b/frameworks/native/main_thread_sampler.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RELIABILITY_MAIN_THREAD_SAMPLER_H +#define RELIABILITY_MAIN_THREAD_SAMPLER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "singleton.h" + +#include "unwinder.h" +#include "unique_stack_table.h" +#include "dfx_accessors.h" +#include "dfx_maps.h" +#include "unwind_context.h" + +namespace OHOS { +namespace HiviewDFX { +// todo merge to ThreadContext in unwinder +struct ThreadUnwindContext { + uintptr_t pc {0}; + uintptr_t sp {0}; + uintptr_t fp {0}; + uintptr_t lr {0}; + uint64_t requestTime {0}; // begin sample + uint64_t snapshotTime {0}; // end of stack copy in signal handler + uint64_t processTime {0}; // end of unwind and unique stack + uint8_t buffer[16*1024] {0}; // 16K stack buffer +}; +// should always call in watchdog thread +class MainThreadSampler : public Singleton { + DECLARE_SINGLETON(MainThreadSampler); +public: + static const int32_t SAMPLER_STATUS_STOPPED = 0; + static const int32_t SAMPLER_STATUS_RUNNING = 1; + static const int32_t SAMPLER_STATUS_STOPING = 2; + static const int32_t SAMPLER_DEFAULT_INTERVAL = 100; // 100 MS + static const int32_t SAMPLER_MAX_BUFFER_SZ = 2; // may be 2 block is enough? + static void ThreadSamplerSignalHandler(int sig, siginfo_t * si, void * context); + + bool Init(); + int32_t Sample(int32_t interval); + bool CollectStack(std::string& stack, bool treeFormat = false); + void InvalidateCache(); + +private: + bool InitUnwinder(); + bool InitRecordBuffer(); + bool InstallSignalHandler(); + void ReleaseRecordBuffer(); + void SendSampleRequest(); + void ProcessStackBuffer(); + int AccessElfMem(uintptr_t addr, uintptr_t *val); + + static int FindUnwindTable(uintptr_t pc, UnwindTableInfo& outTableInfo, void *arg); + static int AccessMem(uintptr_t addr, uintptr_t *val, void *arg); + static int GetMapByPc(uintptr_t pc, std::shared_ptr& map, void *arg); + + ThreadUnwindContext* GetReadContext(); + ThreadUnwindContext* GetWriteContext(); + void WriteContext(void* context); + + bool init_ = false; + int32_t interval_ = SAMPLER_DEFAULT_INTERVAL; + int32_t mainThreadTimerStatus_ = SAMPLER_STATUS_STOPPED; + uintptr_t mainStackBegin_ = 0; + uintptr_t mainStackEnd_ = 0; + int32_t pid_; + std::atomic writeIndex_ {0}; + std::atomic readIndex_ {0}; + void* mmapStart_ = MAP_FAILED; + int32_t bufferSize_ = 0; + std::shared_ptr unwinder_ = nullptr; + std::unique_ptr uniqueStackTable_ = nullptr; + std::shared_ptr accessors_; + std::shared_ptr maps_; + uint64_t mainThreadCount_; + uint64_t mainThreadTimeCost_; + uint64_t unwinderCount_; + uint64_t unwinderTimeCost_; + uint64_t symbolicCount_; + uint64_t symbolicTimeCost_; + uint64_t overallTimeCost_; + uint64_t sampleCount_; + uint64_t requestCount_; + uint64_t processCount_; + // stackId time + std::map> stackIdTimeMap_; + std::mutex mutex_; +}; +} // end of namespace HiviewDFX +} // end of namespace OHOS +#endif diff --git a/frameworks/native/sample_stack_printer.cpp b/frameworks/native/sample_stack_printer.cpp new file mode 100644 index 0000000..d0eb951 --- /dev/null +++ b/frameworks/native/sample_stack_printer.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sample_stack_printer.h" + +#include +#include + +#include "dfx_frame_formatter.h" + +namespace OHOS { +namespace HiviewDFX { +SampleStackItem* SampleStackPrinter::Insert(SampleStackItem* curNode, uint64_t pc, int32_t count) +{ + if (curNode == nullptr) { + return nullptr; + } + + if (curNode->pc == pc) { + curNode->count += count; + return curNode; + } + + if (curNode->pc == 0) { + curNode->pc = pc; + curNode->count += count; + curNode->current = std::make_shared(); + curNode->current->index = curNode->level; + // todo fill js fram automatically? + unwinder_->GetFramesByPc(pc, maps_, *(curNode->current)); + return curNode; + } + + if (curNode->child == nullptr) { + curNode->child = new SampleStackItem; + curNode->child->level = curNode->level + 1; + allNodes_.push_back(curNode->child); + return Insert(curNode->child, pc, count); + } + + if (curNode->child->pc == pc) { + curNode->child->count += count; + return curNode->child; + } + + if (curNode->siblings == nullptr) { + curNode->siblings = new SampleStackItem; + curNode->siblings->level = curNode->level; + allNodes_.push_back(curNode->siblings); + return Insert(curNode->siblings, pc, count); + } + + if (curNode->siblings->pc == pc) { + curNode->siblings->count += count; + return curNode->siblings; + } + + return Insert(curNode->siblings, pc, count); +} + +void SampleStackPrinter::Insert(std::vector& pcs, int32_t count) +{ + if (root_ == nullptr) { + root_ = new SampleStackItem; + root_->level = 0; + allNodes_.push_back(root_); + } + + SampleStackItem* curNode = root_; + for (auto iter = pcs.rbegin(); iter != pcs.rend(); ++iter) { + curNode = Insert(curNode, *iter, count); + } +} + +std::string SampleStackPrinter::Print() +{ + if (root_ == nullptr) { + return std::string(""); + } + + std::stringstream ret; + std::vector nodes; + nodes.push_back(root_); + int indent = 4; + while (!nodes.empty()) { + SampleStackItem* back = nodes.back(); + SampleStackItem* sibling = back->siblings; + SampleStackItem* child = back->child; + std::string space(indent * back->level, ' '); + nodes.pop_back(); + // todo modify formatter + ret << space << back->count << " " << DfxFrameFormatter::GetFrameStr(back->current); + if (sibling != nullptr) { + nodes.push_back(sibling); + } + + if (child != nullptr) { + nodes.push_back(child); + } + } + return ret.str(); +} + +void SampleStackPrinter::FreeNodes() +{ + std::vector allNodes_; + for (auto node : allNodes_) { + delete node; + } +} +} // end of namespace HiviewDFX +} // end of namespace OHOS diff --git a/frameworks/native/sample_stack_printer.h b/frameworks/native/sample_stack_printer.h new file mode 100644 index 0000000..322e41d --- /dev/null +++ b/frameworks/native/sample_stack_printer.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RELIABILITY_SAMPLE_STACK_PRINTER_H +#define RELIABILITY_SAMPLE_STACK_PRINTER_H + +#include "unwinder.h" +#include "dfx_frame.h" + +namespace OHOS { +namespace HiviewDFX { +struct SampleStackItem { + uintptr_t pc; + int32_t count; + int32_t level; + std::shared_ptr current; + SampleStackItem* child; + SampleStackItem* siblings; + + SampleStackItem() : + pc(0), + count(0), + level(0), + current(nullptr), + child(nullptr), + siblings(nullptr) + {}; +}; +class SampleStackPrinter { +public: + SampleStackPrinter(std::shared_ptr unwinder, std::shared_ptr maps) : unwinder_(unwinder), maps_(maps) + { + root_ = nullptr; + }; + ~SampleStackPrinter() + { + FreeNodes(); + }; + + void Insert(std::vector& pcs, int32_t count); + std::string Print(); + +private: + void FreeNodes(); + SampleStackItem* Insert(SampleStackItem* curNode, uint64_t pc, int32_t count); + SampleStackItem* root_; + std::shared_ptr unwinder_; + std::shared_ptr maps_; + std::vector allNodes_; +}; +} // end of namespace HiviewDFX +} // end of namespace OHOS +#endif diff --git a/frameworks/native/test/unittest/common/BUILD.gn b/frameworks/native/test/unittest/common/BUILD.gn index 655f216..ef049bf 100644 --- a/frameworks/native/test/unittest/common/BUILD.gn +++ b/frameworks/native/test/unittest/common/BUILD.gn @@ -135,6 +135,22 @@ ohos_unittest("WatchdogTaskTest") { } } +ohos_unittest("MainThreadSamplerTest") { + module_out_path = module_output_path + sources = [ "main_thread_sampler_test.cpp" ] + configs = [ ":module_private_config" ] + deps = [ "//third_party/googletest:gtest_main" ] + external_deps = [ + "c_utils:utils", + "ffrt:libffrt", + "hicollie:libhicollie", + "hilog:libhilog", + "hisysevent:libhisysevent", + "faultloggerd:libunwinder", + "faultloggerd:libasync_stack", + ] +} + ############################################################################### group("unittest") { testonly = true @@ -145,6 +161,7 @@ group("unittest") { ":WatchdogTaskTest", ":WatchdogUnitTest", ":XCollieUnitTest", + ":MainThreadSamplerTest", ] } ############################################################################### diff --git a/frameworks/native/test/unittest/common/main_thread_sampler_test.cpp b/frameworks/native/test/unittest/common/main_thread_sampler_test.cpp new file mode 100644 index 0000000..2e058fc --- /dev/null +++ b/frameworks/native/test/unittest/common/main_thread_sampler_test.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "main_thread_sampler_test.h" + +#include "watchdog.h" + +using namespace testing::ext; +namespace OHOS { +namespace HiviewDFX { +void MainThreadSamplerTest::SetUpTestCase(void) +{ + printf("SetUpTestCase.\n"); +} + +void MainThreadSamplerTest::TearDownTestCase(void) +{ + printf("TearDownTestCase.\n"); + Watchdog::GetInstance().StopWatchdog(); +} + +void MainThreadSamplerTest::SetUp(void) +{ + printf("SetUp.\n"); +} + +void MainThreadSamplerTest::TearDown(void) +{ + printf("TearDown.\n"); +} + +/** + * @tc.name: MainThreadSamplerTest_001 + * @tc.desc: sample main thread at 10Hz and check the stacktrace + * @tc.type: FUNC + * @tc.require + */ +HWTEST_F(MainThreadSamplerTest, MainThreadSamplerTest_001, TestSize.Level3) +{ + printf("MainThreadSamplerTest_001\n"); + printf("Total:10S Sample:100MS \n"); + Watchdog::GetInstance().StartProfileMainThread(100); + int32_t left = 10; + int32_t end = time(nullptr) + left; + while (left > 0) { + left = end - time(nullptr); + } + Watchdog::GetInstance().StopProfileMainThread(); + sleep(1); + std::string stack; + Watchdog::GetInstance().CollectStack(stack); + printf("stack:%s\n", stack.c_str()); +} + +/** + * @tc.name: MainThreadSamplerTest_002 + * @tc.desc: sample main thread and stop sampling + * @tc.type: FUNC + * @tc.require + */ +HWTEST_F(MainThreadSamplerTest, MainThreadSamplerTest_002, TestSize.Level3) +{ + printf("MainThreadSamplerTest_002\n"); + printf("Total:5S Sample:500MS \n"); + Watchdog::GetInstance().StartProfileMainThread(500); + int32_t left = 5; + int32_t end = time(nullptr) + left; + while (left > 0) { + left = end - time(nullptr); + } + std::string stack; + Watchdog::GetInstance().StopProfileMainThread(); + Watchdog::GetInstance().CollectStack(stack); + left = 5; + end = time(nullptr) + left; + while (left > 0) { + left = end - time(nullptr); + } + Watchdog::GetInstance().CollectStack(stack); + printf("stack:%s\n", stack.c_str()); +} + +/** + * @tc.name: MainThreadSamplerTest_003 + * @tc.desc: sample main thread and stop sampling and restart sampling + * @tc.type: FUNC + * @tc.require + */ +HWTEST_F(MainThreadSamplerTest, MainThreadSamplerTest_003, TestSize.Level3) +{ + printf("MainThreadSamplerTest_003_001\n"); + printf("Total:5S Sample:200MS \n"); + Watchdog::GetInstance().StartProfileMainThread(200); + int32_t left = 5; + int32_t end = time(nullptr) + left; + while (left > 0) { + left = end - time(nullptr); + } + std::string stack; + Watchdog::GetInstance().StopProfileMainThread(); + Watchdog::GetInstance().CollectStack(stack); + printf("stack:%s\n", stack.c_str()); + + left = 5; + end = time(nullptr) + left; + while (left > 0) { + left = end - time(nullptr); + } + Watchdog::GetInstance().CollectStack(stack); + printf("stack:%s\n", stack.c_str()); + + printf("MainThreadSamplerTest_003_002\n"); + printf("Total:5S Sample:500MS \n"); + Watchdog::GetInstance().StartProfileMainThread(500); + left = 5; + end = time(nullptr) + left; + while (left > 0) { + left = end - time(nullptr); + } + Watchdog::GetInstance().StopProfileMainThread(); + Watchdog::GetInstance().CollectStack(stack); + printf("stack:%s\n", stack.c_str()); +} + +/** + * @tc.name: MainThreadSamplerTest_004 + * @tc.desc: test callstack output in tree format + * @tc.type: FUNC + * @tc.require + */ +HWTEST_F(MainThreadSamplerTest, MainThreadSamplerTest_004, TestSize.Level3) +{ + printf("MainThreadSamplerTest_004\n"); + printf("Total:5S Sample:200MS \n"); + Watchdog::GetInstance().StartProfileMainThread(200); + int32_t left = 5; + int32_t end = time(nullptr) + left; + while (left > 0) { + left = end - time(nullptr); + } + std::string stack; + Watchdog::GetInstance().StopProfileMainThread(); + sleep(1); + Watchdog::GetInstance().CollectStack(stack); + printf("stack:%s\n", stack.c_str()); +} +} // end of namespace HiviewDFX +} // end of namespace OHOS \ No newline at end of file diff --git a/frameworks/native/test/unittest/common/main_thread_sampler_test.h b/frameworks/native/test/unittest/common/main_thread_sampler_test.h new file mode 100644 index 0000000..6611333 --- /dev/null +++ b/frameworks/native/test/unittest/common/main_thread_sampler_test.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RELIABILITY_MAIN_THREAD_SAMPLER_TEST_H +#define RELIABILITY_MAIN_THREAD_SAMPLER_TEST_H + +#include + +namespace OHOS { +namespace HiviewDFX { +class MainThreadSamplerTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; +} // end of namespace HiviewDFX +} // end of namespace OHOS +#endif diff --git a/frameworks/native/watchdog_inner.cpp b/frameworks/native/watchdog_inner.cpp index afdc7ec..06b06a9 100644 --- a/frameworks/native/watchdog_inner.cpp +++ b/frameworks/native/watchdog_inner.cpp @@ -30,11 +30,16 @@ #include #include "backtrace_local.h" +#include "bundle_mgr_client.h" #include "hisysevent.h" #include "xcollie_utils.h" #include "xcollie_define.h" #include "dfx_define.h" #include "parameter.h" +#include "main_thread_sampler.h" +#include "file_ex.h" +#include "directory_ex.h" +#include "client/trace_collector.h" typedef void(*ThreadInfoCallBack)(char* buf, size_t len, void* ucontext); extern "C" void SetThreadInfoCallback(ThreadInfoCallBack func) __attribute__((weak)); @@ -61,6 +66,13 @@ static int32_t g_fd = -1; static bool g_existFile = true; using TimePoint = AppExecFwk::InnerEvent::TimePoint; static const int64_t DISTRIBUTE_TIME = 2000; +static const int64_t DUMPSTACK_TIME = 150; +static const int64_t DUMPTRACE_TIME = 450; +static int g_sampleTaskState = 0; +static int g_stackState = 0; +static int g_traceState = 0; +static TimePoint beginTime_; +static TimePoint endTime_; namespace { void ThreadInfo(char *buf __attribute__((unused)), size_t len __attribute__((unused)), @@ -98,6 +110,82 @@ static bool IsInAppspwan() return false; } +bool WatchdogInner::ReportMainThreadEvent() +{ + ProcessStackBuffer(); + std::string stack = ""; + CollectStack(stack); + std::string path = ""; + if (!WriteStackToFd(getpid(), path, stack)) { + XCOLLIE_LOGI("WriteStackToFd Failed"); + } + std::string bundleName = GetApplicationNameById(getuid()); + std::string version = GetApplicationVersion(getpid()); + int64_t begin_ = std::chrono::duration_cast + (beginTime_.time_since_epoch()).count(); + int64_t end_ = std::chrono::duration_cast + (endTime_.time_since_epoch()).count(); + int32_t level = g_traceState == DumpStackState::DEFAULT ? DumpStackState::DEFAULT : + DumpStackState::COMPLETE; + int result = HiSysEventWrite(HiSysEvent::Domain::FRAMEWORK, "MAIN_THREAD_JANK", + HiSysEvent::EventType::FAULT, + "BUNDLE_VERSION", version, + "BUNDLE_NAME", bundleName, + "BEGIN_TIME", begin_, + "END_TIME", end_, + "EXTERNAL_LOG", path, + "STACK", stack, + "JANK_LEVEL", level); + XCOLLIE_LOGI("HiSysEventWrite result=%{public}d", result); + return result >= 0; +} + +int32_t WatchdogInner::StartProfileMainThread(int32_t interval) +{ + std::unique_lock lock(lock_); + if (isMainThreadProfileTaskEnabled) { + return 0; + } + + if (!MainThreadSampler::GetInstance().Init()) { + return -1; + } + + auto sampleTask = [interval, this]() { + if (g_sampleTaskState < DumpStackState::SAMPLE_COMPLETE) { + MainThreadSampler::GetInstance().Sample(interval); + g_sampleTaskState++; + } else if (g_sampleTaskState == DumpStackState::SAMPLE_COMPLETE) { + ReportMainThreadEvent(); + isMainThreadProfileTaskEnabled = false; + } else { + return; + } + }; + + WatchdogTask task("MainThreadSampler", sampleTask, 0, interval, true); + task.isMainThreadProfileTask = true; + isMainThreadProfileTaskEnabled = true; + InsertWatchdogTaskLocked("MainThreadSampler", std::move(task)); + return 0; +} + +void WatchdogInner::StopProfileMainThread() +{ + g_sampleTaskState = 0; + isMainThreadProfileTaskEnabled = false; +} + +void WatchdogInner::ProcessStackBuffer() +{ + MainThreadSampler::GetInstance().ProcessStackBuffer(); +} + +bool WatchdogInner::CollectStack(std::string& stack) +{ + return MainThreadSampler::GetInstance().CollectStack(stack, true); +} + static TimePoint DistributeStart(const std::string& name) { return std::chrono::steady_clock::now(); @@ -113,6 +201,33 @@ static void DistributeEnd(const std::string& name, const TimePoint& startTime) XCOLLIE_LOGI("BlockMonitor event name: %{public}s, Duration Time: %{public}" PRId64 " ms", name.c_str(), durationTime); } + + if (g_stackState == DumpStackState::COMPLETE) { + auto diff = endTime - endTime_; + int64_t intervalTime = std::chrono::duration_cast + (diff).count(); + if (intervalTime >= ONE_DAY_LIMIT) { + g_stackState = 0; + WatchdogInner::GetInstance().StopProfileMainThread(); + if (g_traceState == DumpStackState::COMPLETE) { + g_traceState = 0; + } + } + } + if ((endTime - std::chrono::milliseconds(DUMPSTACK_TIME) > startTime && + g_stackState == DumpStackState::COMPLETE)) { + beginTime_ = startTime; + endTime_ = endTime; + g_stackState++; + WatchdogInner::GetInstance().StartProfileMainThread(150); + } + if ((endTime - std::chrono::milliseconds(DUMPTRACE_TIME) > startTime && + g_traceState == DumpStackState::COMPLETE)) { + auto traceCollector = UCollectClient::TraceCollector::Create(); + auto result = traceCollector->CaptureDurationTrace(); + XCOLLIE_LOGI("TraceCollector result: %{public}d", result.retCode); + g_traceState++; + } } int WatchdogInner::AddThread(const std::string &name, @@ -354,13 +469,18 @@ uint64_t WatchdogInner::FetchNextTask(uint64_t now, WatchdogTask& task) } const WatchdogTask& queuedTask = checkerQueue_.top(); - if (&(queuedTask.name) == nullptr) { checkerQueue_.pop(); XCOLLIE_LOGW("queuedTask, failed."); return DEFAULT_TIMEOUT; } + if (queuedTask.isMainThreadProfileTask && !isMainThreadProfileTaskEnabled) { + checkerQueue_.pop(); + taskNameSet_.erase("MainThreadSampler"); + return DEFAULT_TIMEOUT; + } + if (g_existFile && queuedTask.name == IPC_FULL && now - g_nextKickTime > INTERVAL_KICK_TIME) { if (KickWatchdog()) { g_nextKickTime = now; diff --git a/frameworks/native/watchdog_inner.h b/frameworks/native/watchdog_inner.h index 6b60bc8..de82386 100644 --- a/frameworks/native/watchdog_inner.h +++ b/frameworks/native/watchdog_inner.h @@ -58,6 +58,11 @@ public: static void KillPeerBinderProcess(const std::string &description); std::string currentScene_; + int32_t StartProfileMainThread(int32_t interval); + void StopProfileMainThread(); + void ProcessStackBuffer(); + bool CollectStack(std::string& stack); + private: bool Start(); bool Stop(); @@ -69,6 +74,7 @@ private: uint64_t FetchNextTask(uint64_t now, WatchdogTask& task); void ReInsertTaskIfNeed(WatchdogTask& task); void CreateWatchdogThreadIfNeed(); + bool ReportMainThreadEvent(); static const unsigned int MAX_WATCH_NUM = 128; // 128: max handler thread std::priority_queue checkerQueue_; // protected by lock_ @@ -84,6 +90,9 @@ private: int cntCallback_; time_t timeCallback_; bool isHmos = false; + + bool isMainThreadProfileTaskEnabled {false}; + int32_t sampleInterval {500}; }; } // end of namespace HiviewDFX } // end of namespace OHOS diff --git a/frameworks/native/watchdog_task.h b/frameworks/native/watchdog_task.h index 0a43c43..9347b06 100644 --- a/frameworks/native/watchdog_task.h +++ b/frameworks/native/watchdog_task.h @@ -83,6 +83,7 @@ public: int countLimit; int noTriggerCount; std::vector triggerTimes; + bool isMainThreadProfileTask {false}; }; } // end of namespace HiviewDFX } // end of namespace OHOS diff --git a/frameworks/native/xcollie_utils.cpp b/frameworks/native/xcollie_utils.cpp index a5f08c3..bfb8996 100644 --- a/frameworks/native/xcollie_utils.cpp +++ b/frameworks/native/xcollie_utils.cpp @@ -15,6 +15,7 @@ #include "xcollie_utils.h" #include +#include #include #include @@ -22,6 +23,12 @@ #include #include #include +#include +#include +#include "bundle_mgr_client.h" +#include "directory_ex.h" +#include "file_ex.h" +#include "storage_acl.h" #include "parameter.h" namespace OHOS { @@ -274,5 +281,91 @@ bool KillProcessByPid(int32_t pid) } return (ret < 0 ? false : true); } + +std::string GetFormatDate() +{ + time_t t = time(0); + char tmp[32] = {0}; + strftime(tmp, sizeof(tmp), "%Y%m%d%H%M%S", localtime(&t)); + std::string date(tmp); + return date; +} + +bool WriteStackToFd(int32_t pid, std::string& path, std::string& stack) +{ + std::string watchdogDir = "/data/storage/el2/log/watchdog"; + constexpr mode_t defaultLogDirMode = 0770; + if (!OHOS::FileExists(watchdogDir)) { + OHOS::ForceCreateDirectory(watchdogDir); + OHOS::ChangeModeDirectory(watchdogDir, defaultLogDirMode); + } + if (OHOS::StorageDaemon::AclSetAccess(watchdogDir, "g:1201:rwx") != 0) { + XCOLLIE_LOGI("Failed to AclSetAccess"); + return false; + } + std::string time = GetFormatDate(); + path = watchdogDir + "/" + "MAIN_THREAD_JANK" + "_" + time.c_str() + "_" + + std::to_string(pid).c_str() + ".txt"; + constexpr mode_t defaultLogFileMode = 0644; + auto fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, defaultLogFileMode); + if (fd < 0) { + XCOLLIE_LOGI("Failed to create path"); + return false; + } else { + XCOLLIE_LOGI("path=%{public}s", path.c_str()); + } + OHOS::SaveStringToFd(fd, stack); + close(fd); + + return true; +} + +std::string GetApplicationNameById(int32_t uid) +{ + std::string bundleName; + AppExecFwk::BundleMgrClient client; + if (client.GetNameForUid(uid, bundleName) != ERR_OK) { + XCOLLIE_LOGI("Failed to query bundleName from bms, uid=%{public}d", uid); + } else { + XCOLLIE_LOGI("bundleName of uid%{public}d is %{public}s", uid, bundleName.c_str()); + } + return bundleName; +} + +int32_t GetProcessUid(int32_t pid) +{ + int32_t uid = -1; + struct stat st; + std::string path = "/proc/" + std::to_string(pid) + "/attr"; + if (stat(path.c_str(), &st) == -1) { + XCOLLIE_LOGI("stat %{public}s error.", path.c_str()); + return uid; + } + uid = static_cast(st.st_uid); + return uid; +} + +std::string GetApplicationVersion(int32_t pid) +{ + int32_t uid = GetProcessUid(pid); + if (uid < MIN_APP_USERID) { + return ""; + } + const std::string bundleName = GetApplicationNameById(uid); + if (bundleName.empty()) { + return ""; + } + AppExecFwk::BundleInfo info; + AppExecFwk::BundleMgrClient client; + if (!client.GetBundleInfo(bundleName, AppExecFwk::BundleFlag::GET_BUNDLE_DEFAULT, + info, AppExecFwk::Constants::ALL_USERID)) { + XCOLLIE_LOGI("Failed to query bundleName from bms, uid=%{public}d", uid); + return ""; + } else { + XCOLLIE_LOGI("version of %{public}s is %{public}s", bundleName.c_str(), + info.versionName.c_str()); + } + return info.versionName; +} } // end of HiviewDFX } // end of OHOS \ No newline at end of file diff --git a/frameworks/native/xcollie_utils.h b/frameworks/native/xcollie_utils.h index 83c60e9..5e5448e 100644 --- a/frameworks/native/xcollie_utils.h +++ b/frameworks/native/xcollie_utils.h @@ -32,6 +32,11 @@ namespace OHOS { namespace HiviewDFX { +enum DumpStackState { + DEFAULT = 0, + COMPLETE = 1, + SAMPLE_COMPLETE = 2 +}; constexpr char WMS_FULL_NAME[] = "WindowManagerService"; constexpr char IPC_FULL[] = "IPC_FULL"; constexpr char IPC_CHECKER[] = "IpcChecker"; @@ -39,8 +44,10 @@ constexpr int64_t SEC_TO_MANOSEC = 1000000000; constexpr int64_t SEC_TO_MICROSEC = 1000000; const int BUFF_STACK_SIZE = 20 * 1024; const int FFRT_BUFFER_SIZE = 512 * 1024; +const int MIN_APP_USERID = 10000; const int MAX_NAME_SIZE = 128; const int MIN_WAIT_NUM = 3; +const int64_t ONE_DAY_LIMIT = 8640000; const inline std::string LOGGER_BINDER_PROC_PATH = "/proc/transaction_proc"; #define XCOLLIE_LOGF(...) HILOG_FATAL(LOG_CORE, ##__VA_ARGS__) @@ -74,6 +81,16 @@ void SplitStr(const std::string& str, const std::string& sep, int ParsePeerBinderPid(std::ifstream& fin, int32_t pid); bool KillProcessByPid(int32_t pid); + +std::string GetFormatDate(); + +bool WriteStackToFd(int32_t pid, std::string& path, std::string& stack); + +std::string GetApplicationNameById(int32_t uid); + +int32_t GetProcessUid(int32_t pid); + +std::string GetApplicationVersion(int32_t pid); } // end of HiviewDFX } // end of OHOS #endif -- Gitee