/* * Copyright (c) 2021-2022 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 "hiperf_client.h" #include #include #include #include #include #include #include #include #include #include "hiperf_hilog.h" using namespace std::chrono; namespace OHOS { namespace Developtools { namespace HiPerf { namespace HiperfClient { const ssize_t ERRINFOLEN = 512; void RecordOption::SetOption(const std::string &name, bool enable) { auto it = std::find(args_.begin(), args_.end(), name); if (enable) { if (it == args_.end()) { args_.emplace_back(name); } return; } if (it != args_.end()) { args_.erase(it); return; } } void RecordOption::SetOption(const std::string &name, int value) { auto it = std::find(args_.begin(), args_.end(), name); if (it != args_.end()) { it++; *it = std::to_string(value); return; } args_.emplace_back(name); args_.emplace_back(std::to_string(value)); return; } void RecordOption::SetOption(const std::string &name, const std::vector &vInt) { auto it = std::find(args_.begin(), args_.end(), name); if (vInt.empty()) { if (it != args_.end()) { it = args_.erase(it); // remove key if (it != args_.end()) { args_.erase(it); // remove value } } return; } std::string str; for (auto n : vInt) { str.append(std::to_string(n)); str.append(","); } str.pop_back(); // remove the last ',' if (it != args_.end()) { it++; *it = str; return; } args_.emplace_back(name); args_.emplace_back(str); } void RecordOption::SetOption(const std::string &name, const std::string &str) { auto it = std::find(args_.begin(), args_.end(), name); if (str.empty()) { if (it != args_.end()) { args_.erase(it); args_.erase(it); // remove value } return; } if (it != args_.end()) { it++; *it = str; return; } args_.emplace_back(name); args_.emplace_back(str); } void RecordOption::SetOption(const std::string &name, const std::vector &vStr) { auto it = std::find(args_.begin(), args_.end(), name); if (vStr.empty()) { if (it != args_.end()) { args_.erase(it); args_.erase(it); // remove value } return; } std::string str; for (auto substr : vStr) { str.append(substr); str.append(","); } str.pop_back(); // remove the last ',' if (it != args_.end()) { it++; *it = str; return; } args_.emplace_back(name); args_.emplace_back(str); } void RecordOption::SetTargetSystemWide(bool enable) { SetOption(ArgTargetSystemWide, enable); } void RecordOption::SetCompressData(bool enable) { SetOption(ArgCompressData, enable); } void RecordOption::SetSelectCpus(const std::vector &cpus) { SetOption(ArgSelectCpus, cpus); } void RecordOption::SetTimeStopSec(int timeStopSec) { this->timeSpec_ = true; SetOption(ArgTimeStopSec, timeStopSec); } void RecordOption::SetFrequency(int frequency) { SetOption(ArgFrequency, frequency); } void RecordOption::SetPeriod(int period) { SetOption(ArgPeriod, period); } void RecordOption::SetSelectEvents(const std::vector &selectEvents) { SetOption(ArgSelectEvents, selectEvents); } void RecordOption::SetSelectGroups(const std::vector &selectGroups) { SetOption(ArgSelectGroups, selectGroups); } void RecordOption::SetNoInherit(bool enable) { SetOption(ArgNoInherit, enable); } void RecordOption::SetSelectPids(const std::vector &selectPids) { SetOption(ArgSelectPids, selectPids); } void RecordOption::SetCallStackSamplingConfigs(int duration) { if (duration <= 0) { duration = DEFAULT_DURATION_TIME; } RecordOption opt; SetSelectEvents(opt.GetSelectEvents()); SetTimeStopSec(duration); SetFrequency(DEFAULT_FREQUENCY_TIME); SetCallGraph("fp"); } void RecordOption::SetSelectTids(const std::vector &selectTids) { SetOption(ArgSelectTids, selectTids); } void RecordOption::SetExcludePerf(bool excludePerf) { SetOption(ArgExcludePerf, excludePerf); } void RecordOption::SetCpuPercent(int cpuPercent) { SetOption(ArgCpuPercent, cpuPercent); } void RecordOption::SetOffCPU(bool offCPU) { SetOption(ArgOffCPU, offCPU); } void RecordOption::SetCallGraph(const std::string &callGraph) { SetOption(ArgCallGraph, callGraph); } void RecordOption::SetDelayUnwind(bool delayUnwind) { SetOption(ArgDelayUnwind, delayUnwind); } void RecordOption::SetDisableUnwind(bool disableUnwind) { SetOption(ArgDisableUnwind, disableUnwind); } void RecordOption::SetDisableCallstackMerge(bool disableCallstackMerge) { SetOption(ArgDisableCallstackMerge, disableCallstackMerge); } void RecordOption::SetSymbolDir(const std::string &symbolDir_) { SetOption(ArgSymbolDir, symbolDir_); } void RecordOption::SetDataLimit(const std::string &limit) { SetOption(ArgDataLimit, limit); } void RecordOption::SetAppPackage(const std::string &appPackage) { SetOption(ArgAppPackage, appPackage); } void RecordOption::SetClockId(const std::string &clockId) { SetOption(ArgClockId, clockId); } void RecordOption::SetVecBranchSampleTypes(const std::vector &vecBranchSampleTypes) { SetOption(ArgVecBranchSampleTypes, vecBranchSampleTypes); } void RecordOption::SetMmapPages(int mmapPages) { SetOption(ArgMmapPages, mmapPages); } Client::Client(const std::string &outputDir) { HIPERF_HILOGD(MODULE_CPP_API, "%" HILOG_PUBLIC "s default init with %" HILOG_PUBLIC "s\n", __FUNCTION__, outputDir.c_str()); Setup(outputDir); } bool Client::Setup(std::string outputDir) { std::string CurrentCommandPath = CurrentPath + HiperfCommandName; std::string SystemCommandPath = SystemBinPath + HiperfCommandName; std::string TempCommandPath = TempBinPath + HiperfCommandName; if (!outputDir.empty() && outputDir.back() != '/') { outputDir.push_back('/'); } HIPERF_HILOGD(MODULE_CPP_API, "outputDir setup to %" HILOG_PUBLIC "s\n", outputDir.c_str()); // found command path if (access(CurrentCommandPath.c_str(), X_OK) == 0) { executeCommandPath_ = CurrentCommandPath; } else if (access(TempCommandPath.c_str(), X_OK) == 0) { executeCommandPath_ = TempCommandPath; } else if (access(SystemCommandPath.c_str(), X_OK) == 0) { executeCommandPath_ = SystemCommandPath; } else { HIPERF_HILOGD(MODULE_CPP_API, "no hiperf command found\n"); return ready_; } // check output path // found command path if (access(outputDir.c_str(), W_OK) == 0) { outputDir_ = outputDir; } else if (access(CurrentPath.c_str(), W_OK) == 0) { outputDir_ = CurrentPath; } else { HIPERF_HILOGD(MODULE_CPP_API, "no writeable output path found\n"); return ready_; } outputFileName_ = PerfDataName; myPid_ = getpid(); // every thing check ok ready_ = true; return ready_; } Client::~Client() { KillChild(); } bool Client::IsReady() { return ready_; } void Client::SetDebugMode() { debug_ = true; } void Client::SetDebugMuchMode() { debugMuch_ = true; } bool Client::Start() { HIPERF_HILOGD(MODULE_CPP_API, "Client:%" HILOG_PUBLIC "s\n", __FUNCTION__); std::vector args; args.push_back("-p"); args.push_back(std::to_string(getpid())); return Start(args); } void Client::PrepareExecCmd(std::vector &cmd) { cmd.clear(); cmd.emplace_back(executeCommandPath_); if (debug_) { cmd.emplace_back(ArgDebug); } else if (debugMuch_) { cmd.emplace_back(ArgDebugMuch); } if (hilog_) { cmd.emplace_back(ArgHilog); } cmd.emplace_back(CommandRecord); cmd.emplace_back(ArgOutputPath); cmd.emplace_back(GetOutputPerfDataPath()); } void Client::GetExecCmd(std::vector &cmd, int pipeIn, int pipeOut, const std::vector &args) { PrepareExecCmd(cmd); cmd.emplace_back(ArgPipeInput); cmd.emplace_back(std::to_string(pipeIn)); cmd.emplace_back(ArgPipeOutput); cmd.emplace_back(std::to_string(pipeOut)); cmd.insert(cmd.end(), args.begin(), args.end()); } void Client::GetExecCmd(std::vector &cmd, const std::vector &args) { PrepareExecCmd(cmd); cmd.insert(cmd.end(), args.begin(), args.end()); } bool Client::Start(const std::vector &args) { HIPERF_HILOGD(MODULE_CPP_API, "Client:%" HILOG_PUBLIC "s\n", __FUNCTION__); if (!ready_) { HIPERF_HILOGD(MODULE_CPP_API, "Client:hiperf not ready.\n"); return false; } int clientToServerFd[2]; int serverToClientFd[2]; if (pipe(clientToServerFd) != 0) { char errInfo[ERRINFOLEN] = { 0 }; strerror_r(errno, errInfo, ERRINFOLEN); HIPERF_HILOGD(MODULE_CPP_API, "failed to create pipe: %" HILOG_PUBLIC "s", errInfo); return false; } else if (pipe(serverToClientFd) != 0) { char errInfo[ERRINFOLEN] = { 0 }; strerror_r(errno, errInfo, ERRINFOLEN); HIPERF_HILOGD(MODULE_CPP_API, "failed to create pipe: %" HILOG_PUBLIC "s", errInfo); close(clientToServerFd[PIPE_READ]); close(clientToServerFd[PIPE_WRITE]); return false; } hperfPid_ = fork(); if (hperfPid_ == -1) { char errInfo[ERRINFOLEN] = { 0 }; strerror_r(errno, errInfo, ERRINFOLEN); HIPERF_HILOGD(MODULE_CPP_API, "failed to fork: %" HILOG_PUBLIC "s", errInfo); close(clientToServerFd[PIPE_READ]); close(clientToServerFd[PIPE_WRITE]); close(serverToClientFd[PIPE_READ]); close(serverToClientFd[PIPE_WRITE]); return false; } else if (hperfPid_ == 0) { // child process prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); close(clientToServerFd[PIPE_WRITE]); close(serverToClientFd[PIPE_READ]); std::vector cmd; GetExecCmd(cmd, clientToServerFd[PIPE_READ], serverToClientFd[PIPE_WRITE], args); ChildRunExecv(cmd); } else { // parent process close(clientToServerFd[PIPE_READ]); close(serverToClientFd[PIPE_WRITE]); clientToServerFd_ = clientToServerFd[PIPE_WRITE]; serverToClientFd_ = serverToClientFd[PIPE_READ]; } using namespace std::chrono_literals; if (!WaitCommandReply(1000ms)) { HIPERF_HILOGD(MODULE_CPP_API, "start failed . lets kill it"); KillChild(); return false; } return true; } bool Client::Start(const RecordOption &option) { HIPERF_HILOGD(MODULE_CPP_API, "Client:%" HILOG_PUBLIC "s\n", __FUNCTION__); if (!option.GetOutputFileName().empty()) { outputFileName_ = option.GetOutputFileName(); } if (option.IsTimeSpecified()) { return RunHiperfCmdSync(option); } return Start(option.GetOptionVecString()); } void Client::ChildRunExecv(std::vector &cmd) { // conver vector to array for execvp() char *argv[cmd.size() + SIZE_ARGV_TAIL]; size_t i = 0; for (i = 0; i < cmd.size(); ++i) { HIPERF_HILOGD(MODULE_CPP_API, "args %" HILOG_PUBLIC "zu : %" HILOG_PUBLIC "s", i, cmd[i].c_str()); argv[i] = cmd[i].data(); } argv[i] = nullptr; execv(argv[0], argv); // never reach the following line, unless calling of execv function failed. char errInfo[ERRINFOLEN] = { 0 }; strerror_r(errno, errInfo, ERRINFOLEN); HIPERF_HILOGD(MODULE_CPP_API, "failed to call exec: '%" HILOG_PUBLIC "s' %" HILOG_PUBLIC "s\n", executeCommandPath_.c_str(), errInfo); exit(EXIT_FAILURE); // EXIT_FAILURE 1 } bool Client::ParentWait(pid_t &wpid, int &childStatus) { bool ret = false; do { // blocking... int option; #ifdef WCONTINUED option = WUNTRACED | WCONTINUED; #else option = WUNTRACED; #endif wpid = waitpid(hperfPid_, &childStatus, option); if (wpid == -1) { perror("waitpid"); exit(EXIT_FAILURE); } if (WIFEXITED(childStatus)) { // child normally exit // WEXITSTATUS(childStatus) value : // true -> Calling of execv func successed, and recording finished // and child will return the value of recording process's retVal // false -> Calling of execv func failed, child will output errInfo ret = WEXITSTATUS(childStatus) == 0 ? true : false; HIPERF_HILOGD(MODULE_CPP_API, "Hiperf Api Child normally exit Calling of execv : '%" HILOG_PUBLIC "s' \n", ret ? "success" : "failed"); return ret; } else if (WIFSIGNALED(childStatus)) [[unlikely]] { // child was killed by SIGKILL HIPERF_HILOGD(MODULE_CPP_API, "Hiperf recording process was killed by signal SIGKILL\n"); ret = false; return ret; } else if (WIFSTOPPED(childStatus)) [[unlikely]] { // child was stopped by SIGSTOP, and waiting for SIGCONT HIPERF_HILOGD(MODULE_CPP_API, "Hiperf recording process was stopped by signal SIGSTOP\n"); #ifdef WIFCONTINUED } else if (WIFCONTINUED(childStatus)) { // child was continued by SIGCONT HIPERF_HILOGD(MODULE_CPP_API, "Hiperf recording process was continued\n by SIGCONT"); #endif } else [[unlikely]] { // non-standard case, may never happen HIPERF_HILOGD(MODULE_CPP_API, "Hiperf recording process Unexpected status\n"); } } while (!WIFEXITED(childStatus) && !WIFSIGNALED(childStatus)); // normal exit. if (WIFEXITED(childStatus)) { ret = WEXITSTATUS(childStatus) == HIPERF_EXIT_CODE; } else { // signal exit, means Hiperf recording process may occur some runtime errors. HIPERF_HILOGD(MODULE_CPP_API, "Hiperf recording occurs some runtime errors, end with signal : %" HILOG_PUBLIC "d, exit status : %" HILOG_PUBLIC "d\n", WIFSIGNALED(childStatus), WEXITSTATUS(childStatus)); ret = false; } return ret; } bool Client::RunHiperfCmdSync(const RecordOption &option) { HIPERF_HILOGD(MODULE_CPP_API, "Client:%" HILOG_PUBLIC "s\n", __FUNCTION__); if (!ready_) { HIPERF_HILOGD(MODULE_CPP_API, "Client:hiperf not ready.\n"); return false; } const std::vector &args = option.GetOptionVecString(); pid_t wpid; int childStatus; bool ret = false; hperfPid_ = fork(); if (hperfPid_ == -1) { char errInfo[ERRINFOLEN] = { 0 }; strerror_r(errno, errInfo, ERRINFOLEN); HIPERF_HILOGD(MODULE_CPP_API, "failed to fork: %" HILOG_PUBLIC "s", errInfo); return false; } else if (hperfPid_ == 0) { // child execute std::vector cmd; GetExecCmd(cmd, args); ChildRunExecv(cmd); } else { ret = ParentWait(wpid, childStatus); } return ret; } bool Client::WaitCommandReply(std::chrono::milliseconds timeOut) { std::string reply; struct pollfd pollFd; pollFd.fd = serverToClientFd_; pollFd.events = POLLIN; pollFd.revents = 0; // wait some data int polled = poll(&pollFd, 1, timeOut.count()); if (polled > 0) { while (true) { char c; ssize_t result = TEMP_FAILURE_RETRY(read(serverToClientFd_, &c, 1)); if (result <= 0) { HIPERF_HILOGD(MODULE_CPP_API, "read failed from pipe"); return false; // read fial means not ok } reply.push_back(c); if (c == '\n') { break; } } } else if (polled == 0) { HIPERF_HILOGD(MODULE_CPP_API, "Client:command no response %" HILOG_PUBLIC "" PRIu64 ".\n", (uint64_t)timeOut.count()); } else { HIPERF_HILOGD(MODULE_CPP_API, "Client:command poll failed.\n"); } HIPERF_HILOGD(MODULE_CPP_API, "Client:new reply:%" HILOG_PUBLIC "s\n", reply.c_str()); if (reply == ReplyOK) { return true; } else { return false; } } void Client::KillChild() { HIPERF_HILOGD(MODULE_CPP_API, "Client:%" HILOG_PUBLIC "s\n", __FUNCTION__); if (clientToServerFd_ != -1) { close(clientToServerFd_); } if (serverToClientFd_ != -1) { close(serverToClientFd_); } if (hperfPid_ > 0) { kill(hperfPid_, SIGKILL); } } bool Client::SendCommandAndWait(const std::string &cmd) { if (clientToServerFd_ == -1) { HIPERF_HILOGD(MODULE_CPP_API, "fd not ready. maybe not called start."); return false; } size_t size = write(clientToServerFd_, cmd.c_str(), cmd.size()); HIPERF_HILOGD(MODULE_CPP_API, "Client:%" HILOG_PUBLIC "s -> %" HILOG_PUBLIC "d : %" HILOG_PUBLIC "zd\n", cmd.c_str(), clientToServerFd_, size); if (size == cmd.size()) { return WaitCommandReply(); } else { return false; } } bool Client::Pause() { if (!ready_) { HIPERF_HILOGD(MODULE_CPP_API, "Client:hiperf not ready.\n"); return false; } HIPERF_HILOGD(MODULE_CPP_API, "Client:%" HILOG_PUBLIC "s\n", __FUNCTION__); if (SendCommandAndWait(ReplyPause)) { return true; } return false; } bool Client::Resume() { if (!ready_) { HIPERF_HILOGD(MODULE_CPP_API, "Client:hiperf not ready.\n"); return false; } HIPERF_HILOGD(MODULE_CPP_API, "Client:%" HILOG_PUBLIC "s\n", __FUNCTION__); if (SendCommandAndWait(ReplyResume)) { return true; } return false; } bool Client::Stop() { if (!ready_) { HIPERF_HILOGD(MODULE_CPP_API, "Client:hiperf not ready.\n"); return false; } HIPERF_HILOGD(MODULE_CPP_API, "Client:%" HILOG_PUBLIC "s\n", __FUNCTION__); if (SendCommandAndWait(ReplyStop)) { // wait sampling process exit really while (SendCommandAndWait(ReplyCheck)) { std::this_thread::sleep_for(1s); } return true; } return false; } void Client::EnableHilog() { HIPERF_HILOGD(MODULE_CPP_API, "Client:%" HILOG_PUBLIC "s\n", __FUNCTION__); hilog_ = true; } } // namespace HiperfClient } // namespace HiPerf } // namespace Developtools } // namespace OHOS