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