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