• 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 "Dump"
16 
17 #include "subcommand_dump.h"
18 
19 #include <cerrno>
20 #include <cinttypes>
21 #include <cstring>
22 #include <iostream>
23 #include <memory>
24 
25 #include "debug_logger.h"
26 #include "option.h"
27 #include "perf_event_record.h"
28 #include "perf_events.h"
29 #include "symbols_file.h"
30 #include "utilities.h"
31 #include "virtual_runtime.h"
32 
33 namespace OHOS {
34 namespace Developtools {
35 namespace HiPerf {
36 #define LEVEL1 (indent + 1)
37 #define LEVEL2 (indent + 2)
38 #define LEVEL3 (indent + 3)
39 
CheckInputFile()40 bool SubCommandDump::CheckInputFile()
41 {
42     if (!dumpFileName_.empty()) {
43         if (elfFileName_.empty() && protobufDumpFileName_.empty()) {
44             return true;
45         }
46     } else if (!elfFileName_.empty()) {
47         if (protobufDumpFileName_.empty()) {
48             return true;
49         }
50     } else if (!protobufDumpFileName_.empty()) {
51         return true;
52     } else { // all is empty
53         dumpFileName_ = DEFAULT_DUMP_FILENAME;
54         return true;
55     }
56 
57     printf("options conflict, please check usage\n");
58     return false;
59 }
60 
ParseOption(std::vector<std::string> & args)61 bool SubCommandDump::ParseOption(std::vector<std::string> &args)
62 {
63     if (!Option::GetOptionValue(args, "--head", dumpHeader_)) {
64         HLOGD("get option --head failed");
65         return false;
66     }
67     if (!Option::GetOptionValue(args, "-f", dumpFeatures_)) {
68         HLOGD("get option -f failed");
69         return false;
70     }
71     if (!Option::GetOptionValue(args, "-d", dumpData_)) {
72         HLOGD("get option -d failed");
73         return false;
74     }
75     if (!Option::GetOptionValue(args, "--sympath", dumpSymbolsPaths_)) {
76         HLOGD("get option --sympath failed");
77         return false;
78     }
79     if (!Option::GetOptionValue(args, "--elf", elfFileName_)) {
80         HLOGD("get option --elf failed");
81         return false;
82     }
83     if (!Option::GetOptionValue(args, "-i", dumpFileName_)) {
84         return false;
85     }
86 #if HAVE_PROTOBUF
87     if (!Option::GetOptionValue(args, "--proto", protobufDumpFileName_)) {
88         HLOGD("get option --proto failed");
89         return false;
90     }
91 #endif
92     if (!Option::GetOptionValue(args, "-o", outputFilename_)) {
93         return false;
94     }
95     if (!Option::GetOptionValue(args, "--export", exportSampleIndex_)) {
96         HLOGD("get option --export failed");
97         return false;
98     }
99 
100     if (dumpHeader_ || dumpFeatures_ || dumpData_) {
101         dumpAll_ = false;
102     }
103     if (!args.empty()) {
104         printf("'%s' option usage error, please check usage.\n", VectorToString(args).c_str());
105         return false;
106     }
107 
108     return CheckInputFile();
109 }
110 
PrepareDumpOutput()111 bool SubCommandDump::PrepareDumpOutput()
112 {
113     if (outputFilename_.empty()) {
114         return true;
115     }
116     std::string resolvedPath = CanonicalizeSpecPath(outputFilename_.c_str());
117     outputDump_ = fopen(resolvedPath.c_str(), "w");
118     if (outputDump_ == nullptr) {
119         printf("unable open file to '%s' because '%d'\n", outputFilename_.c_str(), errno);
120         return false;
121     }
122     printf("dump result will save at '%s'\n", outputFilename_.c_str());
123     return true;
124 }
125 
~SubCommandDump()126 SubCommandDump::~SubCommandDump()
127 {
128     if (outputDump_ != nullptr && outputDump_ != stdout) {
129         fclose(outputDump_);
130     }
131     SymbolsFile::onRecording_ = true; // back to default for UT
132 }
133 
OnSubCommand(std::vector<std::string> & args)134 bool SubCommandDump::OnSubCommand(std::vector<std::string> &args)
135 {
136     if (!PrepareDumpOutput()) {
137         return false;
138     }
139 
140     if (!elfFileName_.empty()) {
141         return DumpElfFile();
142     }
143 
144 #if HAVE_PROTOBUF
145     if (!protobufDumpFileName_.empty()) {
146         return DumpProtoFile();
147     }
148 #endif
149 
150     if (access(dumpFileName_.c_str(), F_OK) != 0) {
151         printf("Can not access data file %s\n", dumpFileName_.c_str());
152         return false;
153     }
154     // only one file should created
155     HLOG_ASSERT_MESSAGE(reader_ == nullptr, " perf file reader for %s\n", dumpFileName_.c_str());
156     reader_ = PerfFileReader::Instance(dumpFileName_);
157     if (reader_ == nullptr) {
158         HLOGE("HiperfFileReader::Instance(%s) return null", dumpFileName_.c_str());
159         return false;
160     }
161 
162     // any way tell symbols this is not on device
163     SymbolsFile::onRecording_ = false;
164     // we need unwind it (for function name match) even not give us path
165     vr_.SetDisableUnwind(false);
166 
167     if (!dumpSymbolsPaths_.empty()) {
168         // user give us path , we enable unwind
169         if (!vr_.SetSymbolsPaths(dumpSymbolsPaths_)) {
170             printf("Failed to set symbol path(%s)\n", VectorToString(dumpSymbolsPaths_).c_str());
171             return false;
172         }
173     }
174 
175     if (dumpHeader_ || dumpAll_) {
176         DumpPrintFileHeader(indent_);
177         DumpAttrPortion(indent_);
178     }
179 
180     if (dumpAll_ || dumpData_) {
181         DumpDataPortion(indent_);
182     }
183 
184     if (dumpFeatures_ || dumpAll_) {
185         DumpFeaturePortion(indent_);
186     }
187 
188     return true;
189 }
190 
DumpElfFile()191 bool SubCommandDump::DumpElfFile()
192 {
193     printf("dump elf: '%s'\n", elfFileName_.c_str());
194     auto elf = SymbolsFile::CreateSymbolsFile(elfFileName_);
195     if (!elf->LoadSymbols("")) {
196         printf("load elf failed.\n");
197         return false;
198     } else {
199         printf("load elf succeed.\n");
200     }
201     return true;
202 }
203 #if HAVE_PROTOBUF
DumpProtoFile()204 bool SubCommandDump::DumpProtoFile()
205 {
206     printf("dump protobuf file: '%s'\n", protobufDumpFileName_.c_str());
207     protobufInputFileReader_ = std::make_unique<ReportProtobufFileReader>();
208     if (!protobufInputFileReader_->Dump(protobufDumpFileName_)) {
209         printf("load proto failed.\n");
210         return false;
211     }
212     return true;
213 }
214 #endif
215 
PrintHeaderInfo(const int & indent)216 void SubCommandDump::PrintHeaderInfo(const int &indent)
217 {
218     const perf_file_header &header = reader_->GetHeader();
219     // magic
220     PrintIndent(indent, "magic: ");
221     for (size_t i = 0; i < sizeof(header.magic); ++i) {
222         PrintIndent(indent, "%c", header.magic[i]);
223     }
224     PrintIndent(indent, "\n");
225     PrintIndent(indent, "header_size: %" PRId64 "\n", header.size);
226     if (header.size != sizeof(header)) {
227         HLOGW("record file header size doesn't match");
228     }
229     PrintIndent(indent, "attr_size: %" PRId64 "\n", header.attrSize);
230     if (header.attrSize != sizeof(perf_file_attr)) {
231         HLOGW("attr size doesn't match");
232     }
233     // attr
234     PrintIndent(indent, "attrs[file section]: offset %" PRId64 ", size %" PRId64 "\n",
235                 header.attrs.offset, header.attrs.size);
236     // data
237     PrintIndent(indent, "data[file section]: offset %" PRId64 ", size %" PRId64 "\n",
238                 header.data.offset, header.data.size);
239     PrintIndent(indent, "event_types[file section]: offset %" PRId64 ", size %" PRId64 "\n",
240                 header.eventTypes.offset, header.eventTypes.size);
241     // feature
242     PrintIndent(indent,
243                 "adds_features[]: 0x%" PRIX64 " 0x%" PRIX64 " 0x%" PRIX64 " 0x%" PRIX64 "\n",
244                 *(reinterpret_cast<const uint64_t *>(&header.features[0])),
245                 *(reinterpret_cast<const uint64_t *>(&header.features[8])),
246                 *(reinterpret_cast<const uint64_t *>(&header.features[16])),
247                 *(reinterpret_cast<const uint64_t *>(&header.features[24])));
248 }
249 
DumpPrintFileHeader(int indent)250 void SubCommandDump::DumpPrintFileHeader(int indent)
251 {
252     // print header
253     PrintHeaderInfo(indent);
254 
255     // print feature
256     auto features = reader_->GetFeatures();
257     for (auto feature : features) {
258         PrintIndent(indent, "feature: %s\n", PerfFileSection::GetFeatureName(feature).c_str());
259     }
260 
261     // read here , because we need found symbols
262     reader_->ReadFeatureSection();
263 
264     SetDeviceArch(GetArchTypeFromUname(reader_->GetFeatureString(FEATURE::ARCH)));
265 
266     // found symbols in file
267     for (auto &featureSection : reader_->GetFeatureSections()) {
268         if (featureSection.get()->featureId_ == FEATURE::HIPERF_FILES_SYMBOL) {
269             const PerfFileSectionSymbolsFiles *sectionSymbolsFiles =
270                 static_cast<const PerfFileSectionSymbolsFiles *>(featureSection.get());
271             vr_.UpdateFromPerfData(sectionSymbolsFiles->symbolFileStructs_);
272         }
273     }
274 }
275 
276 static std::map<int, std::string> g_sampleTypeNames = {
277     {PERF_SAMPLE_IP, "ip"},
278     {PERF_SAMPLE_TID, "tid"},
279     {PERF_SAMPLE_TIME, "time"},
280     {PERF_SAMPLE_ADDR, "addr"},
281     {PERF_SAMPLE_READ, "read"},
282     {PERF_SAMPLE_CALLCHAIN, "callchain"},
283     {PERF_SAMPLE_ID, "id"},
284     {PERF_SAMPLE_CPU, "cpu"},
285     {PERF_SAMPLE_PERIOD, "period"},
286     {PERF_SAMPLE_STREAM_ID, "stream_id"},
287     {PERF_SAMPLE_RAW, "raw"},
288     {PERF_SAMPLE_BRANCH_STACK, "stack"},
289     {PERF_SAMPLE_REGS_USER, "regs_user"},
290     {PERF_SAMPLE_STACK_USER, "stack_user"},
291     {PERF_SAMPLE_WEIGHT, "weight"},
292     {PERF_SAMPLE_DATA_SRC, "data_src"},
293     {PERF_SAMPLE_IDENTIFIER, "identifier"},
294     {PERF_SAMPLE_TRANSACTION, "transaction"},
295     {PERF_SAMPLE_REGS_INTR, "reg_intr"},
296 };
297 
DumpSampleType(uint64_t sampleType,int indent)298 void SubCommandDump::DumpSampleType(uint64_t sampleType, int indent)
299 {
300     std::string names;
301     for (auto &pair : g_sampleTypeNames) {
302         if (sampleType & pair.first) {
303             if (!names.empty()) {
304                 names.append(",");
305             }
306             names.append(pair.second);
307         }
308     }
309     PrintIndent(LEVEL1, "sample_type names: %s\n", names.c_str());
310 }
311 
DumpPrintEventAttr(const perf_event_attr & attr,int indent)312 void SubCommandDump::DumpPrintEventAttr(const perf_event_attr &attr, int indent)
313 {
314     PrintIndent(indent, "event_attr: \n");
315 
316     PrintIndent(LEVEL1, "type %u, size %u, config %llu\n", attr.type, attr.size, attr.config);
317 
318     if (attr.freq != 0) {
319         PrintIndent(LEVEL1, "sample_freq %llu\n", attr.sample_freq);
320     } else {
321         PrintIndent(LEVEL1, "sample_period %llu\n", attr.sample_period);
322     }
323 
324     PrintIndent(LEVEL1, "sample_type (0x%llx) \n", attr.sample_type);
325     DumpSampleType(attr.sample_type, indent);
326 
327     PrintIndent(LEVEL1, "read_format (0x%llx) \n", attr.read_format);
328 
329     PrintIndent(LEVEL1, "disabled %u, inherit %u, pinned %u, exclusive %u\n", attr.disabled,
330                 attr.inherit, attr.pinned, attr.exclusive);
331 
332     PrintIndent(LEVEL1, "exclude_user %u, exclude_kernel %u, exclude_hv %u, exclude_idle %u\n",
333                 attr.exclude_user, attr.exclude_kernel, attr.exclude_hv, attr.exclude_idle);
334 
335     PrintIndent(LEVEL1, "mmap %u, mmap2 %u, comm %u, comm_exec %u, freq %u\n", attr.mmap,
336                 attr.mmap2, attr.comm, attr.comm_exec, attr.freq);
337 
338     PrintIndent(LEVEL1, "inherit_stat %u, enable_on_exec %u, task %u, use_clockid %u\n",
339                 attr.inherit_stat, attr.enable_on_exec, attr.task, attr.use_clockid);
340 
341     PrintIndent(LEVEL1, "watermark %u, precise_ip %u, mmap_data %u, clockid %d\n", attr.watermark,
342                 attr.precise_ip, attr.mmap_data, attr.clockid);
343 
344     PrintIndent(LEVEL1, "sample_id_all %u, exclude_host %u, exclude_guest %u\n", attr.sample_id_all,
345                 attr.exclude_host, attr.exclude_guest);
346     PrintIndent(LEVEL1, "branch_sample_type 0x%llx\n", attr.branch_sample_type);
347     PrintIndent(LEVEL1, "exclude_callchain_kernel %u, exclude_callchain_user %u\n",
348                 attr.exclude_callchain_kernel, attr.exclude_callchain_user);
349     PrintIndent(LEVEL1, "sample_regs_user 0x%llx\n", attr.sample_regs_user);
350     PrintIndent(LEVEL1, "sample_stack_user 0x%x\n", attr.sample_stack_user);
351 }
352 
DumpAttrPortion(int indent)353 void SubCommandDump::DumpAttrPortion(int indent)
354 {
355     attrIds_ = reader_->GetAttrSection();
356     for (size_t i = 0; i < attrIds_.size(); ++i) {
357         const AttrWithId &attr = attrIds_[i];
358         PrintIndent(indent, "attr %zu:\n", i + 1);
359         DumpPrintEventAttr(attr.attr, indent_ + 1);
360         if (!attr.ids.empty()) {
361             PrintIndent(indent, "  ids:");
362             for (const auto &id : attr.ids) {
363                 PrintIndent(indent, " %" PRId64, id);
364             }
365             PrintIndent(indent, "\n");
366         }
367     }
368 }
369 
ExprotUserStack(const PerfRecordSample & recordSample)370 void SubCommandDump::ExprotUserStack(const PerfRecordSample &recordSample)
371 {
372     if (recordSample.data_.reg_nr > 0 and recordSample.data_.dyn_size > 0) {
373         // <pid>_<tid>_user_regs_<time>
374         std::string userRegs =
375             StringPrintf("hiperf_%d_%d_user_regs_%zu.dump", recordSample.data_.pid,
376                          recordSample.data_.tid, exportSampleIndex_);
377         std::string resolvedPath = CanonicalizeSpecPath(userRegs.c_str());
378         std::unique_ptr<FILE, decltype(&fclose)> fpUserRegs(fopen(resolvedPath.c_str(), "wb"), fclose);
379         fwrite(recordSample.data_.user_regs, sizeof(u64), recordSample.data_.reg_nr,
380                fpUserRegs.get());
381 
382         std::string userData =
383             StringPrintf("hiperf_%d_%d_user_data_%zu.dump", recordSample.data_.pid,
384                          recordSample.data_.tid, exportSampleIndex_);
385         std::string resolvePath = CanonicalizeSpecPath(userData.c_str());
386         std::unique_ptr<FILE, decltype(&fclose)> fpUserData(fopen(resolvePath.c_str(), "wb"), fclose);
387         fwrite(recordSample.data_.stack_data, sizeof(u8), recordSample.data_.dyn_size,
388                fpUserData.get());
389     }
390 }
391 
ExprotUserData(std::unique_ptr<PerfEventRecord> & record)392 void SubCommandDump::ExprotUserData(std::unique_ptr<PerfEventRecord> &record)
393 {
394     if (record->GetType() == PERF_RECORD_SAMPLE) {
395         if (currectSampleIndex_++ != exportSampleIndex_) {
396             return;
397         }
398         PerfRecordSample *recordSample = static_cast<PerfRecordSample *>(record.get());
399         ExprotUserStack(*recordSample);
400 
401         std::string userData =
402             StringPrintf("hiperf_%d_%d_sample_record_%zu_%" PRIu64 ".dump", recordSample->data_.pid,
403                          recordSample->data_.tid, exportSampleIndex_, recordSample->data_.time);
404         std::string resolvedPath = CanonicalizeSpecPath(userData.c_str());
405         std::unique_ptr<FILE, decltype(&fclose)> fpUserData(fopen(resolvedPath.c_str(), "wb"), fclose);
406         std::vector<u8> buf(RECORD_SIZE_LIMIT);
407         if (!recordSample->GetBinary(buf)) {
408             HLOGE("export user sample data failed");
409             return;
410         }
411         fwrite(buf.data(), sizeof(u8), recordSample->GetSize(), fpUserData.get());
412 
413         HLOGD("export user data index %d time %llu", exportSampleIndex_, recordSample->data_.time);
414     }
415 }
DumpCallChain(int indent,std::unique_ptr<PerfRecordSample> & sample)416 void SubCommandDump::DumpCallChain(int indent, std::unique_ptr<PerfRecordSample> &sample)
417 {
418     PrintIndent(indent, "\n callchain: %zu\n", sample->callFrames_.size());
419     if (sample->callFrames_.size() > 0) {
420         indent += LEVEL1;
421         for (auto frameIt = sample->callFrames_.begin(); frameIt != sample->callFrames_.end();
422              frameIt++) {
423             PrintIndent(indent, "%02zd:%s\n", std::distance(frameIt, sample->callFrames_.end()),
424                         frameIt->ToSymbolString().c_str());
425         }
426     }
427 }
428 
DumpDataPortion(int indent)429 void SubCommandDump::DumpDataPortion(int indent)
430 {
431     int recordCount = 0;
432     auto record_callback = [&](std::unique_ptr<PerfEventRecord> record) {
433         if (record == nullptr) {
434             // return false in callback can stop the read process
435             return false;
436         }
437 
438         // for UT
439         if (exportSampleIndex_ > 0) {
440             ExprotUserData(record);
441         }
442 
443         // tell process tree what happend for rebuild symbols
444         vr_.UpdateFromRecord(*record);
445 
446         recordCount++;
447         record->Dump(indent, outputFilename_);
448 
449         if (record->GetType() == PERF_RECORD_SAMPLE) {
450             std::unique_ptr<PerfRecordSample> sample(
451                 static_cast<PerfRecordSample *>(record.release()));
452             DumpCallChain(indent, sample);
453         }
454 
455         return true;
456     };
457 
458     reader_->ReadDataSection(record_callback);
459 
460     PrintIndent(indent, "\n ======= there are %d records ======== \n", recordCount);
461 }
462 
PrintSymbolFile(const int & indent,const SymbolFileStruct & symbolFileStruct)463 void SubCommandDump::PrintSymbolFile(const int &indent, const SymbolFileStruct &symbolFileStruct)
464 {
465     PrintIndent(LEVEL2, "filePath:%s\n", symbolFileStruct.filePath_.c_str());
466     PrintIndent(LEVEL2, "symbolType:%u\n", symbolFileStruct.symbolType_);
467     PrintIndent(LEVEL2, "minExecAddr:0x%" PRIx64 "\n", symbolFileStruct.textExecVaddr_);
468     PrintIndent(LEVEL2, "minExecAddrFileOffset:0x%08" PRIx64 "\n",
469                 symbolFileStruct.textExecVaddrFileOffset_);
470     if (!symbolFileStruct.buildId_.empty()) {
471         PrintIndent(LEVEL2, "buildId:'%s'\n", symbolFileStruct.buildId_.c_str());
472     }
473     PrintIndent(LEVEL2, "symbol number: %zu\n", symbolFileStruct.symbolStructs_.size());
474     int symbolid = 0;
475     for (auto &symbolStruct : symbolFileStruct.symbolStructs_) {
476         PrintIndent(LEVEL3, "%05d [0x%016" PRIx64 "@0x%08x]  %s\n", symbolid, symbolStruct.vaddr_,
477                     symbolStruct.len_, symbolStruct.symbolName_.c_str());
478         symbolid++;
479     }
480 }
481 
PrintFeatureEventdesc(int indent,const PerfFileSectionEventDesc & sectionEventdesc)482 void SubCommandDump::PrintFeatureEventdesc(int indent,
483                                            const PerfFileSectionEventDesc &sectionEventdesc)
484 {
485     PrintIndent(LEVEL2, "Event descriptions: %zu\n", sectionEventdesc.eventDesces_.size());
486     for (size_t i = 0; i < sectionEventdesc.eventDesces_.size(); i++) {
487         const AttrWithId &desc = sectionEventdesc.eventDesces_[i];
488         PrintIndent(LEVEL2, "event name[%zu]: %s ids: %s\n", i, desc.name.c_str(),
489                     VectorToString(desc.ids).c_str());
490 
491         // attr is duplicated the attrs section
492     }
493     PrintIndent(LEVEL2, "\n");
494 }
495 
DumpFeaturePortion(int indent)496 void SubCommandDump::DumpFeaturePortion(int indent)
497 {
498     PrintIndent(indent, "\n ==== features ====\n");
499     auto features = reader_->GetFeatures();
500     for (auto feature : features) {
501         PrintIndent(LEVEL1, "feature %d:%s\n", feature,
502                     PerfFileSection::GetFeatureName(feature).c_str());
503     }
504 
505     const auto &featureSections = reader_->GetFeatureSections();
506     HLOGV("featureSections: %zu ", featureSections.size());
507 
508     PrintIndent(indent, "\n ==== feature sections ====\n");
509 
510     for (auto &featureSection : featureSections) {
511         PrintIndent(LEVEL1, "feature %d:%s content: \n", featureSection.get()->featureId_,
512                     PerfFileSection::GetFeatureName(featureSection.get()->featureId_).c_str());
513         if (reader_->IsFeatrureStringSection(featureSection.get()->featureId_)) {
514             const PerfFileSectionString *sectionString =
515                 static_cast<const PerfFileSectionString *>(featureSection.get());
516             PrintIndent(LEVEL2, "%s\n", sectionString->toString().c_str());
517             continue;
518         } else if (featureSection.get()->featureId_ == FEATURE::EVENT_DESC) {
519             PrintFeatureEventdesc(
520                 indent, *static_cast<const PerfFileSectionEventDesc *>(featureSection.get()));
521             continue;
522         }
523 
524         const PerfFileSectionSymbolsFiles *sectionSymbolsFiles =
525             static_cast<const PerfFileSectionSymbolsFiles *>(featureSection.get());
526         if (sectionSymbolsFiles != nullptr) {
527             PrintIndent(LEVEL2, "SymbolFiles:%zu\n",
528                         sectionSymbolsFiles->symbolFileStructs_.size());
529 
530             int fileid = 0;
531             for (auto &symbolFileStruct : sectionSymbolsFiles->symbolFileStructs_) {
532                 PrintIndent(LEVEL2, "\n");
533                 PrintIndent(LEVEL2, "fileid:%d\n", fileid);
534                 fileid++;
535                 // symbol file info
536                 PrintSymbolFile(indent, symbolFileStruct);
537             }
538             continue;
539         }
540 
541         PrintIndent(LEVEL2, "not support dump this feature.\n");
542     }
543 }
544 
RegisterSubCommandDump()545 bool SubCommandDump::RegisterSubCommandDump()
546 {
547     return SubCommand::RegisterSubCommand("dump", std::make_unique<SubCommandDump>());
548 }
549 } // namespace HiPerf
550 } // namespace Developtools
551 } // namespace OHOS
552