diff --git a/BUILD.gn b/BUILD.gn index fc23c698274daa161258abb68748c08216c5a744..3fc3e4bf797e82e822e2e36958aa9506d07b6752 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -160,6 +160,7 @@ sources_platform_linux = [ "./src/subcommand_record.cpp", "./src/subcommand_list.cpp", "./src/spe_decoder.cpp", + "./src/perf_pipe.cpp", ] common_deps = [ diff --git a/include/command_reporter.h b/include/command_reporter.h index af782b30583054be70da7cbdeb685faf362a6bc7..06a4ccf2bfff54ea1b05d6d6c29eadd8f81f68e6 100644 --- a/include/command_reporter.h +++ b/include/command_reporter.h @@ -48,7 +48,8 @@ namespace OHOS::Developtools::HiPerf { GEN_ITEM(OPEN_DATA_FILE_FAIL), /* 26 */ \ GEN_ITEM(SET_SYMBOLS_PATH_FAIL), /* 27 */ \ GEN_ITEM(OPTION_NOT_SUPPORT), /* 28 */ \ - GEN_ITEM(SUBCOMMAND_OPTIONS_ERROR) /* 29 */ + GEN_ITEM(SUBCOMMAND_OPTIONS_ERROR), /* 29 */ \ + GEN_ITEM(WRONG_CONTROL_CMD) /* 30 */ #define FOR_ERROR_ENUM(x) x diff --git a/include/perf_events.h b/include/perf_events.h index 38004e6c48f9fcae91633639a3681035609aa2c1..3a3fd49cc7b131dc22f20066151100cb46c33110 100644 --- a/include/perf_events.h +++ b/include/perf_events.h @@ -449,11 +449,12 @@ public: std::vector summaries; }; using StatCallBack = - std::function> &)>; + std::function> &, FILE*)>; using RecordCallBack = std::function; void SetStatCallBack(StatCallBack reportCallBack); void SetRecordCallBack(RecordCallBack recordCallBack); + void SetStatReportFd(FILE* reportFd); void GetLostSamples(size_t &lostSamples, size_t &lostNonSamples) { lostSamples = lostSamples_; @@ -647,6 +648,7 @@ private: unsigned int mmapPages_ = 0; int clockId_ = -1; uint64_t branchSampleType_ = 0; + FILE* reportFd_ = nullptr; SampleStackType sampleStackType_ = SampleStackType::NONE; uint32_t dwarfSampleStackSize_ = MAX_SAMPLE_STACK_SIZE; diff --git a/include/perf_pipe.h b/include/perf_pipe.h new file mode 100644 index 0000000000000000000000000000000000000000..7c60a50318c0751db947afac6f0cafae833b79c6 --- /dev/null +++ b/include/perf_pipe.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 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. + */ + +#ifndef HIPERF_PERF_PIPE +#define HIPERF_PERF_PIPE + +#include +#include +#include "utilities.h" +#include "perf_events.h" + +namespace OHOS { +namespace Developtools { +namespace HiPerf { +enum CommandType { + RECORD = 0, + STAT, +}; +const std::string CONTROL_CMD_PREPARE = "prepare"; +const std::string CONTROL_CMD_START = "start"; +const std::string CONTROL_CMD_PAUSE = "pause"; +const std::string CONTROL_CMD_RESUME = "resume"; +const std::string CONTROL_CMD_OUTPUT = "output"; +const std::string CONTROL_CMD_STOP = "stop"; +class PerfPipe { +private: + std::string fifoFileC2S_; + std::string fifoFileS2C_; + std::string controlCmd_; + std::string perfCmd; + bool outputEnd_ = false; + +public: + void SetFifoFileName(const CommandType& commandType, std::string& controlCmd, + std::string& fifoFileC2S, std::string& fifoFileS2C); + bool CreateFifoFile(); + bool SendFifoAndWaitReply(const std::string &cmd, const std::chrono::milliseconds &timeOut); + bool WaitFifoReply(int fd, const std::chrono::milliseconds &timeOut); + void WaitFifoReply(int fd, const std::chrono::milliseconds &timeOut, std::string& reply); + void ProcessStopCommand(bool ret); + void ProcessOutputCommand(bool ret); + bool ProcessControlCmd(); + void SetOutPutEnd(bool outputEnd); +}; +} // namespace HiPerf +} // namespace Developtools +} // namespace OHOS +#endif // HIPERF_PERF_PIPE \ No newline at end of file diff --git a/include/subcommand_record.h b/include/subcommand_record.h index 35f06d2edffd651f85d81f42e7726543cae94820..5319b98ce2aa977eb8acac112f33b5b2ae64ddc5 100644 --- a/include/subcommand_record.h +++ b/include/subcommand_record.h @@ -33,6 +33,7 @@ #include "perf_event_record.h" #include "perf_events.h" #include "perf_file_writer.h" +#include "perf_pipe.h" #include "subcommand.h" #include "virtual_runtime.h" @@ -224,6 +225,7 @@ public: private: PerfEvents perfEvents_; + PerfPipe perfPipe_; bool targetSystemWide_ = false; bool compressData_ = false; @@ -251,6 +253,8 @@ private: int checkAppMs_ = DEFAULT_CHECK_APP_MS; std::string clockId_ = {}; std::string strLimit_ = {}; + std::string fifoFileC2S_; + std::string fifoFileS2C_; std::vector selectCpus_ = {}; std::vector selectPids_ = {}; std::vector selectTids_ = {}; @@ -301,8 +305,11 @@ private: // for client int clientPipeInput_ = -1; int clientPipeOutput_ = -1; + int readFd_ = -1; + int writeFd_ = -1; int nullFd_ = -1; std::thread clientCommandHanle_; + std::thread replyCommandHanle_; bool clientRunning_ = true; struct ControlCommandHandler { std::function preProcess = []() -> bool { @@ -312,11 +319,15 @@ private: }; std::unordered_map controlCommandHandlerMap_ = {}; inline void CreateClientThread(); + inline void CreateReplyThread(); void ClientCommandHandle(); + void ReplyCommandHandle(); void InitControlCommandHandlerMap(); void DispatchControlCommand(const std::string& command); bool ClientCommandResponse(bool response); bool ClientCommandResponse(const std::string& str); + bool ChildResponseToMain(bool response); + bool ChildResponseToMain(const std::string& str); bool IsSamplingRunning(); // for cmdline client @@ -327,14 +338,10 @@ private: bool dedupStack_ = false; std::map> mapPids_; bool ProcessControl(); - void ProcessStopCommand(bool ret); - void ProcessOutputCommand(bool ret); bool CreateFifoServer(); - bool SendFifoAndWaitReply(const std::string &cmd, const std::chrono::milliseconds &timeOut); - bool WaitFifoReply(int fd, const std::chrono::milliseconds &timeOut); - void WaitFifoReply(int fd, const std::chrono::milliseconds &timeOut, std::string& reply); + bool MainRecvFromChild(int fd, std::string& reply); void CloseClientThread(); - std::string HandleAppInfo(); + void CloseReplyThread(); bool PreparePerfEvent(); bool PrepareSysKernel(); @@ -391,6 +398,12 @@ private: bool CheckReportOption(); bool CheckBacktrackOption(); bool CheckSpeOption(); + bool IsAppRestarted(const std::string &appPackage); + bool CheckAppRestart(std::string &appPackage, bool targetSystemWide, bool restart, + std::vector &selectPids); + pid_t GetPidFromAppPackage(const std::string &appPackage, const pid_t oldPid, const int checkAppMs, + const uint64_t waitAppTimeOut); + bool IsAppRunning(std::vector &selectPids, const std::string &appPackage, int checkAppMs); void WriteCommEventBeforeSampling(); void RemoveVdsoTmpFile(); diff --git a/include/subcommand_stat.h b/include/subcommand_stat.h index c961291d09122bd8708d02193ea5cbcd91078839..3edfed72e5af6b526c6948fe77320b5aef160a22 100644 --- a/include/subcommand_stat.h +++ b/include/subcommand_stat.h @@ -17,6 +17,7 @@ #include "option.h" #include "perf_events.h" +#include "perf_pipe.h" #include "subcommand.h" namespace OHOS { @@ -82,12 +83,20 @@ public: " Show more detailed reports.\n" " --dumpoptions\n" " Dump command options.\n" + " --control \n" + " Control counting by , the can be:\n" + " prepare: set arguments and prepare counting\n" + " start: start counting\n" + " stop: stop counting\n" + " -o \n" + " Set output file name, default is /data/local/tmp/perf_stat.txt.\n" + " Only restrain using with --control prepare.\n" // clang-format on ), targetSystemWide_(false) { } - + ~SubCommandStat(); HiperfError OnSubCommand(std::vector& args) override; bool ParseOption(std::vector &args) override; bool ParseSpecialOption(std::vector &args); @@ -100,6 +109,7 @@ public: private: PerfEvents perfEvents_; + PerfPipe perfPipe_; bool targetSystemWide_ {false}; std::vector selectCpus_ = {}; float timeStopSec_ = PerfEvents::DEFAULT_TIMEOUT; @@ -109,6 +119,9 @@ private: bool restart_ {false}; bool noCreateNew_ {false}; std::string appPackage_ = {}; + std::string outputFilename_ = ""; + std::string fifoFileC2S_; + std::string fifoFileS2C_; int checkAppMs_ = DEFAULT_CHECK_APP_MS; std::vector selectPids_; std::vector selectTids_; @@ -120,6 +133,7 @@ private: bool helpOption_ {false}; bool CheckOptionPidAndApp(std::vector pids); bool CheckOptionPid(std::vector pids); + bool CheckOutPutFile(); static bool FindEventCount( const std::map> &countEvents, const std::string &configName, const __u64 group_id, __u64 &eventcount, double &scale); @@ -133,14 +147,15 @@ private: static std::string GetCommentConfigName( const std::unique_ptr &countEvent, std::string eventName); - static void Report(const std::map> &countEvents); - static void PrintPerHead(); + static void Report(const std::map> &countEvents, FILE* fd); + static void PrintPerHead(FILE* fd); static void GetPerKey(std::string &perKey, const PerfEvents::Summary &summary); static void MakeComments(const std::unique_ptr &reportSum, std::string &commentStr); - static void ReportNormal(const std::map> &countEvents); - static void ReportDetailInfos(const std::map> &countEvents); + static void ReportNormal(const std::map> &countEvents, FILE* fd); + static void ReportDetailInfos(const std::map> &countEvents, + FILE* fd); static void PrintPerValue(const std::unique_ptr &reportSum, const float &ratio, - std::string &configName); + std::string &configName, FILE* fd); static void InitPerMap(const std::unique_ptr &newPerMap, const PerfEvents::Summary &summary, VirtualRuntime& virtualInstance); static bool FindPerCoreEventCount(PerfEvents::Summary &summary, __u64 &eventCount, double &scale); @@ -160,6 +175,41 @@ private: bool CheckSelectCpuPidOption(); void SetReportFlags(bool cpuFlag, bool threadFlag); void SetPerfEvent(); + HiperfError CheckStatOption(); + + // for client + int clientPipeInput_ = -1; + int clientPipeOutput_ = -1; + int nullFd_ = -1; + FILE* fileFd_ = nullptr; + std::thread clientCommandHanle_; + bool clientRunning_ = true; + struct ControlCommandHandler { + std::function preProcess = []() -> bool { + return false; + }; + std::function postProcess = [](bool) {}; + }; + std::unordered_map controlCommandHandlerMap_ = {}; + inline void CreateClientThread(); + void ClientCommandHandle(); + void InitControlCommandHandlerMap(); + void DispatchControlCommand(const std::string& command); + bool ClientCommandResponse(bool response); + bool ClientCommandResponse(const std::string& str); + bool IsSamplingRunning(); + + // for cmdline client + bool allowIpc_ = true; + std::string controlCmd_ = {}; + bool isFifoServer_ = false; + bool isFifoClient_ = false; + bool dedupStack_ = false; + std::map> mapPids_; + bool ProcessControl(); + bool CreateFifoServer(); + void CloseClientThread(); + bool ParseControlCmd(const std::string cmd); }; bool RegisterSubCommandStat(void); diff --git a/include/utilities.h b/include/utilities.h index b5a810e689a6aaeb40c81d5ea7100a52c6245dd3..f8ae3612e01011aa6558fd21c150ec5148cfb160 100644 --- a/include/utilities.h +++ b/include/utilities.h @@ -387,6 +387,7 @@ const std::string GetUserType(); bool GetDeveloperMode(); bool IsArkJsFile(const std::string& filepath); std::string GetProcessName(int pid); +std::string HandleAppInfo(const std::string& appPackage, const std::vector &inputPidTidArgs); bool NeedAdaptSandboxPath(char *filename, int pid, u16 &headerSize); bool NeedAdaptHMBundlePath(std::string& filename, const std::string& threadname); diff --git a/src/perf_events.cpp b/src/perf_events.cpp index 6c98ef9ae3dbfc69ca8436df1a31e24723764cd3..1e0c2cce6342a489f4ad23a69e6b9148f3e41e0f 100644 --- a/src/perf_events.cpp +++ b/src/perf_events.cpp @@ -195,6 +195,10 @@ PerfEvents::~PerfEvents() } ExitReadRecordBufThread(); + if (reportFd_ != nullptr) { + fclose(reportFd_); + reportFd_ = nullptr; + } } bool PerfEvents::IsEventSupport(perf_type_id type, __u64 config) @@ -738,6 +742,7 @@ bool PerfEvents::StopTracking(void) { if (g_trackRunning) { printf("some one called StopTracking\n"); + HLOGI("some one called StopTracking"); HIPERF_HILOGI(MODULE_DEFAULT, "some one called StopTracking"); g_trackRunning = false; if (trackedCommand_) { @@ -1014,6 +1019,12 @@ void PerfEvents::SetStatCallBack(StatCallBack reportCallBack) { reportCallBack_ = reportCallBack; } + +void PerfEvents::SetStatReportFd(FILE* reportFd) +{ + reportFd_ = reportFd; +} + void PerfEvents::SetRecordCallBack(RecordCallBack recordCallBack) { recordCallBack_ = recordCallBack; @@ -1272,7 +1283,7 @@ bool PerfEvents::StatReport(const __u64 &durationInSec) } } - reportCallBack_(countEvents_); + reportCallBack_(countEvents_, reportFd_); return true; } @@ -1838,8 +1849,13 @@ void PerfEvents::StatLoop() usedTimeMsTick = duration_cast(thisTime - startTime); durationInSec = usedTimeMsTick.count(); auto lefTimeMsTick = duration_cast(endTime - thisTime); - printf("\nReport at %" PRId64 " ms (%" PRId64 " ms left):\n", - (uint64_t)usedTimeMsTick.count(), (uint64_t)lefTimeMsTick.count()); + if (reportFd_ == nullptr) { + printf("\nReport at %" PRId64 " ms (%" PRId64 " ms left):\n", + (uint64_t)usedTimeMsTick.count(), (uint64_t)lefTimeMsTick.count()); + } else { + fprintf(reportFd_, "\nReport at %" PRId64 " ms (%" PRId64 " ms left):\n", + (uint64_t)usedTimeMsTick.count(), (uint64_t)lefTimeMsTick.count()); + } // end of comments nextReportTime += timeReport_; StatReport(durationInSec); @@ -1853,7 +1869,11 @@ void PerfEvents::StatLoop() if (thisTime >= endTime) { usedTimeMsTick = duration_cast(thisTime - startTime); durationInSec = usedTimeMsTick.count(); - printf("Timeout exit (total %" PRId64 " ms)\n", (uint64_t)usedTimeMsTick.count()); + if (reportFd_ == nullptr) { + printf("Timeout exit (total %" PRId64 " ms)\n", (uint64_t)usedTimeMsTick.count()); + } else { + fprintf(reportFd_, "Timeout exit (total %" PRId64 " ms)\n", (uint64_t)usedTimeMsTick.count()); + } if (trackedCommand_) { trackedCommand_->Stop(); } diff --git a/src/perf_pipe.cpp b/src/perf_pipe.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7b18e9dbca1ecceb92e3bd86439582699c00a980 --- /dev/null +++ b/src/perf_pipe.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (c) 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 "perf_pipe.h" +#include "hiperf_client.h" +#include "ipc_utilities.h" +#include "utilities.h" +#if defined(is_ohos) && is_ohos +#include "hiperf_hilog.h" +#endif + +using namespace std::chrono; +namespace OHOS { +namespace Developtools { +namespace HiPerf { +const std::string RECORD_CONTROL_FIFO_FILE_C2S = "/data/log/hiperflog/.hiperf_record_control_c2s"; +const std::string RECORD_CONTROL_FIFO_FILE_S2C = "/data/log/hiperflog/.hiperf_record_control_s2c"; +const std::string STAT_CONTROL_FIFO_FILE_C2S = "/data/log/hiperflog/.hiperf_stat_control_c2s"; +const std::string STAT_CONTROL_FIFO_FILE_S2C = "/data/log/hiperflog/.hiperf_stat_control_s2c"; +const std::chrono::milliseconds CONTROL_WAITREPY_TIMEOUT = 2000ms; +const std::chrono::milliseconds CONTROL_WAITREPY_TIMEOUT_CHECK = 1000ms; +static constexpr uint64_t CHECK_WAIT_TIME_MS = 200; +static constexpr uint32_t MAX_CLIENT_OUTPUT_WAIT_COUNT = 240; + +void PerfPipe::SetFifoFileName(const CommandType& commandType, std::string& controlCmd, + std::string& fifoFileC2S, std::string& fifoFileS2C) +{ + if (commandType == CommandType::RECORD) { + fifoFileC2S_ = RECORD_CONTROL_FIFO_FILE_C2S; + fifoFileS2C_ = RECORD_CONTROL_FIFO_FILE_S2C; + perfCmd = "sampling"; + } else if (commandType == CommandType::STAT) { + fifoFileC2S_ = STAT_CONTROL_FIFO_FILE_C2S; + fifoFileS2C_ = STAT_CONTROL_FIFO_FILE_S2C; + perfCmd = "counting"; + } + fifoFileC2S = fifoFileC2S_; + fifoFileS2C = fifoFileS2C_; + controlCmd_ = controlCmd; + HLOGD("C2S:%s, S2C:%s", fifoFileC2S.c_str(), fifoFileS2C.c_str()); +} + +bool PerfPipe::CreateFifoFile() +{ + char errInfo[ERRINFOLEN] = { 0 }; + const mode_t fifoMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + std::string tempPath("/data/log/hiperflog/"); + if (!IsDirectoryExists(tempPath)) { + HIPERF_HILOGI(MODULE_DEFAULT, "%{public}s not exist.", tempPath.c_str()); + if (!CreateDirectory(tempPath, HIPERF_FILE_PERM_770)) { + HIPERF_HILOGI(MODULE_DEFAULT, "create %{public}s failed.", tempPath.c_str()); + } + } + if (mkfifo(fifoFileS2C_.c_str(), fifoMode) != 0 || + mkfifo(fifoFileC2S_.c_str(), fifoMode) != 0) { + if (errno == EEXIST) { + printf("another %s service is running.\n", perfCmd.c_str()); + } else { + remove(fifoFileS2C_.c_str()); + remove(fifoFileC2S_.c_str()); + } + strerror_r(errno, errInfo, ERRINFOLEN); + HLOGE("create fifo file failed. %d:%s", errno, errInfo); + return false; + } + return true; +} + +bool PerfPipe::SendFifoAndWaitReply(const std::string &cmd, const std::chrono::milliseconds &timeOut) +{ + // need open for read first, because server maybe send reply before client wait to read + int fdRead = open(fifoFileS2C_.c_str(), O_RDONLY | O_NONBLOCK); + if (fdRead == -1) { + HLOGE("can not open fifo file(%s)", fifoFileS2C_.c_str()); + HIPERF_HILOGE(MODULE_DEFAULT, "can not open fifo file: %{public}s.", fifoFileS2C_.c_str()); + return false; + } + int fdWrite = open(fifoFileC2S_.c_str(), O_WRONLY | O_NONBLOCK); + if (fdWrite == -1) { + HLOGE("can not open fifo file(%s)", fifoFileC2S_.c_str()); + HIPERF_HILOGE(MODULE_DEFAULT, "can not open fifo file: %{public}s.", fifoFileC2S_.c_str()); + close(fdRead); + return false; + } + size_t size = write(fdWrite, cmd.c_str(), cmd.size()); + if (size != cmd.size()) { + HLOGE("failed to write fifo file(%s) command(%s)", fifoFileC2S_.c_str(), cmd.c_str()); + HIPERF_HILOGE(MODULE_DEFAULT, "failed to write fifo file(%{public}s) command(%{public}s).", + fifoFileC2S_.c_str(), cmd.c_str()); + close(fdWrite); + close(fdRead); + return false; + } + close(fdWrite); + + bool ret = WaitFifoReply(fdRead, timeOut); + close(fdRead); + return ret; +} + +bool PerfPipe::WaitFifoReply(int fd, const std::chrono::milliseconds &timeOut) +{ + std::string reply; + WaitFifoReply(fd, timeOut, reply); + return reply == HiperfClient::ReplyOK; +} + +void PerfPipe::WaitFifoReply(int fd, const std::chrono::milliseconds &timeOut, std::string& reply) +{ + struct pollfd pollFd { + fd, POLLIN, 0 + }; + int polled = poll(&pollFd, 1, timeOut.count()); + reply.clear(); + if (polled > 0) { + bool exitLoop = false; + while (!exitLoop) { + char c; + ssize_t result = TEMP_FAILURE_RETRY(read(fd, &c, 1)); + if (result <= 0) { + HLOGE("read from fifo file(%s) failed", fifoFileS2C_.c_str()); + HIPERF_HILOGE(MODULE_DEFAULT, "read from fifo file(%{public}s) failed", fifoFileS2C_.c_str()); + exitLoop = true; + } + reply.push_back(c); + if (c == '\n') { + exitLoop = true; + } + } + } else if (polled == 0) { + HLOGD("wait fifo file(%s) timeout", fifoFileS2C_.c_str()); + HIPERF_HILOGD(MODULE_DEFAULT, "wait fifo file(%{public}s) timeout", fifoFileS2C_.c_str()); + } else { + HLOGD("wait fifo file(%s) failed", fifoFileS2C_.c_str()); + HIPERF_HILOGD(MODULE_DEFAULT, "wait fifo file(%{public}s) failed", fifoFileS2C_.c_str()); + } +} + +void PerfPipe::SetOutPutEnd(bool outputEnd) +{ + outputEnd_ = outputEnd; +} + +void PerfPipe::ProcessStopCommand(bool ret) +{ + if (ret) { + // wait sampling process exit really + static constexpr uint64_t waitCheckSleepMs = 200; + std::this_thread::sleep_for(milliseconds(waitCheckSleepMs)); + while (SendFifoAndWaitReply(HiperfClient::ReplyCheck, CONTROL_WAITREPY_TIMEOUT_CHECK)) { + std::this_thread::sleep_for(milliseconds(waitCheckSleepMs)); + } + HLOGI("wait reply check end."); + } + + if (remove(fifoFileC2S_.c_str()) != 0) { + HLOGE("remove fifo file %s failed", fifoFileC2S_.c_str()); + } + if (remove(fifoFileS2C_.c_str()) != 0) { + HLOGE("remove fifo file %s failed", fifoFileS2C_.c_str()); + } +} + +void PerfPipe::ProcessOutputCommand(bool ret) +{ + if (!ret) { + HLOGI("send fifo and wait repoy fail"); + HIPERF_HILOGI(MODULE_DEFAULT, "send fifo and wait repoy fail"); + return; + } + + std::this_thread::sleep_for(milliseconds(CHECK_WAIT_TIME_MS)); + uint32_t outputFailCount = 0; + while (!outputEnd_) { + ret = SendFifoAndWaitReply(HiperfClient::ReplyOutputCheck, CONTROL_WAITREPY_TIMEOUT_CHECK); + if (outputFailCount++ > MAX_CLIENT_OUTPUT_WAIT_COUNT || ret) { + break; + } + std::this_thread::sleep_for(milliseconds(CHECK_WAIT_TIME_MS)); + } +} + +bool PerfPipe::ProcessControlCmd() +{ + bool ret = false; + if (controlCmd_ == CONTROL_CMD_START) { + ret = SendFifoAndWaitReply(HiperfClient::ReplyStart, CONTROL_WAITREPY_TIMEOUT); + } else if (controlCmd_ == CONTROL_CMD_RESUME) { + ret = SendFifoAndWaitReply(HiperfClient::ReplyResume, CONTROL_WAITREPY_TIMEOUT); + } else if (controlCmd_ == CONTROL_CMD_PAUSE) { + ret = SendFifoAndWaitReply(HiperfClient::ReplyPause, CONTROL_WAITREPY_TIMEOUT); + } else if (controlCmd_ == CONTROL_CMD_STOP) { + ret = SendFifoAndWaitReply(HiperfClient::ReplyStop, CONTROL_WAITREPY_TIMEOUT); + if (!ret) { + ret = SendFifoAndWaitReply(HiperfClient::ReplyStop, CONTROL_WAITREPY_TIMEOUT); + } + ProcessStopCommand(ret); + } else if (controlCmd_ == CONTROL_CMD_OUTPUT) { + ret = SendFifoAndWaitReply(HiperfClient::ReplyOutput, CONTROL_WAITREPY_TIMEOUT); + ProcessOutputCommand(ret); + } + if (ret) { + printf("%s %s success.\n", controlCmd_.c_str(), perfCmd.c_str()); + HIPERF_HILOGI(MODULE_DEFAULT, "%{public}s %{public}s success.", controlCmd_.c_str(), perfCmd.c_str()); + } else { + printf("%s %s failed.\n", controlCmd_.c_str(), perfCmd.c_str()); + HIPERF_HILOGI(MODULE_DEFAULT, "%{public}s %{public}s failed.", controlCmd_.c_str(), perfCmd.c_str()); + } + return ret; +} +} // namespace HiPerf +} // namespace Developtools +} // namespace OHOS \ No newline at end of file diff --git a/src/subcommand_record.cpp b/src/subcommand_record.cpp index 8f922fde869e103d3f0b3505573c3e7c52559ce9..451bd8fbc012270ccaa1fe677a637887a63368c1 100644 --- a/src/subcommand_record.cpp +++ b/src/subcommand_record.cpp @@ -45,15 +45,6 @@ using namespace std::chrono; namespace OHOS { namespace Developtools { namespace HiPerf { -const std::string CONTROL_CMD_PREPARE = "prepare"; -const std::string CONTROL_CMD_START = "start"; -const std::string CONTROL_CMD_PAUSE = "pause"; -const std::string CONTROL_CMD_RESUME = "resume"; -const std::string CONTROL_CMD_OUTPUT = "output"; -const std::string CONTROL_CMD_STOP = "stop"; -const std::string CONTROL_FIFO_FILE_C2S = "/data/log/hiperflog/.hiperf_record_control_c2s"; -const std::string CONTROL_FIFO_FILE_S2C = "/data/log/hiperflog/.hiperf_record_control_s2c"; - const std::string PERF_CPU_TIME_MAX_PERCENT = "/proc/sys/kernel/perf_cpu_time_max_percent"; const std::string PERF_EVENT_MAX_SAMPLE_RATE = "/proc/sys/kernel/perf_event_max_sample_rate"; const std::string PERF_EVENT_MLOCK_KB = "/proc/sys/kernel/perf_event_mlock_kb"; @@ -64,7 +55,8 @@ const std::string SAVED_CMDLINES_SIZE = "/sys/kernel/tracing/saved_cmdlines_size // when there are many events, start record will take more time. const std::chrono::milliseconds CONTROL_WAITREPY_TIMEOUT = 2000ms; -const std::chrono::milliseconds CONTROL_WAITREPY_TIMEOUT_CHECK = 1000ms; +const int CONTROL_WAIT_RESPONSE_TIMEOUT = 35000; // 35s: Timeout for main process wait app restart finish +constexpr const uint8_t WAIT_TIMEOUT = 40; // 40s: Timeout for the main process exit constexpr uint64_t MASK_ALIGNED_8 = 7; constexpr size_t MAX_DWARF_CALL_CHAIN = 2; @@ -72,9 +64,11 @@ constexpr uint64_t TYPE_PERF_SAMPLE_BRANCH = PERF_SAMPLE_BRANCH_ANY | PERF_SAMPL PERF_SAMPLE_BRANCH_ANY_RETURN | PERF_SAMPLE_BRANCH_IND_JUMP | PERF_SAMPLE_BRANCH_IND_CALL | PERF_SAMPLE_BRANCH_COND | PERF_SAMPLE_BRANCH_CALL; +static constexpr uint64_t PIPE_READ = 0; +static constexpr uint64_t PIPE_WRITE = 1; static constexpr uint64_t CHECK_WAIT_TIME_MS = 200; static constexpr uint32_t MAX_SERVER_OUTPUT_WAIT_COUNT = 600; -static constexpr uint32_t MAX_CLIENT_OUTPUT_WAIT_COUNT = 240; +static std::atomic_bool g_callStop = false; int GetClockId(const std::string &name) { @@ -116,7 +110,16 @@ uint64_t GetBranchSampleType(const std::string &name) SubCommandRecord::~SubCommandRecord() { + CloseReplyThread(); CloseClientThread(); + if (readFd_ != -1) { + close(readFd_); + readFd_ = -1; + } + if (writeFd_ != -1) { + close(writeFd_); + writeFd_ = -1; + } } void SubCommandRecord::DumpOptions() const @@ -609,10 +612,7 @@ bool SubCommandRecord::CheckTargetProcessOptions() printf("please select a target process\n"); return false; } - if (controlCmd_ == CONTROL_CMD_PREPARE) { - CHECK_TRUE(!CheckRestartOption(appPackage_, targetSystemWide_, restart_, selectPids_), false, 0, ""); - } - return CheckTargetPids(); + return true; } bool SubCommandRecord::CheckTargetPids() @@ -629,7 +629,7 @@ bool SubCommandRecord::CheckTargetPids() return false; } } - if (!CheckAppIsRunning(selectPids_, appPackage_, checkAppMs_)) { + if (!IsAppRunning(selectPids_, appPackage_, checkAppMs_)) { return false; } if (!selectPids_.empty()) { @@ -693,6 +693,111 @@ bool SubCommandRecord::CheckSpeOption() return true; } +bool SubCommandRecord::IsAppRestarted(const std::string &appPackage) +{ + if (controlCmd_ == CONTROL_CMD_PREPARE) { + std::string info = "please restart " + appPackage + " for profiling within 30 seconds\n"; + ChildResponseToMain(info); + } + printf("please restart %s for profiling within 30 seconds\n", appPackage.c_str()); + std::set oldPids {}; + std::set newPids {}; + std::vector intersection; + const auto startTime = steady_clock::now(); + const auto endTime = startTime + std::chrono::seconds(CHECK_TIMEOUT); + CollectPidsByAppname(oldPids, appPackage); + do { + CollectPidsByAppname(newPids, appPackage); + std::set_intersection(oldPids.begin(), oldPids.end(), + newPids.begin(), newPids.end(), std::back_insert_iterator(intersection)); + // app names are same, no intersection, means app restarted + CHECK_TRUE(intersection.empty(), true, 0, ""); + intersection.clear(); + newPids.clear(); + std::this_thread::sleep_for(milliseconds(CHECK_FREQUENCY)); + } while (steady_clock::now() < endTime && !g_callStop); + if (!g_callStop && controlCmd_ == CONTROL_CMD_PREPARE) { + std::string err = "app " + appPackage + " was not stopped within 30 seconds\n"; + ChildResponseToMain(err); + } + printf("app %s was not stopped within 30 seconds\n", appPackage.c_str()); + return false; +} + +bool SubCommandRecord::CheckAppRestart(std::string &appPackage, bool targetSystemWide, bool restart, + std::vector &selectPids) +{ + if (!restart) { + return true; + } + if (appPackage.empty()) { + if (controlCmd_ == CONTROL_CMD_PREPARE) { + std::string err = "to detect the performance of application startup, --app option must be given\n"; + ChildResponseToMain(err); + } + printf("to detect the performance of application startup, --app option must be given\n"); + return false; + } + if (targetSystemWide || !selectPids.empty()) { + if (controlCmd_ == CONTROL_CMD_PREPARE) { + std::string err = "option --restart and -p/-a is conflict, please check usage\n"; + ChildResponseToMain(err); + } + printf("option --restart and -p/-a is conflict, please check usage\n"); + return false; + } + + if (!appPackage.empty()) { + return IsAppRestarted(appPackage); + } + return false; +} + +pid_t SubCommandRecord::GetPidFromAppPackage(const std::string &appPackage, const pid_t oldPid, const int checkAppMs, + const uint64_t waitAppTimeOut) +{ + pid_t res {-1}; + const std::string basePath {"/proc/"}; + const std::string cmdline {"/cmdline"}; + const auto startTime = steady_clock::now(); + const auto endTime = startTime + std::chrono::seconds(waitAppTimeOut); + do { + std::vector subDirs = GetSubDirs(basePath); + for (const auto &subDir : subDirs) { + if (!IsDigits(subDir)) { + continue; + } + std::string fileName {basePath + subDir + cmdline}; + if (IsSameCommand(ReadFileToString(fileName), appPackage)) { + res = std::stoul(subDir, nullptr); + HLOGD("[GetAppPackagePid]: get appid for %s is %d", appPackage.c_str(), res); + return res; + } + } + std::this_thread::sleep_for(milliseconds(checkAppMs)); + } while (steady_clock::now() < endTime && !g_callStop); + + return res; +} + +bool SubCommandRecord::IsAppRunning(std::vector &selectPids, const std::string &appPackage, int checkAppMs) +{ + if (!appPackage.empty()) { + pid_t appPid = GetPidFromAppPackage(appPackage, -1, checkAppMs, waitAppRunCheckTimeOut); + if (appPid <= 0) { + if (controlCmd_ == CONTROL_CMD_PREPARE && !g_callStop) { + std::string err = "app " + appPackage + " not running\n"; + ChildResponseToMain(err); + } + printf("app %s not running\n", appPackage.c_str()); + return false; + } + HLOGD("[CheckAppIsRunning] get appPid %d for app %s", appPid, appPackage.c_str()); + selectPids.push_back(appPid); + } + return true; +} + bool SubCommandRecord::ParseDataLimitOption(const std::string &str) { uint unit = 1; @@ -1071,11 +1176,76 @@ bool SubCommandRecord::ClientCommandResponse(bool response) bool SubCommandRecord::ClientCommandResponse(const std::string& str) { + clientPipeOutput_ = open(fifoFileS2C_.c_str(), O_WRONLY); + if (clientPipeOutput_ == -1) { + char errInfo[ERRINFOLEN] = { 0 }; + strerror_r(errno, errInfo, ERRINFOLEN); + HLOGE("open fifo file(%s) failed, errno:%d:%s", fifoFileS2C_.c_str(), errno, errInfo); + HIPERF_HILOGE(MODULE_DEFAULT, "open fifo file(%{public}s) failed, errno:%{public}d:%{public}s", + fifoFileS2C_.c_str(), errno, errInfo); + return false; + } size_t size = write(clientPipeOutput_, str.c_str(), str.size()); if (size != str.size()) { char errInfo[ERRINFOLEN] = { 0 }; strerror_r(errno, errInfo, ERRINFOLEN); HLOGD("Server:%s -> %d : %zd %d:%s", str.c_str(), clientPipeOutput_, size, errno, errInfo); + HIPERF_HILOGE(MODULE_DEFAULT, "Server:%{public}s -> %{public}d : %{public}zd %{public}d:%{public}s", + str.c_str(), clientPipeOutput_, size, errno, errInfo); + return false; + } + return true; +} + +bool SubCommandRecord::ChildResponseToMain(bool response) +{ + return ChildResponseToMain(response ? HiperfClient::ReplyOK : HiperfClient::ReplyFAIL); +} + +bool SubCommandRecord::ChildResponseToMain(const std::string& str) +{ + size_t size = write(writeFd_, str.c_str(), str.size()); + if (size != str.size()) { + char errInfo[ERRINFOLEN] = { 0 }; + strerror_r(errno, errInfo, ERRINFOLEN); + HLOGE("write pipe failed. str:%s, size:%zd, errno:%d:%s", str.c_str(), size, errno, errInfo); + HIPERF_HILOGE(MODULE_DEFAULT, + "write pipe failed. str:%{public}s, size:%{public}zd, errno:%{public}d:%{public}s", + str.c_str(), size, errno, errInfo); + return false; + } + return true; +} + +bool SubCommandRecord::MainRecvFromChild(int fd, std::string& reply) +{ + struct pollfd pollFd { + fd, POLLIN, 0 + }; + int polled = poll(&pollFd, 1, CONTROL_WAIT_RESPONSE_TIMEOUT); + reply.clear(); + if (polled > 0) { + bool exitLoop = false; + while (!exitLoop) { + char c; + ssize_t result = TEMP_FAILURE_RETRY(read(fd, &c, 1)); + if (result <= 0) { + HLOGD("read from pipeFd failed"); + HIPERF_HILOGE(MODULE_DEFAULT, "read from pipeFd failed"); + exitLoop = true; + } + reply.push_back(c); + if (c == '\n') { + exitLoop = true; + } + } + } else if (polled == 0) { + HLOGD("wait pipeFd timeout"); + HIPERF_HILOGE(MODULE_DEFAULT, "wait pipeFd timeout"); + return false; + } else { + HLOGD("wait pipeFd failed"); + HIPERF_HILOGE(MODULE_DEFAULT, "wait pipeFd failed"); return false; } return true; @@ -1116,6 +1286,7 @@ bool SubCommandRecord::PreOutputRecordFile() return false; } outputEnd_ = false; + perfPipe_.SetOutPutEnd(outputEnd_); return true; } @@ -1145,6 +1316,7 @@ bool SubCommandRecord::PostOutputRecordFile(bool output) fileWriter_ = nullptr; outputEnd_ = true; + perfPipe_.SetOutPutEnd(outputEnd_); StringViewHold::Get().Clean(); return true; } @@ -1182,20 +1354,31 @@ void SubCommandRecord::InitControlCommandHandlerMap() }); } +inline void SubCommandRecord::CreateReplyThread() +{ + replyCommandHanle_ = std::thread(&SubCommandRecord::ReplyCommandHandle, this); +} + +void SubCommandRecord::ReplyCommandHandle() +{ + if (!IsSamplingRunning()) { + HLOGI("IsSamplingRunning() return false"); + HIPERF_HILOGI(MODULE_DEFAULT, "IsSamplingRunning() return false"); + ChildResponseToMain(false); + return; + } + ChildResponseToMain(true); +} + inline void SubCommandRecord::CreateClientThread() { // make a thread wait the other command - if (clientPipeOutput_ != -1) { - clientCommandHanle_ = std::thread(&SubCommandRecord::ClientCommandHandle, this); - } + clientCommandHanle_ = std::thread(&SubCommandRecord::ClientCommandHandle, this); } void SubCommandRecord::ClientCommandHandle() { using namespace HiperfClient; - CHECK_TRUE(!IsSamplingRunning(), NO_RETVAL, 0, ""); - // tell the caller if Exist - ClientCommandResponse(true); InitControlCommandHandlerMap(); bool hasRead = true; @@ -1205,7 +1388,7 @@ void SubCommandRecord::ClientCommandHandle() // after read(), block is disabled, the poll will be waked neven if no data close(clientPipeInput_); } - clientPipeInput_ = open(CONTROL_FIFO_FILE_C2S.c_str(), O_RDONLY | O_NONBLOCK); + clientPipeInput_ = open(fifoFileC2S_.c_str(), O_RDONLY | O_NONBLOCK); } struct pollfd pollFd { clientPipeInput_, POLLIN, 0 @@ -1231,6 +1414,11 @@ void SubCommandRecord::ClientCommandHandle() } HLOGD("server:new command %s", command.c_str()); HIPERF_HILOGI(MODULE_DEFAULT, "server:new command : %{public}s", command.c_str()); + if (command.find("STOP") != std::string::npos) { + HLOGD("receive sop command, set g_callStop to true"); + HIPERF_HILOGI(MODULE_DEFAULT, "receive sop command, set g_callStop to true"); + g_callStop = true; + } DispatchControlCommand(command); } } @@ -1254,99 +1442,30 @@ bool SubCommandRecord::ProcessControl() return true; } HIPERF_HILOGI(MODULE_DEFAULT, "control cmd : %{public}s", controlCmd_.c_str()); + perfPipe_.SetFifoFileName(CommandType::RECORD, controlCmd_, fifoFileC2S_, fifoFileS2C_); if (controlCmd_ == CONTROL_CMD_PREPARE) { CHECK_TRUE(!CreateFifoServer(), false, 0, ""); return true; } isFifoClient_ = true; - bool ret = false; - if (controlCmd_ == CONTROL_CMD_START) { - ret = SendFifoAndWaitReply(HiperfClient::ReplyStart, CONTROL_WAITREPY_TIMEOUT); - } else if (controlCmd_ == CONTROL_CMD_RESUME) { - ret = SendFifoAndWaitReply(HiperfClient::ReplyResume, CONTROL_WAITREPY_TIMEOUT); - } else if (controlCmd_ == CONTROL_CMD_PAUSE) { - ret = SendFifoAndWaitReply(HiperfClient::ReplyPause, CONTROL_WAITREPY_TIMEOUT); - } else if (controlCmd_ == CONTROL_CMD_STOP) { - ret = SendFifoAndWaitReply(HiperfClient::ReplyStop, CONTROL_WAITREPY_TIMEOUT); - if (!ret) { - ret = SendFifoAndWaitReply(HiperfClient::ReplyStop, CONTROL_WAITREPY_TIMEOUT); - } - ProcessStopCommand(ret); - } else if (controlCmd_ == CONTROL_CMD_OUTPUT) { - ret = SendFifoAndWaitReply(HiperfClient::ReplyOutput, CONTROL_WAITREPY_TIMEOUT); - ProcessOutputCommand(ret); - } - - if (ret) { - printf("%s sampling success.\n", controlCmd_.c_str()); - HIPERF_HILOGI(MODULE_DEFAULT, "%{public}s sampling success.", controlCmd_.c_str()); - } else { - printf("%s sampling failed.\n", controlCmd_.c_str()); - HIPERF_HILOGI(MODULE_DEFAULT, "%{public}s sampling failed.", controlCmd_.c_str()); - } - return ret; -} - -void SubCommandRecord::ProcessStopCommand(bool ret) -{ - if (ret) { - // wait sampling process exit really - static constexpr uint64_t waitCheckSleepMs = 200; - std::this_thread::sleep_for(milliseconds(waitCheckSleepMs)); - while (SendFifoAndWaitReply(HiperfClient::ReplyCheck, CONTROL_WAITREPY_TIMEOUT_CHECK)) { - std::this_thread::sleep_for(milliseconds(waitCheckSleepMs)); - } - HLOGI("wait reply check end."); - } - - if (remove(CONTROL_FIFO_FILE_C2S.c_str()) != 0) { - HLOGE("remove fifo file %s failed", CONTROL_FIFO_FILE_C2S.c_str()); - } - if (remove(CONTROL_FIFO_FILE_S2C.c_str()) != 0) { - HLOGE("remove fifo file %s failed", CONTROL_FIFO_FILE_S2C.c_str()); - } -} - -void SubCommandRecord::ProcessOutputCommand(bool ret) -{ - if (!ret) { - HLOGI("send fifo and wait repoy fail"); - return; - } - - std::this_thread::sleep_for(milliseconds(CHECK_WAIT_TIME_MS)); - uint32_t outputFailCount = 0; - while (!outputEnd_) { - ret = SendFifoAndWaitReply(HiperfClient::ReplyOutputCheck, CONTROL_WAITREPY_TIMEOUT_CHECK); - if (outputFailCount++ > MAX_CLIENT_OUTPUT_WAIT_COUNT || ret) { - break; - } - std::this_thread::sleep_for(milliseconds(CHECK_WAIT_TIME_MS)); - } + return perfPipe_.ProcessControlCmd(); } bool SubCommandRecord::CreateFifoServer() { char errInfo[ERRINFOLEN] = { 0 }; - const mode_t fifoMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - std::string tempPath("/data/log/hiperflog/"); - if (!IsDirectoryExists(tempPath)) { - HIPERF_HILOGI(MODULE_DEFAULT, "%{public}s not exist.", tempPath.c_str()); - if (!CreateDirectory(tempPath, HIPERF_FILE_PERM_770)) { - HIPERF_HILOGI(MODULE_DEFAULT, "create %{public}s failed.", tempPath.c_str()); - } - } - if (mkfifo(CONTROL_FIFO_FILE_S2C.c_str(), fifoMode) != 0 || - mkfifo(CONTROL_FIFO_FILE_C2S.c_str(), fifoMode) != 0) { - if (errno == EEXIST) { - printf("another sampling service is running.\n"); - } else { - remove(CONTROL_FIFO_FILE_S2C.c_str()); - remove(CONTROL_FIFO_FILE_C2S.c_str()); - } + if (!perfPipe_.CreateFifoFile()) { + return false; + } + + int pipeFd[2]; + if (pipe(pipeFd) == -1) { strerror_r(errno, errInfo, ERRINFOLEN); - HLOGE("create fifo file failed. %d:%s", errno, errInfo); + HLOGE("pipe creation error, errno:(%d:%s)", errno, errInfo); + HIPERF_HILOGE(MODULE_DEFAULT, "pipe creation error, errno:(%{public}d:%{public}s)", errno, errInfo); + remove(fifoFileS2C_.c_str()); + remove(fifoFileC2S_.c_str()); return false; } @@ -1357,115 +1476,60 @@ bool SubCommandRecord::CreateFifoServer() if (pid == -1) { strerror_r(errno, errInfo, ERRINFOLEN); HLOGE("fork failed. %d:%s", errno, errInfo); + close(pipeFd[PIPE_READ]); + close(pipeFd[PIPE_WRITE]); return false; } else if (pid == 0) { // child process close(STDIN_FILENO); close(STDERR_FILENO); + close(pipeFd[PIPE_READ]); + writeFd_ = pipeFd[PIPE_WRITE]; isFifoServer_ = true; - clientPipeOutput_ = open(CONTROL_FIFO_FILE_S2C.c_str(), O_WRONLY); - if (clientPipeOutput_ == -1) { - strerror_r(errno, errInfo, ERRINFOLEN); - HLOGE("open fifo file(%s) failed. %d:%s", CONTROL_FIFO_FILE_S2C.c_str(), errno, errInfo); - return false; - } nullFd_ = open("/dev/null", O_WRONLY); (void)dup2(nullFd_, STDOUT_FILENO); // redirect stdout to /dev/null - std::string err = HandleAppInfo(); + std::string err = OHOS::Developtools::HiPerf::HandleAppInfo(appPackage_, inputPidTidArgs_); if (!err.empty()) { - ClientCommandResponse(err); + ChildResponseToMain(err); + ChildResponseToMain(false); return false; } } else { // parent process + close(pipeFd[PIPE_WRITE]); + readFd_ = pipeFd[PIPE_READ]; isFifoClient_ = true; - int fd = open(CONTROL_FIFO_FILE_S2C.c_str(), O_RDONLY | O_NONBLOCK); - std::string reply = ""; - if (fd != -1) { - WaitFifoReply(fd, CONTROL_WAITREPY_TIMEOUT, reply); - } - if (fd == -1 || reply != HiperfClient::ReplyOK) { - if (reply != HiperfClient::ReplyOK) { - printf("%s\n", reply.c_str()); + bool isSuccess = false; + const auto startTime = steady_clock::now(); + const auto endTime = startTime + std::chrono::seconds(WAIT_TIMEOUT); + do { + std::string reply = ""; + bool ret = MainRecvFromChild(readFd_, reply); + if (ret && reply.find("OK") != std::string::npos) { + printf("%s control hiperf sampling success.\n", restart_ ? "start" : "create"); + isSuccess = true; + break; + } + HLOGE("reply is (%s)", reply.c_str()); + HIPERF_HILOGE(MODULE_DEFAULT, "reply is (%s)", reply.c_str()); + if (ret && reply.find("FAIL") == std::string::npos) { + printf("%s", reply.c_str()); + continue; } - close(fd); + if (!ret || reply.find("FAIL") != std::string::npos) { + break; + } + } while (steady_clock::now() < endTime); + if (!isSuccess) { kill(pid, SIGKILL); - remove(CONTROL_FIFO_FILE_C2S.c_str()); - remove(CONTROL_FIFO_FILE_S2C.c_str()); + remove(fifoFileC2S_.c_str()); + remove(fifoFileS2C_.c_str()); strerror_r(errno, errInfo, ERRINFOLEN); printf("create control hiperf sampling failed. %d:%s\n", errno, errInfo); return false; } - close(fd); - printf("%s control hiperf sampling success.\n", restart_ ? "start" : "create"); } return true; } -bool SubCommandRecord::SendFifoAndWaitReply(const std::string &cmd, const std::chrono::milliseconds &timeOut) -{ - // need open for read first, because server maybe send reply before client wait to read - int fdRead = open(CONTROL_FIFO_FILE_S2C.c_str(), O_RDONLY | O_NONBLOCK); - if (fdRead == -1) { - HLOGE("can not open fifo file(%s)", CONTROL_FIFO_FILE_S2C.c_str()); - HIPERF_HILOGI(MODULE_DEFAULT, "can not open fifo file: %{public}s.", CONTROL_FIFO_FILE_S2C.c_str()); - return false; - } - int fdWrite = open(CONTROL_FIFO_FILE_C2S.c_str(), O_WRONLY | O_NONBLOCK); - if (fdWrite == -1) { - HLOGE("can not open fifo file(%s)", CONTROL_FIFO_FILE_C2S.c_str()); - HIPERF_HILOGI(MODULE_DEFAULT, "can not open fifo file: %{public}s.", CONTROL_FIFO_FILE_C2S.c_str()); - close(fdRead); - return false; - } - size_t size = write(fdWrite, cmd.c_str(), cmd.size()); - if (size != cmd.size()) { - HLOGE("failed to write fifo file(%s) command(%s)", CONTROL_FIFO_FILE_C2S.c_str(), - cmd.c_str()); - HIPERF_HILOGI(MODULE_DEFAULT, "failed to write fifo file: %{public}s.", CONTROL_FIFO_FILE_C2S.c_str()); - close(fdWrite); - close(fdRead); - return false; - } - close(fdWrite); - - bool ret = WaitFifoReply(fdRead, timeOut); - close(fdRead); - return ret; -} - -bool SubCommandRecord::WaitFifoReply(int fd, const std::chrono::milliseconds &timeOut) -{ - std::string reply; - WaitFifoReply(fd, timeOut, reply); - return reply == HiperfClient::ReplyOK; -} - -void SubCommandRecord::WaitFifoReply(int fd, const std::chrono::milliseconds &timeOut, std::string& reply) -{ - struct pollfd pollFd { - fd, POLLIN, 0 - }; - int polled = poll(&pollFd, 1, timeOut.count()); - reply.clear(); - if (polled > 0) { - while (true) { - char c; - ssize_t result = TEMP_FAILURE_RETRY(read(fd, &c, 1)); - if (result <= 0) { - HLOGD("read from fifo file(%s) failed", CONTROL_FIFO_FILE_S2C.c_str()); - break; - } - reply.push_back(c); - if (c == '\n') { - break; - } - } - } else if (polled == 0) { - HLOGD("wait fifo file(%s) timeout", CONTROL_FIFO_FILE_S2C.c_str()); - } else { - HLOGD("wait fifo file(%s) failed", CONTROL_FIFO_FILE_S2C.c_str()); - } -} - HiperfError SubCommandRecord::OnSubCommand(std::vector& args) { HIPERF_HILOGI(MODULE_DEFAULT, "SubCommandRecord onSubCommand start"); @@ -1475,26 +1539,76 @@ HiperfError SubCommandRecord::OnSubCommand(std::vector& args) return HiperfError::NO_ERR; } + if (controlCmd_ == CONTROL_CMD_PREPARE) { + CreateClientThread(); + if (!CheckAppRestart(appPackage_, targetSystemWide_, restart_, selectPids_)) { + ChildResponseToMain(false); + CloseClientThread(); + return HiperfError::CHECK_RESTART_OPTION_FAIL; + } + } + + if (!CheckTargetPids()) { + if (controlCmd_ == CONTROL_CMD_PREPARE) { + ChildResponseToMain(false); + CloseClientThread(); + } + return HiperfError::CHECK_OPTION_PID_FAIL; + } // prepare PerfEvents - RETURN_IF(!PrepareSysKernel(), HiperfError::PREPARE_SYS_KERNEL_FAIL); - RETURN_IF(!PreparePerfEvent(), HiperfError::PREPARE_PERF_EVENT_FAIL); + if (!PrepareSysKernel()) { + if (controlCmd_ == CONTROL_CMD_PREPARE) { + ChildResponseToMain(false); + CloseClientThread(); + } + return HiperfError::PREPARE_SYS_KERNEL_FAIL; + } + + if (!PreparePerfEvent()) { + if (controlCmd_ == CONTROL_CMD_PREPARE) { + ChildResponseToMain(false); + CloseClientThread(); + } + return HiperfError::PREPARE_PERF_EVENT_FAIL; + } // prepar some attr before CreateInitRecordFile - CHECK_TRUE(!perfEvents_.PrepareTracking(), HiperfError::PREPARE_TACKING_FAIL, - LOG_TYPE_WITH_HILOG, "Fail to prepare tracking "); + if (!perfEvents_.PrepareTracking()) { + if (controlCmd_ == CONTROL_CMD_PREPARE) { + ChildResponseToMain(false); + CloseClientThread(); + } + HIPERF_HILOGE(MODULE_DEFAULT, "Fail to prepare tracking"); + HLOGE("Fail to prepare tracking"); + return HiperfError::PREPARE_TACKING_FAIL; + } HIPERF_HILOGI(MODULE_DEFAULT, "SubCommandRecord perfEvents prepared"); if (!backtrack_ && !CreateInitRecordFile(delayUnwind_ ? false : compressData_)) { + if (controlCmd_ == CONTROL_CMD_PREPARE) { + ChildResponseToMain(false); + CloseClientThread(); + } HLOGE("Fail to create record file %s", outputFilename_.c_str()); HIPERF_HILOGE(MODULE_DEFAULT, "Fail to create record file %{public}s", outputFilename_.c_str()); return HiperfError::CREATE_OUTPUT_FILE_FAIL; } - RETURN_IF(!PrepareVirtualRuntime(), HiperfError::PREPARE_VIRTUAL_RUNTIME_FAIL); + if (!PrepareVirtualRuntime()) { + if (controlCmd_ == CONTROL_CMD_PREPARE) { + ChildResponseToMain(false); + CloseClientThread(); + } + HLOGE("Fail to prepare virtualRuntime"); + HIPERF_HILOGE(MODULE_DEFAULT, "Fail to prepare virtualRuntime"); + return HiperfError::PREPARE_VIRTUAL_RUNTIME_FAIL; + } HIPERF_HILOGI(MODULE_DEFAULT, "SubCommandRecord virtualRuntime prepared"); - CreateClientThread(); + if (controlCmd_ == CONTROL_CMD_PREPARE) { + CreateReplyThread(); + } //write comm event WriteCommEventBeforeSampling(); @@ -1533,6 +1647,7 @@ HiperfError SubCommandRecord::OnSubCommand(std::vector& args) // finial report RecoverSavedCmdlinesSize(); OnlineReportData(); + CloseReplyThread(); CloseClientThread(); RemoveVdsoTmpFile(); HIPERF_HILOGI(MODULE_DEFAULT, "SubCommandRecord finish"); @@ -1551,12 +1666,21 @@ void SubCommandRecord::CloseClientThread() close(clientPipeInput_); close(clientPipeOutput_); if (isFifoServer_) { - remove(CONTROL_FIFO_FILE_C2S.c_str()); - remove(CONTROL_FIFO_FILE_S2C.c_str()); + remove(fifoFileC2S_.c_str()); + remove(fifoFileS2C_.c_str()); } } } +void SubCommandRecord::CloseReplyThread() +{ + if (replyCommandHanle_.joinable()) { + clientRunning_ = false; + HLOGI("CloseReplyThread"); + replyCommandHanle_.join(); + } +} + void SubCommandRecord::RemoveVdsoTmpFile() { std::vector fileName = {"/data/log/hiperflog/[shmm]", "/data/log/hiperflog/[vdso]"}; @@ -2190,21 +2314,6 @@ bool SubCommandRecord::OnlineReportData() return ret; } -std::string SubCommandRecord::HandleAppInfo() -{ - std::string err = ""; - if (!appPackage_.empty()) { - if (!IsExistDebugByApp(appPackage_, err)) { - return err; - } - } else { - if (!IsExistDebugByPid(inputPidTidArgs_, err)) { - return err; - } - } - return err; -} - void SubCommandRecord::AddReportArgs(CommandReporter& reporter) { if (targetSystemWide_) { diff --git a/src/subcommand_stat.cpp b/src/subcommand_stat.cpp index 00fd3df4c4759e599aa7626a4630516e939d8ea1..aa26095da15b4dc8632f1375ab77343b66ab422a 100644 --- a/src/subcommand_stat.cpp +++ b/src/subcommand_stat.cpp @@ -18,22 +18,42 @@ #include "subcommand_stat.h" #include +#include +#include #include #include +#include +#include #include "debug_logger.h" +#include "hiperf_client.h" #include "hiperf_hilog.h" +#include "ipc_utilities.h" #include "utilities.h" +using namespace std::chrono; const uint16_t ONE_HUNDRED = 100; const uint16_t THOUSANDS_SEPARATOR = 3; namespace OHOS { namespace Developtools { namespace HiPerf { +const std::string STAT_FILE = "/data/local/tmp/perf_stat.txt"; +// when there are many events, start record will take more time. +const std::chrono::milliseconds CONTROL_WAITREPY_TIMEOUT = 2000ms; static std::map thread_map_; static bool g_reportCpuFlag = false; static bool g_reportThreadFlag = false; static VirtualRuntime g_runtimeInstance; + +SubCommandStat::~SubCommandStat() +{ + if (fileFd_ != nullptr) { + fclose(fileFd_); + fileFd_ = nullptr; + } + CloseClientThread(); +} + void SubCommandStat::DumpOptions() const { printf("DumpOptions:\n"); @@ -95,12 +115,19 @@ bool SubCommandStat::ParseOption(std::vector &args) HLOGD("get option --no-inherit failed"); return false; } + if (!Option::GetOptionValue(args, "-o", outputFilename_)) { + return false; + } if (!Option::GetOptionValue(args, "--app", appPackage_)) { HLOGD("get option --app failed"); return false; } + if (!Option::GetOptionValue(args, "--control", controlCmd_)) { + return false; + } + allowIpc_ = controlCmd_ != CONTROL_CMD_PREPARE; std::string err = ""; - if (!IsExistDebugByApp(appPackage_, err)) { + if (allowIpc_ && !IsExistDebugByApp(appPackage_, err)) { return false; } if (!Option::GetOptionValue(args, "--chkms", checkAppMs_)) { @@ -159,7 +186,8 @@ void SubCommandStat::SetReportFlags(bool cpuFlag, bool threadFlag) g_reportThreadFlag = threadFlag; } -void SubCommandStat::Report(const std::map> &countEvents) +void SubCommandStat::Report(const std::map> &countEvents, + FILE* fd) { bool isNeedPerCpuTid = false; for (const auto &it : countEvents) { @@ -169,32 +197,46 @@ void SubCommandStat::Report(const std::map &reportSum, const float &ratio, - std::string &configName) + std::string &configName, FILE* fd) { if (reportSum == nullptr) { return; @@ -212,16 +254,33 @@ void SubCommandStat::PrintPerValue(const std::unique_ptr MakeComments(reportSum, commentStr); if (g_reportCpuFlag && g_reportThreadFlag) { - printf(" %24s %-30s | %-30s %10d %10d %10d | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), - reportSum->threadName.c_str(), reportSum->pid, reportSum->tid, reportSum->cpu, commentStr.c_str(), - reportSum->scaleSum * ratio); + if (fd == nullptr) { + printf(" %24s %-30s | %-30s %10d %10d %10d | %-32s | (%.0lf%%)\n", strEventCount.c_str(), + configName.c_str(), reportSum->threadName.c_str(), reportSum->pid, reportSum->tid, reportSum->cpu, + commentStr.c_str(), reportSum->scaleSum * ratio); + } else { + fprintf(fd, " %24s %-30s | %-30s %10d %10d %10d | %-32s | (%.0lf%%)\n", strEventCount.c_str(), + configName.c_str(), reportSum->threadName.c_str(), reportSum->pid, reportSum->tid, + reportSum->cpu, commentStr.c_str(), reportSum->scaleSum * ratio); + } } else if (g_reportCpuFlag) { - printf(" %24s %-30s | %10d | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), - reportSum->cpu, commentStr.c_str(), reportSum->scaleSum * ratio); + if (fd == nullptr) { + printf(" %24s %-30s | %10d | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), + reportSum->cpu, commentStr.c_str(), reportSum->scaleSum * ratio); + } else { + fprintf(fd, " %24s %-30s | %10d | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), + reportSum->cpu, commentStr.c_str(), reportSum->scaleSum * ratio); + } } else { - printf(" %24s %-30s | %-30s %10d %10d | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), - reportSum->threadName.c_str(), reportSum->pid, reportSum->tid, commentStr.c_str(), - reportSum->scaleSum * ratio); + if (fd == nullptr) { + printf(" %24s %-30s | %-30s %10d %10d | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), + reportSum->threadName.c_str(), reportSum->pid, reportSum->tid, commentStr.c_str(), + reportSum->scaleSum * ratio); + } else { + fprintf(fd, " %24s %-30s | %-30s %10d %10d | %-32s | (%.0lf%%)\n", + strEventCount.c_str(), configName.c_str(), reportSum->threadName.c_str(), + reportSum->pid, reportSum->tid, commentStr.c_str(), reportSum->scaleSum * ratio); + } } fflush(stdout); } @@ -257,7 +316,7 @@ void SubCommandStat::GetPerKey(std::string &perKey, const PerfEvents::Summary &s } void SubCommandStat::ReportDetailInfos( - const std::map> &countEvents) + const std::map> &countEvents, FILE* fd) { std::string perKey = ""; std::map> perMaps; @@ -286,16 +345,20 @@ void SubCommandStat::ReportDetailInfos( } } for (auto iper = perMaps.begin(); iper != perMaps.end(); iper++) { - PrintPerValue(iper->second, ratio, configName); + PrintPerValue(iper->second, ratio, configName, fd); } } } void SubCommandStat::ReportNormal( - const std::map> &countEvents) + const std::map> &countEvents, FILE* fd) { // print head - printf(" %24s %-30s | %-32s | %s\n", "count", "name", "comment", "coverage"); + if (fd == nullptr) { + printf(" %24s %-30s | %-32s | %s\n", "count", "name", "comment", "coverage"); + } else { + fprintf(fd, " %24s %-30s | %-32s | %s\n", "count", "name", "comment", "coverage"); + } std::map comments; GetComments(countEvents, comments); for (auto it = countEvents.begin(); it != countEvents.end(); ++it) { @@ -313,9 +376,13 @@ void SubCommandStat::ReportNormal( if (it->second->timeRunning < it->second->timeEnabled && it->second->timeRunning != 0) { scale = 1 / (static_cast(it->second->timeEnabled) / it->second->timeRunning); } - printf(" %24s %-30s | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), - comment.c_str(), scale * ratio); - + if (fd == nullptr) { + printf(" %24s %-30s | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), + comment.c_str(), scale * ratio); + } else { + fprintf(fd, " %24s %-30s | %-32s | (%.0lf%%)\n", + strEventCount.c_str(), configName.c_str(), comment.c_str(), scale * ratio); + } fflush(stdout); } } @@ -630,11 +697,217 @@ void SubCommandStat::SetPerfEvent() perfEvents_.SetTrackedCommand(trackedCommand_); // set report handle perfEvents_.SetStatCallBack(Report); + perfEvents_.SetStatReportFd(fileFd_); } -HiperfError SubCommandStat::OnSubCommand(std::vector& args) +bool SubCommandStat::CreateFifoServer() +{ + char errInfo[ERRINFOLEN] = { 0 }; + if (!perfPipe_.CreateFifoFile()) { + return false; + } + + CheckIpcBeforeFork(); + pid_t pid = fork(); + allowIpc_ = true; + + if (pid == -1) { + strerror_r(errno, errInfo, ERRINFOLEN); + HLOGE("fork failed. %d:%s", errno, errInfo); + return false; + } else if (pid == 0) { // child process + close(STDIN_FILENO); + close(STDERR_FILENO); + isFifoServer_ = true; + clientPipeOutput_ = open(fifoFileS2C_.c_str(), O_WRONLY); + if (clientPipeOutput_ == -1) { + strerror_r(errno, errInfo, ERRINFOLEN); + HLOGE("open fifo file(%s) failed. %d:%s", fifoFileS2C_.c_str(), errno, errInfo); + HIPERF_HILOGE(MODULE_DEFAULT, "open fifo file(%{public}s) failed. %d:%s", + fifoFileS2C_.c_str(), errno, errInfo); + return false; + } + nullFd_ = open("/dev/null", O_WRONLY); + (void)dup2(nullFd_, STDOUT_FILENO); // redirect stdout to /dev/null + std::string err = OHOS::Developtools::HiPerf::HandleAppInfo(appPackage_, inputPidTidArgs_); + if (!err.empty()) { + ClientCommandResponse(err); + return false; + } + } else { // parent process + isFifoClient_ = true; + int fd = open(fifoFileS2C_.c_str(), O_RDONLY | O_NONBLOCK); + std::string reply = ""; + if (fd != -1) { + perfPipe_.WaitFifoReply(fd, CONTROL_WAITREPY_TIMEOUT, reply); + } + if (fd == -1 || reply != HiperfClient::ReplyOK) { + if (reply != HiperfClient::ReplyOK) { + printf("%s", reply.c_str()); + HLOGE("reply is %s", reply.c_str()); + HIPERF_HILOGE(MODULE_DEFAULT, "reply is %{public}s", reply.c_str()); + } + HLOGI("fd is %d", fd); + HIPERF_HILOGI(MODULE_DEFAULT, "fd is %{public}d", fd); + close(fd); + if (kill(pid, SIGTERM) != 0) { + HLOGE("Failed to send SIGTERM: %d", pid); + HIPERF_HILOGE(MODULE_DEFAULT, "Failed to send SIGTERM to pid: %{public}d", pid); + } + // wait for process exit + if (waitpid(pid, nullptr, 0) == -1) { + HLOGE("Failed to wait for pid: %d", pid); + HIPERF_HILOGE(MODULE_DEFAULT, "Failed to wait for pid: %{public}d", pid); + } + remove(fifoFileC2S_.c_str()); + remove(fifoFileS2C_.c_str()); + strerror_r(errno, errInfo, ERRINFOLEN); + printf("create control hiperf counting failed.\n"); + HLOGI("errno is %d:%s", errno, errInfo); + HIPERF_HILOGI(MODULE_DEFAULT, "errno is %{public}d:%{public}s", errno, errInfo); + return false; + } + close(fd); + printf("%s control hiperf counting success.\n", restart_ ? "start" : "create"); + printf("stat result will saved in %s.\n", outputFilename_ .c_str()); + } + return true; +} + +bool SubCommandStat::ClientCommandResponse(bool response) +{ + return ClientCommandResponse(response ? HiperfClient::ReplyOK : HiperfClient::ReplyFAIL); +} + +bool SubCommandStat::ClientCommandResponse(const std::string& str) +{ + size_t size = write(clientPipeOutput_, str.c_str(), str.size()); + if (size != str.size()) { + char errInfo[ERRINFOLEN] = { 0 }; + strerror_r(errno, errInfo, ERRINFOLEN); + HLOGD("Server:%s -> %d : %zd %d:%s", str.c_str(), clientPipeOutput_, size, errno, errInfo); + return false; + } + return true; +} + +bool SubCommandStat::IsSamplingRunning() +{ + constexpr int maxWaitTrackingCount = 3000 / 100; // wait 3 second + int waitTrackingCount = maxWaitTrackingCount; + while (!perfEvents_.IsTrackRunning()) { + waitTrackingCount--; + if (waitTrackingCount <= 0) { + return false; + } + constexpr uint64_t waitTrackingSleepMs = 100; + std::this_thread::sleep_for(milliseconds(waitTrackingSleepMs)); + } + return true; +} + +void SubCommandStat::InitControlCommandHandlerMap() +{ + controlCommandHandlerMap_.clear(); + controlCommandHandlerMap_.emplace(HiperfClient::ReplyStart, ControlCommandHandler{ + std::bind(&PerfEvents::EnableTracking, &perfEvents_) + }); + + controlCommandHandlerMap_.emplace(HiperfClient::ReplyCheck, ControlCommandHandler{ + std::bind(&SubCommandStat::clientRunning_, this) + }); + + controlCommandHandlerMap_.emplace(HiperfClient::ReplyStop, ControlCommandHandler{ + std::bind(&PerfEvents::StopTracking, &perfEvents_) + }); +} + +inline void SubCommandStat::CreateClientThread() +{ + // make a thread wait the other command + if (clientPipeOutput_ != -1) { + clientCommandHanle_ = std::thread(&SubCommandStat::ClientCommandHandle, this); + } +} + +void SubCommandStat::ClientCommandHandle() +{ + using namespace HiperfClient; + CHECK_TRUE(!IsSamplingRunning(), NO_RETVAL, 0, ""); + // tell the caller if Exist + ClientCommandResponse(true); + InitControlCommandHandlerMap(); + + bool hasRead = true; + while (clientRunning_) { + if (isFifoServer_ && hasRead) { + if (clientPipeInput_ != -1) { + // after read(), block is disabled, the poll will be waked neven if no data + close(clientPipeInput_); + } + clientPipeInput_ = open(fifoFileC2S_.c_str(), O_RDONLY | O_NONBLOCK); + } + struct pollfd pollFd { + clientPipeInput_, POLLIN, 0 + }; + int polled = poll(&pollFd, 1, CONTROL_WAITREPY_TIMEOUT.count()); + if (polled <= 0) { + hasRead = false; + continue; + } + hasRead = true; + std::string command; + bool exitLoop = false; + while (!exitLoop) { + char c; + ssize_t result = TEMP_FAILURE_RETRY(read(clientPipeInput_, &c, 1)); + if (result <= 0) { + HLOGD("server :read from pipe file failed"); + HIPERF_HILOGI(MODULE_DEFAULT, "server :read from pipe file failed"); + exitLoop = true; + } + command.push_back(c); + if (c == '\n') { + exitLoop = true; + } + } + HLOGD("server:new command %s", command.c_str()); + HIPERF_HILOGI(MODULE_DEFAULT, "server:new command : %{public}s", command.c_str()); + DispatchControlCommand(command); + } +} + +void SubCommandStat::DispatchControlCommand(const std::string& command) +{ + auto it = controlCommandHandlerMap_.find(command); + if (it == controlCommandHandlerMap_.end()) { + return; + } + + ControlCommandHandler& handler = it->second; + bool ret = handler.preProcess(); + ClientCommandResponse(ret); + handler.postProcess(ret); +} + +bool SubCommandStat::ProcessControl() +{ + if (controlCmd_.empty()) { + return true; + } + HIPERF_HILOGI(MODULE_DEFAULT, "control cmd : %{public}s", controlCmd_.c_str()); + perfPipe_.SetFifoFileName(CommandType::STAT, controlCmd_, fifoFileC2S_, fifoFileS2C_); + if (controlCmd_ == CONTROL_CMD_PREPARE) { + CHECK_TRUE(!CreateFifoServer(), false, 0, ""); + return true; + } + + isFifoClient_ = true; + return perfPipe_.ProcessControlCmd(); +} + +HiperfError SubCommandStat::CheckStatOption() { - CHECK_TRUE(HelpOption(), HiperfError::NO_ERR, 0, ""); if (!CheckRestartOption(appPackage_, targetSystemWide_, restart_, selectPids_)) { return HiperfError::CHECK_RESTART_OPTION_FAIL; } @@ -673,9 +946,29 @@ HiperfError SubCommandStat::OnSubCommand(std::vector& args) return HiperfError::CHECK_OPTION_PID_APP_FAIL; } std::string err = ""; - if (!IsExistDebugByPid(inputPidTidArgs_, err)) { + if (allowIpc_ && !IsExistDebugByPid(inputPidTidArgs_, err)) { return HiperfError::CHECK_OPTION_PID_APP_FAIL; } + return HiperfError::NO_ERR; +} + +HiperfError SubCommandStat::OnSubCommand(std::vector& args) +{ + CHECK_TRUE(HelpOption(), HiperfError::NO_ERR, 0, ""); + if (!ParseControlCmd(controlCmd_)) { + return HiperfError::WRONG_CONTROL_CMD; + } + if (controlCmd_.empty() || controlCmd_ == CONTROL_CMD_PREPARE) { + HiperfError errorCode = CheckStatOption(); + if (errorCode != HiperfError::NO_ERR) { + return errorCode; + } + } + if (!ProcessControl()) { + return HiperfError::PROCESS_CONTROL_FAIL; + } else if (isFifoClient_) { + return HiperfError::NO_ERR; + } SetPerfEvent(); if (!PrepairEvents()) { HLOGV("PrepairEvents() failed"); @@ -684,13 +977,46 @@ HiperfError SubCommandStat::OnSubCommand(std::vector& args) // preapare fd perfEvents_.PrepareTracking(); - + CreateClientThread(); // start tracking - perfEvents_.StartTracking(); - + if (restart_ && controlCmd_ == CONTROL_CMD_PREPARE) { + RETURN_IF(!perfEvents_.StartTracking(isFifoServer_), HiperfError::PREPARE_START_TRACKING_FAIL); + } else { + RETURN_IF(!perfEvents_.StartTracking((!isFifoServer_) && (clientPipeInput_ == -1)), + HiperfError::START_TRACKING_FAIL); + } + CloseClientThread(); return HiperfError::NO_ERR; } +void SubCommandStat::CloseClientThread() +{ + if (clientCommandHanle_.joinable()) { + clientRunning_ = false; + HLOGI("CloseClientThread"); + if (nullFd_ != -1) { + close(nullFd_); + } + clientCommandHanle_.join(); + close(clientPipeInput_); + close(clientPipeOutput_); + if (isFifoServer_) { + remove(fifoFileC2S_.c_str()); + remove(fifoFileS2C_.c_str()); + } + } +} + +bool SubCommandStat::ParseControlCmd(const std::string cmd) +{ + if (cmd.empty() || cmd == CONTROL_CMD_PREPARE || cmd == CONTROL_CMD_START || cmd == CONTROL_CMD_STOP) { + return true; + } + + printf("Invalid --control %s option, command should be: prepare, start, stop.\n", cmd.c_str()); + return false; +} + bool RegisterSubCommandStat() { return SubCommand::RegisterSubCommand("stat", SubCommandStat::GetInstance); @@ -810,6 +1136,31 @@ bool SubCommandStat::CheckOptions(const std::vector &pids) printf("print interval should be non-negative but %d is given\n", timeReportMs_); return false; } + if (!CheckOutPutFile()) { + return false; + } + return true; +} + +bool SubCommandStat::CheckOutPutFile() +{ + if (controlCmd_ != CONTROL_CMD_PREPARE) { + if (!outputFilename_.empty()) { + printf("-o option must use with --control prepare option\n"); + return false; + } else { + return true; + } + } + if (outputFilename_.empty()) { + outputFilename_ = STAT_FILE; + } + std::string resolvedPath = CanonicalizeSpecPath(outputFilename_.c_str()); + fileFd_ = fopen(resolvedPath.c_str(), "w"); + if (fileFd_ == nullptr) { + printf("unable open file to '%s' because '%d'\n", outputFilename_.c_str(), errno); + return false; + } return true; } diff --git a/src/utilities.cpp b/src/utilities.cpp index 99aad1df4e6ea5d14b47e46ab92350dec94a24c3..3638711a0fa5873d31392509d95cc1b4a02d76b3 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -669,7 +669,7 @@ pid_t GetAppPackagePid(const std::string &appPackage, const pid_t oldPid, const return res; } -bool CheckAppIsRunning (std::vector &selectPids, const std::string &appPackage, int checkAppMs) +bool CheckAppIsRunning(std::vector &selectPids, const std::string &appPackage, int checkAppMs) { if (!appPackage.empty()) { pid_t appPid = GetAppPackagePid(appPackage, -1, checkAppMs, waitAppRunCheckTimeOut); @@ -692,7 +692,7 @@ bool IsExistDebugByApp(const std::string& bundleName, std::string& err) } if (!IsSupportNonDebuggableApp() && !bundleNameTmp.empty() && !IsDebugableApp(bundleNameTmp)) { HLOGE("--app option only support debug application."); - err = "--app option only support debug application"; + err = "--app option only support debug application\n"; printf("%s\n", err.c_str()); return false; } @@ -704,7 +704,7 @@ bool IsExistDebugByPid(const std::vector &pids, std::string& err) CHECK_TRUE(pids.empty(), true, 1, "IsExistDebugByPid: pids is empty."); for (auto pid : pids) { if (pid <= 0) { - err = "Invalid -p value '" + std::to_string(pid) + "', the pid should be larger than 0"; + err = "Invalid -p value '" + std::to_string(pid) + "', the pid should be larger than 0\n"; printf("%s\n", err.c_str()); return false; } @@ -715,7 +715,7 @@ bool IsExistDebugByPid(const std::vector &pids, std::string& err) } if (!IsSupportNonDebuggableApp() && !IsDebugableApp(bundleName)) { HLOGE("-p option only support debug application for %s", bundleName.c_str()); - err = "-p option only support debug application"; + err = "-p option only support debug application\n"; printf("%s\n", err.c_str()); return false; } @@ -723,6 +723,21 @@ bool IsExistDebugByPid(const std::vector &pids, std::string& err) return true; } +std::string HandleAppInfo(const std::string& appPackage, const std::vector &inputPidTidArgs) +{ + std::string err = ""; + if (!appPackage.empty()) { + if (!IsExistDebugByApp(appPackage, err)) { + return err; + } + } else { + if (!IsExistDebugByPid(inputPidTidArgs, err)) { + return err; + } + } + return err; +} + bool IsSupportNonDebuggableApp() { // root first diff --git a/test/BUILD.gn b/test/BUILD.gn index b513fd3b36854d28021bbf050eeb17fc68c76b50..4761168973d4add2d4f29d1ee383b7aa122febee 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -163,6 +163,7 @@ ohos_unittest("hiperf_unittest") { "./../src/utilities.cpp", "./../src/virtual_runtime.cpp", "./../src/virtual_thread.cpp", + "./../src/perf_pipe.cpp", ] if (is_ohos) { diff --git a/test/unittest/common/native/include/perf_pipe_test.h b/test/unittest/common/native/include/perf_pipe_test.h new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/unittest/common/native/perf_events_test.cpp b/test/unittest/common/native/perf_events_test.cpp index 3fc82aad2ef6b2b14e1c7cec61f1078dafb03415..c4f176db02b4608045531056995396ce9182a306 100644 --- a/test/unittest/common/native/perf_events_test.cpp +++ b/test/unittest/common/native/perf_events_test.cpp @@ -47,7 +47,7 @@ public: static void SetAllConfig(PerfEvents &event); static bool RecordCount(PerfEventRecord& record); static void StatCount( - const std::map> &countEvents); + const std::map> &countEvents, FILE* fd); static constexpr int TEST_CODE_MEM_FILE_SIZE = 1024; static constexpr auto TEST_CODE_SLEEP_BEFORE_RUN = 500ms; @@ -82,7 +82,7 @@ bool PerfEventsTest::RecordCount(PerfEventRecord& record) } void PerfEventsTest::StatCount( - const std::map> &countEvents) + const std::map> &countEvents, FILE* fd) { gStatCount++; } @@ -290,6 +290,7 @@ HWTEST_F(PerfEventsTest, StatNormal, TestSize.Level0) stdoutRecord.Start(); PerfEvents event; + FILE* fd = nullptr; // prepare gStatCount = 0; std::vector selectCpus_; @@ -306,6 +307,7 @@ HWTEST_F(PerfEventsTest, StatNormal, TestSize.Level0) event.AddDefaultEvent(PERF_TYPE_SOFTWARE); event.AddDefaultEvent(PERF_TYPE_TRACEPOINT); event.SetStatCallBack(StatCount); + event.SetStatReportFd(fd); ASSERT_EQ(event.PrepareTracking(), true); std::thread runThread(RunTrack, std::ref(event)); std::vector testThreads; diff --git a/test/unittest/common/native/perf_pipe_test.cpp b/test/unittest/common/native/perf_pipe_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/unittest/common/native/subcommand_record_test.cpp b/test/unittest/common/native/subcommand_record_test.cpp index 9608fc778b5977dc3a28606d15f0a7b4c482f470..6f72220c9f114596741f239a22db93277db3bc9a 100644 --- a/test/unittest/common/native/subcommand_record_test.cpp +++ b/test/unittest/common/native/subcommand_record_test.cpp @@ -29,6 +29,7 @@ #include "command.h" #include "debug_logger.h" #include "hisysevent_manager.h" +#include "perf_pipe.h" #include "subcommand_dump.h" #include "subcommand_report.h" #include "subcommand_test.h" @@ -1595,7 +1596,7 @@ HWTEST_F(SubCommandRecordTest, SendFifoAndWaitReply, TestSize.Level1) { SubCommandRecord cmd; std::string test = "test"; - EXPECT_EQ(cmd.SendFifoAndWaitReply(test, CONTROL_WAITREPY_TOMEOUT), false); + EXPECT_EQ(cmd.perfPipe_.SendFifoAndWaitReply(test, CONTROL_WAITREPY_TOMEOUT), false); } /** diff --git a/test/unittest/common/native/subcommand_stat_test.cpp b/test/unittest/common/native/subcommand_stat_test.cpp index 6252521e24807aa4874fee7954b133ee7cfafe1b..650949dd6243ca813f34058f47e6abd2266c2eda 100644 --- a/test/unittest/common/native/subcommand_stat_test.cpp +++ b/test/unittest/common/native/subcommand_stat_test.cpp @@ -28,6 +28,7 @@ #include #include "perf_events.h" +#include "perf_pipe.h" #include "tracked_command.h" using namespace testing::ext; @@ -2112,11 +2113,12 @@ HWTEST_F(SubCommandStatTest, TestReport, TestSize.Level1) StdoutRecord stdoutRecord; stdoutRecord.Start(); SubCommandStat cmdStat; + FILE* fd = nullptr; std::map> countEvents; std::unique_ptr testEvent(std::make_unique()); std::string test = "test"; countEvents[test] = std::move(testEvent); - cmdStat.Report(countEvents); + cmdStat.Report(countEvents, fd); std::string stringOut = stdoutRecord.Stop(); EXPECT_TRUE(stringOut.find("test") != std::string::npos); EXPECT_TRUE(stringOut.find("count name") != std::string::npos); @@ -2135,6 +2137,7 @@ HWTEST_F(SubCommandStatTest, TestReport_Piling, TestSize.Level2) "sw-context-switches", "sw-page-faults", "sw-task-clock", "sw-cpu-migrations"}; StdoutRecord stdoutRecord; stdoutRecord.Start(); + FILE* fd = nullptr; std::map> countEvents; for (int i = 0; i < 8; i++) { auto countEvent = std::make_unique(PerfEvents::CountEvent {}); @@ -2159,7 +2162,7 @@ HWTEST_F(SubCommandStatTest, TestReport_Piling, TestSize.Level2) countEventTmp->id = 0; countEventTmp->usedCpus = countEventTmp->eventCount / 1e9; } - cmdStat.Report(countEvents); + cmdStat.Report(countEvents, fd); std::string stringOut = stdoutRecord.Stop(); printf("output: %s\n", stringOut.c_str()); EXPECT_EQ(FindExpectStr(stringOut, "G/sec"), true);