diff --git a/include/subcommand_stat.h b/include/subcommand_stat.h index c961291d09122bd8708d02193ea5cbcd91078839..de5abb27e7b5eb7bca46742e7f5e2f828e60903b 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 sampling by , the can be:\n" + " prepare: set arguments and prepare sampling\n" + " start: start sampling\n" + " stop: stop sampling\n" + " -o \n" + " Set output file name, default is /data/local/tmp/perf_stat.txt." + " Only restrain using with --control.\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_ = "/data/local/tmp/perf_stat.txt"; int checkAppMs_ = DEFAULT_CHECK_APP_MS; std::vector selectPids_; std::vector selectTids_; @@ -160,6 +169,45 @@ private: bool CheckSelectCpuPidOption(); void SetReportFlags(bool cpuFlag, bool threadFlag); void SetPerfEvent(); + HiperfError CheckStatOption(); + + // for client + int clientPipeInput_ = -1; + int clientPipeOutput_ = -1; + int nullFd_ = -1; + 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(); + 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 6c98ef9ae3dbfc69ca8436df1a31e24723764cd3..3fd147cccebbbe260829f8e87f00c8b46d344056 100644 --- a/src/perf_events.cpp +++ b/src/perf_events.cpp @@ -44,6 +44,7 @@ namespace HiPerf { bool PerfEvents::updateTimeThreadRunning_ = true; std::atomic PerfEvents::currentTimeSecond_ = 0; static std::atomic_bool g_trackRunning = false; +static std::atomic_bool g_captureSig = false; static constexpr int32_t UPDATE_TIME_INTERVAL = 10; // 10ms static constexpr uint64_t NANO_SECONDS_PER_SECOND = 1000000000; static constexpr uint32_t POLL_FAIL_COUNT_THRESHOLD = 10; @@ -567,6 +568,7 @@ static bool CaptureSig() sig.sa_handler = [](int sig) { printf("\n Ctrl + C detected.\n"); g_trackRunning = false; + g_captureSig = true; }; sig.sa_flags = 0; @@ -737,7 +739,7 @@ bool PerfEvents::StartTracking(bool immediately) 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_) { @@ -1872,7 +1874,7 @@ void PerfEvents::StatLoop() std::this_thread::sleep_for(microseconds(defaultSleepUs)); } - if (!g_trackRunning) { + if (!g_trackRunning && g_captureSig) { // for user interrupt situation, print time statistic usedTimeMsTick = duration_cast(steady_clock::now() - startTime); printf("User interrupt exit (total %" PRId64 " ms)\n", (uint64_t)usedTimeMsTick.count()); diff --git a/src/subcommand_stat.cpp b/src/subcommand_stat.cpp index 00fd3df4c4759e599aa7626a4630516e939d8ea1..245376aab1bd9ae37b0d257b9525cbc3d1e6cf97 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 "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"; +// 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() +{ + 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_)) { @@ -632,12 +659,357 @@ void SubCommandStat::SetPerfEvent() perfEvents_.SetStatCallBack(Report); } -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 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()); + } +} + +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 (!IsFileExists(tempPath)) { + HIPERF_HILOGI(MODULE_DEFAULT, "%{public}s not exist.", tempPath.c_str()); + CreateDirectory(tempPath, HIPERF_FILE_PERM_770); + } + 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()); + } + 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; + } + FILE* fileFd = fopen(outputFilename_.c_str(), "w"); + if (fileFd == nullptr) { + HLOGE("open file(%s) failed. %d:%s", outputFilename_.c_str(), errno, errInfo); + HIPERF_HILOGE(MODULE_DEFAULT, "open file(%{public}s) failed. %d:%s", + outputFilename_.c_str(), errno, errInfo); + return false; + } else { + (void)dup2(fileno(fileFd), STDOUT_FILENO); + fclose(fileFd); + } + 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("reply is %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 sampling 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 sampling success.\n", restart_ ? "start" : "create"); + } + 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) { + 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()); + } +} + +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; + while (true) { + 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"); + break; + } + command.push_back(c); + if (c == '\n') { + break; + } + } + 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 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; +} + +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 +1045,26 @@ 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 (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 +1073,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); @@ -810,6 +1232,9 @@ bool SubCommandStat::CheckOptions(const std::vector &pids) printf("print interval should be non-negative but %d is given\n", timeReportMs_); return false; } + if (!ParseControlCmd(controlCmd_)) { + return false; + } return true; }