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
34namespace OHOS {
35namespace Developtools {
36namespace HiPerf {
37bool 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
115void 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}
123bool 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
141bool 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
190void 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
200void 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
226bool 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
274void SubCommandReport::LoadPerfDataCompleted()
275{
276    if (jsonFormat_) {
277        reportJsonFile_->UpdateCallNodeEventCount();
278    }
279    HLOGV("load perf data done");
280}
281
282void 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
308void 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
319void 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
339void 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
359void 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
401void 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
426void 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
438void 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
452bool 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
493bool 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
530bool 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
543bool 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
575SubCommandReport::~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
589bool 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
622bool SubCommandReport::RegisterSubCommandReport()
623{
624    std::unique_ptr<SubCommand> cmd = std::make_unique<SubCommandReport>();
625    return SubCommand::RegisterSubCommand("report", std::move(cmd));
626}
627
628void 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