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) or 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) or 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 or reportOption_.heatLimit_ > max) {
155         printf("head limit error. must in (0 <= limit < 100).\n");
156         return false;
157     }
158     if (reportOption_.callStackHeatLimit_ < min or 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_ or jsonFormat_ or showCallStack_) {
168             printf("diff don't support any export mode(like json , flame or proto)\n");
169         } else {
170             diffMode_ = true;
171         }
172     }
173 
174     // default report file name
175     if (reportFile_.empty()) {
176         if (protobufFormat_) {
177             reportFile_ = "perf.proto";
178         } else if (jsonFormat_) {
179             reportFile_ = "perf.json";
180         }
181     }
182 
183     // misc config
184     reportOption_.debug_ = debug_;
185     ReportJsonFile::debug_ = debug_;
186 
187     return VerifyDisplayOption();
188 }
189 
BroadcastSample(std::unique_ptr<PerfRecordSample> &sample)190 void SubCommandReport::BroadcastSample(std::unique_ptr<PerfRecordSample> &sample)
191 {
192     // this func use for cpuoff mode , it will Broadcast the sampe to every event config
193     for (auto &config : GetReport().configs_) {
194         HLOGM("resend as id %" PRIu64 "", config.ids_[0]);
195         sample->data_.id = config.ids_[0];
196         ProcessSample(sample);
197     }
198 }
199 
ProcessSample(std::unique_ptr<PerfRecordSample> &sample)200 void SubCommandReport::ProcessSample(std::unique_ptr<PerfRecordSample> &sample)
201 {
202     sample->DumpLog(__FUNCTION__);
203     if (jsonFormat_) {
204         reportJsonFile_->UpdateReportSample(sample->data_.id, sample->data_.pid, sample->data_.tid,
205                                             sample->data_.period);
206         reportJsonFile_->UpdateReportCallStack(sample->data_.id, sample->data_.pid,
207                                                sample->data_.tid, sample->data_.period,
208                                                sample->callFrames_);
209     } else if (protobufFormat_) {
210 #if defined(HAVE_PROTOBUF) && HAVE_PROTOBUF
211         // make some cook
212         // redesgin here
213         protobufOutputFileWriter_->ProcessSampleRecord(
214             *sample, static_cast<uint32_t>(GetReport().GetConfigIndex(sample->data_.id)),
215             GetReport().virtualRuntime_.GetSymbolsFiles());
216 #endif
217     } else {
218         if (branch_) {
219             GetReport().AddReportItemBranch(*sample);
220         } else {
221             GetReport().AddReportItem(*sample, showCallStack_);
222         }
223     }
224 }
225 
RecordCallBack(std::unique_ptr<PerfEventRecord> record)226 bool SubCommandReport::RecordCallBack(std::unique_ptr<PerfEventRecord> record)
227 {
228     // tell process tree what happend for rebuild symbols
229     GetReport().virtualRuntime_.UpdateFromRecord(*record);
230 
231     if (record->GetType() == PERF_RECORD_SAMPLE) {
232         std::unique_ptr<PerfRecordSample> sample(static_cast<PerfRecordSample *>(record.release()));
233         std::unique_ptr<PerfRecordSample> prevSample = nullptr;
234         if (cpuOffMode_) {
235             auto prevIt = prevSampleCache_.find(sample->data_.tid);
236             if (prevIt == prevSampleCache_.end()) {
237                 // this thread first sample
238                 prevSampleCache_[sample->data_.tid] = std::move(sample);
239                 // do nothing because we unable to calc the period
240                 return true;
241             } else {
242                 // we have prev sample
243                 prevSample = std::move(prevIt->second);
244                 HLOGV("calc time %llu - %llu", sample->data_.time, prevSample->data_.time);
245                 if (sample->data_.time > prevSample->data_.time) {
246                     prevSample->data_.period = sample->data_.time - prevSample->data_.time;
247                 } else {
248                     prevSample->data_.period = 1u;
249                 }
250 
251                 // current move the prev
252                 prevIt->second = std::move(sample);
253                 // go on with prevSample
254                 sample = std::move(prevSample);
255 
256                 HLOGV("current sample period %llu ", sample->data_.period);
257             }
258         }
259         if (cpuOffMode_ and cpuOffids_.size() > 0 and cpuOffids_.count(sample->data_.id) > 0) {
260             BroadcastSample(sample);
261         } else {
262             ProcessSample(sample);
263         }
264     } else {
265 #if defined(HAVE_PROTOBUF) && HAVE_PROTOBUF
266         if (protobufFormat_) {
267             protobufOutputFileWriter_->ProcessRecord(*record);
268         }
269 #endif
270     }
271     return true;
272 }
273 
LoadPerfDataCompleted()274 void SubCommandReport::LoadPerfDataCompleted()
275 {
276     if (jsonFormat_) {
277         reportJsonFile_->UpdateCallNodeEventCount();
278     }
279     HLOGV("load perf data done");
280 }
281 
ProcessSymbolsData()282 void SubCommandReport::ProcessSymbolsData()
283 {
284     GetReport().virtualRuntime_.SetSymbolsPaths(symbolsPaths_);
285     // we need unwind it (for function name match) even not give us path
286     GetReport().virtualRuntime_.SetDisableUnwind(false);
287 
288     // found symbols in file
289     const auto featureSection = recordFileReader_->GetFeatureSection(FEATURE::HIPERF_FILES_SYMBOL);
290     if (featureSection != nullptr) {
291         const PerfFileSectionSymbolsFiles *sectionSymbolsFiles =
292             static_cast<const PerfFileSectionSymbolsFiles *>(featureSection);
293         GetReport().virtualRuntime_.UpdateFromPerfData(sectionSymbolsFiles->symbolFileStructs_);
294     }
295 #if defined(HAVE_PROTOBUF) && HAVE_PROTOBUF
296     // we have load the elf
297     // write it to proto first
298     if (protobufFormat_) {
299         protobufOutputFileWriter_->ProcessSymbolsFiles(
300             GetReport().virtualRuntime_.GetSymbolsFiles());
301     }
302 #endif
303     if (jsonFormat_) {
304         reportJsonFile_->ProcessSymbolsFiles(GetReport().virtualRuntime_.GetSymbolsFiles());
305     }
306 }
307 
ProcessUniStackTableData()308 void SubCommandReport::ProcessUniStackTableData()
309 {
310     auto featureSection = recordFileReader_->GetFeatureSection(FEATURE::HIPERF_FILES_UNISTACK_TABLE);
311     if (featureSection != nullptr) {
312         PerfFileSectionUniStackTable *sectioniStackTable =
313             static_cast<PerfFileSectionUniStackTable *>(const_cast<PerfFileSection *>(featureSection));
314         GetReport().virtualRuntime_.ImportUniqueStackNodes(sectioniStackTable->uniStackTableInfos_);
315         GetReport().virtualRuntime_.SetDedupStack();
316     }
317 }
318 
UpdateReportInfo()319 void SubCommandReport::UpdateReportInfo()
320 {
321     // get some meta info for protobuf
322     if (protobufFormat_) {
323         // workload
324         const PerfFileSection *featureSection =
325             recordFileReader_->GetFeatureSection(FEATURE::HIPERF_WORKLOAD_CMD);
326         std::string workloader = "";
327         if (featureSection != nullptr) {
328             HLOGV("found HIPERF_META_WORKLOAD_CMD");
329             const PerfFileSectionString *sectionString =
330                 static_cast<const PerfFileSectionString *>(featureSection);
331             workloader = sectionString->ToString();
332         } else {
333             HLOGW("NOT found HIPERF_META_WORKLOAD_CMD");
334         }
335         protobufOutputFileWriter_->ProcessReportInfo(configNames_, workloader);
336     }
337 }
338 
LoadEventConfigData()339 void SubCommandReport::LoadEventConfigData()
340 {
341     auto features = recordFileReader_->GetFeatures();
342     cpuOffMode_ = find(features.begin(), features.end(), FEATURE::HIPERF_CPU_OFF) != features.end();
343     if (cpuOffMode_) {
344         HLOGD("this is cpuOffMode ");
345     }
346     const PerfFileSection *featureSection =
347         recordFileReader_->GetFeatureSection(FEATURE::EVENT_DESC);
348     if (featureSection != nullptr) {
349         HLOGV("have EVENT_DESC");
350         LoadEventDesc();
351     } else {
352         HLOGV("have Attr Section");
353         LoadAttrSection();
354     }
355     HLOG_ASSERT(GetReport().configs_.size() > 0);
356     HLOGV("record %d have %zu configs", index_, GetReport().configs_.size());
357 }
358 
LoadEventDesc()359 void SubCommandReport::LoadEventDesc()
360 {
361     const PerfFileSection *featureSection =
362         recordFileReader_->GetFeatureSection(FEATURE::EVENT_DESC);
363     CHECK_TRUE(featureSection == nullptr, NO_RETVAL, 0, "");
364     const PerfFileSectionEventDesc &sectionEventdesc =
365         *static_cast<const PerfFileSectionEventDesc *>(featureSection);
366     HLOGV("Event descriptions: %zu", sectionEventdesc.eventDesces_.size());
367     for (size_t i = 0; i < sectionEventdesc.eventDesces_.size(); i++) {
368         const AttrWithId &fileAttr = sectionEventdesc.eventDesces_[i];
369 
370         HLOGV("event name[%zu]: %s ids: %s", i, fileAttr.name.c_str(),
371               VectorToString(fileAttr.ids).c_str());
372         if (cpuOffMode_ and fileAttr.name == cpuOffEventName) {
373             // found cpuoff event id
374             std::set<uint64_t> cpuOffids(fileAttr.ids.begin(), fileAttr.ids.end());
375             cpuOffids_ = cpuOffids;
376             HLOGV("this is cpu off event");
377         } else {
378             // don't add cpuoff event
379             if (protobufFormat_) {
380                 configNames_.emplace_back(fileAttr.name);
381             }
382             for (uint64_t id : fileAttr.ids) {
383                 GetReport().configIdIndexMaps_[id] = GetReport().configs_.size(); // setup index
384                 HLOGV("add config id map %" PRIu64 " to %zu", id, GetReport().configs_.size());
385             }
386             // when cpuOffMode_ , don't use count mode , use time mode.
387             auto &config =
388                 GetReport().configs_.emplace_back(fileAttr.name, fileAttr.attr.type,
389                                                   fileAttr.attr.config, cpuOffMode_ ? false : true);
390             config.ids_ = fileAttr.ids;
391             HLOG_ASSERT(config.ids_.size() > 0);
392             if (jsonFormat_) {
393                 reportJsonFile_->reportConfigItems_.emplace(
394                     fileAttr.ids,
395                     ReportConfigItem(reportJsonFile_->reportConfigItems_.size(), fileAttr.name));
396             }
397         }
398     }
399 }
400 
LoadAttrSection()401 void SubCommandReport::LoadAttrSection()
402 {
403     std::vector<AttrWithId> attrIds = recordFileReader_->GetAttrSection();
404     for (size_t i = 0; i < attrIds.size(); ++i) {
405         const AttrWithId &fileAttr = attrIds[i];
406         std::string name = PerfEvents::GetStaticConfigName(
407             static_cast<perf_type_id>(fileAttr.attr.type), fileAttr.attr.config);
408         configNames_.emplace_back(name);
409         for (uint64_t id : fileAttr.ids) {
410             GetReport().configIdIndexMaps_[id] = GetReport().configs_.size(); // setup index
411             HLOGV("add config id map %" PRIu64 " to %zu", id, GetReport().configs_.size());
412         }
413         auto &config = GetReport().configs_.emplace_back(fileAttr.name, fileAttr.attr.type,
414                                                          fileAttr.attr.config);
415         config.ids_ = fileAttr.ids;
416         HLOG_ASSERT(config.ids_.size() > 0);
417         if (jsonFormat_) {
418             reportJsonFile_->reportConfigItems_.emplace(
419                 fileAttr.ids, ReportConfigItem(reportJsonFile_->reportConfigItems_.size(), name));
420         }
421         HLOGV("event name[%zu]: %s ids: %s", i, name_.c_str(),
422               VectorToString(fileAttr.ids).c_str());
423     }
424 }
425 
ProcessFeaturesData()426 void SubCommandReport::ProcessFeaturesData()
427 {
428     LoadEventConfigData();
429 
430     // update device arch from feature
431     SetDeviceArch(GetArchTypeFromUname(recordFileReader_->GetFeatureString(FEATURE::ARCH)));
432 
433 #if defined(HAVE_PROTOBUF) && HAVE_PROTOBUF
434     UpdateReportInfo();
435 #endif
436 }
437 
FlushCacheRecord()438 void SubCommandReport::FlushCacheRecord()
439 {
440     for (auto &pair : prevSampleCache_) {
441         std::unique_ptr<PerfRecordSample> sample = std::move(pair.second);
442         sample->data_.period = 1u;
443         if (cpuOffMode_ and cpuOffids_.size() > 0 and cpuOffids_.count(sample->data_.id) > 0) {
444             BroadcastSample(sample);
445         } else {
446             ProcessSample(sample);
447         }
448     }
449     prevSampleCache_.clear();
450 }
451 
LoadPerfData()452 bool SubCommandReport::LoadPerfData()
453 {
454     // check if file exist
455     if (access(recordFile_[index_].c_str(), F_OK) != 0) {
456         // file not exists
457         printf("Can not access data file %s\n", recordFile_[index_].c_str());
458         return false;
459     }
460 
461     // try load the file
462     recordFileReader_ = PerfFileReader::Instance(recordFile_[index_]);
463     if (recordFileReader_ == nullptr) {
464         HLOGE("FileReader::Instance(%s) return null", recordFile_[index_].c_str());
465         return false;
466     }
467 
468     CHECK_TRUE(!recordFileReader_->ReadFeatureSection(), false, LOG_TYPE_PRINTF, "record format error.\n");
469     if (jsonFormat_) {
470         reportJsonFile_ =
471             std::make_unique<ReportJsonFile>(recordFileReader_, GetReport().virtualRuntime_);
472     }
473 
474     ProcessFeaturesData();
475     ProcessSymbolsData();
476     ProcessUniStackTableData();
477     HLOGD("process record");
478     // before load data section
479     SetHM();
480     recordFileReader_->ReadDataSection(
481         [this] (std::unique_ptr<PerfEventRecord> record) -> bool {
482             return this->RecordCallBack(std::move(record));
483         });
484     if (cpuOffMode_) {
485         FlushCacheRecord();
486     }
487     HLOGD("process record completed");
488 
489     LoadPerfDataCompleted();
490     return true;
491 }
492 
OutputStd()493 bool SubCommandReport::OutputStd()
494 {
495     if (fprintf(output_, "<<Hiperf Report%s>>\n", diffMode_ ? " Diff" : "") < 0) {
496         return false;
497     }
498 
499     // feature string:
500     const auto &featureSections = recordFileReader_->GetFeatureSections();
501     HLOGV("featureSections: %zu ", featureSections.size());
502 
503     for (auto &featureSection : featureSections) {
504         if (recordFileReader_->IsFeatrureStringSection(featureSection->featureId_)) {
505             const PerfFileSectionString *sectionString =
506                 static_cast<const PerfFileSectionString *>(featureSection.get());
507 
508             fprintf(output_, "%s: %s\n",
509                     PerfFileSection::GetFeatureName(featureSection.get()->featureId_).c_str(),
510                     sectionString->ToString().c_str());
511         }
512     }
513 
514     if (cpuOffMode_) {
515         fprintf(output_, "cpu off mode: enabled\n");
516     }
517 
518     if (!diffMode_) {
519         GetReport(FIRST).AdjustReportItems();
520         GetReport(FIRST).OutputStd(output_);
521     } else {
522         GetReport(FIRST).AdjustReportItems();
523         GetReport(SECOND).AdjustReportItems();
524         GetReport(FIRST).OutputStdDiff(output_, GetReport(SECOND));
525     }
526 
527     return true;
528 }
529 
OutputReport()530 bool SubCommandReport::OutputReport()
531 {
532     if (output_ == nullptr) {
533         HLOGD("nothing need output");
534         return true; //
535     } else if (jsonFormat_) {
536         HLOGD("report as json");
537         return reportJsonFile_->OutputJson(output_);
538     } else {
539         return OutputStd();
540     }
541 }
542 
PrepareOutput()543 bool SubCommandReport::PrepareOutput()
544 {
545     if (protobufFormat_) {
546 #if defined(HAVE_PROTOBUF) && HAVE_PROTOBUF
547         // check if file exist
548         if (access(recordFile_[index_].c_str(), F_OK) != 0) {
549             printf("Can not access data file %s\n", recordFile_[index_].c_str());
550             return false;
551         }
552         printf("save to protobuf file: '%s'\n", reportFile_.c_str());
553         protobufOutputFileWriter_ = std::make_unique<ReportProtobufFileWriter>();
554         protobufOutputFileWriter_->Create(reportFile_);
555 #endif
556         return true;
557     }
558 
559     if (!reportFile_.empty()) {
560         std::string resolvedPath = CanonicalizeSpecPath(reportFile_.c_str());
561         output_ = fopen(resolvedPath.c_str(), "w");
562         if (output_ == nullptr) {
563             printf("unable open file to '%s' because '%d'\n", reportFile_.c_str(), errno);
564             return false;
565         } else {
566             printf("report will save at '%s'\n", reportFile_.c_str());
567         }
568     } else {
569         output_ = stdout;
570     }
571 
572     return true;
573 }
574 
~SubCommandReport()575 SubCommandReport::~SubCommandReport()
576 {
577 #if defined(HAVE_PROTOBUF) && HAVE_PROTOBUF
578     if (protobufOutputFileWriter_ != nullptr) {
579         protobufOutputFileWriter_->Close();
580     }
581 #endif
582     if (output_ != nullptr && output_ != stdout) {
583         fclose(output_);
584     }
585 
586     SymbolsFile::onRecording_ = true; // back to default for UT
587 }
588 
OnSubCommand(std::vector<std::string> &args)589 bool SubCommandReport::OnSubCommand(std::vector<std::string> &args)
590 {
591     if (!PrepareOutput()) {
592         return false;
593     }
594 
595     // any way tell symbols this is not on recording
596     SymbolsFile::onRecording_ = false;
597 
598     printf("loading data\n");
599     if (!LoadPerfData()) {
600         return false;
601     }
602 
603     if (diffMode_) {
604         // we are in diff mode
605         index_ = SECOND;
606         // load again with second file
607         CHECK_TRUE(!LoadPerfData(), false, 0, "");
608         // back to first
609         index_ = FIRST;
610     }
611     printf("prepare report\n");
612     CHECK_TRUE(!OutputReport(), false, 1, "OutputReport failed");
613 #ifdef HIPERF_DEBUG_TIME
614     printf("SymbolicRecordTimes: %0.3f ms\n",
615            GetReport(FIRST).virtualRuntime_.symbolicRecordTimes_.count() / MS_DURATION);
616 #endif
617 
618     printf("report done\n");
619     return true;
620 }
621 
RegisterSubCommandReport()622 bool SubCommandReport::RegisterSubCommandReport()
623 {
624     std::unique_ptr<SubCommand> cmd = std::make_unique<SubCommandReport>();
625     return SubCommand::RegisterSubCommand("report", std::move(cmd));
626 }
627 
SetHM()628 void SubCommandReport::SetHM()
629 {
630     std::string os = recordFileReader_->GetFeatureString(FEATURE::OSRELEASE);
631     isHM_ = os.find(HMKERNEL) != std::string::npos;
632     GetReport().virtualRuntime_.SetHM(isHM_);
633     HLOGD("Set isHM_: %d", isHM_);
634     if (isHM_) {
635         pid_t devhost = -1;
636         std::string str = recordFileReader_->GetFeatureString(FEATURE::HIPERF_HM_DEVHOST);
637         if (str != EMPTY_STRING) {
638             devhost = std::stoll(str);
639         }
640         GetReport().virtualRuntime_.SetDevhostPid(devhost);
641     }
642 }
643 } // namespace HiPerf
644 } // namespace Developtools
645 } // namespace OHOS
646