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 §ionEventdesc = 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