* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define HILOG_TAG "Stat"
#include "subcommand_stat.h"
#include <csignal>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <sys/wait.h>
#include <unistd.h>
#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 {
#if defined(is_sandbox_mapping) && is_sandbox_mapping
const std::string DEFAULT_STAT_FILE = GetDefaultPathByEnv("perf_stat.txt");
#else
const std::string DEFAULT_STAT_FILE = "/data/local/tmp/perf_stat.txt";
#endif
const std::chrono::milliseconds CONTROL_WAITREPY_TIMEOUT = 2000ms;
static std::map<pid_t, ThreadInfos> 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");
printf(" targetSystemWide:\t%s\n", targetSystemWide_ ? "true" : "false");
printf(" selectCpus:\t%s\n", VectorToString(selectCpus_).c_str());
printf(" timeStopSec:\t%f sec\n", timeStopSec_);
printf(" timeReportMs:\t%d ms\n", timeReportMs_);
printf(" selectEvents:\t%s\n", VectorToString(selectEvents_).c_str());
printf(" selectGroups:\t%s\n", VectorToString(selectGroups_).c_str());
printf(" noCreateNew:\t%s\n", noCreateNew_ ? "true" : "false");
printf(" appPackage:\t%s\n", appPackage_.c_str());
printf(" checkAppMs_:\t%d\n", checkAppMs_);
printf(" selectPids:\t%s\n", VectorToString(selectPids_).c_str());
printf(" selectTids:\t%s\n", VectorToString(selectTids_).c_str());
printf(" restart:\t%s\n", restart_ ? "true" : "false");
printf(" perCore:\t%s\n", perCpus_ ? "true" : "false");
printf(" perTread:\t%s\n", perThreads_ ? "true" : "false");
printf(" verbose:\t%s\n", verboseReport_ ? "true" : "false");
}
bool SubCommandStat::ParseOption(std::vector<std::string> &args)
{
if (args.size() == 1 && args[0] == "-h") {
args.clear();
helpOption_ = true;
PrintUsage();
return true;
}
if (!Option::GetOptionValue(args, "-a", targetSystemWide_)) {
HLOGD("get option -a failed");
return false;
}
if (targetSystemWide_ && !IsSupportNonDebuggableApp()) {
HLOGD("-a option needs root privilege for system wide profiling.");
printf("-a option needs root privilege for system wide profiling.\n");
return false;
}
if (!Option::GetOptionValue(args, "-c", selectCpus_)) {
HLOGD("get option -c failed");
return false;
}
if (!Option::GetOptionValue(args, "-d", timeStopSec_)) {
HLOGD("get option -d failed");
return false;
}
if (!Option::GetOptionValue(args, "-i", timeReportMs_)) {
HLOGD("get option -i failed");
return false;
}
if (!Option::GetOptionValue(args, "-e", selectEvents_)) {
HLOGD("get option -e failed");
return false;
}
if (!Option::GetOptionValue(args, "-g", selectGroups_)) {
HLOGD("get option -g failed");
return false;
}
if (!Option::GetOptionValue(args, "--no-inherit", noCreateNew_)) {
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 (allowIpc_ && !IsExistDebugByApp(appPackage_, err)) {
return false;
}
if (!Option::GetOptionValue(args, "--chkms", checkAppMs_)) {
return false;
}
if (!Option::GetOptionValue(args, "-p", selectPids_)) {
HLOGD("get option -p failed");
return false;
}
inputPidTidArgs_ = selectPids_;
if (!Option::GetOptionValue(args, "-t", selectTids_)) {
HLOGD("get option -t failed");
return false;
}
inputPidTidArgs_.insert(inputPidTidArgs_.end(), selectTids_.begin(), selectTids_.end());
if (!Option::GetOptionValue(args, "--restart", restart_)) {
HLOGD("get option --restart failed");
return false;
}
if (!Option::GetOptionValue(args, "--per-core", perCpus_)) {
HLOGD("get option --per-core failed");
return false;
}
if (!Option::GetOptionValue(args, "--per-thread", perThreads_)) {
HLOGD("get option --per-thread failed");
return false;
}
if (!Option::GetOptionValue(args, "--verbose", verboseReport_)) {
HLOGD("get option --verbose failed");
return false;
}
return ParseSpecialOption(args);
}
bool SubCommandStat::ParseSpecialOption(std::vector<std::string> &args)
{
if (!Option::GetOptionTrackedCommand(args, trackedCommand_)) {
HLOGD("get cmd failed");
return false;
}
if (!args.empty()) {
HLOGD("redundant option(s)");
return false;
}
return true;
}
void SubCommandStat::PrintUsage()
{
printf("%s\n", Help().c_str());
}
void SubCommandStat::SetReportFlags(const bool cpuFlag, const bool threadFlag)
{
g_reportCpuFlag = cpuFlag;
g_reportThreadFlag = threadFlag;
}
void SubCommandStat::Report(const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents,
FILE* filePtr)
{
bool isNeedPerCpuTid = false;
for (const auto &it : countEvents) {
if (!(it.second->summaries.empty())) {
isNeedPerCpuTid = true;
break;
}
}
if (isNeedPerCpuTid) {
PrintPerHead(filePtr);
ReportDetailInfos(countEvents, filePtr);
} else {
ReportNormal(countEvents, filePtr);
}
}
void SubCommandStat::PrintPerHead(FILE* filePtr)
{
if (g_reportCpuFlag && g_reportThreadFlag) {
if (filePtr == nullptr) {
printf(" %24s %-30s | %-30s %10s %10s %10s | %-32s | %s\n", "count", "event_name", "thread_name",
"pid", "tid", "coreid", "comment", "coverage");
} else {
fprintf(filePtr, " %24s %-30s | %-30s %10s %10s %10s | %-32s | %s\n",
"count", "event_name", "thread_name", "pid", "tid", "coreid", "comment", "coverage");
}
return;
}
if (g_reportCpuFlag) {
if (filePtr == nullptr) {
printf(" %24s %-30s | %10s | %-32s | %s\n", "count", "event_name", "coreid", "comment", "coverage");
} else {
fprintf(filePtr, " %24s %-30s | %10s | %-32s | %s\n",
"count", "event_name", "coreid", "comment", "coverage");
}
return;
}
if (filePtr == nullptr) {
printf(" %24s %-30s | %-30s %10s %10s | %-32s | %s\n", "count", "event_name", "thread_name", "pid", "tid",
"comment", "coverage");
} else {
fprintf(filePtr, " %24s %-30s | %-30s %10s %10s | %-32s | %s\n", "count", "event_name", "thread_name",
"pid", "tid", "comment", "coverage");
}
return;
}
void SubCommandStat::PrintPerValue(const std::unique_ptr<PerfEvents::ReportSum> &reportSum, const float &ratio,
const std::string &configName, FILE* filePtr)
{
if (reportSum == nullptr) {
return;
}
std::string strEventCount = std::to_string(reportSum->eventCountSum);
for (size_t i = strEventCount.size() >= 1 ? strEventCount.size() - 1 : 0, j = 1; i > 0; --i, ++j) {
if (j == THOUSANDS_SEPARATOR) {
j = 0;
strEventCount.insert(strEventCount.begin() + i, ',');
}
}
std::string commentStr;
MakeComments(reportSum, commentStr);
if (g_reportCpuFlag && g_reportThreadFlag) {
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) {
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 {
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);
}
void SubCommandStat::InitPerMap(const std::unique_ptr<PerfEvents::ReportSum> &newPerMap,
const PerfEvents::Summary &summary, VirtualRuntime& virtualInstance)
{
CHECK_TRUE(newPerMap != nullptr, NO_RETVAL, 0, "");
newPerMap->cpu = summary.cpu;
if (g_reportCpuFlag && !g_reportThreadFlag) {
return;
}
newPerMap->tid = summary.tid;
newPerMap->pid = thread_map_.find(summary.tid)->second.pid;
bool isTid = true;
if (newPerMap->pid == newPerMap->tid) {
isTid = false;
}
newPerMap->threadName = virtualInstance.ReadThreadName(summary.tid, isTid);
}
void SubCommandStat::GetPerKey(std::string &perKey, const PerfEvents::Summary &summary)
{
perKey = "";
if (g_reportCpuFlag) {
perKey += std::to_string(summary.cpu);
perKey += "|";
}
if (g_reportThreadFlag) {
perKey += std::to_string(summary.tid);
}
return;
}
void SubCommandStat::ReportDetailInfos(
const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents, FILE* filePtr)
{
std::string perKey = "";
std::map<std::string, std::unique_ptr<PerfEvents::ReportSum>> perMaps;
for (auto event = countEvents.begin(); event != countEvents.end(); ++event) {
if (event->second == nullptr || event->second->eventCount == 0) {
continue;
}
constexpr float ratio {100.0};
std::string configName = event->first;
perMaps.clear();
for (auto &it : event->second->summaries) {
GetPerKey(perKey, it);
if (perMaps.count(perKey) == 0) {
auto perMap = std::make_unique<PerfEvents::ReportSum>(PerfEvents::ReportSum {});
InitPerMap(perMap, it, g_runtimeInstance);
perMaps[perKey] = std::move(perMap);
}
if (perMaps[perKey] == nullptr) {
continue;
}
perMaps[perKey]->configName = GetDetailComments(event->second, perMaps[perKey]->commentSum,
it, configName);
perMaps[perKey]->eventCountSum += it.eventCount;
if (it.timeRunning < it.timeEnabled && it.timeRunning != 0) {
perMaps[perKey]->scaleSum = 1 / (static_cast<double>(it.timeEnabled) / it.timeRunning);
}
}
for (auto iper = perMaps.begin(); iper != perMaps.end(); iper++) {
PrintPerValue(iper->second, ratio, configName, filePtr);
}
}
}
void SubCommandStat::ReportNormal(
const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents, FILE* filePtr)
{
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<std::string, std::string> comments;
GetComments(countEvents, comments);
for (auto it = countEvents.begin(); it != countEvents.end(); ++it) {
double scale = 1.0;
constexpr float ratio {100.0};
std::string configName = it->first;
std::string comment = comments[configName];
std::string strEventCount = std::to_string(it->second->eventCount);
for (size_t i = strEventCount.size() >= 1 ? strEventCount.size() - 1 : 0, j = 1; i > 0; --i, ++j) {
if (j == THOUSANDS_SEPARATOR) {
strEventCount.insert(strEventCount.begin() + i, ',');
j = 0;
}
}
if (it->second->timeRunning < it->second->timeEnabled && it->second->timeRunning != 0) {
scale = 1 / (static_cast<double>(it->second->timeEnabled) / it->second->timeRunning);
}
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);
}
}
bool SubCommandStat::FindEventCount(const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents,
const std::string &configName, const __u64 group_id, __u64 &eventCount, double &scale)
{
auto itr = countEvents.find(configName);
if (itr != countEvents.end()) {
eventCount = itr->second->eventCount;
if (itr->second->id == group_id
&& itr->second->timeRunning < itr->second->timeEnabled
&& itr->second->timeRunning != 0) {
scale = static_cast<double>(itr->second->timeEnabled) / itr->second->timeRunning;
return true;
}
}
return false;
}
bool SubCommandStat::FindPerCoreEventCount(PerfEvents::Summary &summary, __u64 &eventCount, double &scale)
{
eventCount = summary.eventCount;
if (summary.timeRunning < summary.timeEnabled && summary.timeRunning != 0) {
scale = static_cast<double>(summary.timeEnabled) / summary.timeRunning;
return true;
}
return false;
}
std::string SubCommandStat::GetCommentConfigName(
const std::unique_ptr<PerfEvents::CountEvent> &countEvent, const std::string &eventName)
{
std::string commentConfigName = "";
CHECK_TRUE(countEvent != nullptr && eventName.length() != 0, commentConfigName, 1, "countEvent is nullptr");
if (countEvent->userOnly) {
commentConfigName = eventName + ":u";
} else if (countEvent->kernelOnly) {
commentConfigName = eventName + ":k";
} else {
commentConfigName = eventName;
}
return commentConfigName;
}
void SubCommandStat::MakeComments(const std::unique_ptr<PerfEvents::ReportSum> &reportSum, std::string &commentStr)
{
CHECK_TRUE(reportSum != nullptr && reportSum->commentSum != 0, NO_RETVAL, 0, "");
if (reportSum->configName == "sw-task-clock") {
commentStr = StringPrintf("%lf cpus used", reportSum->commentSum);
return;
}
if (reportSum->configName == "hw-cpu-cycles") {
commentStr = StringPrintf("%lf GHz", reportSum->commentSum);
return;
}
if (reportSum->configName == "hw-instructions") {
commentStr = StringPrintf("%lf cycles per instruction", reportSum->commentSum);
return;
}
if (reportSum->configName == "hw-branch-misses") {
commentStr = StringPrintf("%lf miss rate", reportSum->commentSum);
return;
}
if (reportSum->commentSum > 1e9) {
commentStr = StringPrintf("%.3lf G/sec", reportSum->commentSum / 1e9);
return;
}
if (reportSum->commentSum > 1e6) {
commentStr = StringPrintf("%.3lf M/sec", reportSum->commentSum / 1e6);
return;
}
if (reportSum->commentSum > 1e3) {
commentStr = StringPrintf("%.3lf K/sec", reportSum->commentSum / 1e3);
return;
}
commentStr = StringPrintf("%.3lf /sec", reportSum->commentSum);
}
std::string SubCommandStat::GetDetailComments(const std::unique_ptr<PerfEvents::CountEvent> &countEvent,
double &comment, PerfEvents::Summary &summary, std::string &configName)
{
double running_time_in_sec = 0;
double main_scale = 1.0;
bool findRunningTime = FindPercoreRunningTime(summary, running_time_in_sec, main_scale);
if (configName == GetCommentConfigName(countEvent, "sw-cpu-clock")) {
comment = 0;
return "sw-cpu-clock";
}
double scale = 1.0;
if (summary.timeRunning < summary.timeEnabled && summary.timeRunning != 0) {
scale = static_cast<double>(summary.timeEnabled) / summary.timeRunning;
}
if (configName == GetCommentConfigName(countEvent, "sw-task-clock")) {
comment += countEvent->usedCpus * scale;
return "sw-task-clock";
}
if (configName == GetCommentConfigName(countEvent, "hw-cpu-cycles")) {
if (findRunningTime) {
double hz = 0;
if (abs(running_time_in_sec) > ALMOST_ZERO) {
hz = summary.eventCount / (running_time_in_sec / scale);
}
comment += hz / 1e9;
}
return "hw-cpu-cycles";
}
if (configName == GetCommentConfigName(countEvent, "hw-instructions") && summary.eventCount != 0) {
double otherScale = 1.0;
__u64 cpuCyclesCount = 0;
bool other = FindPerCoreEventCount(summary, cpuCyclesCount, otherScale);
if (other || (IsMonitoredAtAllTime(otherScale) && IsMonitoredAtAllTime(scale))) {
comment += static_cast<double>(cpuCyclesCount) / summary.eventCount;
return "hw-instructions";
}
}
if (configName == GetCommentConfigName(countEvent, "hw-branch-misses")) {
double otherScale = 1.0;
__u64 branchInstructionsCount = 0;
bool other = FindPerCoreEventCount(summary, branchInstructionsCount, otherScale);
if ((other || (IsMonitoredAtAllTime(otherScale) && IsMonitoredAtAllTime(scale))) &&
branchInstructionsCount != 0) {
comment += (static_cast<double>(summary.eventCount) / branchInstructionsCount) * ONE_HUNDRED;
return "hw-branch-misses";
}
}
return HandleOtherConfig(comment, summary, running_time_in_sec, scale, findRunningTime);
}
std::string SubCommandStat::HandleOtherConfig(double &comment, PerfEvents::Summary &summary,
const double running_time_in_sec, const double scale,
const bool findRunningTime)
{
comment = 0;
if (findRunningTime) {
double rate = 0;
if (scale != 0) {
rate = summary.eventCount / (running_time_in_sec / scale);
}
comment += rate;
}
return "";
}
bool SubCommandStat::IsMonitoredAtAllTime(const double &scale)
{
constexpr double SCALE_ERROR_LIMIT = 1e-5;
return (fabs(scale - 1.0) < SCALE_ERROR_LIMIT);
}
void SubCommandStat::GetHwCpuCyclesComments(
const std::unique_ptr<PerfEvents::CountEvent> &countEvent,
std::map<std::string, std::string> &comments, std::string &configName,
double scale, EventProcessingContext &context)
{
if (context.findRunningTime &&
((context.group_id == countEvent->id) ||
(IsMonitoredAtAllTime(context.main_scale) && IsMonitoredAtAllTime(scale)))) {
double hz = 0;
if (std::abs(context.running_time_in_sec) > ALMOST_ZERO &&
std::abs(scale) > ALMOST_ZERO) {
hz = countEvent->eventCount / (context.running_time_in_sec / scale);
}
comments[configName] = StringPrintf("%lf GHz", hz / 1e9);
} else {
comments[configName] = "";
}
}
bool SubCommandStat::GetHwInstructionsComments(
const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents,
const std::unique_ptr<PerfEvents::CountEvent> &countEvent,
std::map<std::string, std::string> &comments, std::string &configName, double scale)
{
std::string cpuSyclesName = GetCommentConfigName(countEvent, "hw-cpu-cycles");
double otherScale = 1.0;
__u64 cpuCyclesCount = 0;
bool other = FindEventCount(countEvents, cpuSyclesName, countEvent->id, cpuCyclesCount,
otherScale);
if (other || (IsMonitoredAtAllTime(otherScale) && IsMonitoredAtAllTime(scale))) {
CHECK_TRUE(countEvent->eventCount != 0, false, 0, "");
double cpi = static_cast<double>(cpuCyclesCount) / countEvent->eventCount;
comments[configName] = StringPrintf("%lf cycles per instruction", cpi);
return true;
}
return false;
}
bool SubCommandStat::GetHwBranchMissesComments(
const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents,
const std::unique_ptr<PerfEvents::CountEvent> &countEvent,
std::map<std::string, std::string> &comments, std::string &configName, double scale)
{
std::string branchInsName = GetCommentConfigName(countEvent, "hw-branch-instructions");
double otherScale = 1.0;
__u64 branchInstructionsCount = 0;
bool other = FindEventCount(countEvents, branchInsName, countEvent->id,
branchInstructionsCount, otherScale);
if ((other || (IsMonitoredAtAllTime(otherScale) && IsMonitoredAtAllTime(scale))) &&
branchInstructionsCount != 0) {
double missRate =
static_cast<double>(countEvent->eventCount) / branchInstructionsCount;
comments[configName] = StringPrintf("%lf miss rate", missRate * ONE_HUNDRED);
return true;
}
return false;
}
bool SubCommandStat::GetRateComments(
const std::unique_ptr<PerfEvents::CountEvent> &countEvent,
std::map<std::string, std::string> &comments, std::string &configName,
EventProcessingContext &context, double scale)
{
double rate = 0.0;
if (fabs(context.running_time_in_sec) > ALMOST_ZERO &&
fabs(scale) > ALMOST_ZERO) {
rate = countEvent->eventCount / (context.running_time_in_sec / scale);
}
if (rate > 1e9) {
comments[configName] = StringPrintf("%.3lf G/sec", rate / 1e9);
return true;
}
if (rate > 1e6) {
comments[configName] = StringPrintf("%.3lf M/sec", rate / 1e6);
return true;
}
if (rate > 1e3) {
comments[configName] = StringPrintf("%.3lf K/sec", rate / 1e3);
return true;
}
comments[configName] = StringPrintf("%.3lf /sec", rate);
return false;
}
void SubCommandStat::GetComments(const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents,
std::map<std::string, std::string> &comments)
{
EventProcessingContext context;
context.running_time_in_sec = 0;
context.group_id = 0;
context.main_scale = 1.0;
context.findRunningTime = FindRunningTime(countEvents,
context.running_time_in_sec, context.group_id, context.main_scale);
for (auto it = countEvents.begin(); it != countEvents.end(); it++) {
std::string configName = it->first;
std::string commentConfigName = GetCommentConfigName(it->second, "sw-cpu-clock");
if (configName == commentConfigName) {
comments[configName] = "";
continue;
}
double scale = 1.0;
if (it->second->timeRunning < it->second->timeEnabled && it->second->timeRunning != 0) {
scale = static_cast<double>(it->second->timeEnabled) / it->second->timeRunning;
}
commentConfigName = GetCommentConfigName(it->second, "sw-task-clock");
if (configName == commentConfigName) {
double usedCpus = it->second->usedCpus * scale;
comments[configName] = StringPrintf("%lf cpus used", usedCpus);
continue;
}
commentConfigName = GetCommentConfigName(it->second, "hw-cpu-cycles");
if (configName == commentConfigName) {
GetHwCpuCyclesComments(it->second, comments, configName, scale, context);
continue;
}
commentConfigName = GetCommentConfigName(it->second, "hw-instructions");
if (configName == commentConfigName && it->second->eventCount != 0) {
if (GetHwInstructionsComments(countEvents, it->second, comments, configName, scale)) {
continue;
}
}
commentConfigName = GetCommentConfigName(it->second, "hw-branch-misses");
if (configName == commentConfigName) {
if (GetHwBranchMissesComments(countEvents, it->second, comments, configName, scale)) {
continue;
}
}
if (context.findRunningTime && ((context.group_id == it->second->id) ||
(IsMonitoredAtAllTime(context.main_scale) && IsMonitoredAtAllTime(scale)))) {
if (GetRateComments(it->second, comments, configName, context, scale)) {
continue;
}
} else {
comments[configName] = "";
}
}
}
bool SubCommandStat::FindRunningTime(
const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents,
double &running_time_in_sec, __u64 &group_id, double &main_scale)
{
for (auto it = countEvents.begin(); it != countEvents.end(); it++) {
if ((it->first == "sw-task-clock" || it->first == "sw-task-clock:u" ||
it->first == "sw-task-clock:k" || it->first == "sw-cpu-clock" ||
it->first == "sw-cpu-clock:u" || it->first == "sw-cpu-clock:k") &&
it->second->eventCount != 0u) {
group_id = it->second->id;
running_time_in_sec = it->second->eventCount / 1e9;
if (it->second->timeRunning < it->second->timeEnabled &&
it->second->timeRunning != 0) {
main_scale =
static_cast<double>(it->second->timeEnabled) / it->second->timeRunning;
}
return true;
}
}
return false;
}
bool SubCommandStat::FindPercoreRunningTime(PerfEvents::Summary &summary, double &running_time_int_sec,
double &main_scale)
{
CHECK_TRUE(summary.eventCount != 0, false, 0, "");
running_time_int_sec = summary.eventCount / 1e9;
if (summary.timeRunning < summary.timeEnabled && summary.timeRunning != 0) {
main_scale = static_cast<double>(summary.timeEnabled) / summary.timeRunning;
}
return true;
}
bool SubCommandStat::CheckOptionPidAndApp(const std::vector<pid_t>& pids)
{
if (!CheckOptionPid(pids)) {
printf("Problems finding threads of monitor\n\n");
printf("Usage: perf stat [<options>] [<command>]\n\n");
printf("-p <pid> stat events on existing process id\n");
printf("-t <tid> stat events on existing thread id\n");
return false;
}
return true;
}
bool SubCommandStat::CheckOptionPid(const std::vector<pid_t>& pids)
{
if (pids.empty()) {
return true;
}
for (auto pid : pids) {
std::ostringstream oss;
oss << "/proc/" << pid;
if (!IsDir(oss.str())) {
printf("not exit pid %d\n", pid);
return false;
}
}
return true;
}
void SubCommandStat::SetPerfEvent()
{
SetReportFlags(perCpus_, perThreads_);
perfEvents_.SetSystemTarget(targetSystemWide_);
perfEvents_.SetTimeOut(timeStopSec_);
perfEvents_.SetTimeReport(timeReportMs_);
perfEvents_.SetPerCpu(perCpus_);
perfEvents_.SetPerThread(perThreads_);
perfEvents_.SetVerboseReport(verboseReport_);
perfEvents_.SetInherit(!noCreateNew_);
perfEvents_.SetTrackedCommand(trackedCommand_);
perfEvents_.SetStatCallBack(Report);
perfEvents_.SetStatReportFd(filePtr_);
}
bool SubCommandStat::CreateFifoServer()
{
if (!perfPipe_.CreateFifoFile()) {
return false;
}
CheckIpcBeforeFork();
pid_t pid = fork();
allowIpc_ = true;
if (pid == -1) {
char errInfo[ERRINFOLEN] = { 0 };
strerror_r(errno, errInfo, ERRINFOLEN);
HLOGE("fork failed. %d:%s", errno, errInfo);
return false;
}
return (pid == 0) ? HandleChildProcess() : HandleParentProcess(pid);
}
bool SubCommandStat::HandleChildProcess()
{
close(STDIN_FILENO);
close(STDERR_FILENO);
isFifoServer_ = true;
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. %d:%s", fifoFileS2C_.c_str(), errno, errInfo);
HIPERF_HILOGE(MODULE_DEFAULT, "open fifo file(%{public}s) failed. %{public}d:%{public}s",
fifoFileS2C_.c_str(), errno, errInfo);
return false;
}
nullFd_ = open("/dev/null", O_WRONLY);
(void)dup2(nullFd_, STDOUT_FILENO);
std::string err = OHOS::Developtools::HiPerf::HandleAppInfo(appPackage_, inputPidTidArgs_);
if (!err.empty()) {
ClientCommandResponse(err);
return false;
}
return true;
}
bool SubCommandStat::HandleParentProcess(const pid_t& pid)
{
isFifoClient_ = true;
int fd = open(fifoFileS2C_.c_str(), O_RDONLY | O_NONBLOCK);
if (fd == -1) {
HandleCommunicationError(fd, pid, "");
return false;
}
std::string reply = "";
perfPipe_.WaitFifoReply(fd, CONTROL_WAITREPY_TIMEOUT, reply);
if (reply != HiperfClient::REPLY_OK) {
HandleCommunicationError(fd, pid, reply);
close(fd);
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;
}
void SubCommandStat::HandleCommunicationError(const int& fd, const pid_t& pid, const std::string& reply)
{
if (reply != HiperfClient::REPLY_OK) {
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);
if (kill(pid, SIGTERM) != 0) {
HLOGE("Failed to send SIGTERM to %d", pid);
HIPERF_HILOGE(MODULE_DEFAULT, "Failed to send SIGTERM to %{public}d", pid);
}
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);
}
perfPipe_.RemoveFifoFile();
printf("create control hiperf counting failed.\n");
HIPERF_HILOGE(MODULE_DEFAULT, "create control hiperf counting failed.");
}
bool SubCommandStat::ClientCommandResponse(const bool response)
{
return ClientCommandResponse(response ? HiperfClient::REPLY_OK : HiperfClient::REPLY_FAIL);
}
bool SubCommandStat::ClientCommandResponse(const std::string& str)
{
ssize_t size = write(clientPipeOutput_, str.c_str(), str.size());
if (size != static_cast<ssize_t>(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;
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::REPLY_START, ControlCommandHandler{
std::bind(&PerfEvents::EnableTracking, &perfEvents_)
});
controlCommandHandlerMap_.emplace(HiperfClient::REPLY_CHECK, ControlCommandHandler{
std::bind(&SubCommandStat::clientRunning_, this)
});
controlCommandHandlerMap_.emplace(HiperfClient::REPLY_STOP, ControlCommandHandler{
std::bind(&PerfEvents::StopTracking, &perfEvents_)
});
}
inline void SubCommandStat::CreateClientThread()
{
if (clientPipeOutput_ != -1) {
clientCommandHandle_ = std::thread(&SubCommandStat::ClientCommandHandle, this);
}
}
void SubCommandStat::ClientCommandHandle()
{
using namespace HiperfClient;
CHECK_TRUE(IsSamplingRunning(), NO_RETVAL, 0, "");
ClientCommandResponse(true);
InitControlCommandHandlerMap();
bool hasRead = true;
while (clientRunning_.load()) {
if (isFifoServer_ && hasRead) {
if (clientPipeInput_ != -1) {
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()
{
if (!CheckRestartOption(appPackage_, targetSystemWide_, restart_, selectPids_)) {
return HiperfError::CHECK_RESTART_OPTION_FAIL;
}
if (!CheckSelectCpuPidOption()) {
return HiperfError::CHECK_SELECT_CPU_PID_FAIL;
}
if (!CheckOptions(selectPids_)) {
HLOGV("CheckOptions() failed");
return HiperfError::CHECK_STAT_OPTION_FAIL;
}
if (!CheckAppIsRunning(selectPids_, appPackage_, checkAppMs_)) {
HLOGV("CheckAppIsRunning() failed");
return HiperfError::CHECK_APP_RUNNING_FAIL;
}
if (!CheckOptionPid(selectPids_)) {
HLOGV("CheckOptionPid() failed");
return HiperfError::CHECK_OPTION_PID_FAIL;
}
perfEvents_.SetCpu(selectCpus_);
std::vector<pid_t> pids;
for (auto selectPid : selectPids_) {
HLOGD("[OnSubCommand] selectPid %d\n", selectPid);
std::vector<pid_t> subTids = GetSubthreadIDs(selectPid, thread_map_);
if (!subTids.empty()) {
pids.insert(pids.end(), subTids.begin(), subTids.end());
} else {
HLOGD("[OnSubCommand] subTids empty for %d\n", selectPid);
}
}
pids.insert(pids.end(), selectTids_.begin(), selectTids_.end());
perfEvents_.SetPid(pids);
if (!CheckOptionPidAndApp(pids)) {
HLOGV("CheckOptionPidAndApp() failed");
return HiperfError::CHECK_OPTION_PID_APP_FAIL;
}
std::string err = "";
if (allowIpc_ && !IsExistDebugByPid(inputPidTidArgs_, err)) {
return HiperfError::CHECK_OPTION_PID_APP_FAIL;
}
return HiperfError::NO_ERR;
}
HiperfError SubCommandStat::OnSubCommand(std::vector<std::string>& 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 (!CheckOutPutFile()) {
return HiperfError::CHECK_OUT_PUT_ERROR;
}
if (!ProcessControl()) {
return HiperfError::PROCESS_CONTROL_FAIL;
} else if (isFifoClient_) {
return HiperfError::NO_ERR;
}
SetPerfEvent();
if (!PrepairEvents()) {
HLOGV("PrepairEvents() failed");
return HiperfError::PREPAIR_EVENTS_FAIL;
}
perfEvents_.PrepareTracking();
CreateClientThread();
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 (clientCommandHandle_.joinable()) {
clientRunning_.store(false);
HLOGI("CloseClientThread");
if (nullFd_ != -1) {
close(nullFd_);
}
clientCommandHandle_.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);
}
bool SubCommandStat::PrepairEvents()
{
if (selectEvents_.empty() && selectGroups_.empty()) {
perfEvents_.AddDefaultEvent(PERF_TYPE_HARDWARE);
perfEvents_.AddDefaultEvent(PERF_TYPE_SOFTWARE);
} else {
for (auto events : selectEvents_) {
if (!perfEvents_.AddEvents(events)) {
HLOGV("add events failed");
return false;
}
}
for (auto events : selectGroups_) {
if (!perfEvents_.AddEvents(events, true)) {
HLOGV("add groups failed");
return false;
}
}
}
return true;
}
bool SubCommandStat::CheckSelectCpuPidOption()
{
if (!selectCpus_.empty()) {
if (!(selectCpus_.size() == 1 && selectCpus_.front() == -1)) {
int maxCpuid = sysconf(_SC_NPROCESSORS_CONF) - 1;
for (auto cpu : selectCpus_) {
if (cpu < 0 || cpu > maxCpuid) {
printf("Invalid -c value '%d', the CPU ID should be in 0~%d \n", cpu, maxCpuid);
return false;
}
}
}
} else {
if (!targetSystemWide_) {
selectCpus_.push_back(-1);
}
}
if (!selectPids_.empty()) {
for (auto pid : selectPids_) {
if (pid <= 0) {
printf("Invalid -p value '%d', the pid should be larger than 0\n", pid);
return false;
}
}
}
if (!selectTids_.empty()) {
for (auto tid : selectTids_) {
if (tid <= 0) {
printf("Invalid -t value '%d', the tid should be larger than 0\n", tid);
return false;
}
}
}
return true;
}
bool SubCommandStat::CheckSystemWideConflicts(const std::vector<pid_t>& pids)
{
if (targetSystemWide_) {
if (!pids.empty() || !selectTids_.empty()) {
printf("You cannot specify -a and -t/-p at the same time\n");
return false;
}
if (!appPackage_.empty()) {
printf("You cannot specify -a and --app at the same time\n");
return false;
}
}
return true;
}
bool SubCommandStat::CheckAppConflicts(const std::vector<pid_t>& pids)
{
if (!appPackage_.empty() && (!pids.empty() || !selectTids_.empty())) {
printf("You cannot specify --app and -t/-p at the same time\n");
return false;
}
return true;
}
bool SubCommandStat::CheckRequiredOptions(const std::vector<pid_t>& pids)
{
if (!targetSystemWide_ && trackedCommand_.empty() && pids.empty() && appPackage_.empty()
&& selectTids_.empty()) {
printf("You need to set the -p option or --app option.\n");
return false;
}
return true;
}
bool SubCommandStat::CheckTrackedCommandConflicts(const std::vector<pid_t>& pids)
{
if (!trackedCommand_.empty()) {
if (targetSystemWide_) {
printf("You cannot specify -a and a cmd at the same time\n");
return false;
}
if (!pids.empty() || !selectTids_.empty()) {
printf("You cannot specify a cmd and -t/-p at the same time\n");
return false;
}
if (!appPackage_.empty()) {
printf("You cannot specify a cmd and --app at the same time\n");
return false;
}
if (!IsRoot()) {
printf("%s options needs root privilege, please check usage\n",
VectorToString(trackedCommand_).c_str());
return false;
}
}
return true;
}
bool SubCommandStat::CheckNumericConfigRanges()
{
if (checkAppMs_ < MIN_CHECK_APP_MS || checkAppMs_ > MAX_CHECK_APP_MS) {
printf("Invalid --chkms value '%d', the milliseconds should be in %d~%d \n", checkAppMs_,
MIN_CHECK_APP_MS, MAX_CHECK_APP_MS);
return false;
}
if (timeStopSec_ < 0) {
printf("monitoring duration should be positive but %f is given\n", timeStopSec_);
return false;
}
if (timeReportMs_ < 0) {
printf("print interval should be non-negative but %d is given\n", timeReportMs_);
return false;
}
return true;
}
bool SubCommandStat::CheckOptions(const std::vector<pid_t>& pids)
{
return CheckSystemWideConflicts(pids) && CheckAppConflicts(pids) &&
CheckRequiredOptions(pids) && CheckTrackedCommandConflicts(pids) && CheckNumericConfigRanges();
}
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) {
char errInfo[ERRINFOLEN] = { 0 };
strerror_r(errno, errInfo, ERRINFOLEN);
printf("unable open file to '%s' because '%d:%s'\n", outputFilename_.c_str(), errno, errInfo);
return false;
}
return true;
}
void SubCommandStat::AddReportArgs(CommandReporter& reporter)
{
if (targetSystemWide_) {
reporter.targetProcess_ = "ALL";
} else if (!appPackage_.empty()) {
reporter.targetProcess_ = appPackage_;
} else {
std::unordered_set<std::string> processNames = {};
for_each(selectPids_.begin(), selectPids_.end(), [&processNames] (const pid_t& pid) {
processNames.insert(GetProcessName(pid));
});
reporter.targetProcess_ = SetToString<std::string>(processNames);
}
}
SubCommand& SubCommandStat::GetInstance()
{
static SubCommandStat subCommand;
return subCommand;
}
}
}
}