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 #define HILOG_TAG "Report"
16
17 #include "subcommand_report.h"
18
19 #include <memory>
20 #include <set>
21 #include <sstream>
22
23 #if is_mingw
24 #include <windows.h>
25 #else
26 #include <sys/ioctl.h>
27 #endif
28
29 #include "perf_events.h"
30 #include "register.h"
31 #include "utilities.h"
32
33 namespace OHOS {
34 namespace Developtools {
35 namespace HiPerf {
ParseOption(std::vector<std::string> & args)36 bool SubCommandReport::ParseOption(std::vector<std::string> &args)
37 {
38 if (!Option::GetOptionValue(args, "-i", recordFile_[FIRST])) {
39 return false;
40 }
41 if (!Option::GetOptionValue(args, "-o", reportFile_)) {
42 return false;
43 }
44 if (!Option::GetOptionValue(args, "--diff", recordFile_[SECOND])) {
45 return false;
46 }
47 if (!recordFile_[SECOND].empty()) {
48 // remove tid pid
49 reportOption_.sortKeys_ = {"comm", "dso", "func"};
50 }
51 if (!Option::GetOptionValue(args, "--sort", reportOption_.sortKeys_)) {
52 return false;
53 }
54
55 if (!Option::GetOptionValue(args, "--symbol-dir", symbolsPaths_)) {
56 return false;
57 }
58 if (!Option::GetOptionValue(args, "--limit-percent", reportOption_.heatLimit_)) {
59 return false;
60 }
61
62 if (!Option::GetOptionValue(args, "-s", showCallStack_)) {
63 return false;
64 }
65 if (!Option::GetOptionValue(args, "--call-stack", showCallStack_)) {
66 return false;
67 }
68
69 if (!Option::GetOptionValue(args, "--call-stack-limit-percent",
70 reportOption_.callStackHeatLimit_)) {
71 return false;
72 }
73
74 if (!Option::GetOptionValue(args, "--comms", reportOption_.displayComms_)) {
75 return false;
76 }
77 if (!Option::GetOptionValue(args, "--pids", reportOption_.displayPids_)) {
78 return false;
79 }
80 if (!Option::GetOptionValue(args, "--tids", reportOption_.displayTids_)) {
81 return false;
82 }
83 if (!Option::GetOptionValue(args, "--dsos", reportOption_.displayDsos_)) {
84 return false;
85 }
86 if (!Option::GetOptionValue(args, "--funcs", reportOption_.displayFuncs_)) {
87 return false;
88 }
89 if (!Option::GetOptionValue(args, "--from_dsos", reportOption_.displayFuncs_)) {
90 return false;
91 }
92 if (!Option::GetOptionValue(args, "--from_funcs", reportOption_.displayFuncs_)) {
93 return false;
94 }
95 if (!Option::GetOptionValue(args, "--proto", protobufFormat_)) {
96 return false;
97 }
98 if (!Option::GetOptionValue(args, "--json", jsonFormat_)) {
99 return false;
100 }
101 if (!Option::GetOptionValue(args, "--debug", debug_)) {
102 return false;
103 }
104 if (!Option::GetOptionValue(args, "--branch", branch_)) {
105 return false;
106 }
107 // this is a hidden option for compare result
108 if (!Option::GetOptionValue(args, "--hide_count", reportOption_.hideCount_)) {
109 return false;
110 }
111 return VerifyOption();
112 }
113
DumpOptions() const114 void SubCommandReport::DumpOptions() const
115 {
116 printf("DumpOptions:\n");
117 printf(" recordFile_:\t%s\n", recordFile_[FIRST].c_str());
118 printf(" recordFile_:\t%s\n", recordFile_[SECOND].c_str());
119 printf(" reportFile_:\t%s\n", reportFile_.c_str());
120 printf(" sortKeys:\t%s\n", VectorToString(reportOption_.sortKeys_).c_str());
121 }
VerifyDisplayOption()122 bool SubCommandReport::VerifyDisplayOption()
123 {
124 for (std::string &number : reportOption_.displayPids_) {
125 if (!IsDigits(number) or number.front() == '-') {
126 printf("error number for pid '%s'\n", number.c_str());
127 return false;
128 }
129 }
130
131 for (std::string &number : reportOption_.displayTids_) {
132 if (!IsDigits(number) or number.front() == '-') {
133 printf("error number for tid '%s'\n", number.c_str());
134 return false;
135 }
136 }
137 return true;
138 }
139
VerifyOption()140 bool SubCommandReport::VerifyOption()
141 {
142 for (auto key : reportOption_.sortKeys_) {
143 if (key == "count") {
144 printf("unknown sort key name '%s'\n", key.c_str());
145 return false;
146 } else if (GetReport().reportKeyMap_.count(key) == 0) {
147 printf("unknown sort key name '%s'\n", key.c_str());
148 return false;
149 }
150 }
151 const float min = 0.0;
152 const float max = 100.0;
153 if (reportOption_.heatLimit_ < min or reportOption_.heatLimit_ > max) {
154 printf("head limit error. must in (0 <= limit < 100).\n");
155 return false;
156 }
157 if (reportOption_.callStackHeatLimit_ < min or reportOption_.callStackHeatLimit_ > max) {
158 printf("head limit error. must in (0 <= limit < 100).\n");
159 return false;
160 }
161 if (!recordFile_[SECOND].empty()) {
162 if (protobufFormat_ or jsonFormat_ or showCallStack_) {
163 printf("diff don't support any export mode(like json , flame or proto)\n");
164 } else {
165 diffMode_ = true;
166 }
167 }
168
169 // default report file name
170 if (reportFile_.empty()) {
171 if (protobufFormat_) {
172 reportFile_ = "perf.proto";
173 } else if (jsonFormat_) {
174 reportFile_ = "perf.json";
175 }
176 }
177
178 // misc config
179 reportOption_.debug_ = debug_;
180 ReportJsonFile::debug_ = debug_;
181
182 return VerifyDisplayOption();
183 }
184
BroadcastSample(std::unique_ptr<PerfRecordSample> & sample)185 void SubCommandReport::BroadcastSample(std::unique_ptr<PerfRecordSample> &sample)
186 {
187 // this func use for cpuoff mode , it will Broadcast the sampe to every event config
188 for (auto &config : GetReport().configs_) {
189 HLOGM("resend as id %" PRIu64 "", config.ids_[0]);
190 sample->data_.id = config.ids_[0];
191 ProcessSample(sample);
192 }
193 }
194
ProcessSample(std::unique_ptr<PerfRecordSample> & sample)195 void SubCommandReport::ProcessSample(std::unique_ptr<PerfRecordSample> &sample)
196 {
197 sample->DumpLog(__FUNCTION__);
198 if (jsonFormat_) {
199 reportJsonFile_->UpdateReportSample(sample->data_.id, sample->data_.pid, sample->data_.tid,
200 sample->data_.period);
201 reportJsonFile_->UpdateReportCallStack(sample->data_.id, sample->data_.pid,
202 sample->data_.tid, sample->data_.period,
203 sample->callFrames_);
204 } else if (protobufFormat_) {
205 #if HAVE_PROTOBUF
206 // make some cook
207 // redesgin here
208 protobufOutputFileWriter_->ProcessSampleRecord(
209 *sample, static_cast<uint32_t>(GetReport().GetConfigIndex(sample->data_.id)),
210 GetReport().virtualRuntime_.GetSymbolsFiles());
211 #endif
212 } else {
213 if (branch_) {
214 GetReport().AddReportItemBranch(*sample);
215 } else {
216 GetReport().AddReportItem(*sample, showCallStack_);
217 }
218 }
219 }
220
RecordCallBack(std::unique_ptr<PerfEventRecord> record)221 bool SubCommandReport::RecordCallBack(std::unique_ptr<PerfEventRecord> record)
222 {
223 // tell process tree what happend for rebuild symbols
224 GetReport().virtualRuntime_.UpdateFromRecord(*record);
225
226 if (record->GetType() == PERF_RECORD_SAMPLE) {
227 std::unique_ptr<PerfRecordSample> sample(static_cast<PerfRecordSample *>(record.release()));
228 std::unique_ptr<PerfRecordSample> prevSample = nullptr;
229 if (cpuOffMode_) {
230 auto prevIt = prevSampleCache_.find(sample->data_.tid);
231 if (prevIt == prevSampleCache_.end()) {
232 // this thread first sample
233 prevSampleCache_[sample->data_.tid] = std::move(sample);
234 // do nothing because we unable to calc the period
235 return true;
236 } else {
237 // we have prev sample
238 prevSample = std::move(prevIt->second);
239 HLOGV("calc time %llu - %llu", sample->data_.time, prevSample->data_.time);
240 if (sample->data_.time > prevSample->data_.time) {
241 prevSample->data_.period = sample->data_.time - prevSample->data_.time;
242 } else {
243 prevSample->data_.period = 1u;
244 }
245
246 // current move the prev
247 prevIt->second = std::move(sample);
248 // go on with prevSample
249 sample = std::move(prevSample);
250
251 HLOGV("current sample period %llu ", sample->data_.period);
252 }
253 }
254 if (cpuOffMode_ and cpuOffids_.size() > 0 and cpuOffids_.count(sample->data_.id) > 0) {
255 BroadcastSample(sample);
256 } else {
257 ProcessSample(sample);
258 }
259 } else {
260 #if HAVE_PROTOBUF
261 if (protobufFormat_) {
262 protobufOutputFileWriter_->ProcessRecord(*record);
263 }
264 #endif
265 }
266 return true;
267 }
268
LoadPerfDataCompleted()269 void SubCommandReport::LoadPerfDataCompleted()
270 {
271 if (jsonFormat_) {
272 reportJsonFile_->UpdateCallNodeEventCount();
273 }
274 HLOGV("load perf data done");
275 }
276
ProcessSymbolsData()277 void SubCommandReport::ProcessSymbolsData()
278 {
279 GetReport().virtualRuntime_.SetSymbolsPaths(symbolsPaths_);
280 // we need unwind it (for function name match) even not give us path
281 GetReport().virtualRuntime_.SetDisableUnwind(false);
282
283 // found symbols in file
284 const auto featureSection = recordFileReader_->GetFeatureSection(FEATURE::HIPERF_FILES_SYMBOL);
285 if (featureSection != nullptr) {
286 const PerfFileSectionSymbolsFiles *sectionSymbolsFiles =
287 static_cast<const PerfFileSectionSymbolsFiles *>(featureSection);
288 GetReport().virtualRuntime_.UpdateFromPerfData(sectionSymbolsFiles->symbolFileStructs_);
289 }
290 #if HAVE_PROTOBUF
291 // we have load the elf
292 // write it to proto first
293 if (protobufFormat_) {
294 protobufOutputFileWriter_->ProcessSymbolsFiles(
295 GetReport().virtualRuntime_.GetSymbolsFiles());
296 }
297 #endif
298 if (jsonFormat_) {
299 reportJsonFile_->ProcessSymbolsFiles(GetReport().virtualRuntime_.GetSymbolsFiles());
300 }
301 }
302
UpdateReportInfo()303 void SubCommandReport::UpdateReportInfo()
304 {
305 // get some meta info for protobuf
306 if (protobufFormat_) {
307 // workload
308 const PerfFileSection *featureSection =
309 recordFileReader_->GetFeatureSection(FEATURE::HIPERF_WORKLOAD_CMD);
310 std::string workloader = "";
311 if (featureSection != nullptr) {
312 HLOGV("found HIPERF_META_WORKLOAD_CMD");
313 const PerfFileSectionString *sectionString =
314 static_cast<const PerfFileSectionString *>(featureSection);
315 workloader = sectionString->toString();
316 } else {
317 HLOGW("NOT found HIPERF_META_WORKLOAD_CMD");
318 }
319 protobufOutputFileWriter_->ProcessReportInfo(configNames_, workloader);
320 }
321 }
322
LoadEventConfigData()323 void SubCommandReport::LoadEventConfigData()
324 {
325 auto features = recordFileReader_->GetFeatures();
326 cpuOffMode_ = find(features.begin(), features.end(), FEATURE::HIPERF_CPU_OFF) != features.end();
327 if (cpuOffMode_) {
328 HLOGD("this is cpuOffMode ");
329 }
330 const PerfFileSection *featureSection =
331 recordFileReader_->GetFeatureSection(FEATURE::EVENT_DESC);
332 if (featureSection != nullptr) {
333 HLOGV("have EVENT_DESC");
334 LoadEventDesc();
335 } else {
336 HLOGV("have Attr Section");
337 LoadAttrSection();
338 }
339 HLOG_ASSERT(GetReport().configs_.size() > 0);
340 HLOGV("record %d have %zu configs", index_, GetReport().configs_.size());
341 }
342
LoadEventDesc()343 void SubCommandReport::LoadEventDesc()
344 {
345 const PerfFileSection *featureSection =
346 recordFileReader_->GetFeatureSection(FEATURE::EVENT_DESC);
347 const PerfFileSectionEventDesc §ionEventdesc =
348 *static_cast<const PerfFileSectionEventDesc *>(featureSection);
349 HLOGV("Event descriptions: %zu", sectionEventdesc.eventDesces_.size());
350 for (size_t i = 0; i < sectionEventdesc.eventDesces_.size(); i++) {
351 const AttrWithId &fileAttr = sectionEventdesc.eventDesces_[i];
352
353 HLOGV("event name[%zu]: %s ids: %s", i, fileAttr.name.c_str(),
354 VectorToString(fileAttr.ids).c_str());
355 if (cpuOffMode_ and fileAttr.name == cpuOffEventName) {
356 // found cpuoff event id
357 std::set<uint64_t> cpuOffids(fileAttr.ids.begin(), fileAttr.ids.end());
358 cpuOffids_ = cpuOffids;
359 HLOGV("this is cpu off event");
360 } else {
361 // don't add cpuoff event
362 if (protobufFormat_) {
363 configNames_.emplace_back(fileAttr.name);
364 }
365 for (uint64_t id : fileAttr.ids) {
366 GetReport().configIdIndexMaps_[id] = GetReport().configs_.size(); // setup index
367 HLOGV("add config id map %" PRIu64 " to %zu", id, GetReport().configs_.size());
368 }
369 // when cpuOffMode_ , don't use count mode , use time mode.
370 auto &config =
371 GetReport().configs_.emplace_back(fileAttr.name, fileAttr.attr.type,
372 fileAttr.attr.config, cpuOffMode_ ? false : true);
373 config.ids_ = fileAttr.ids;
374 HLOG_ASSERT(config.ids_.size() > 0);
375 if (jsonFormat_) {
376 reportJsonFile_->reportConfigItems_.emplace(
377 fileAttr.ids,
378 ReportConfigItem(reportJsonFile_->reportConfigItems_.size(), fileAttr.name));
379 }
380 }
381 }
382 }
383
LoadAttrSection()384 void SubCommandReport::LoadAttrSection()
385 {
386 std::vector<AttrWithId> attrIds = recordFileReader_->GetAttrSection();
387 for (size_t i = 0; i < attrIds.size(); ++i) {
388 const AttrWithId &fileAttr = attrIds[i];
389 std::string name = PerfEvents::GetStaticConfigName(
390 static_cast<perf_type_id>(fileAttr.attr.type), fileAttr.attr.config);
391 configNames_.emplace_back(name);
392 for (uint64_t id : fileAttr.ids) {
393 GetReport().configIdIndexMaps_[id] = GetReport().configs_.size(); // setup index
394 HLOGV("add config id map %" PRIu64 " to %zu", id, GetReport().configs_.size());
395 }
396 auto &config = GetReport().configs_.emplace_back(fileAttr.name, fileAttr.attr.type,
397 fileAttr.attr.config);
398 config.ids_ = fileAttr.ids;
399 HLOG_ASSERT(config.ids_.size() > 0);
400 if (jsonFormat_) {
401 reportJsonFile_->reportConfigItems_.emplace(
402 fileAttr.ids, ReportConfigItem(reportJsonFile_->reportConfigItems_.size(), name));
403 }
404 HLOGV("event name[%zu]: %s ids: %s", i, name_.c_str(),
405 VectorToString(fileAttr.ids).c_str());
406 }
407 }
408
ProcessFeaturesData()409 void SubCommandReport::ProcessFeaturesData()
410 {
411 LoadEventConfigData();
412
413 // update device arch from feature
414 SetDeviceArch(GetArchTypeFromUname(recordFileReader_->GetFeatureString(FEATURE::ARCH)));
415
416 #if HAVE_PROTOBUF
417 UpdateReportInfo();
418 #endif
419 }
420
FlushCacheRecord()421 void SubCommandReport::FlushCacheRecord()
422 {
423 for (auto &pair : prevSampleCache_) {
424 std::unique_ptr<PerfRecordSample> sample = std::move(pair.second);
425 sample->data_.period = 1u;
426 if (cpuOffMode_ and cpuOffids_.size() > 0 and cpuOffids_.count(sample->data_.id) > 0) {
427 BroadcastSample(sample);
428 } else {
429 ProcessSample(sample);
430 }
431 }
432 prevSampleCache_.clear();
433 }
434
LoadPerfData()435 bool SubCommandReport::LoadPerfData()
436 {
437 HLOGV("enter");
438 // check if file exist
439 if (access(recordFile_[index_].c_str(), F_OK) != 0) {
440 // file not exists
441 printf("Can not access data file %s\n", recordFile_[index_].c_str());
442 return false;
443 }
444
445 // try load the file
446 recordFileReader_ = PerfFileReader::Instance(recordFile_[index_]);
447 if (recordFileReader_ == nullptr) {
448 HLOGE("FileReader::Instance(%s) return null", recordFile_[index_].c_str());
449 return false;
450 }
451
452 if (!recordFileReader_->ReadFeatureSection()) {
453 printf("record format error.\n");
454 return false;
455 }
456 if (jsonFormat_) {
457 reportJsonFile_ =
458 std::make_unique<ReportJsonFile>(recordFileReader_, GetReport().virtualRuntime_);
459 }
460
461 ProcessFeaturesData();
462 ProcessSymbolsData();
463
464 HLOGD("process record");
465 recordFileReader_->ReadDataSection(
466 std::bind(&SubCommandReport::RecordCallBack, this, std::placeholders::_1));
467 if (cpuOffMode_) {
468 FlushCacheRecord();
469 }
470 HLOGD("process record completed");
471
472 LoadPerfDataCompleted();
473 return true;
474 }
475
OutputStd()476 bool SubCommandReport::OutputStd()
477 {
478 if (fprintf(output_, "<<Hiperf Report%s>>\n", diffMode_ ? " Diff" : "") < 0) {
479 return false;
480 }
481
482 // feature string:
483 const auto &featureSections = recordFileReader_->GetFeatureSections();
484 HLOGV("featureSections: %zu ", featureSections.size());
485
486 for (auto &featureSection : featureSections) {
487 if (recordFileReader_->IsFeatrureStringSection(featureSection->featureId_)) {
488 const PerfFileSectionString *sectionString =
489 static_cast<const PerfFileSectionString *>(featureSection.get());
490
491 fprintf(output_, "%s: %s\n",
492 PerfFileSection::GetFeatureName(featureSection.get()->featureId_).c_str(),
493 sectionString->toString().c_str());
494 }
495 }
496
497 if (cpuOffMode_) {
498 fprintf(output_, "cpu off mode: enabled\n");
499 }
500
501 if (!diffMode_) {
502 GetReport(FIRST).AdjustReportItems();
503 GetReport(FIRST).OutputStd(output_);
504 } else {
505 GetReport(FIRST).AdjustReportItems();
506 GetReport(SECOND).AdjustReportItems();
507 GetReport(FIRST).OutputStdDiff(output_, GetReport(SECOND));
508 }
509
510 return true;
511 }
512
OutputReport()513 bool SubCommandReport::OutputReport()
514 {
515 HLOGV("enter");
516 if (output_ == nullptr) {
517 HLOGD("nothing need output");
518 return true; //
519 } else if (jsonFormat_) {
520 HLOGD("report as json");
521 return reportJsonFile_->OutputJson(output_);
522 } else {
523 return OutputStd();
524 }
525 }
526
PrepareOutput()527 bool SubCommandReport::PrepareOutput()
528 {
529 HLOGV("enter");
530 if (protobufFormat_) {
531 #if HAVE_PROTOBUF
532 printf("save to protobuf file: '%s'\n", reportFile_.c_str());
533 protobufOutputFileWriter_ = std::make_unique<ReportProtobufFileWriter>();
534 protobufOutputFileWriter_->Create(reportFile_);
535 #endif
536 return true;
537 }
538
539 if (!reportFile_.empty()) {
540 std::string resolvedPath = CanonicalizeSpecPath(reportFile_.c_str());
541 output_ = fopen(resolvedPath.c_str(), "w");
542 if (output_ == nullptr) {
543 printf("unable open file to '%s' because '%d'\n", reportFile_.c_str(), errno);
544 return false;
545 } else {
546 printf("report will save at '%s'\n", reportFile_.c_str());
547 }
548 } else {
549 output_ = stdout;
550 }
551
552 return true;
553 }
554
~SubCommandReport()555 SubCommandReport::~SubCommandReport()
556 {
557 HLOGV("enter");
558 #if HAVE_PROTOBUF
559 if (protobufOutputFileWriter_ != nullptr) {
560 protobufOutputFileWriter_->Close();
561 }
562 #endif
563 if (output_ != nullptr && output_ != stdout) {
564 fclose(output_);
565 }
566
567 SymbolsFile::onRecording_ = true; // back to default for UT
568 }
569
OnSubCommand(std::vector<std::string> & args)570 bool SubCommandReport::OnSubCommand(std::vector<std::string> &args)
571 {
572 HLOGV("enter");
573
574 if (!PrepareOutput()) {
575 return false;
576 }
577
578 // any way tell symbols this is not on recording
579 SymbolsFile::onRecording_ = false;
580
581 printf("loading data\n");
582 if (!LoadPerfData()) {
583 return false;
584 }
585
586 if (diffMode_) {
587 // we are in diff mode
588 index_ = SECOND;
589 // load again with second file
590 if (!LoadPerfData()) {
591 return false;
592 }
593 // back to first
594 index_ = FIRST;
595 }
596 printf("prepare report\n");
597 if (!OutputReport()) {
598 HLOGD("OutputReport failed");
599 return false;
600 }
601 #ifdef HIPERF_DEBUG_TIME
602 printf("SymbolicRecordTimes: %0.3f ms\n",
603 GetReport(FIRST).virtualRuntime_.symbolicRecordTimes_.count() / MS_DUARTION);
604 #endif
605
606 printf("report done\n");
607 return true;
608 }
609
RegisterSubCommandReport()610 bool SubCommandReport::RegisterSubCommandReport()
611 {
612 HLOGV("enter");
613 std::unique_ptr<SubCommand> cmd = std::make_unique<SubCommandReport>();
614 return SubCommand::RegisterSubCommand("report", std::move(cmd));
615 }
616 } // namespace HiPerf
617 } // namespace Developtools
618 } // namespace OHOS