diff --git a/include/command_reporter.h b/include/command_reporter.h index af782b30583054be70da7cbdeb685faf362a6bc7..ba857c73e96a1096beb8dd0afea81f05db8048d1 100644 --- a/include/command_reporter.h +++ b/include/command_reporter.h @@ -48,7 +48,9 @@ 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(CHECK_OUT_PUT_ERROR), /* 30 */ \ + GEN_ITEM(WRONG_CONTROL_CMD) /* 31 */ #define FOR_ERROR_ENUM(x) x diff --git a/include/perf_events.h b/include/perf_events.h index 29abc2cd8a9f67968a7dcc05c0da9b58a595e577..c99325ab1d1eb801f6c89cb95da20752898d14ff 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* reportPtr); void GetLostSamples(size_t &lostSamples, size_t &lostNonSamples) { lostSamples = lostSamples_; @@ -654,6 +655,7 @@ private: unsigned int mmapPages_ = 0; int clockId_ = -1; uint64_t branchSampleType_ = 0; + FILE* reportPtr_ = nullptr; SampleStackType sampleStackType_ = SampleStackType::NONE; uint32_t dwarfSampleStackSize_ = MAX_SAMPLE_STACK_SIZE; diff --git a/include/subcommand_stat.h b/include/subcommand_stat.h index c961291d09122bd8708d02193ea5cbcd91078839..62da0c36e554300366ff47fbbddf92a0b2de64f7 100644 --- a/include/subcommand_stat.h +++ b/include/subcommand_stat.h @@ -82,12 +82,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); @@ -109,6 +117,7 @@ private: bool restart_ {false}; bool noCreateNew_ {false}; std::string appPackage_ = {}; + std::string outputFilename_ = ""; int checkAppMs_ = DEFAULT_CHECK_APP_MS; std::vector selectPids_; std::vector selectTids_; @@ -120,6 +129,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 +143,16 @@ 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* filePtr); + static void PrintPerHead(FILE* filePtr); 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* filePtr); + static void ReportDetailInfos(const std::map> &countEvents, FILE* filePtr); static void PrintPerValue(const std::unique_ptr &reportSum, const float &ratio, - std::string &configName); + std::string &configName, FILE* filePtr); 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 +172,44 @@ 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* filePtr_ = 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 ProcessControl(); + void ProcessStopCommand(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); + void CloseClientThread(); + std::string HandleAppInfo(); + bool ParseControlCmd(const std::string cmd); }; bool RegisterSubCommandStat(void); diff --git a/src/perf_events.cpp b/src/perf_events.cpp index 3af407b30a428068d5da01fa5c89e545b699e5c9..7580005a4a13c8e915772e2c765db37ee97040ab 100644 --- a/src/perf_events.cpp +++ b/src/perf_events.cpp @@ -195,6 +195,10 @@ PerfEvents::~PerfEvents() } ExitReadRecordBufThread(); + if (reportPtr_ != nullptr) { + fclose(reportPtr_); + reportPtr_ = 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* reportPtr) +{ + reportPtr_ = reportPtr; +} + void PerfEvents::SetRecordCallBack(RecordCallBack recordCallBack) { recordCallBack_ = recordCallBack; @@ -1272,7 +1283,7 @@ bool PerfEvents::StatReport(const __u64 &durationInSec) } } - reportCallBack_(countEvents_); + reportCallBack_(countEvents_, reportPtr_); return true; } @@ -1868,8 +1879,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 (reportPtr_ == nullptr) { + printf("\nReport at %" PRId64 " ms (%" PRId64 " ms left):\n", + (uint64_t)usedTimeMsTick.count(), (uint64_t)lefTimeMsTick.count()); + } else { + fprintf(reportPtr_, "\nReport at %" PRId64 " ms (%" PRId64 " ms left):\n", + (uint64_t)usedTimeMsTick.count(), (uint64_t)lefTimeMsTick.count()); + } // end of comments nextReportTime += timeReport_; StatReport(durationInSec); @@ -1883,7 +1899,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 (reportPtr_ == nullptr) { + printf("Timeout exit (total %" PRId64 " ms)\n", (uint64_t)usedTimeMsTick.count()); + } else { + fprintf(reportPtr_, "Timeout exit (total %" PRId64 " ms)\n", (uint64_t)usedTimeMsTick.count()); + } if (trackedCommand_) { trackedCommand_->Stop(); } diff --git a/src/subcommand_stat.cpp b/src/subcommand_stat.cpp index 00fd3df4c4759e599aa7626a4630516e939d8ea1..055e695d99a3b884fc022dcf1052d90c22c7117c 100644 --- a/src/subcommand_stat.cpp +++ b/src/subcommand_stat.cpp @@ -18,22 +18,48 @@ #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 CONTROL_CMD_PREPARE = "prepare"; +const std::string CONTROL_CMD_START = "start"; +const std::string CONTROL_CMD_STOP = "stop"; +const std::string CONTROL_FIFO_FILE_C2S = "/data/log/hiperflog/.hiperf_stat_control_c2s"; +const std::string CONTROL_FIFO_FILE_S2C = "/data/log/hiperflog/.hiperf_stat_control_s2c"; +const std::string DEFAULT_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; +const std::chrono::milliseconds CONTROL_WAITREPY_TIMEOUT_CHECK = 1000ms; static std::map thread_map_; static bool g_reportCpuFlag = false; static bool g_reportThreadFlag = false; static VirtualRuntime g_runtimeInstance; + +SubCommandStat::~SubCommandStat() +{ + if (filePtr_ != nullptr) { + fclose(filePtr_); + filePtr_ = nullptr; + } + CloseClientThread(); +} + void SubCommandStat::DumpOptions() const { printf("DumpOptions:\n"); @@ -95,12 +121,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 +192,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* filePtr) { bool isNeedPerCpuTid = false; for (const auto &it : countEvents) { @@ -169,32 +203,46 @@ void SubCommandStat::Report(const std::map &reportSum, const float &ratio, - std::string &configName) + std::string &configName, FILE* filePtr) { if (reportSum == nullptr) { return; @@ -212,16 +260,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 (filePtr == 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(filePtr, " %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 (filePtr == nullptr) { + printf(" %24s %-30s | %10d | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), + reportSum->cpu, commentStr.c_str(), reportSum->scaleSum * ratio); + } else { + fprintf(filePtr, " %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 (filePtr == 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(filePtr, " %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 +322,7 @@ void SubCommandStat::GetPerKey(std::string &perKey, const PerfEvents::Summary &s } void SubCommandStat::ReportDetailInfos( - const std::map> &countEvents) + const std::map> &countEvents, FILE* filePtr) { std::string perKey = ""; std::map> perMaps; @@ -286,16 +351,20 @@ void SubCommandStat::ReportDetailInfos( } } for (auto iper = perMaps.begin(); iper != perMaps.end(); iper++) { - PrintPerValue(iper->second, ratio, configName); + PrintPerValue(iper->second, ratio, configName, filePtr); } } } void SubCommandStat::ReportNormal( - const std::map> &countEvents) + const std::map> &countEvents, FILE* filePtr) { // print head - printf(" %24s %-30s | %-32s | %s\n", "count", "name", "comment", "coverage"); + if (filePtr == nullptr) { + printf(" %24s %-30s | %-32s | %s\n", "count", "name", "comment", "coverage"); + } else { + fprintf(filePtr, " %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 +382,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 (filePtr == nullptr) { + printf(" %24s %-30s | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), + comment.c_str(), scale * ratio); + } else { + fprintf(filePtr, " %24s %-30s | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(), + comment.c_str(), scale * ratio); + } fflush(stdout); } } @@ -630,14 +703,360 @@ void SubCommandStat::SetPerfEvent() perfEvents_.SetTrackedCommand(trackedCommand_); // set report handle perfEvents_.SetStatCallBack(Report); + perfEvents_.SetStatReportFd(filePtr_); } -HiperfError SubCommandStat::OnSubCommand(std::vector& args) +std::string SubCommandStat::HandleAppInfo() +{ + std::string err = ""; + if (!appPackage_.empty()) { + if (!IsExistDebugByApp(appPackage_, err)) { + return err; + } + } else { + if (!IsExistDebugByPid(inputPidTidArgs_, err)) { + return err; + } + } + return err; +} + +void SubCommandStat::ProcessStopCommand(bool ret) +{ + if (ret) { + // wait counting 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()); + } +} + +bool SubCommandStat::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 counting service is running.\n"); + } else { + remove(CONTROL_FIFO_FILE_S2C.c_str()); + remove(CONTROL_FIFO_FILE_C2S.c_str()); + } + strerror_r(errno, errInfo, ERRINFOLEN); + HLOGE("create fifo file failed. %d:%s", errno, errInfo); + 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(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); + HIPERF_HILOGE(MODULE_DEFAULT, "open fifo file(%{public}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(); + if (!err.empty()) { + ClientCommandResponse(err); + return false; + } + } else { // parent process + 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()); + 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(CONTROL_FIFO_FILE_C2S.c_str()); + remove(CONTROL_FIFO_FILE_S2C.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::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 SubCommandStat::WaitFifoReply(int fd, const std::chrono::milliseconds &timeOut) +{ + std::string reply; + WaitFifoReply(fd, timeOut, reply); + return reply == HiperfClient::ReplyOK; +} + +void SubCommandStat::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) { + HLOGD("read from fifo file(%s) failed", CONTROL_FIFO_FILE_S2C.c_str()); + HIPERF_HILOGD(MODULE_DEFAULT, "read from fifo file(%{public}s) failed.", CONTROL_FIFO_FILE_S2C.c_str()); + exitLoop = true; + } + reply.push_back(c); + if (c == '\n') { + exitLoop = true; + } + } + } else if (polled == 0) { + HLOGD("wait fifo file(%s) timeout", CONTROL_FIFO_FILE_S2C.c_str()); + HIPERF_HILOGD(MODULE_DEFAULT, "wait fifo file(%{public}s) timeout.", CONTROL_FIFO_FILE_S2C.c_str()); + } else { + HLOGD("wait fifo file(%s) failed", CONTROL_FIFO_FILE_S2C.c_str()); + HIPERF_HILOGD(MODULE_DEFAULT, "wait fifo file(%{public}s) failed.", CONTROL_FIFO_FILE_S2C.c_str()); + } +} + +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(CONTROL_FIFO_FILE_C2S.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()); + 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_STOP) { + ret = SendFifoAndWaitReply(HiperfClient::ReplyStop, CONTROL_WAITREPY_TIMEOUT); + if (!ret) { + ret = SendFifoAndWaitReply(HiperfClient::ReplyStop, CONTROL_WAITREPY_TIMEOUT); + } + ProcessStopCommand(ret); + } + + if (ret) { + printf("%s counting success.\n", controlCmd_.c_str()); + HIPERF_HILOGI(MODULE_DEFAULT, "%{public}s counting success.", controlCmd_.c_str()); + } else { + printf("%s counting failed.\n", controlCmd_.c_str()); + HIPERF_HILOGI(MODULE_DEFAULT, "%{public}s counting failed.", controlCmd_.c_str()); + } + return ret; +} + +HiperfError SubCommandStat::CheckStatOption() { - CHECK_TRUE(HelpOption(), HiperfError::NO_ERR, 0, ""); if (!CheckRestartOption(appPackage_, targetSystemWide_, restart_, selectPids_)) { return HiperfError::CHECK_RESTART_OPTION_FAIL; } + // check option if (!CheckSelectCpuPidOption()) { return HiperfError::CHECK_SELECT_CPU_PID_FAIL; @@ -673,9 +1092,32 @@ 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 (!CheckOutPutFile()) { + return HiperfError::CHECK_OUT_PUT_ERROR; + } + 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 +1126,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(CONTROL_FIFO_FILE_C2S.c_str()); + remove(CONTROL_FIFO_FILE_S2C.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); @@ -813,6 +1288,32 @@ bool SubCommandStat::CheckOptions(const std::vector &pids) 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_ = DEFAULT_STAT_FILE; + } + if (!IsValidOutPath(outputFilename_)) { + printf("Invalid output file path, permission denied\n"); + return false; + } + std::string resolvedPath = CanonicalizeSpecPath(outputFilename_.c_str()); + filePtr_ = fopen(resolvedPath.c_str(), "w"); + if (filePtr_ == nullptr) { + printf("unable open file to '%s' because '%d'\n", outputFilename_.c_str(), errno); + return false; + } + return true; +} + void SubCommandStat::AddReportArgs(CommandReporter& reporter) { if (targetSystemWide_) { diff --git a/test/unittest/common/native/perf_events_test.cpp b/test/unittest/common/native/perf_events_test.cpp index 597e3866f91b340f7d84f6bb72ed84d554931e73..0b3df9e6f948680824d4ceb20c197941a448360a 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* filePtr); 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* filePtr) { gStatCount++; } @@ -290,6 +290,7 @@ HWTEST_F(PerfEventsTest, StatNormal, TestSize.Level0) stdoutRecord.Start(); PerfEvents event; + FILE* filePtr = 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(filePtr); ASSERT_EQ(event.PrepareTracking(), true); std::thread runThread(RunTrack, std::ref(event)); std::vector testThreads; diff --git a/test/unittest/common/native/subcommand_stat_test.cpp b/test/unittest/common/native/subcommand_stat_test.cpp index 6252521e24807aa4874fee7954b133ee7cfafe1b..35ee7914fa3c8fd690c64dd110dbf3cdc448a209 100644 --- a/test/unittest/common/native/subcommand_stat_test.cpp +++ b/test/unittest/common/native/subcommand_stat_test.cpp @@ -18,10 +18,14 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include #include #include @@ -34,6 +38,7 @@ using namespace testing::ext; namespace OHOS { namespace Developtools { namespace HiPerf { +const int CMD_OUTPUT_BUF = 1024; static std::atomic g_wait = false; class SubCommandStatTest : public testing::Test { public: @@ -52,6 +57,9 @@ public: int CounterValue(const std::string &stringOut, const std::string &configName) const; void CheckGroupCoverage(const std::string &stringOut, const std::string &groupCounterName) const; + bool RunCmd(const string& cmdstr) const; + bool CheckTraceCommandOutput(const std::string& cmd, + const std::vector& keywords) const; const std::vector defaultConfigNames_ = { "hw-branch-misses", @@ -226,6 +234,53 @@ void SubCommandStatTest::CheckGroupCoverage(const std::string &stringOut, } } +bool SubCommandStatTest::RunCmd(const string& cmdstr) const +{ + if (cmdstr.empty()) { + return false; + } + FILE *fp = popen(cmdstr.c_str(), "r"); + if (fp == nullptr) { + return false; + } + char res[CMD_OUTPUT_BUF] = { '\0' }; + while (fgets(res, sizeof(res), fp) != nullptr) { + std::cout << res; + } + pclose(fp); + return true; +} + +bool SubCommandStatTest::CheckTraceCommandOutput(const std::string& cmd, + const std::vector& keywords) const +{ + if (cmd.empty()) { + return false; + } + FILE* fp = popen(cmd.c_str(), "r"); + if (fp == nullptr) { + return false; + } + + char buffer[CMD_OUTPUT_BUF]; + int checkIdx = 0; + while (fgets(buffer, sizeof(buffer), fp) != nullptr) { + while (checkIdx < keywords.size() && strstr(buffer, keywords[checkIdx].c_str()) != nullptr) { + GTEST_LOG_(INFO) << "match keyword :" << keywords[checkIdx]; + checkIdx++; + if (checkIdx == keywords.size()) { + break; + } + } + } + + pclose(fp); + if (checkIdx < keywords.size()) { + GTEST_LOG_(ERROR) << "Failed to match keyword : " << keywords[checkIdx]; + } + return checkIdx == keywords.size(); +} + /** * @tc.name: TestOnSubCommand_a * @tc.desc: -a @@ -2112,11 +2167,12 @@ HWTEST_F(SubCommandStatTest, TestReport, TestSize.Level1) StdoutRecord stdoutRecord; stdoutRecord.Start(); SubCommandStat cmdStat; + FILE* filePtr = 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, filePtr); std::string stringOut = stdoutRecord.Stop(); EXPECT_TRUE(stringOut.find("test") != std::string::npos); EXPECT_TRUE(stringOut.find("count name") != std::string::npos); @@ -2136,6 +2192,7 @@ HWTEST_F(SubCommandStatTest, TestReport_Piling, TestSize.Level2) StdoutRecord stdoutRecord; stdoutRecord.Start(); std::map> countEvents; + FILE* filePtr = nullptr; for (int i = 0; i < 8; i++) { auto countEvent = std::make_unique(PerfEvents::CountEvent {}); std::string configName = eventNames[i]; @@ -2159,7 +2216,7 @@ HWTEST_F(SubCommandStatTest, TestReport_Piling, TestSize.Level2) countEventTmp->id = 0; countEventTmp->usedCpus = countEventTmp->eventCount / 1e9; } - cmdStat.Report(countEvents); + cmdStat.Report(countEvents, filePtr); std::string stringOut = stdoutRecord.Stop(); printf("output: %s\n", stringOut.c_str()); EXPECT_EQ(FindExpectStr(stringOut, "G/sec"), true); @@ -2333,6 +2390,49 @@ HWTEST_F(SubCommandStatTest, GetInstance, TestSize.Level1) EXPECT_EQ(SubCommandStat::GetInstance().Name(), "stat"); } + +/** + * @tc.name: TestOnSubCommand_control01 + * @tc.desc: prepare, start, stop + * @tc.type: FUNC + */ +HWTEST_F(SubCommandStatTest, TestOnSubCommand_control01, TestSize.Level1) +{ + ASSERT_TRUE(RunCmd("hiperf stat --control stop")); + EXPECT_EQ(CheckTraceCommandOutput("hiperf stat --control prepare -a", + {"create control hiperf counting success", "stat result will saved in /data/local/tmp/perf_stat.txt"}), true); + EXPECT_EQ(CheckTraceCommandOutput("hiperf stat --control start", + {"start counting success"}), true); + EXPECT_EQ(CheckTraceCommandOutput("hiperf stat --control stop", + {"stop counting success"}), true); +} + +/** + * @tc.name: TestOnSubCommand_control02 + * @tc.desc: prepare, prepare + * @tc.type: FUNC + */ +HWTEST_F(SubCommandStatTest, TestOnSubCommand_control02, TestSize.Level1) +{ + ASSERT_TRUE(RunCmd("hiperf stat --control stop")); + ASSERT_TRUE(RunCmd("hiperf stat --control prepare -a")); + EXPECT_EQ(CheckTraceCommandOutput("hiperf stat --control prepare -a", + {"another counting service is running"}), true); +} + +/** + * @tc.name: TestOnSubCommand_control03 + * @tc.desc: start, stop + * @tc.type: FUNC + */ +HWTEST_F(SubCommandStatTest, TestOnSubCommand_control03, TestSize.Level1) +{ + ASSERT_TRUE(RunCmd("hiperf stat --control stop")); + EXPECT_EQ(CheckTraceCommandOutput("hiperf stat --control start", + {"start counting failed"}), true); + EXPECT_EQ(CheckTraceCommandOutput("hiperf stat --control stop", + {"stop counting failed"}), true); +} } // namespace HiPerf } // namespace Developtools } // namespace OHOS