• 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 #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 &sectionEventdesc =
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