From c1207b1a3459690ab85015b70fd9da44a92392e7 Mon Sep 17 00:00:00 2001 From: klcf0220 Date: Tue, 1 Jul 2025 09:49:03 +0800 Subject: [PATCH] lite perf Signed-off-by: klcf0220 --- faultloggerd.gni | 1 + interfaces/common/dfx_define.h | 5 + interfaces/common/dfx_dump_request.h | 1 + interfaces/common/dfx_dump_res.h | 14 + interfaces/common/dfx_lperf.h | 40 ++ interfaces/common/dfx_socket_request.h | 2 + interfaces/innerkits/dump_catcher/BUILD.gn | 1 + .../dump_catcher/dfx_dump_catcher.cpp | 11 - .../dump_catcher/include/lite_perf.h | 64 +++ .../dump_catcher/libdfx_dumpcatcher.map | 4 + .../innerkits/dump_catcher/lite_perf.cpp | 377 ++++++++++++++++++ .../faultloggerd_client.cpp | 33 ++ .../include/faultloggerd_client.h | 16 + .../signal_handler/dfx_dumprequest.c | 6 +- services/fault_logger_pipe.cpp | 49 +++ services/fault_logger_pipe.h | 21 + services/fault_logger_server.cpp | 1 + services/fault_logger_service.cpp | 65 +++ services/fault_logger_service.h | 9 + test/unittest/dump_catcher/BUILD.gn | 31 +- test/unittest/dump_catcher/lite_perf_test.cpp | 195 +++++++++ .../faultloggerd/faultlogger_server_test.cpp | 83 ++++ tools/process_dump/BUILD.gn | 10 + tools/process_dump/lite_perf_dumper.cpp | 134 +++++++ tools/process_dump/lite_perf_dumper.h | 44 ++ .../process_dump/lperf/lperf_event_record.cpp | 134 +++++++ tools/process_dump/lperf/lperf_event_record.h | 112 ++++++ tools/process_dump/lperf/lperf_events.cpp | 258 ++++++++++++ tools/process_dump/lperf/lperf_events.h | 78 ++++ tools/process_dump/lperf/lperf_record.cpp | 125 ++++++ tools/process_dump/lperf/lperf_record.h | 60 +++ tools/process_dump/main.cpp | 15 +- 32 files changed, 1979 insertions(+), 20 deletions(-) create mode 100644 interfaces/common/dfx_lperf.h create mode 100644 interfaces/innerkits/dump_catcher/include/lite_perf.h create mode 100644 interfaces/innerkits/dump_catcher/lite_perf.cpp create mode 100644 test/unittest/dump_catcher/lite_perf_test.cpp create mode 100644 tools/process_dump/lite_perf_dumper.cpp create mode 100644 tools/process_dump/lite_perf_dumper.h create mode 100644 tools/process_dump/lperf/lperf_event_record.cpp create mode 100644 tools/process_dump/lperf/lperf_event_record.h create mode 100644 tools/process_dump/lperf/lperf_events.cpp create mode 100644 tools/process_dump/lperf/lperf_events.h create mode 100644 tools/process_dump/lperf/lperf_record.cpp create mode 100644 tools/process_dump/lperf/lperf_record.h diff --git a/faultloggerd.gni b/faultloggerd.gni index 300de23f8..ac10b3373 100644 --- a/faultloggerd.gni +++ b/faultloggerd.gni @@ -22,6 +22,7 @@ declare_args() { has_libunwindstack = false processdump_minidebuginfo_enable = true faultloggerd_hisysevent_enable = false + faultloggerd_liteperf_enable = false processdump_parse_lock_owner_enable = true faultloggerd_enable_build_targets = true if (defined(global_parts_info)) { diff --git a/interfaces/common/dfx_define.h b/interfaces/common/dfx_define.h index 44fc1c82e..7a41b72c0 100644 --- a/interfaces/common/dfx_define.h +++ b/interfaces/common/dfx_define.h @@ -67,6 +67,11 @@ static const char* const PROC_SELF_CMDLINE_PATH = "/proc/self/cmdline"; static const char* const PROC_SELF_COMM_PATH = "/proc/self/comm"; static const char* const PROC_SELF_MAPS_PATH = "/proc/self/maps"; static const char* const PROC_SELF_EXE_PATH = "/proc/self/exe"; +#ifdef DFX_LOG_HILOG_BASE +static const char* const PROCESSDUMP_PATH = "/system/bin/processdump"; +#else +static const char* const PROCESSDUMP_PATH = "/bin/processdump"; +#endif #ifndef LIKELY #define LIKELY(exp) (__builtin_expect(!!(exp), true)) diff --git a/interfaces/common/dfx_dump_request.h b/interfaces/common/dfx_dump_request.h index eab73f447..9180fcd2e 100644 --- a/interfaces/common/dfx_dump_request.h +++ b/interfaces/common/dfx_dump_request.h @@ -35,6 +35,7 @@ enum ProcessDumpType : int32_t { DUMP_TYPE_JEMALLOC, DUMP_TYPE_BADFD, DUMP_TYPE_COREDUMP, + DUMP_TYPE_LITEPERF, }; /** diff --git a/interfaces/common/dfx_dump_res.h b/interfaces/common/dfx_dump_res.h index dd55937f2..6d63f9959 100644 --- a/interfaces/common/dfx_dump_res.h +++ b/interfaces/common/dfx_dump_res.h @@ -20,6 +20,20 @@ namespace OHOS { namespace HiviewDFX { +/** + * @brief dump poll error code + * It describes the status of dump poll. + */ +enum DfxDumpPollRes : int32_t { + DUMP_POLL_INIT = -1, + DUMP_POLL_OK, + DUMP_POLL_FD, + DUMP_POLL_FAILED, + DUMP_POLL_TIMEOUT, + DUMP_POLL_RETURN, + DUMP_POLL_NO_PARSE_SYMBOL, + DUMP_POLL_PARSE_SYMBOL_TIMEOUT, +}; /** * @brief Processdump error code diff --git a/interfaces/common/dfx_lperf.h b/interfaces/common/dfx_lperf.h new file mode 100644 index 000000000..76d0f13a0 --- /dev/null +++ b/interfaces/common/dfx_lperf.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 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 DFX_LPERF_H +#define DFX_LPERF_H + +#include + +namespace OHOS { +namespace HiviewDFX { +namespace { +constexpr int MAX_PERF_TIDS_SIZE = 5; +const char* const LITE_PERF_SPLIT = "tid: "; +} + +/** + * @brief lite perf struct + * It serves as the public definition of the lite perf. + */ +struct LitePerfParam { + int pid; + int tids[MAX_PERF_TIDS_SIZE]; + int freq = 0; + int durationMs = 0; + bool parseMiniDebugInfo = false; +}; +} // namespace HiviewDFX +} // namespace OHOS +#endif diff --git a/interfaces/common/dfx_socket_request.h b/interfaces/common/dfx_socket_request.h index 0c3f7018a..73850a059 100644 --- a/interfaces/common/dfx_socket_request.h +++ b/interfaces/common/dfx_socket_request.h @@ -77,6 +77,8 @@ typedef enum FaultLoggerClientType : int8_t { CANCEL_COREDUMP_CLIENT, /** For request to report coredump status */ COREDUMP_PROCESS_DUMP_CLIENT, + /** For request liteperf file descriptor of pipe */ + PIPE_FD_LITEPERF_CLIENT, } FaultLoggerClientType; typedef struct RequestDataHead { diff --git a/interfaces/innerkits/dump_catcher/BUILD.gn b/interfaces/innerkits/dump_catcher/BUILD.gn index 9cdebc4d6..bdf127e7e 100644 --- a/interfaces/innerkits/dump_catcher/BUILD.gn +++ b/interfaces/innerkits/dump_catcher/BUILD.gn @@ -70,6 +70,7 @@ if (defined(ohos_lite)) { "$faultloggerd_path/common/build:coverage_flags", ] sources = dumpcatcher_sources + sources += [ "lite_perf.cpp" ] cflags = [ "-fstack-protector-strong" ] include_dirs = [ "$faultloggerd_interfaces_path/common" ] version_script = "libdfx_dumpcatcher.map" diff --git a/interfaces/innerkits/dump_catcher/dfx_dump_catcher.cpp b/interfaces/innerkits/dump_catcher/dfx_dump_catcher.cpp index 5ae66bd17..18cc9e30f 100644 --- a/interfaces/innerkits/dump_catcher/dfx_dump_catcher.cpp +++ b/interfaces/innerkits/dump_catcher/dfx_dump_catcher.cpp @@ -63,17 +63,6 @@ static constexpr int WAIT_GET_KERNEL_STACK_TIMEOUT = 1000; // 1000 : time out 10 static constexpr uint32_t HIVIEW_UID = 1201; static constexpr uint32_t FOUNDATION_UID = 5523; -enum DfxDumpPollRes : int32_t { - DUMP_POLL_INIT = -1, - DUMP_POLL_OK, - DUMP_POLL_FD, - DUMP_POLL_FAILED, - DUMP_POLL_TIMEOUT, - DUMP_POLL_RETURN, - DUMP_POLL_NO_PARSE_SYMBOL, - DUMP_POLL_PARSE_SYMBOL_TIMEOUT, -}; - enum DfxDumpStatRes : int32_t { DUMP_RES_NO_KERNELSTACK = -2, DUMP_RES_WITH_KERNELSTACK = -1, diff --git a/interfaces/innerkits/dump_catcher/include/lite_perf.h b/interfaces/innerkits/dump_catcher/include/lite_perf.h new file mode 100644 index 000000000..7d839913f --- /dev/null +++ b/interfaces/innerkits/dump_catcher/include/lite_perf.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025 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 LITE_PERF_H +#define LITE_PERF_H + +#include +#include +#include + +namespace OHOS { +namespace HiviewDFX { +class LitePerf { +public: + LitePerf(); + ~LitePerf() = default; + LitePerf(const LitePerf&) = delete; + LitePerf& operator=(const LitePerf&) = delete; + + /** + * @brief Start process stack sampling + * + * @param tids thread ids + * @param freq freq + * @param durationMs duration ms + * @param parseMiniDebugInfo whether parse MiniDebugInfo + * @return if succeed return 0, otherwise return -1 + */ + int StartProcessStackSampling(const std::vector& tids, int freq, int durationMs, bool parseMiniDebugInfo); + + /** + * @brief Collect process stack sampling By tid + * + * @param tid thread id + * @param stack stack of thread id + * @return if succeed return 0, otherwise return -1 + */ + int CollectSampleStackByTid(int tid, std::string& stack); + + /** + * @brief Finish process stack sampling + * + * @return if succeed return 0, otherwise return -1 + */ + int FinishProcessStackSampling(); + +private: + class Impl; + std::shared_ptr impl_; +}; +} // namespace HiviewDFX +} // namespace OHOS +#endif // LITE_PERF_H \ No newline at end of file diff --git a/interfaces/innerkits/dump_catcher/libdfx_dumpcatcher.map b/interfaces/innerkits/dump_catcher/libdfx_dumpcatcher.map index 18b3a75e8..54fce2ae3 100644 --- a/interfaces/innerkits/dump_catcher/libdfx_dumpcatcher.map +++ b/interfaces/innerkits/dump_catcher/libdfx_dumpcatcher.map @@ -3,6 +3,10 @@ extern "C++" { OHOS::HiviewDFX::DfxDumpCatcher::DfxDumpCatcher*; OHOS::HiviewDFX::DfxDumpCatcher::DumpCatch*; + OHOS::HiviewDFX::LitePerf::LitePerf*; + OHOS::HiviewDFX::LitePerf::StartProcessStackSampling*; + OHOS::HiviewDFX::LitePerf::CollectSampleStackByTid*; + OHOS::HiviewDFX::LitePerf::FinishProcessStackSampling*; }; local: *; diff --git a/interfaces/innerkits/dump_catcher/lite_perf.cpp b/interfaces/innerkits/dump_catcher/lite_perf.cpp new file mode 100644 index 000000000..50e901221 --- /dev/null +++ b/interfaces/innerkits/dump_catcher/lite_perf.cpp @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2025 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 "lite_perf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dfx_define.h" +#include "dfx_dump_res.h" +#include "dfx_log.h" +#include "dfx_lperf.h" +#include "dfx_util.h" +#include "faultloggerd_client.h" +#include "smart_fd.h" + +namespace OHOS { +namespace HiviewDFX { +namespace { +#undef LOG_DOMAIN +#define LOG_DOMAIN 0xD002D11 +#undef LOG_TAG +#define LOG_TAG "DfxLitePerf" + +static constexpr int DUMP_LITEPERF_TIMEOUT_DELAY = 3000; +} + +class LitePerf::Impl { +public: + int StartProcessStackSampling(const std::vector& tids, int freq, int durationMs, bool parseMiniDebugInfo); + int CollectSampleStackByTid(int tid, std::string& stack); + int FinishProcessStackSampling(); + +private: + int ExecDump(const std::vector& tids, int freq, int durationMs, bool parseMiniDebugInfo); + bool ExecDumpPipe(const int (&pipefd)[2], const LitePerfParam& lperf); + + int DumpPoll(const int (&pipeFds)[2], const int timeout); + bool HandlePollEvents(const struct pollfd (&readFds)[2], const int (&pipeFds)[2], bool& bPipeConnect, int& pollRet); + bool DoReadBuf(int fd); + bool DoReadRes(int fd, int& pollRet); + bool ParseSampleStacks(const std::string& source); + + std::string bufMsg_; + std::string resMsg_; + std::map stackMaps_{}; + std::mutex mutex_; + std::atomic isRunning_{false}; +}; + +LitePerf::LitePerf() : impl_(std::make_shared()) +{} + +int LitePerf::StartProcessStackSampling(const std::vector& tids, int freq, int durationMs, bool parseMiniDebugInfo) +{ + return impl_->StartProcessStackSampling(tids, freq, durationMs, parseMiniDebugInfo); +} + +int LitePerf::CollectSampleStackByTid(int tid, std::string& stack) +{ + return impl_->CollectSampleStackByTid(tid, stack); +} + +int LitePerf::FinishProcessStackSampling() +{ + return impl_->FinishProcessStackSampling(); +} + +int LitePerf::Impl::StartProcessStackSampling(const std::vector& tids, int freq, int durationMs, + bool parseMiniDebugInfo) +{ + DFXLOGI("StartProcessStackSampling."); + int res = 0; + if (tids.size() > MAX_PERF_TIDS_SIZE || durationMs < 1) { + DFXLOGE("Failed to tids size."); + return -1; + } + bool expected = false; + if (!isRunning_.compare_exchange_strong(expected, true)) { + DFXLOGW("Process is being sampling."); + return -1; + } + + int pipeReadFd[] = {-1, -1}; + int req = RequestLitePerfPipeFd(FaultLoggerPipeType::PIPE_FD_READ, pipeReadFd); + if (req != ResponseCode::REQUEST_SUCCESS) { + DFXLOGE("Failed to request liteperf pipe read."); + isRunning_.store(false); + res = -1; + return res; + } + + do { + if (ExecDump(tids, freq, durationMs, parseMiniDebugInfo) < 0) { + res = -1; + break; + } + + int timeout = durationMs + DUMP_LITEPERF_TIMEOUT_DELAY; + if (DumpPoll(pipeReadFd, timeout) < 0) { + res = -1; + break; + } + } while (false); + + req = RequestLitePerfDelPipeFd(); + if (req != ResponseCode::REQUEST_SUCCESS) { + DFXLOGE("Failed to request liteperf pipe delete."); + } + isRunning_.store(false); + return res; +} + +int LitePerf::Impl::DumpPoll(const int (&pipeFds)[2], const int timeout) +{ + MAYBE_UNUSED int pollRet = DUMP_POLL_INIT; + struct pollfd readFds[2] = {}; + readFds[0].fd = pipeFds[PIPE_BUF_INDEX]; + readFds[0].events = POLLIN; + readFds[1].fd = pipeFds[PIPE_RES_INDEX]; + readFds[1].events = POLLIN; + int fdsSize = sizeof(readFds) / sizeof(readFds[0]); + bool bPipeConnect = false; + uint64_t endTime = GetAbsTimeMilliSeconds() + static_cast(timeout); + bool isContinue = true; + do { + uint64_t now = GetAbsTimeMilliSeconds(); + if (now >= endTime) { + pollRet = DUMP_POLL_TIMEOUT; + resMsg_.append("Result: poll timeout.\n"); + isContinue = false; + break; + } + int remainTime = static_cast(endTime - now); + + int pRet = poll(readFds, fdsSize, remainTime); + if (pRet < 0) { + if (errno == EINTR) { + continue; + } + pollRet = DUMP_POLL_FAILED; + resMsg_.append("Result: poll error, errno(" + std::to_string(errno) + ")\n"); + isContinue = false; + break; + } else if (pRet == 0) { + pollRet = DUMP_POLL_TIMEOUT; + resMsg_.append("Result: poll timeout.\n"); + isContinue = false; + break; + } + + if (!HandlePollEvents(readFds, pipeFds, bPipeConnect, pollRet)) { + isContinue = false; + break; + } + } while (isContinue); + return pollRet; +} + +bool LitePerf::Impl::HandlePollEvents(const struct pollfd (&readFds)[2], const int (&pipeFds)[2], + bool& bPipeConnect, int& pollRet) +{ + bool bufRet = true; + bool resRet = false; + bool eventRet = true; + for (auto& readFd : readFds) { + if (!bPipeConnect && (static_cast(readFd.revents) & POLLIN)) { + bPipeConnect = true; + } + + if (bPipeConnect && + ((static_cast(readFd.revents) & POLLERR) || (static_cast(readFd.revents) & POLLHUP))) { + resMsg_.append("Result: poll events error.\n"); + eventRet = false; + break; + } + + if ((static_cast(readFd.revents) & POLLIN) != POLLIN) { + continue; + } + + if (readFd.fd == pipeFds[PIPE_BUF_INDEX]) { + bufRet = DoReadBuf(pipeFds[PIPE_BUF_INDEX]); + } else if (readFd.fd == pipeFds[PIPE_RES_INDEX]) { + resRet = DoReadRes(pipeFds[PIPE_RES_INDEX], pollRet); + } + } + if ((eventRet == false) || (bufRet == false) || (resRet == true)) { + DFXLOGI("eventRet:%{public}d bufRet:%{public}d resRet:%{public}d", eventRet, bufRet, resRet); + return false; + } + return true; +} + +bool LitePerf::Impl::DoReadBuf(int fd) +{ + std::vector buffer(MAX_PIPE_SIZE, 0); + ssize_t nread = OHOS_TEMP_FAILURE_RETRY(read(fd, buffer.data(), MAX_PIPE_SIZE)); + if (nread <= 0) { + DFXLOGW("%{public}s :: read error", __func__); + return false; + } + DFXLOGI("%{public}s :: nread: %{public}zu", __func__, nread); + bufMsg_.append(buffer.data(), static_cast(nread)); + return true; +} + +bool LitePerf::Impl::DoReadRes(int fd, int& pollRet) +{ + int32_t res = DumpErrorCode::DUMP_ESUCCESS; + ssize_t nread = OHOS_TEMP_FAILURE_RETRY(read(fd, &res, sizeof(res))); + if (nread <= 0 || nread != sizeof(res)) { + DFXLOGW("%{public}s :: read error", __func__); + return false; + } + pollRet = (res == DUMP_ESUCCESS) ? DUMP_POLL_OK : DUMP_POLL_FAILED; + resMsg_.append("Result: " + DfxDumpRes::ToString(res) + "\n"); + return true; +} + +int LitePerf::Impl::ExecDump(const std::vector& tids, int freq, int durationMs, bool parseMiniDebugInfo) +{ + LitePerfParam lperf; + lperf.pid = getpid(); + for (size_t i = 0; i < tids.size() && i < MAX_PERF_TIDS_SIZE; ++i) { + lperf.tids[i] = tids[i]; + } + lperf.freq = freq; + lperf.durationMs = durationMs; + lperf.parseMiniDebugInfo = parseMiniDebugInfo; + + pid_t pid = 0; + pid = vfork(); + if (pid < 0) { + DFXLOGE("Failed to vfork."); + return -1; + } + if (pid == 0) { + int pipefd[PIPE_NUM_SZ] = {-1, -1}; + if (pipe2(pipefd, O_NONBLOCK) != 0) { + DFXLOGE("Failed to create pipe, errno: %{public}d.", errno); + _exit(-1); + } + + pid_t dumpPid = vfork(); + if (dumpPid < 0) { + DFXLOGE("Failed to vfork."); + _exit(-1); + } + if (dumpPid == 0) { + ExecDumpPipe(pipefd, lperf); + _exit(0); + } else { + _exit(0); + } + } + int res = waitpid(pid, nullptr, 0); + if (res < 0) { + DFXLOGE("Failed to wait pid(%{public}d), errno(%{public}d)", pid, errno); + } else { + DFXLOGI("wait pid(%{public}d) exit", pid); + } + return 0; +} + +bool LitePerf::Impl::ExecDumpPipe(const int (&pipefd)[2], const LitePerfParam& lperf) +{ + ssize_t writeLen = sizeof(LitePerfParam); + if (fcntl(pipefd[1], F_SETPIPE_SZ, writeLen) < writeLen) { + DFXLOGE("Failed to set pipe buffer size, errno(%{public}d).", errno); + return false; + } + + struct iovec iovs[1] = { + { + .iov_base = (void *)(&lperf), + .iov_len = sizeof(struct LitePerfParam) + }, + }; + + if (OHOS_TEMP_FAILURE_RETRY(writev(pipefd[PIPE_WRITE], iovs, 1)) != writeLen) { + DFXLOGE("Failed to write pipe, errno(%{public}d)", errno); + return false; + } + OHOS_TEMP_FAILURE_RETRY(dup2(pipefd[PIPE_READ], STDOUT_FILENO)); + if (pipefd[PIPE_READ] != STDOUT_FILENO) { + close(pipefd[PIPE_READ]); + } + close(pipefd[PIPE_WRITE]); + + DFXLOGI("execl processdump -liteperf."); + execl(PROCESSDUMP_PATH, "processdump", "-liteperf", nullptr); + DFXLOGE("Failed to execl processdump -liteperf, errno(%{public}d)", errno); + return false; +} + +bool LitePerf::Impl::ParseSampleStacks(const std::string& source) +{ + if (source.empty()) { + return false; + } + + std::unique_lock lck(mutex_); + if (stackMaps_.empty()) { + std::vector stacks; + OHOS::SplitStr(source, LITE_PERF_SPLIT, stacks); + for (auto& str : stacks) { + size_t pos = str.find("\n"); + if (pos == std::string::npos) { + DFXLOGW("error str: %s", str.c_str()); + continue; + } + + std::string tidStr = str.substr(0, pos); + int tmpTid; + int ret = sscanf_s(tidStr.c_str(), "%d", &tmpTid); + if (ret != 1) { + DFXLOGE("sscanf %{public}s failed.", tidStr.c_str()); + continue; + } + std::string stack = str.substr(pos + 1); + DFXLOGD("emplace tid:%{public}d, str: %s", tmpTid, stack.c_str()); + stackMaps_.emplace(tmpTid, std::move(stack)); + } + } + return (stackMaps_.size() > 0); +} + +int LitePerf::Impl::CollectSampleStackByTid(int tid, std::string& stack) +{ + DFXLOGI("CollectSampleStackByTid, tid:%{public}d.", tid); + if (!ParseSampleStacks(bufMsg_)) { + return -1; + } + + std::unique_lock lck(mutex_); + if (stackMaps_.find(tid) != stackMaps_.end()) { + stack = stackMaps_[tid]; + if (stack.size() > 0) { + return 0; + } + } + return -1; +} + +int LitePerf::Impl::FinishProcessStackSampling() +{ + DFXLOGI("FinishProcessStackSampling."); + std::unique_lock lck(mutex_); + stackMaps_.clear(); + bufMsg_.clear(); + resMsg_.clear(); + return 0; +} +} // namespace HiviewDFX +} // namespace OHOS \ No newline at end of file diff --git a/interfaces/innerkits/faultloggerd_client/faultloggerd_client.cpp b/interfaces/innerkits/faultloggerd_client/faultloggerd_client.cpp index dcaf2fb18..b43863c92 100644 --- a/interfaces/innerkits/faultloggerd_client/faultloggerd_client.cpp +++ b/interfaces/innerkits/faultloggerd_client/faultloggerd_client.cpp @@ -131,6 +131,39 @@ int32_t RequestSdkDump(int32_t pid, int32_t tid, int (&pipeReadFd)[2], bool isJs #endif } +int32_t RequestLitePerfPipeFd(int32_t pipeType, int (&pipeFd)[2]) +{ +#ifndef is_ohos_lite + if (pipeType < FaultLoggerPipeType::PIPE_FD_READ || pipeType > FaultLoggerPipeType::PIPE_FD_DELETE) { + DFXLOGE("%{public}s.%{public}s :: pipeType: %{public}d failed.", FAULTLOGGERD_CLIENT_TAG, __func__, pipeType); + return ResponseCode::DEFAULT_ERROR_CODE; + } + DFXLOGI("%{public}s.%{public}s :: pipeType: %{public}d.", FAULTLOGGERD_CLIENT_TAG, __func__, pipeType); + PipFdRequestData request{}; + request.pipeType = static_cast(pipeType); + FillRequestHeadData(request.head, FaultLoggerClientType::PIPE_FD_LITEPERF_CLIENT); + request.pid = getpid(); + SocketRequestData socketRequestData = {&request, sizeof(request)}; + SocketFdData socketFdData = {pipeFd, PIPE_NUM_SZ}; + return SendRequestToServer(GetSocketName().c_str(), socketRequestData, CRASHDUMP_SOCKET_TIMEOUT, &socketFdData); +#else + return ResponseCode::DEFAULT_ERROR_CODE; +#endif +} + +int32_t RequestLitePerfDelPipeFd() +{ +#ifndef is_ohos_lite + PipFdRequestData request{}; + FillRequestHeadData(request.head, FaultLoggerClientType::PIPE_FD_LITEPERF_CLIENT); + request.pipeType = FaultLoggerPipeType::PIPE_FD_DELETE; + request.pid = getpid(); + return SendRequestToServer(GetSocketName().c_str(), {&request, sizeof(request)}, SDKDUMP_SOCKET_TIMEOUT); +#else + return ResponseCode::DEFAULT_ERROR_CODE; +#endif +} + int32_t RequestPipeFd(int32_t pid, int32_t pipeType, int (&pipeFd)[2]) { #ifndef is_ohos_lite diff --git a/interfaces/innerkits/faultloggerd_client/include/faultloggerd_client.h b/interfaces/innerkits/faultloggerd_client/include/faultloggerd_client.h index e7dd60ab2..1441eb1c4 100644 --- a/interfaces/innerkits/faultloggerd_client/include/faultloggerd_client.h +++ b/interfaces/innerkits/faultloggerd_client/include/faultloggerd_client.h @@ -55,6 +55,22 @@ int32_t RequestDelPipeFd(int32_t pid); */ int32_t RequestFileDescriptorEx(struct FaultLoggerdRequest* request); +/** + * @brief request lipeperf pipe file descriptor + * @param pipeType type of request about pipe + * @param pipeFd pipeFd[0] to transfer call stack message and + * pipeFd[1] to transfer backtrace result + * @return if succeed return 0, otherwise return -1 +*/ +int32_t RequestLitePerfPipeFd(int32_t pipeType, int (&pipeFd)[2]); + +/** + * @brief request delete lite perf file descriptor + * @param pid process id of request pipe + * @return if succeed return 0, otherwise return the error code +*/ +int32_t RequestLitePerfDelPipeFd(); + /** * @brief request dump stack about process * @param pid process id diff --git a/interfaces/innerkits/signal_handler/dfx_dumprequest.c b/interfaces/innerkits/signal_handler/dfx_dumprequest.c index 3dd41b89b..798823dfd 100644 --- a/interfaces/innerkits/signal_handler/dfx_dumprequest.c +++ b/interfaces/innerkits/signal_handler/dfx_dumprequest.c @@ -260,11 +260,7 @@ static int DFX_ExecDump(void) return INHERIT_CAP_FAIL; } DFXLOGI("execl processdump."); -#ifdef DFX_LOG_HILOG_BASE - execl("/system/bin/processdump", "processdump", "-signalhandler", NULL); -#else - execl("/bin/processdump", "processdump", "-signalhandler", NULL); -#endif + execl(PROCESSDUMP_PATH, "processdump", "-signalhandler", NULL); DFXLOGE("Failed to execl processdump, errno(%{public}d)", errno); FillCrashExceptionAndReport(CRASH_SIGNAL_EEXECL); return errno; diff --git a/services/fault_logger_pipe.cpp b/services/fault_logger_pipe.cpp index 80fe932bb..3214a888c 100644 --- a/services/fault_logger_pipe.cpp +++ b/services/fault_logger_pipe.cpp @@ -70,6 +70,7 @@ int FaultLoggerPipe::GetWriteFd() } std::list FaultLoggerPipePair::sdkDumpPipes_{}; +std::list LitePerfPipePair::pipes_{}; FaultLoggerPipePair::FaultLoggerPipePair(int32_t pid, uint64_t requestTime) : pid_(pid), requestTime_(requestTime) {} @@ -130,5 +131,53 @@ void FaultLoggerPipePair::DelSdkDumpPipePair(int pid) return faultLoggerPipePair.pid_ == pid; }); } + +LitePerfPipePair::LitePerfPipePair(int32_t uid) : uid_(uid) {} + +int32_t LitePerfPipePair::GetPipeFd(PipeFdUsage usage, FaultLoggerPipeType pipeType) +{ + FaultLoggerPipe& targetPipe = (usage == PipeFdUsage::BUFFER_FD) ? faultLoggerPipeBuf_ : faultLoggerPipeRes_; + if (pipeType == FaultLoggerPipeType::PIPE_FD_READ) { + return targetPipe.GetReadFd(); + } + if (pipeType == FaultLoggerPipeType::PIPE_FD_WRITE) { + return targetPipe.GetWriteFd(); + } + return -1; +} + +LitePerfPipePair& LitePerfPipePair::CreatePipePair(int uid) +{ + auto pipePair = GetPipePair(uid); + if (pipePair != nullptr) { + return *pipePair; + } + return pipes_.emplace_back(uid); +} + +bool LitePerfPipePair::CheckDumpRecord(int uid) +{ + auto iter = std::find_if(pipes_.begin(), pipes_.end(), + [uid](const LitePerfPipePair& pipePair) { + return pipePair.uid_ == uid; + }); + return iter != pipes_.end(); +} + +LitePerfPipePair* LitePerfPipePair::GetPipePair(int uid) +{ + auto iter = std::find_if(pipes_.begin(), pipes_.end(), + [uid](const LitePerfPipePair& pipePair) { + return pipePair.uid_ == uid; + }); + return iter == pipes_.end() ? nullptr : &(*iter); +} + +void LitePerfPipePair::DelPipePair(int uid) +{ + pipes_.remove_if([uid](const LitePerfPipePair& pipePair) { + return pipePair.uid_ == uid; + }); +} } // namespace HiviewDfx } // namespace OHOS diff --git a/services/fault_logger_pipe.h b/services/fault_logger_pipe.h index 73fbba699..8e72ede69 100644 --- a/services/fault_logger_pipe.h +++ b/services/fault_logger_pipe.h @@ -51,6 +51,7 @@ public: FaultLoggerPipePair(int32_t pid, uint64_t requestTime); FaultLoggerPipePair(FaultLoggerPipePair&&) noexcept = default; FaultLoggerPipePair& operator=(FaultLoggerPipePair&&) noexcept = default; + static FaultLoggerPipePair& CreateSdkDumpPipePair(int pid, uint64_t requestTime); static bool CheckSdkDumpRecord(int pid, uint64_t checkTime); static FaultLoggerPipePair* GetSdkDumpPipePair(int pid); @@ -65,6 +66,26 @@ private: FaultLoggerPipe faultLoggerPipeRes_; static std::list sdkDumpPipes_; }; + +class LitePerfPipePair { +public: + int32_t GetPipeFd(PipeFdUsage usage, FaultLoggerPipeType pipeType); + explicit LitePerfPipePair(int32_t uid); + LitePerfPipePair(LitePerfPipePair&&) noexcept = default; + LitePerfPipePair& operator=(LitePerfPipePair&&) noexcept = default; + + static LitePerfPipePair& CreatePipePair(int uid); + static bool CheckDumpRecord(int uid); + static LitePerfPipePair* GetPipePair(int uid); + static void DelPipePair(int uid); +private: + LitePerfPipePair(const LitePerfPipePair&) = delete; + LitePerfPipePair& operator=(const LitePerfPipePair&) = delete; + int32_t uid_; + FaultLoggerPipe faultLoggerPipeBuf_; + FaultLoggerPipe faultLoggerPipeRes_; + static std::list pipes_; +}; } // namespace HiviewDFX } // namespace OHOS #endif // FAULT_LOGGER_PIPE_H diff --git a/services/fault_logger_server.cpp b/services/fault_logger_server.cpp index 7cbc9f0cf..e8e164348 100644 --- a/services/fault_logger_server.cpp +++ b/services/fault_logger_server.cpp @@ -52,6 +52,7 @@ bool SocketServer::Init() AddService(PIPE_FD_CLIENT, std::make_unique()); AddService(SDK_DUMP_CLIENT, std::make_unique()); + AddService(PIPE_FD_LITEPERF_CLIENT, std::make_unique()); if (!AddServerListener(SERVER_SDKDUMP_SOCKET_NAME)) { return false; } diff --git a/services/fault_logger_service.cpp b/services/fault_logger_service.cpp index 3621c9091..eedf6a755 100644 --- a/services/fault_logger_service.cpp +++ b/services/fault_logger_service.cpp @@ -732,6 +732,71 @@ int32_t PipeService::OnRequest(const std::string& socketName, int32_t connection SendFileDescriptorToSocket(connectionFd, fds, PIPE_NUM_SZ); return responseData; } + +bool LitePerfPipeService::Filter(const std::string &socketName, int32_t connectionFd, + const PipFdRequestData &requestData, int& uid) +{ + if (requestData.pipeType > FaultLoggerPipeType::PIPE_FD_DELETE || + requestData.pipeType < FaultLoggerPipeType::PIPE_FD_READ) { + return false; + } + + struct ucred creds{}; + if (!FaultCommonUtil::GetUcredByPeerCred(creds, connectionFd)) { + return false; + } + if (creds.pid != requestData.pid) { + DFXLOGW("Failed to check request credential request:%{public}d, cred:%{public}d, fd:%{public}d", + requestData.pid, creds.pid, connectionFd); + return false; + } + uid = creds.uid; + return true; +} + +int32_t LitePerfPipeService::OnRequest(const std::string& socketName, int32_t connectionFd, + const PipFdRequestData& requestData) +{ + DFX_TRACE_SCOPED("LitePerfPipeServiceOnRequest"); + int uid; + if (!Filter(socketName, connectionFd, requestData, uid)) { + return ResponseCode::REQUEST_REJECT; + } + int32_t responseData = ResponseCode::REQUEST_SUCCESS; + if (requestData.pipeType == FaultLoggerPipeType::PIPE_FD_DELETE) { + LitePerfPipePair::DelPipePair(uid); + SendMsgToSocket(connectionFd, &responseData, sizeof(responseData)); + return responseData; + } + + int32_t fds[PIPE_NUM_SZ] = {0}; + if (requestData.pipeType == FaultLoggerPipeType::PIPE_FD_READ) { + if (LitePerfPipePair::CheckDumpRecord(uid)) { + DFXLOGE("%{public}s :: uid(%{public}d) is dumping.", FAULTLOGGERD_SERVICE_TAG, uid); + return ResponseCode::SDK_DUMP_REPEAT; + } + auto& pipePair = LitePerfPipePair::CreatePipePair(uid); + fds[PIPE_BUF_INDEX] = pipePair.GetPipeFd(PipeFdUsage::BUFFER_FD, FaultLoggerPipeType::PIPE_FD_READ); + fds[PIPE_RES_INDEX] = pipePair.GetPipeFd(PipeFdUsage::RESULT_FD, FaultLoggerPipeType::PIPE_FD_READ); + } else if (requestData.pipeType == FaultLoggerPipeType::PIPE_FD_WRITE) { + LitePerfPipePair* pipePair = LitePerfPipePair::GetPipePair(uid); + if (pipePair == nullptr) { + DFXLOGE("%{public}s :: cannot find pipe fd for pid(%{public}d).", + FAULTLOGGERD_SERVICE_TAG, requestData.pid); + return ResponseCode::ABNORMAL_SERVICE; + } + fds[PIPE_BUF_INDEX] = pipePair->GetPipeFd(PipeFdUsage::BUFFER_FD, FaultLoggerPipeType::PIPE_FD_WRITE); + fds[PIPE_RES_INDEX] = pipePair->GetPipeFd(PipeFdUsage::RESULT_FD, FaultLoggerPipeType::PIPE_FD_WRITE); + } + if (fds[PIPE_BUF_INDEX] < 0 || fds[PIPE_RES_INDEX] < 0) { + DFXLOGE("%{public}s :: failed to get fds for pipeType(%{public}d).", FAULTLOGGERD_SERVICE_TAG, + requestData.pipeType); + return ResponseCode::ABNORMAL_SERVICE; + } + SendMsgToSocket(connectionFd, &responseData, sizeof(responseData)); + SendFileDescriptorToSocket(connectionFd, fds, PIPE_NUM_SZ); + return responseData; +} #endif } } \ No newline at end of file diff --git a/services/fault_logger_service.h b/services/fault_logger_service.h index c8214294b..328dc0fcb 100644 --- a/services/fault_logger_service.h +++ b/services/fault_logger_service.h @@ -119,6 +119,15 @@ private: static int32_t SendSigDumpToHapWatchdog(pid_t pid, siginfo_t& si); }; +class LitePerfPipeService : public FaultLoggerService { +public: + int32_t OnRequest(const std::string& socketName, int32_t connectionFd, + const PipFdRequestData& requestData) override; +private: + static bool Filter(const std::string& socketName, int32_t connectionFd, + const PipFdRequestData& requestData, int& uid); +}; + class PipeService : public FaultLoggerService { public: int32_t OnRequest(const std::string& socketName, int32_t connectionFd, diff --git a/test/unittest/dump_catcher/BUILD.gn b/test/unittest/dump_catcher/BUILD.gn index e3886a8f8..f130b93d5 100644 --- a/test/unittest/dump_catcher/BUILD.gn +++ b/test/unittest/dump_catcher/BUILD.gn @@ -105,8 +105,37 @@ if (defined(ohos_lite)) { ] } + ohos_unittest("test_liteperf") { + module_out_path = module_output_path + + visibility = [ "*:*" ] + + include_dirs = [ + "$faultloggerd_interfaces_path/common", + "$faultloggerd_path/interfaces/innerkits/dump_catcher/include", + "$faultloggerd_path/test/utils", + ] + + sources = [ "lite_perf_test.cpp" ] + + deps = [ + "$faultloggerd_common_path/dfxlog:dfx_hilog_base", + "$faultloggerd_path/interfaces/innerkits/dump_catcher:libdfx_dumpcatcher", + "$faultloggerd_path/interfaces/innerkits/faultloggerd_client:libfaultloggerd", + "$faultloggerd_path/test/utils:dfx_test_util", + ] + external_deps = [ + "c_utils:utils", + "googletest:gtest_main", + "hilog:libhilog", + ] + } + group("unittest") { testonly = true - deps = [ ":test_dumpcatcher" ] + deps = [ + ":test_dumpcatcher", + ":test_liteperf", + ] } } diff --git a/test/unittest/dump_catcher/lite_perf_test.cpp b/test/unittest/dump_catcher/lite_perf_test.cpp new file mode 100644 index 000000000..b1a8e65bc --- /dev/null +++ b/test/unittest/dump_catcher/lite_perf_test.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2025 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 +#include +#include +#include +#include +#include "dfx_test_util.h" +#include "file_ex.h" +#include "lite_perf.h" + +using namespace testing; +using namespace testing::ext; + +namespace OHOS { +namespace HiviewDFX { + +class LitePerfTest : public testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + void SetUp(); + void TearDown(); +}; + +void LitePerfTest::SetUpTestCase() +{} + +void LitePerfTest::TearDownTestCase() +{} + +void LitePerfTest::SetUp() +{} + +void LitePerfTest::TearDown() +{} + +/** + * @tc.name: LitePerfTestTest002 + * @tc.desc: test LitePerf invalid tids + * @tc.type: FUNC + */ +HWTEST_F(LitePerfTest, LitePerfTestTest002, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "LitePerfTestTest002: start."; + if (IsLinuxKernel()) { + return; + } + std::vector tids; + int tid = getpid(); + int freq = 100; + int durationMs = 5000; + bool parseMiniDebugInfo = false; + LitePerf litePerf; + int ret = litePerf.StartProcessStackSampling(tids, freq, durationMs, parseMiniDebugInfo); + EXPECT_EQ(ret, -1); + std::string sampleStack; + ret = litePerf.CollectSampleStackByTid(tid, sampleStack); + EXPECT_EQ(ret, -1); + ASSERT_TRUE(sampleStack.size() == 0); + ret = litePerf.FinishProcessStackSampling(); + EXPECT_EQ(ret, 0); + GTEST_LOG_(INFO) << "LitePerfTest002: end."; +} + +/** + * @tc.name: LitePerfTestTest003 + * @tc.desc: test LitePerf invalid freq + * @tc.type: FUNC + */ +HWTEST_F(LitePerfTest, LitePerfTestTest003, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "LitePerfTestTest003: start."; + if (IsLinuxKernel()) { + return; + } + std::vector tids; + int tid = getpid(); + tids.emplace_back(tid); + int freq = 2000; + int durationMs = 5000; + bool parseMiniDebugInfo = false; + LitePerf litePerf; + int ret = litePerf.StartProcessStackSampling(tids, freq, durationMs, parseMiniDebugInfo); + EXPECT_EQ(ret, -1); + std::string sampleStack; + ret = litePerf.CollectSampleStackByTid(tid, sampleStack); + EXPECT_EQ(ret, -1); + ASSERT_TRUE(sampleStack.size() == 0); + ret = litePerf.FinishProcessStackSampling(); + EXPECT_EQ(ret, 0); + GTEST_LOG_(INFO) << "LitePerfTest003: end."; +} + +/** + * @tc.name: LitePerfTestTest004 + * @tc.desc: test LitePerf invalid freq -1 + * @tc.type: FUNC + */ +HWTEST_F(LitePerfTest, LitePerfTestTest004, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "LitePerfTestTest004: start."; + if (IsLinuxKernel()) { + return; + } + std::vector tids; + int tid = getpid(); + tids.emplace_back(tid); + int freq = -1; + int durationMs = 5000; + bool parseMiniDebugInfo = false; + LitePerf litePerf; + int ret = litePerf.StartProcessStackSampling(tids, freq, durationMs, parseMiniDebugInfo); + EXPECT_EQ(ret, -1); + std::string sampleStack; + ret = litePerf.CollectSampleStackByTid(tid, sampleStack); + EXPECT_EQ(ret, -1); + ASSERT_TRUE(sampleStack.size() == 0); + ret = litePerf.FinishProcessStackSampling(); + EXPECT_EQ(ret, 0); + GTEST_LOG_(INFO) << "LitePerfTest004: end."; +} + +/** + * @tc.name: LitePerfTestTest005 + * @tc.desc: test LitePerf invalid time + * @tc.type: FUNC + */ +HWTEST_F(LitePerfTest, LitePerfTestTest005, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "LitePerfTestTest005: start."; + if (IsLinuxKernel()) { + return; + } + std::vector tids; + int tid = getpid(); + tids.emplace_back(tid); + int freq = 100; + int durationMs = 20000; + bool parseMiniDebugInfo = false; + LitePerf litePerf; + int ret = litePerf.StartProcessStackSampling(tids, freq, durationMs, parseMiniDebugInfo); + EXPECT_EQ(ret, -1); + std::string sampleStack; + ret = litePerf.CollectSampleStackByTid(tid, sampleStack); + EXPECT_EQ(ret, -1); + ASSERT_TRUE(sampleStack.size() == 0); + ret = litePerf.FinishProcessStackSampling(); + EXPECT_EQ(ret, 0); + GTEST_LOG_(INFO) << "LitePerfTest005: end."; +} + +/** + * @tc.name: LitePerfTestTest006 + * @tc.desc: test LitePerf invalid time -1 + * @tc.type: FUNC + */ +HWTEST_F(LitePerfTest, LitePerfTestTest006, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "LitePerfTestTest006: start."; + if (IsLinuxKernel()) { + return; + } + std::vector tids; + int tid = getpid(); + tids.emplace_back(tid); + int freq = 100; + int durationMs = -1; + bool parseMiniDebugInfo = false; + LitePerf litePerf; + int ret = litePerf.StartProcessStackSampling(tids, freq, durationMs, parseMiniDebugInfo); + EXPECT_EQ(ret, -1); + std::string sampleStack; + ret = litePerf.CollectSampleStackByTid(tid, sampleStack); + EXPECT_EQ(ret, -1); + ASSERT_TRUE(sampleStack.size() == 0); + ret = litePerf.FinishProcessStackSampling(); + EXPECT_EQ(ret, 0); + GTEST_LOG_(INFO) << "LitePerfTestTest006: end."; +} +} // namespace HiviewDFX +} // namespace OHOS diff --git a/test/unittest/faultloggerd/faultlogger_server_test.cpp b/test/unittest/faultloggerd/faultlogger_server_test.cpp index ea9f13ec0..e683c4c8d 100644 --- a/test/unittest/faultloggerd/faultlogger_server_test.cpp +++ b/test/unittest/faultloggerd/faultlogger_server_test.cpp @@ -337,6 +337,89 @@ HWTEST_F(FaultLoggerdServiceTest, PipeFdClientTest03, TestSize.Level2) int32_t retCode = SendRequestToServer(SERVER_CRASH_SOCKET_NAME, &requestData, sizeof(requestData)); ASSERT_EQ(retCode, ResponseCode::REQUEST_SUCCESS); } + +/** + * @tc.name: LitePerfPipeFdClientTest01 + * @tc.desc: request a pip fd. + * @tc.type: FUNC + */ +HWTEST_F(FaultLoggerdServiceTest, LitePerfPipeFdClientTest01, TestSize.Level2) +{ + PipFdRequestData requestData; + FillRequestHeadData(requestData.head, FaultLoggerClientType::PIPE_FD_LITEPERF_CLIENT); + + requestData.pipeType = FaultLoggerPipeType::PIPE_FD_READ; + requestData.pid = requestData.head.clientPid; + int32_t readFds[FD_PAIR_NUM] = {-1, -1}; + RequestFileDescriptorFromServer(SERVER_CRASH_SOCKET_NAME, &requestData, sizeof(requestData), readFds, FD_PAIR_NUM); + SendRequestToServer(SERVER_SOCKET_NAME, &requestData, sizeof(requestData)); + + SmartFd buffReadFd{readFds[0]}; + SmartFd resReadFd{readFds[FD_PAIR_NUM - 1]}; + ASSERT_GE(buffReadFd.GetFd(), 0); + ASSERT_GE(resReadFd.GetFd(), 0); + + requestData.pipeType = FaultLoggerPipeType::PIPE_FD_WRITE; + int32_t writeFds[FD_PAIR_NUM] = {-1, -1}; + RequestFileDescriptorFromServer(SERVER_CRASH_SOCKET_NAME, &requestData, sizeof(requestData), writeFds, FD_PAIR_NUM); + int retCode = SendRequestToServer(SERVER_CRASH_SOCKET_NAME, &requestData, sizeof(requestData)); + ASSERT_EQ(retCode, ResponseCode::ABNORMAL_SERVICE); + + SmartFd buffWriteFd{writeFds[0]}; + SmartFd resWriteFd{writeFds[FD_PAIR_NUM - 1]}; + ASSERT_GE(buffWriteFd.GetFd(), 0); + ASSERT_GE(resWriteFd.GetFd(), 0); + + int sendMsg = 10; + int recvMsg = 0; + ASSERT_TRUE(SendMsgToSocket(buffWriteFd.GetFd(), &sendMsg, sizeof (sendMsg))); + ASSERT_TRUE(GetMsgFromSocket(buffReadFd.GetFd(), &recvMsg, sizeof (recvMsg))); + ASSERT_EQ(sendMsg, recvMsg); + + ASSERT_TRUE(SendMsgToSocket(resWriteFd.GetFd(), &sendMsg, sizeof (sendMsg))); + ASSERT_TRUE(GetMsgFromSocket(resReadFd.GetFd(), &recvMsg, sizeof (recvMsg))); + ASSERT_EQ(sendMsg, recvMsg); + RequestLitePerfDelPipeFd(); +} + +/** + * @tc.name: LitePerfPipeFdClientTest02 + * @tc.desc: request a pip fd with invalid request data. + * @tc.type: FUNC + */ +HWTEST_F(FaultLoggerdServiceTest, LitePerfPipeFdClientTest02, TestSize.Level2) +{ + PipFdRequestData requestData; + FillRequestHeadData(requestData.head, FaultLoggerClientType::PIPE_FD_LITEPERF_CLIENT); + + requestData.pipeType = FaultLoggerPipeType::PIPE_FD_READ; + requestData.pid = requestData.head.clientPid; + SendRequestToServer(SERVER_SOCKET_NAME, &requestData, sizeof(requestData)); + + requestData.pipeType = -1; + int32_t retCode = SendRequestToServer(SERVER_SOCKET_NAME, &requestData, sizeof(requestData)); + ASSERT_EQ(retCode, ResponseCode::REQUEST_REJECT); + + requestData.pipeType = 3; + retCode = SendRequestToServer(SERVER_SOCKET_NAME, &requestData, sizeof(requestData)); + ASSERT_EQ(retCode, ResponseCode::REQUEST_REJECT); +} + +/** + * @tc.name: LitePerfPipeFdClientTest03 + * @tc.desc: request to delete a pip fd. + * @tc.type: FUNC + */ +HWTEST_F(FaultLoggerdServiceTest, LitePerfPipeFdClientTest03, TestSize.Level2) +{ + PipFdRequestData requestData; + FillRequestHeadData(requestData.head, FaultLoggerClientType::PIPE_FD_LITEPERF_CLIENT); + requestData.pid = requestData.head.clientPid; + + requestData.pipeType = FaultLoggerPipeType::PIPE_FD_DELETE; + int32_t retCode = SendRequestToServer(SERVER_CRASH_SOCKET_NAME, &requestData, sizeof(requestData)); + ASSERT_EQ(retCode, ResponseCode::REQUEST_SUCCESS); +} #endif #ifndef HISYSEVENT_DISABLE diff --git a/tools/process_dump/BUILD.gn b/tools/process_dump/BUILD.gn index 4d7b80083..dde851fc1 100644 --- a/tools/process_dump/BUILD.gn +++ b/tools/process_dump/BUILD.gn @@ -177,7 +177,13 @@ if (defined(ohos_lite)) { ":processdump_config", "$faultloggerd_path/common/build:coverage_flags", ] + include_dirs = [ "lperf" ] sources = processdump_sources + sources += [ + "lperf/lperf_event_record.cpp", + "lperf/lperf_events.cpp", + "lperf/lperf_record.cpp", + ] sources += [ "main.cpp", "reporter.cpp", @@ -187,6 +193,9 @@ if (defined(ohos_lite)) { "DFX_LOG_HILOG_BASE", "DFX_ENABLE_TRACE", ] + if (faultloggerd_liteperf_enable) { + defines += [ "DFX_ENABLE_LPERF" ] + } if (use_clang_coverage) { defines += [ "CLANG_COVERAGE" ] } @@ -201,6 +210,7 @@ if (defined(ohos_lite)) { "$faultloggerd_interfaces_path/innerkits/crash_exception", "$faultloggerd_interfaces_path/innerkits/faultloggerd_client:libfaultloggerd", "$faultloggerd_interfaces_path/innerkits/procinfo:libdfx_procinfo", + "$faultloggerd_interfaces_path/innerkits/stack_printer:libstack_printer", "$faultloggerd_interfaces_path/innerkits/unwinder:libunwinder", ] diff --git a/tools/process_dump/lite_perf_dumper.cpp b/tools/process_dump/lite_perf_dumper.cpp new file mode 100644 index 000000000..e6442c8b0 --- /dev/null +++ b/tools/process_dump/lite_perf_dumper.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025 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 "lite_perf_dumper.h" + +#include +#include +#include +#include +#include + +#include "dfx_define.h" +#include "dfx_dump_request.h" +#include "dfx_dump_res.h" +#include "faultloggerd_client.h" +#include "lperf_event_record.h" +#include "lperf_events.h" +#include "lperf_record.h" + +namespace OHOS { +namespace HiviewDFX { +namespace { +#undef LOG_DOMAIN +#undef LOG_TAG +#define LOG_DOMAIN 0xD002D11 +#define LOG_TAG "DfxLitePerfDump" +} + +LitePerfDumper &LitePerfDumper::GetInstance() +{ + static LitePerfDumper ins; + return ins; +} + +void LitePerfDumper::Perf() +{ + LitePerfParam lperf; + PerfProcess(lperf); +} + +int LitePerfDumper::PerfProcess(LitePerfParam& lperf) +{ + DFX_TRACE_SCOPED("PerfProcess"); + int ret = ReadLperfAndCheck(lperf); + if (ret < 0) { + return ret; + } + + int pipeWriteFd[PIPE_NUM_SZ] = { -1, -1}; + if (RequestLitePerfPipeFd(FaultLoggerPipeType::PIPE_FD_WRITE, pipeWriteFd) == -1) { + DFXLOGE("%{public}s request pipe failed, err:%{public}d", __func__, errno); + return -1; + } + + return PerfRecord(pipeWriteFd, lperf); +} + +int LitePerfDumper::PerfRecord(int (&pipeWriteFd)[2], LitePerfParam& lperf) +{ + SmartFd bufFd(pipeWriteFd[PIPE_BUF_INDEX]); + SmartFd resFd(pipeWriteFd[PIPE_RES_INDEX]); + + std::vector lperfTids; + for (auto i = 0; i < MAX_PERF_TIDS_SIZE; ++i) { + if (lperf.tids[i] <= 0 || !IsThreadInPid(lperf.pid, lperf.tids[i])) { + DFXLOGW("tid(%{public}d) is not in curr pid(%{public}d).", lperf.tids[i], lperf.pid); + continue; + } + lperfTids.emplace_back(lperf.tids[i]); + } + + LperfRecord record; + auto accessor = std::make_shared(); + auto unwinder = std::make_shared(accessor, false); + auto maps = DfxMaps::Create(lperf.pid); + record.SetUnwindInfo(unwinder, maps); + int res = record.StartProcessSampling(lperf.pid, lperfTids, lperf.freq, lperf.durationMs, lperf.parseMiniDebugInfo); + if (res == 0) { + for (const auto& tid : lperfTids) { + std::string stack; + if (record.CollectSampleStack(tid, stack) == 0) { + WriteSampleStackByTid(tid, bufFd.GetFd()); + } else { + DFXLOGW("%{public}s CollectSampleStack tid(%{public}d) fail", __func__, tid); + continue; + } + } + } + ssize_t nresWrite = OHOS_TEMP_FAILURE_RETRY(write(resFd.GetFd(), &res, sizeof(res))); + if (nresWrite != static_cast(sizeof(res))) { + DFXLOGE("%{public}s write res fail, err:%{public}d", __func__, errno); + return -1; + } + record.FinishProcessSampling(); + return 0; +} + +void LitePerfDumper::WriteSampleStackByTid(int tid, int bufFd) +{ + std::string writeStr = LITE_PERF_SPLIT + std::to_string(tid) + "\n"; + constexpr size_t step = MAX_PIPE_SIZE; + writeStr += stack; + for (size_t i = 0; i < writeStr.size(); i += step) { + size_t length = (i + step) < writeStr.size() ? step : writeStr.size() - i; + OHOS_TEMP_FAILURE_RETRY(write(bufFd, writeStr.substr(i, length).c_str(), length)); + } +} + +int32_t LitePerfDumper::ReadLperfAndCheck(LitePerfParam& lperf) +{ + DFX_TRACE_SCOPED("ReadRequestAndCheck"); + ElapsedTime counter("ReadRequestAndCheck", 20); // 20 : limit cost time 20 ms + ssize_t readCount = OHOS_TEMP_FAILURE_RETRY(read(STDOUT_FILENO, &lperf, sizeof(LitePerfParam))); + if (readCount != static_cast(sizeof(LitePerfParam))) { + DFXLOGE("Failed to read LitePerfParam(%{public}d), readCount(%{public}zd).", errno, readCount); + return -1; + } + return 0; +} + +} // namespace HiviewDFX +} // namespace OHOS diff --git a/tools/process_dump/lite_perf_dumper.h b/tools/process_dump/lite_perf_dumper.h new file mode 100644 index 000000000..d9c277cbf --- /dev/null +++ b/tools/process_dump/lite_perf_dumper.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 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 DFX_LITE_PERF_DUMP_H +#define DFX_LITE_PERF_DUMP_H + +#include +#include +#include + +#include "dfx_lperf.h" +#include "nocopyable.h" + +namespace OHOS { +namespace HiviewDFX { +class LitePerfDumper final { +public: + static LitePerfDumper &GetInstance(); + void Perf(); + +private: + LitePerfDumper() = default; + DISALLOW_COPY_AND_MOVE(LitePerfDumper); + + int PerfProcess(LitePerfParam& lperf); + int32_t ReadLperfAndCheck(LitePerfParam& lperf); + int PerfRecord(int (&pipeWriteFd)[2], LitePerfParam& lperf); + void WriteSampleStackByTid(int tid, int bufFd); +}; +} // namespace HiviewDFX +} // namespace OHOS +#endif // DFX_LITE_PERF_DUMP_H diff --git a/tools/process_dump/lperf/lperf_event_record.cpp b/tools/process_dump/lperf/lperf_event_record.cpp new file mode 100644 index 000000000..92a2ed60a --- /dev/null +++ b/tools/process_dump/lperf/lperf_event_record.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025 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 "lperf_event_record.h" + +namespace OHOS { +namespace HiviewDFX { +namespace { +#undef LOG_DOMAIN +#undef LOG_TAG +#define LOG_DOMAIN 0xD002D11 +#define LOG_TAG "DfxLperfRecordSample" + +const char* const PERF_RECORD_TYPE_SAMPLE = "sample"; +} +LperfRecordSample LperfRecordFactory::record_ = {}; + +const char* LperfRecordSample::GetName() +{ + return PERF_RECORD_TYPE_SAMPLE; +} + +uint32_t LperfRecordSample::GetType() const +{ + return header_.type; +} + +void LperfRecordSample::InitHeader(uint8_t* data) +{ + if (data == nullptr) { + header_.type = PERF_RECORD_MMAP; + header_.misc = PERF_RECORD_MISC_USER; + header_.size = 0; + return; + } + header_ = *(reinterpret_cast(data)); +} + +bool LperfRecordSample::Init(uint8_t* data) +{ + InitHeader(data); + CHECK_TRUE_AND_RET(data != nullptr, false, "LperfRecordSample Init error"); + data_ = {}; + uint64_t dataSize = static_cast(RECORD_SIZE_LIMIT); + CHECK_TRUE_AND_RET(SetPointerOffset(data, sizeof(header_), dataSize), false, "set header_ offset error"); + + // parse record according SAMPLE_TYPE + bool popId = PopFromBinary2(sampleType_ & PERF_SAMPLE_TID, + data, data_.pid, data_.tid, dataSize); + CHECK_TRUE_AND_RET(popId, false, "Init PERF_SAMPLE_TID error"); + CHECK_TRUE_AND_RET(PopFromBinary(sampleType_ & PERF_SAMPLE_TIME, data, data_.time, dataSize), + false, "Init PERF_SAMPLE_TIME error"); + CHECK_TRUE_AND_RET(PopFromBinary(sampleType_ & PERF_SAMPLE_CALLCHAIN, data, data_.nr, dataSize), + false, "Init PERF_SAMPLE_CALLCHAIN error"); + if (data_.nr > 0) { + // the pointer is from input(data), require caller keep input(data) with *this together + // think it in next time + data_.ips = reinterpret_cast(data); + CHECK_TRUE_AND_RET(SetPointerOffset(data, data_.nr * sizeof(uint64_t), dataSize), + false, "set ips offset error"); + } + return true; +} + +void LperfRecordSample::Clear() +{ + data_.pid = 0; + data_.tid = 0; + data_.time = 0; + data_.nr = 0; + data_.ips = nullptr; +} + +LperfRecordSample& LperfRecordFactory::GetLperfRecord(uint32_t type, uint8_t* data) +{ + if (type != PERF_RECORD_SAMPLE || !record_.Init(data)) { + record_.Clear(); + DFXLOGE("Init LperfRecordSample data error"); + } + return record_; +} + +void LperfRecordFactory::ClearData() +{ + record_.Clear(); +} + +template +inline bool PopFromBinary(bool condition, uint8_t*& data, T& dest, uint64_t& size) +{ + CHECK_TRUE_AND_RET(sizeof(T) <= size, false, "PopFromBinary error"); + if (condition) { + dest = *(reinterpret_cast(data)); + data += sizeof(T); + size -= sizeof(T); + } + return true; +} + +template +inline bool PopFromBinary2(bool condition, uint8_t*& data, T1& dest1, T2& dest2, uint64_t& size) +{ + CHECK_TRUE_AND_RET(sizeof(T1) + sizeof(T2) <= size, false, "PopFromBinary2 error"); + if (condition) { + dest1 = *(reinterpret_cast(data)); + data += sizeof(T1); + dest2 = *(reinterpret_cast(data)); + data += sizeof(T2); + size -= (sizeof(T1) + sizeof(T2)); + } + return true; +} + +inline bool SetPointerOffset(uint8_t*& data, uint64_t offset, uint64_t& size) +{ + CHECK_TRUE_AND_RET(offset <= size && offset <= RECORD_SIZE_LIMIT, false, "SetPointerOffset error"); + size -= offset; + data += offset; + return true; +} +} // namespace HiviewDFX +} // namespace OHOS \ No newline at end of file diff --git a/tools/process_dump/lperf/lperf_event_record.h b/tools/process_dump/lperf/lperf_event_record.h new file mode 100644 index 000000000..ce5ba1233 --- /dev/null +++ b/tools/process_dump/lperf/lperf_event_record.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025 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 LPERF_EVENT_RECORD_H +#define LPERF_EVENT_RECORD_H + +#include +#include +#include +#include +#include "dfx_log.h" + +namespace OHOS { +namespace HiviewDFX { +constexpr uint32_t RECORD_SIZE_LIMIT = 65535; +constexpr ssize_t ERRINFOLEN = 512; + +struct LperfRecordSampleData { + uint32_t pid = 0; + uint32_t tid = 0; /* if PERF_SAMPLE_TID */ + uint64_t time = 0; /* if PERF_SAMPLE_TIME */ + uint64_t nr = 0; /* if PERF_SAMPLE_CALLCHAIN */ + uint64_t* ips = nullptr; /* if PERF_SAMPLE_CALLCHAIN */ +}; + +inline std::string StringFormat(const char* fmt, ...) +{ + va_list vargs; + char buf[1024] = {0}; // 1024: buf size + std::string format(fmt); + va_start(vargs, fmt); + if (vsnprintf_s(buf, sizeof(buf), sizeof(buf) - 1, format.c_str(), vargs) < 0) { + va_end(vargs); + return ""; + } + + va_end(vargs); + return buf; +} + +class LperfRecordSample { +public: + LperfRecordSampleData data_ = {}; + LperfRecordSample() = default; + + const char* GetName(); + uint32_t GetType() const; + bool Init(uint8_t* data); + void Clear(); +protected: + void InitHeader(uint8_t* p); + +private: + struct perf_event_header header_ = {}; + uint64_t sampleType_ = PERF_SAMPLE_TID | PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_TIME; +}; + +class LperfRecordFactory { +public: + static LperfRecordSample& GetLperfRecord(uint32_t type, uint8_t* data); + static void ClearData(); + +private: + static LperfRecordSample record_; +}; + +template +bool PopFromBinary(bool condition, uint8_t*& p, T& v, uint64_t& size); + +template +bool PopFromBinary2(bool condition, uint8_t*& p, T1& v1, T2& v2, uint64_t& size); + +bool SetPointerOffset(uint8_t*& p, uint64_t offset, uint64_t& size); + +#define NO_RETVAL /* retval */ +#ifndef CHECK_TRUE_AND_RET +#define CHECK_TRUE_AND_RET(condition, retval, fmt, ...) \ + do { \ + if (!(condition)) [[unlikely]] { \ + std::string str = StringFormat(fmt, ##__VA_ARGS__); \ + DFXLOGE("%{public}s", str.c_str()); \ + return retval; \ + } \ + } while (0) +#endif + +#ifndef CHECK_ERR +#define CHECK_ERR(err, fmt, ...) \ + do { \ + if (err < 0) [[unlikely]] { \ + char errInfo[ERRINFOLEN] = { 0 }; \ + strerror_r(errno, errInfo, ERRINFOLEN); \ + DFXLOGE("%{public}s, error: %{public}d, errInfo: %{public}s", \ + StringFormat(fmt, ##__VA_ARGS__).c_str(), errno, errInfo); \ + return false; \ + } \ + } while (0) +#endif +} // namespace HiviewDFX +} // namespace OHOS +#endif // LPERF_EVENT_RECORD_H \ No newline at end of file diff --git a/tools/process_dump/lperf/lperf_events.cpp b/tools/process_dump/lperf/lperf_events.cpp new file mode 100644 index 000000000..0b2c59a7e --- /dev/null +++ b/tools/process_dump/lperf/lperf_events.cpp @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2025 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 "lperf_events.h" + +#include +#include +#include +#include +#include +#include + +namespace OHOS { +namespace HiviewDFX { +namespace { +#undef LOG_DOMAIN +#undef LOG_TAG +#define LOG_DOMAIN 0xD002D11 +#define LOG_TAG "DfxLperfEvents" + +const char *const LPERF_DEV = "/dev/lperf"; +constexpr unsigned long LPERF_IOCTL_INIT = 1075866625; +constexpr unsigned long LPERF_IOCTL_PROFILE = 2148035586; +constexpr unsigned long LPERF_IOCTL_ADD_THREADS = 1074031620; +constexpr unsigned int DEFAULT_WATER_MARK = 5000; +constexpr int MAX_PERF_TIDS_COUNT = 10; + +#define HIPERF_BUF_ALIGN alignas(64) + +struct LperfInitArg { + unsigned int rbSizeIntKb; + unsigned int samplePeriod; + unsigned int sampleInterval; + unsigned int watermark; + unsigned int reserved1; + unsigned long long rbAddr; +}; + +struct LperfThreadInputArg { + unsigned int tidCount; + unsigned int tids[MAX_PERF_TIDS_COUNT]; +}; +} + +LperfEvents::LperfEvents() +{ + pageSize_ = sysconf(_SC_PAGESIZE); +} + +LperfEvents::~LperfEvents() +{ + Clear(); +} + +int LperfEvents::PrepareRecord() +{ + CHECK_TRUE_AND_RET(PrepareFdEvents(), -1, "PrepareFdEvents failed"); + CHECK_TRUE_AND_RET(AddRecordThreads(), -1, "AddRecordThreads failed"); + return 0; +} + +int LperfEvents::StartRecord() +{ + CHECK_TRUE_AND_RET(PerfEventsEnable(true), -1, "LperfEvents EnableTracking() failed"); + CHECK_TRUE_AND_RET(RecordLoop(), -1, "LperfEvents RecordLoop() failed"); + CHECK_TRUE_AND_RET(PerfEventsEnable(false), -1, "LperfEvents RecordLoop() failed"); + ReadRecordsFromMmaps(); + return 0; +} + +bool LperfEvents::StopRecord() +{ + CHECK_TRUE_AND_RET(PerfEventsEnable(false), -1, "LperfEvents StopRecord failed"); + return true; +} + +void LperfEvents::SetTid(const std::vector& tids) +{ + tids_ = tids; +} + +void LperfEvents::SetTimeOut(int timeOut) +{ + if (timeOut > 0) { + timeOut_ = timeOut; + } +} + +void LperfEvents::SetSampleFrequency(unsigned int frequency) +{ + if (frequency > 0) { + sampleFreq_ = frequency; + } +} + +bool LperfEvents::PerfEventsEnable(bool enable) +{ + int err = ioctl(lperfFd_, static_cast(LPERF_IOCTL_PROFILE), enable ? 1 : 0); + CHECK_ERR(err, "enable lperfFd_ failed"); + return true; +} + +void LperfEvents::SetRecordCallBack(ProcessRecordCB recordCallBack) +{ + recordCallBack_ = recordCallBack; +} + +bool LperfEvents::PrepareFdEvents() +{ + unsigned int times = 4; + struct LperfInitArg initArg = { + .rbSizeIntKb = (mmapPages_ + 1) * times, + .samplePeriod = sampleFreq_, + .sampleInterval = timeOut_, + .watermark = DEFAULT_WATER_MARK, + .rbAddr = 0, + }; + lperfFd_ = open(LPERF_DEV, O_RDWR); + CHECK_ERR(lperfFd_, "open lperfFd_ failed"); + int err = ioctl(lperfFd_, LPERF_IOCTL_INIT, &initArg); + CHECK_ERR(err, "init lperf failed"); + lperfMmap_.fd = lperfFd_; + lperfMmap_.mmapPage = reinterpret_cast(initArg.rbAddr); + lperfMmap_.buf = reinterpret_cast(initArg.rbAddr) + pageSize_; + lperfMmap_.bufSize = mmapPages_ * pageSize_; + pollFds_.emplace_back(pollfd {lperfFd_, POLLIN | POLLHUP, 0}); + return true; +} + +bool LperfEvents::AddRecordThreads() +{ + struct LperfThreadInputArg threadInfo; + size_t maxCount = 10; + size_t count = std::min(tids_.size(), maxCount); + threadInfo.tidCount = static_cast(count); + for (size_t i = 0; i < count; i++) { + threadInfo.tids[i] = tids_[i]; + } + int err = ioctl(lperfFd_, static_cast(LPERF_IOCTL_ADD_THREADS), &threadInfo); + CHECK_ERR(err, "add lperf threads failed"); + return true; +} + +bool LperfEvents::GetHeaderFromMmap(MmapFd& mmap) +{ + if (mmap.dataSize <= 0) { + return false; + } + + GetRecordFieldFromMmap(mmap, &(mmap.header), sizeof(mmap.header), mmap.mmapPage->data_tail, sizeof(mmap.header)); + if (mmap.header.type != PERF_RECORD_SAMPLE) { + mmap.mmapPage->data_tail += mmap.header.size; + return false; + } + return true; +} + +void LperfEvents::GetRecordFieldFromMmap(MmapFd& mmap, void* dest, size_t destSize, size_t pos, size_t size) +{ + if (mmap.bufSize == 0) { + return; + } + pos = pos % mmap.bufSize; + size_t tailSize = mmap.bufSize - pos; + size_t copySize = std::min(size, tailSize); + if (memcpy_s(dest, destSize, mmap.buf + pos, copySize) != 0) { + DFXLOGE("memcpy_s %{public}p to %{public}p failed. size %{public}zd", mmap.buf + pos, dest, copySize); + } + if (copySize < size) { + size -= copySize; + if (memcpy_s(static_cast(dest) + copySize, destSize - copySize, mmap.buf, size) != 0) { + DFXLOGE("memcpy_s mmap.buf to dest failed. size %{public}zd", size); + } + } +} + +void LperfEvents::GetRecordFromMmap(MmapFd& mmap) +{ + HIPERF_BUF_ALIGN static uint8_t buf[RECORD_SIZE_LIMIT]; + GetRecordFieldFromMmap(mmap, buf, RECORD_SIZE_LIMIT, mmap.mmapPage->data_tail, mmap.header.size); + __sync_synchronize(); + mmap.mmapPage->data_tail += mmap.header.size; + mmap.dataSize -= mmap.header.size; + recordCallBack_(LperfRecordFactory::GetLperfRecord(mmap.header.type, buf)); +} + +void LperfEvents::ReadRecordsFromMmaps() +{ + ssize_t dataSize = lperfMmap_.mmapPage->data_head - lperfMmap_.mmapPage->data_tail; + __sync_synchronize(); + if (dataSize <= 0) { + return; + } + lperfMmap_.dataSize = dataSize; + while (GetHeaderFromMmap(lperfMmap_)) { + GetRecordFromMmap(lperfMmap_); + } +} + +bool LperfEvents::RecordLoop() +{ + CHECK_TRUE_AND_RET(pollFds_.size() > 0, false, "pollFds_ is invalid"); + const auto endTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeOut_); + + bool loopCondition = true; + int pollTimeout = 500; + while (loopCondition) { + const auto thisTime = std::chrono::steady_clock::now(); + if (thisTime >= endTime) { + break; + } + + if (poll(static_cast(pollFds_.data()), pollFds_.size(), pollTimeout) <= 0) { + DFXLOGE("poll no data"); + continue; + } + ReadRecordsFromMmaps(); + if ((pollFds_[0].revents & POLLHUP) == POLLHUP) { + DFXLOGE("poll status is POLLHUP, revents: %{public}d", pollFds_[0].revents); + loopCondition = false; + } + } + return true; +} + +void LperfEvents::Clear() +{ + LperfRecordFactory::ClearData(); + const unsigned int size = 4096; + if (lperfMmap_.mmapPage != nullptr) { + if (munmap(lperfMmap_.mmapPage, static_cast((mmapPages_ + 1) * size)) == 0) { + lperfMmap_.mmapPage = nullptr; + } else { + DFXLOGE("munmap lperfMmap failed"); + } + } + if (pollFds_.size() > 0) { + pollFds_.clear(); + } + if (lperfFd_ != -1) { + close(lperfFd_); + lperfFd_ = -1; + } +} +} // namespace HiviewDFX +} // namespace OHOS \ No newline at end of file diff --git a/tools/process_dump/lperf/lperf_events.h b/tools/process_dump/lperf/lperf_events.h new file mode 100644 index 000000000..29521b229 --- /dev/null +++ b/tools/process_dump/lperf/lperf_events.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025 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 LPERF_EVENTS_H +#define LPERF_EVENTS_H + +#include +#include +#include +#include +#include + +#include "lperf_event_record.h" + +namespace OHOS { +namespace HiviewDFX { +class LperfEvents { +public: + LperfEvents(); + ~LperfEvents(); + + int PrepareRecord(); + int StartRecord(); + bool StopRecord(); + + void SetTid(const std::vector& tids); + void SetTimeOut(int timeOut); + void SetSampleFrequency(unsigned int frequency); + + using ProcessRecordCB = std::function; + void SetRecordCallBack(ProcessRecordCB recordCallBack); + void Clear(); + +private: + struct MmapFd { + int fd; + perf_event_mmap_page* mmapPage = nullptr; + uint8_t *buf = nullptr; + size_t bufSize = 0; + size_t dataSize = 0; + + perf_event_header header; + }; + + bool PrepareFdEvents(); + bool AddRecordThreads(); + bool GetHeaderFromMmap(MmapFd& mmap); + void GetRecordFieldFromMmap(MmapFd& mmap, void* dest, size_t destSize, size_t pos, size_t size); + void GetRecordFromMmap(MmapFd& mmap); + void ReadRecordsFromMmaps(); + bool PerfEventsEnable(bool enable); + bool RecordLoop(); + + MmapFd lperfMmap_; + ProcessRecordCB recordCallBack_; + + int lperfFd_ = -1; + int timeOut_ = 0; + unsigned int pageSize_ = 4096; + unsigned int mmapPages_ = 1024; + unsigned int sampleFreq_ = 0; + std::vector tids_; + std::vector pollFds_; +}; +} // namespace HiviewDFX +} // namespace OHOS +#endif // LPERF_EVENTS_H \ No newline at end of file diff --git a/tools/process_dump/lperf/lperf_record.cpp b/tools/process_dump/lperf/lperf_record.cpp new file mode 100644 index 000000000..79df77bf5 --- /dev/null +++ b/tools/process_dump/lperf/lperf_record.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2025 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 "lperf_record.h" +#include "unwinder_config.h" + +namespace OHOS { +namespace HiviewDFX { +namespace { +#undef LOG_DOMAIN +#undef LOG_TAG +#define LOG_DOMAIN 0xD002D11 +#define LOG_TAG "DfxLperfRecord" + +constexpr int MIN_SAMPLE_COUNT = 1; +constexpr int MAX_SAMPLE_COUNT = 10; +constexpr int MIN_SAMPLE_FREQUENCY = 1; +constexpr int MAX_SAMPLE_FREQUENCY = 100; +constexpr int MIN_STOP_SECONDS = 1; +constexpr int MAX_STOP_SECONDS = 10000; +const char *const LPERF_UNIQUE_TABLE = "lperf_unique_table"; +constexpr uint32_t UNIQUE_STABLE_SIZE = 1024 * 1024; +} + +void LperfRecord::SetUnwindInfo(const std::shared_ptr& unwinder, const std::shared_ptr& maps) +{ + unwinder_ = unwinder; + maps_ = maps; +} + +int LperfRecord::StartProcessSampling(int pid, const std::vector& tids, + int freq, int duration, bool parseMiniDebugInfo) +{ + CHECK_TRUE_AND_RET(!CheckOutOfRange(tids.size(), MIN_SAMPLE_COUNT, MAX_SAMPLE_COUNT), -1, + "invalid tids count: %d", tids.size()); + CHECK_TRUE_AND_RET(!CheckOutOfRange(freq, MIN_SAMPLE_FREQUENCY, MAX_SAMPLE_FREQUENCY), -1, + "invalid frequency value: %d", freq); + CHECK_TRUE_AND_RET(!CheckOutOfRange(duration, MIN_STOP_SECONDS, MAX_STOP_SECONDS), -1, + "invalid duration value: %d", duration); + for (int tid : tids) { + CHECK_TRUE_AND_RET(tid > 0, -1, "invalid tid: %d", tid); + } + + pid_ = pid; + tids_ = tids; + frequency_ = static_cast(freq); + timeStopSec_ = static_cast(duration); + enableDebugInfoSymbolic_ = parseMiniDebugInfo; + + int ret = OnSubCommand(); + lperfEvents_.Clear(); + return ret; +} + +int LperfRecord::CollectSampleStack(int tid, std::string& stack) +{ + CHECK_TRUE_AND_RET(tid > 0, -1, "invalid tid: %d", tid); + unsigned int uintTid = static_cast(tid); + if (tidStackMaps_.find(uintTid) != tidStackMaps_.end()) { + tidStackMaps_[uintTid]->SetUnwindInfo(unwinder_, maps_); + stack = tidStackMaps_[uintTid]->GetTreeStack(); + if (stack.size() > 0) { + return 0; + } + } + return -1; +} + +void LperfRecord::FinishProcessSampling() +{ + UnwinderConfig::SetEnableMiniDebugInfo(defaultEnableDebugInfo_); + tidStackMaps_.clear(); +} + +int LperfRecord::OnSubCommand() +{ + PrepareLperfEvent(); + CHECK_TRUE_AND_RET(lperfEvents_.PrepareRecord() == 0, -1, "OnSubCommand prepareRecord failed"); + CHECK_TRUE_AND_RET(lperfEvents_.StartRecord() == 0, -1, "OnSubCommand startRecord failed"); + return 0; +} + +void LperfRecord::PrepareLperfEvent() +{ + defaultEnableDebugInfo_ = UnwinderConfig::GetEnableMiniDebugInfo(); + UnwinderConfig::SetEnableMiniDebugInfo(enableDebugInfoSymbolic_); + lperfEvents_.SetTid(tids_); + lperfEvents_.SetTimeOut(timeStopSec_); + lperfEvents_.SetSampleFrequency(frequency_); + auto processRecord = [this](LperfRecordSample& record) -> void { + this->SymbolicRecord(record); + }; + lperfEvents_.SetRecordCallBack(processRecord); +} + +void LperfRecord::SymbolicRecord(LperfRecordSample& record) +{ + CHECK_TRUE_AND_RET(record.data_.tid > 0, NO_RETVAL, "Symbolic invalid Record, tid: %d", record.data_.tid); + unsigned int tid = static_cast(record.data_.tid); + if (tidStackMaps_.find(tid) == tidStackMaps_.end()) { + tidStackMaps_.emplace(tid, std::make_unique()); + tidStackMaps_[tid]->InitUniqueTable(record.data_.pid, UNIQUE_STABLE_SIZE, LPERF_UNIQUE_TABLE); + } + std::vector pcs; + for (unsigned int i = 0; i < record.data_.nr; i++) { + if (record.data_.ips[i] != PERF_CONTEXT_USER) { + pcs.emplace_back(static_cast(record.data_.ips[i])); + } + } + tidStackMaps_[tid]->PutPcsInTable(pcs, record.data_.time); +} +} // namespace HiviewDFX +} // namespace OHOS \ No newline at end of file diff --git a/tools/process_dump/lperf/lperf_record.h b/tools/process_dump/lperf/lperf_record.h new file mode 100644 index 000000000..45578379f --- /dev/null +++ b/tools/process_dump/lperf/lperf_record.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 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 LPERF_RECORD_H +#define LPERF_RECORD_H + +#include +#include + +#include "lperf_events.h" +#include "stack_printer.h" +#include "unwinder.h" + +namespace OHOS { +namespace HiviewDFX { +class LperfRecord { +public: + void SetUnwindInfo(const std::shared_ptr& unwinder, const std::shared_ptr& maps); + + int StartProcessSampling(int pid, const std::vector& tids, int freq, int duration, bool parseMiniDebugInfo); + int CollectSampleStack(int tid, std::string& stack); + void FinishProcessSampling(); + +private: + int OnSubCommand(); + void PrepareLperfEvent(); + void SymbolicRecord(LperfRecordSample& record); + + LperfEvents lperfEvents_; + std::shared_ptr unwinder_; + std::shared_ptr maps_; + std::map> tidStackMaps_; + + unsigned int timeStopSec_ = 5; + unsigned int frequency_ = 0; + int pid_ = 0; + std::vector tids_ = {}; + bool defaultEnableDebugInfo_ = false; + bool enableDebugInfoSymbolic_ = false; +}; + +template +inline bool CheckOutOfRange(const T& value, const T& min, const T& max) +{ + return value < min || value > max; +} +} // namespace HiviewDFX +} // namespace OHOS +#endif // LPERF_RECORD_H \ No newline at end of file diff --git a/tools/process_dump/main.cpp b/tools/process_dump/main.cpp index 626927608..83f58178f 100644 --- a/tools/process_dump/main.cpp +++ b/tools/process_dump/main.cpp @@ -21,6 +21,7 @@ #include "dfx_define.h" #include "dfx_log.h" +#include "lite_perf_dumper.h" #include "process_dumper.h" #if defined(DEBUG_CRASH_LOCAL_HANDLER) @@ -36,7 +37,7 @@ static void PrintCommandHelp() printf("%s\nplease use dumpcatcher\n", DUMP_STACK_TAG_USAGE.c_str()); } -static bool ParseParameters(int argc, char *argv[], bool &isSignalHdlr) +static bool ParseParameters(int argc, char *argv[], bool &isSignalHdlr, bool &isLitePerf) { if (argc <= DUMP_ARG_ONE) { return false; @@ -46,6 +47,9 @@ static bool ParseParameters(int argc, char *argv[], bool &isSignalHdlr) if (!strcmp("-signalhandler", argv[DUMP_ARG_ONE])) { isSignalHdlr = true; return true; + } else if (!strcmp("-liteperf", argv[DUMP_ARG_ONE])) { + isLitePerf = true; + return true; } return false; } @@ -61,17 +65,22 @@ int main(int argc, char *argv[]) } bool isSignalHdlr = false; + bool isLitePerf = false; - alarm(PROCESSDUMP_TIMEOUT); setsid(); - if (!ParseParameters(argc, argv, isSignalHdlr)) { + if (!ParseParameters(argc, argv, isSignalHdlr, isLitePerf)) { PrintCommandHelp(); return 0; } if (isSignalHdlr) { + alarm(PROCESSDUMP_TIMEOUT); OHOS::HiviewDFX::ProcessDumper::GetInstance().Dump(); + } else if (isLitePerf) { +#ifdef DFX_ENABLE_LPERF + OHOS::HiviewDFX::LitePerfDumper::GetInstance().Perf(); +#endif } #ifndef CLANG_COVERAGE _exit(0); -- Gitee