• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #define HILOG_TAG "Stat"
17 
18 #include "subcommand_stat.h"
19 
20 #include <csignal>
21 #include <iostream>
22 #include <memory>
23 
24 #include "debug_logger.h"
25 #include "utilities.h"
26 
27 const uint16_t ONE_HUNDRED = 100;
28 const uint16_t THOUSNADS_SEPARATOR = 3;
29 namespace OHOS {
30 namespace Developtools {
31 namespace HiPerf {
DumpOptions() const32 void SubCommandStat::DumpOptions() const
33 {
34     HLOGV("enter");
35     printf("DumpOptions:\n");
36     printf(" targetSystemWide:\t%s\n", targetSystemWide_ ? "true" : "false");
37     printf(" selectCpus:\t%s\n", VectorToString(selectCpus_).c_str());
38     printf(" timeStopSec:\t%f sec\n", timeStopSec_);
39     printf(" timeReportMs:\t%d ms\n", timeReportMs_);
40     printf(" selectEvents:\t%s\n", VectorToString(selectEvents_).c_str());
41     printf(" selectGroups:\t%s\n", VectorToString(selectGroups_).c_str());
42     printf(" noCreateNew:\t%s\n", noCreateNew_ ? "true" : "false");
43     printf(" selectPids:\t%s\n", VectorToString(selectPids_).c_str());
44     printf(" selectTids:\t%s\n", VectorToString(selectTids_).c_str());
45     printf(" verbose:\t%s\n", verboseReport_ ? "true" : "false");
46 }
47 
ParseOption(std::vector<std::string> & args)48 bool SubCommandStat::ParseOption(std::vector<std::string> &args)
49 {
50     HLOGV("enter");
51     if (args.size() == 1 and args[0] == "-h") {
52         args.clear();
53         helpOption_ = true;
54         PrintUsage();
55         return true;
56     }
57     if (!Option::GetOptionValue(args, "-a", targetSystemWide_)) {
58         HLOGD("get option -a failed");
59         return false;
60     }
61     if (targetSystemWide_ && !IsRoot()) {
62         HLOGD("-a option needs root privilege for system wide profiling.");
63         printf("-a option needs root privilege for system wide profiling.\n");
64         return false;
65     }
66     if (!Option::GetOptionValue(args, "-c", selectCpus_)) {
67         HLOGD("get option -c failed");
68         return false;
69     }
70     if (!Option::GetOptionValue(args, "-d", timeStopSec_)) {
71         HLOGD("get option -d failed");
72         return false;
73     }
74     if (!Option::GetOptionValue(args, "-i", timeReportMs_)) {
75         HLOGD("get option -i failed");
76         return false;
77     }
78     if (!Option::GetOptionValue(args, "-e", selectEvents_)) {
79         HLOGD("get option -e failed");
80         return false;
81     }
82     if (!Option::GetOptionValue(args, "-g", selectGroups_)) {
83         HLOGD("get option -g failed");
84         return false;
85     }
86     if (!Option::GetOptionValue(args, "--no-inherit", noCreateNew_)) {
87         HLOGD("get option --no-inherit failed");
88         return false;
89     }
90     if (!Option::GetOptionValue(args, "-p", selectPids_)) {
91         HLOGD("get option -p failed");
92         return false;
93     }
94     if (!IsExistDebugByPid(selectPids_)) {
95         return false;
96     }
97     if (!Option::GetOptionValue(args, "-t", selectTids_)) {
98         HLOGD("get option -t failed");
99         return false;
100     }
101     if (!Option::GetOptionValue(args, "--verbose", verboseReport_)) {
102         HLOGD("get option --verbose failed");
103         return false;
104     }
105     if (!Option::GetOptionTrackedCommand(args, trackedCommand_)) {
106         HLOGD("get cmd failed");
107         return false;
108     }
109     if (!args.empty()) {
110         HLOGD("redundant option(s)");
111         return false;
112     }
113     return true;
114 }
115 
PrintUsage()116 void SubCommandStat::PrintUsage()
117 {
118     printf("%s\n", Help().c_str());
119 }
120 
Report(const std::map<std::string,std::unique_ptr<PerfEvents::CountEvent>> & countEvents)121 void SubCommandStat::Report(
122     const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents)
123 {
124     // head
125     printf(" %24s  %-30s | %-32s | %s\n", "count", "name", "comment", "coverage");
126 
127     std::map<std::string, std::string> comments;
128     GetComments(countEvents, comments);
129     for (auto it = countEvents.begin(); it != countEvents.end(); it++) {
130         double scale = 1.0;
131         std::string configName = it->first;
132         std::string comment = comments[configName];
133         constexpr float ratio {100.0};
134         std::string strEventCount = std::to_string(it->second->eventCount);
135         for (size_t i = strEventCount.size() - 1, j = 1; i > 0; --i, ++j) {
136             if (j == THOUSNADS_SEPARATOR) {
137                 strEventCount.insert(strEventCount.begin() + i, ',');
138                 j = 0;
139             }
140         }
141         if (it->second->time_running < it->second->time_enabled && it->second->time_running != 0) {
142             scale = 1 / (static_cast<double>(it->second->time_enabled) / it->second->time_running);
143         }
144         printf(" %24s  %-30s | %-32s | (%.0lf%%)\n", strEventCount.c_str(), configName.c_str(),
145                comment.c_str(), scale * ratio);
146 
147         fflush(stdout);
148     }
149 }
150 
FindEventCount(const std::map<std::string,std::unique_ptr<PerfEvents::CountEvent>> & countEvents,const std::string & configName,const __u64 group_id,__u64 & eventCount,double & scale)151 bool SubCommandStat::FindEventCount(
152     const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents,
153     const std::string &configName, const __u64 group_id, __u64 &eventCount, double &scale)
154 {
155     auto itr = countEvents.find(configName);
156     if (itr != countEvents.end()) {
157         eventCount = itr->second->eventCount;
158         if (itr->second->id == group_id
159             && itr->second->time_running < itr->second->time_enabled
160             && itr->second->time_running != 0) {
161             scale = static_cast<double>(itr->second->time_enabled) / itr->second->time_running;
162             return true;
163         }
164     }
165     return false;
166 }
167 
GetCommentConfigName(const std::unique_ptr<PerfEvents::CountEvent> & countEvent,std::string eventName)168 std::string SubCommandStat::GetCommentConfigName(
169     const std::unique_ptr<PerfEvents::CountEvent> &countEvent, std::string eventName)
170 {
171     std::string commentConfigName = "";
172     if (countEvent == nullptr || eventName.length() == 0) {
173         return commentConfigName;
174     }
175     if (countEvent->userOnly) {
176         commentConfigName = eventName + ":u";
177     } else if (countEvent->kernelOnly) {
178         commentConfigName = eventName + ":k";
179     } else {
180         commentConfigName = eventName;
181     }
182     return commentConfigName;
183 }
184 
IsMonitoredAtAllTime(const double & scale)185 bool SubCommandStat::IsMonitoredAtAllTime(const double &scale)
186 {
187     constexpr double SCALE_ERROR_LIMIT = 1e-5;
188     return (fabs(scale - 1.0) < SCALE_ERROR_LIMIT);
189 }
190 
GetComments(const std::map<std::string,std::unique_ptr<PerfEvents::CountEvent>> & countEvents,std::map<std::string,std::string> & comments)191 void SubCommandStat::GetComments(const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents,
192     std::map<std::string, std::string> &comments)
193 {
194     double running_time_in_sec = 0;
195     __u64 group_id = 0;
196     double main_scale = 1.0;
197     bool findRunningTime = FindRunningTime(countEvents, running_time_in_sec, group_id, main_scale);
198     for (auto it = countEvents.begin(); it != countEvents.end(); it++) {
199         std::string configName = it->first;
200         std::string commentConfigName = GetCommentConfigName(it->second, "sw-cpu-clock");
201         if (configName == commentConfigName) {
202             comments[configName] = "";
203             continue;
204         }
205         double scale = 1.0;
206         if (it->second->time_running < it->second->time_enabled && it->second->time_running != 0) {
207             scale = static_cast<double>(it->second->time_enabled) / it->second->time_running;
208         }
209         commentConfigName = GetCommentConfigName(it->second, "sw-task-clock");
210         if (configName == commentConfigName) {
211             double used_cpus = it->second->used_cpus * scale;
212             comments[configName] = StringPrintf("%lf cpus used", used_cpus);
213             continue;
214         }
215         commentConfigName = GetCommentConfigName(it->second, "hw-cpu-cycles");
216         if (configName == commentConfigName) {
217             if (findRunningTime &&
218                 ((group_id == it->second->id) ||
219                  (IsMonitoredAtAllTime(main_scale) && IsMonitoredAtAllTime(scale)))) {
220                 double hz = 0;
221                 if (running_time_in_sec != 0) {
222                     hz = it->second->eventCount / (running_time_in_sec / scale);
223                 }
224                 comments[configName] = StringPrintf("%lf GHz", hz / 1e9);
225             } else {
226                 comments[configName] = "";
227             }
228             continue;
229         }
230         commentConfigName = GetCommentConfigName(it->second, "hw-instructions");
231         if (configName == commentConfigName && it->second->eventCount != 0) {
232             std::string cpuSyclesName = GetCommentConfigName(it->second, "hw-cpu-cycles");
233             double otherScale = 1.0;
234             __u64 cpuCyclesCount = 0;
235             bool other = FindEventCount(countEvents, cpuSyclesName, it->second->id, cpuCyclesCount,
236                                         otherScale);
237             if (other || (IsMonitoredAtAllTime(otherScale) && IsMonitoredAtAllTime(scale))) {
238                 double cpi = static_cast<double>(cpuCyclesCount) / it->second->eventCount;
239                 comments[configName] = StringPrintf("%lf cycles per instruction", cpi);
240                 continue;
241             }
242         }
243         commentConfigName = GetCommentConfigName(it->second, "hw-branch-misses");
244         if (configName == commentConfigName) {
245             std::string branchInsName = GetCommentConfigName(it->second, "hw-branch-instructions");
246             double otherScale = 1.0;
247             __u64 branchInstructionsCount = 0;
248             bool other = FindEventCount(countEvents, branchInsName, it->second->id,
249                                         branchInstructionsCount, otherScale);
250             if ((other || (IsMonitoredAtAllTime(otherScale) && IsMonitoredAtAllTime(scale))) &&
251                 branchInstructionsCount != 0) {
252                 double miss_rate =
253                     static_cast<double>(it->second->eventCount) / branchInstructionsCount;
254                 comments[configName] = StringPrintf("%lf miss rate", miss_rate * ONE_HUNDRED);
255                 continue;
256             }
257         }
258         if (findRunningTime && ((group_id == it->second->id) || (IsMonitoredAtAllTime(main_scale) &&
259                                                                  IsMonitoredAtAllTime(scale)))) {
260             double rate = it->second->eventCount / (running_time_in_sec / scale);
261             if (rate > 1e9) {
262                 comments[configName] = StringPrintf("%.3lf G/sec", rate / 1e9);
263                 continue;
264             }
265             if (rate > 1e6) {
266                 comments[configName] = StringPrintf("%.3lf M/sec", rate / 1e6);
267                 continue;
268             }
269             if (rate > 1e3) {
270                 comments[configName] = StringPrintf("%.3lf K/sec", rate / 1e3);
271                 continue;
272             }
273             comments[configName] = StringPrintf("%.3lf /sec", rate);
274         } else {
275             comments[configName] = "";
276         }
277     }
278 }
279 
FindRunningTime(const std::map<std::string,std::unique_ptr<PerfEvents::CountEvent>> & countEvents,double & running_time_in_sec,__u64 & group_id,double & main_scale)280 bool SubCommandStat::FindRunningTime(
281     const std::map<std::string, std::unique_ptr<PerfEvents::CountEvent>> &countEvents,
282     double &running_time_in_sec, __u64 &group_id, double &main_scale)
283 {
284     for (auto it = countEvents.begin(); it != countEvents.end(); it++) {
285         if ((it->first == "sw-task-clock" || it->first == "sw-task-clock:u" ||
286              it->first == "sw-task-clock:k" || it->first == "sw-cpu-clock" ||
287              it->first == "sw-cpu-clock:u" || it->first == "sw-cpu-clock:k") &&
288             it->second->eventCount != 0u) {
289             group_id = it->second->id;
290             running_time_in_sec = it->second->eventCount / 1e9;
291             if (it->second->time_running < it->second->time_enabled &&
292                 it->second->time_running != 0) {
293                 main_scale =
294                     static_cast<double>(it->second->time_enabled) / it->second->time_running;
295             }
296             return true;
297         }
298     }
299     return false;
300 }
301 
CheckOptionPid(std::vector<pid_t> pids)302 bool SubCommandStat::CheckOptionPid(std::vector<pid_t> pids)
303 {
304     if (pids.empty()) {
305         return true;
306     }
307 
308     for (auto pid : pids) {
309         if (!IsDir("/proc/" + std::to_string(pid))) {
310             printf("not exit pid %d\n", pid);
311             return false;
312         }
313     }
314     return true;
315 }
316 
OnSubCommand(std::vector<std::string> & args)317 bool SubCommandStat::OnSubCommand(std::vector<std::string> &args)
318 {
319     HLOGV("enter");
320 
321     if (HelpOption()) {
322         return true;
323     }
324     // check option
325     if (!CheckSelectCpuPidOption()) {
326         return false;
327     }
328 
329     perfEvents_.SetCpu(selectCpus_);
330     std::vector<pid_t> pids;
331     for (auto selectPid : selectPids_) {
332         pids.push_back(selectPid);
333         std::vector<pid_t> subTids = GetSubthreadIDs(selectPid);
334         if (!subTids.empty()) {
335             pids.insert(pids.end(), subTids.begin(), subTids.end());
336         }
337     }
338     pids.insert(pids.end(), selectTids_.begin(), selectTids_.end());
339     perfEvents_.SetPid(pids);
340     if (!CheckOptions(pids)) {
341         HLOGV("CheckOptions() failed");
342         return false;
343     }
344     if (!CheckOptionPid(pids)) {
345         printf("Problems finding threads of monitor\n\n");
346         printf("Usage: perf stat [<options>] [<command>]\n\n");
347         printf("-p <pid>        stat events on existing process id\n");
348         printf("-t <tid>        stat events on existing thread id\n");
349         return false;
350     }
351     perfEvents_.SetSystemTarget(targetSystemWide_);
352     perfEvents_.SetTimeOut(timeStopSec_);
353     perfEvents_.SetTimeReport(timeReportMs_);
354     perfEvents_.SetVerboseReport(verboseReport_);
355     perfEvents_.SetInherit(!noCreateNew_);
356     perfEvents_.SetTrackedCommand(trackedCommand_);
357     // set report handle
358     perfEvents_.SetStatCallBack(Report);
359     if (!PrepairEvents()) {
360         HLOGV("PrepairEvents() failed");
361         return false;
362     }
363 
364     // preapare fd
365     perfEvents_.PrepareTracking();
366 
367     // start tracking
368     perfEvents_.StartTracking();
369 
370     return true;
371 }
372 
RegisterSubCommandStat()373 bool RegisterSubCommandStat()
374 {
375     HLOGV("enter");
376     return SubCommand::RegisterSubCommand("stat", std::make_unique<SubCommandStat>());
377 }
378 
PrepairEvents()379 bool SubCommandStat::PrepairEvents()
380 {
381     if (selectEvents_.empty() && selectGroups_.empty()) {
382         perfEvents_.AddDefaultEvent(PERF_TYPE_HARDWARE);
383         perfEvents_.AddDefaultEvent(PERF_TYPE_SOFTWARE);
384     } else {
385         for (auto events : selectEvents_) {
386             if (!perfEvents_.AddEvents(events)) {
387                 HLOGV("add events failed");
388                 return false;
389             }
390         }
391         for (auto events : selectGroups_) {
392             if (!perfEvents_.AddEvents(events, true)) {
393                 HLOGV("add groups failed");
394                 return false;
395             }
396         }
397     }
398     return true;
399 }
400 
CheckSelectCpuPidOption()401 bool SubCommandStat::CheckSelectCpuPidOption()
402 {
403     if (!selectCpus_.empty()) {
404         // the only value is not -1
405         if (!(selectCpus_.size() == 1 && selectCpus_.front() == -1)) {
406             int maxCpuid = GetProcessorNum() - 1;
407             for (auto cpu : selectCpus_) {
408                 if (cpu < 0 || cpu > maxCpuid) {
409                     printf("Invalid -c value '%d', the CPU ID should be in 0~%d \n", cpu, maxCpuid);
410                     return false;
411                 }
412             }
413         }
414     } else {
415         // the cpu default -1
416         if (!targetSystemWide_) {
417             selectCpus_.push_back(-1);
418         }
419     }
420 
421     if (!selectPids_.empty()) {
422         for (auto pid : selectPids_) {
423             if (pid <= 0) {
424                 printf("Invalid -p value '%d', the pid should be larger than 0\n", pid);
425                 return false;
426             }
427         }
428     }
429     if (!selectTids_.empty()) {
430         for (auto tid : selectTids_) {
431             if (tid <= 0) {
432                 printf("Invalid -t value '%d', the tid should be larger than 0\n", tid);
433                 return false;
434             }
435         }
436     }
437     return true;
438 }
439 
CheckOptions(const std::vector<pid_t> & pids)440 bool SubCommandStat::CheckOptions(const std::vector<pid_t> &pids)
441 {
442     if (targetSystemWide_ && !pids.empty()) {
443         printf("You cannot specify -a and -t/-p at the same time\n");
444         return false;
445     }
446     if (!targetSystemWide_ && trackedCommand_.empty() && pids.empty()) {
447         printf("You need to set the -p option.\n");
448         return false;
449     }
450     if (targetSystemWide_ && !trackedCommand_.empty()) {
451         printf("You cannot specify -a and a cmd at the same time\n");
452         return false;
453     }
454     if (!trackedCommand_.empty() && !pids.empty()) {
455         printf("You cannot specify a cmd and -t/-p at the same time\n");
456         return false;
457     }
458     if (timeStopSec_ < 0) {
459         printf("monitoring duration should be positive but %f is given\n", timeStopSec_);
460         return false;
461     }
462     if (timeReportMs_ < 0) {
463         printf("print interval should be non-negative but %d is given\n", timeReportMs_);
464         return false;
465     }
466     return true;
467 }
468 } // namespace HiPerf
469 } // namespace Developtools
470 } // namespace OHOS
471