1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <stdint.h>
18 #include <stdio.h>
19 #include <unistd.h>
20
21 #include <memory>
22 #include <optional>
23 #include <string>
24 #include <thread>
25
26 #include <android-base/parseint.h>
27 #include <android-base/strings.h>
28
29 #include "BranchListFile.h"
30 #include "ETMDecoder.h"
31 #include "RegEx.h"
32 #include "ZstdUtil.h"
33 #include "command.h"
34 #include "record_file.h"
35 #include "system/extras/simpleperf/branch_list.pb.h"
36 #include "thread_tree.h"
37 #include "utils.h"
38
39 namespace simpleperf {
40
41 namespace {
42
43 using AddrPair = std::pair<uint64_t, uint64_t>;
44
45 struct AddrPairHash {
operator ()simpleperf::__anon245dd9b60111::AddrPairHash46 size_t operator()(const AddrPair& ap) const noexcept {
47 size_t seed = 0;
48 HashCombine(seed, ap.first);
49 HashCombine(seed, ap.second);
50 return seed;
51 }
52 };
53
54 enum class OutputFormat {
55 AutoFDO,
56 BOLT,
57 BranchList,
58 };
59
60 struct AutoFDOBinaryInfo {
61 std::vector<ElfSegment> executable_segments;
62 std::unordered_map<uint64_t, uint64_t> address_count_map;
63 std::unordered_map<AddrPair, uint64_t, AddrPairHash> range_count_map;
64 std::unordered_map<AddrPair, uint64_t, AddrPairHash> branch_count_map;
65
AddAddresssimpleperf::__anon245dd9b60111::AutoFDOBinaryInfo66 void AddAddress(uint64_t addr) { OverflowSafeAdd(address_count_map[addr], 1); }
67
AddRangesimpleperf::__anon245dd9b60111::AutoFDOBinaryInfo68 void AddRange(uint64_t begin, uint64_t end) {
69 OverflowSafeAdd(range_count_map[std::make_pair(begin, end)], 1);
70 }
71
AddBranchsimpleperf::__anon245dd9b60111::AutoFDOBinaryInfo72 void AddBranch(uint64_t from, uint64_t to) {
73 OverflowSafeAdd(branch_count_map[std::make_pair(from, to)], 1);
74 }
75
AddInstrRangesimpleperf::__anon245dd9b60111::AutoFDOBinaryInfo76 void AddInstrRange(const ETMInstrRange& instr_range) {
77 uint64_t total_count = instr_range.branch_taken_count;
78 OverflowSafeAdd(total_count, instr_range.branch_not_taken_count);
79 OverflowSafeAdd(range_count_map[AddrPair(instr_range.start_addr, instr_range.end_addr)],
80 total_count);
81 if (instr_range.branch_taken_count > 0) {
82 OverflowSafeAdd(branch_count_map[AddrPair(instr_range.end_addr, instr_range.branch_to_addr)],
83 instr_range.branch_taken_count);
84 }
85 }
86
Mergesimpleperf::__anon245dd9b60111::AutoFDOBinaryInfo87 void Merge(const AutoFDOBinaryInfo& other) {
88 for (const auto& p : other.address_count_map) {
89 auto res = address_count_map.emplace(p.first, p.second);
90 if (!res.second) {
91 OverflowSafeAdd(res.first->second, p.second);
92 }
93 }
94 for (const auto& p : other.range_count_map) {
95 auto res = range_count_map.emplace(p.first, p.second);
96 if (!res.second) {
97 OverflowSafeAdd(res.first->second, p.second);
98 }
99 }
100 for (const auto& p : other.branch_count_map) {
101 auto res = branch_count_map.emplace(p.first, p.second);
102 if (!res.second) {
103 OverflowSafeAdd(res.first->second, p.second);
104 }
105 }
106 }
107
VaddrToOffsetsimpleperf::__anon245dd9b60111::AutoFDOBinaryInfo108 std::optional<uint64_t> VaddrToOffset(uint64_t vaddr) const {
109 for (const auto& segment : executable_segments) {
110 if (segment.vaddr <= vaddr && vaddr < segment.vaddr + segment.file_size) {
111 return vaddr - segment.vaddr + segment.file_offset;
112 }
113 }
114 return std::nullopt;
115 }
116 };
117
118 using AutoFDOBinaryCallback = std::function<void(const BinaryKey&, AutoFDOBinaryInfo&)>;
119 using ETMBinaryCallback = std::function<void(const BinaryKey&, ETMBinary&)>;
120 using LBRDataCallback = std::function<void(LBRData&)>;
121
GetExecutableSegments(const Dso * dso)122 static std::vector<ElfSegment> GetExecutableSegments(const Dso* dso) {
123 std::vector<ElfSegment> segments;
124 ElfStatus status;
125 if (auto elf = ElfFile::Open(dso->GetDebugFilePath(), &status); elf) {
126 segments = elf->GetProgramHeader();
127 auto not_executable = [](const ElfSegment& s) { return !s.is_executable; };
128 segments.erase(std::remove_if(segments.begin(), segments.end(), not_executable),
129 segments.end());
130 }
131 return segments;
132 }
133
134 // Base class for reading perf.data and generating AutoFDO or branch list data.
135 class PerfDataReader {
136 public:
GetDataType(RecordFileReader & reader)137 static std::string GetDataType(RecordFileReader& reader) {
138 const EventAttrIds& attrs = reader.AttrSection();
139 if (attrs.size() != 1) {
140 return "unknown";
141 }
142 const perf_event_attr& attr = attrs[0].attr;
143 if (IsEtmEventType(attr.type)) {
144 return "etm";
145 }
146 if (attr.sample_type & PERF_SAMPLE_BRANCH_STACK) {
147 return "lbr";
148 }
149 return "unknown";
150 }
151
PerfDataReader(std::unique_ptr<RecordFileReader> reader,bool exclude_perf,const RegEx * binary_name_regex)152 PerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf,
153 const RegEx* binary_name_regex)
154 : reader_(std::move(reader)),
155 exclude_perf_(exclude_perf),
156 binary_filter_(binary_name_regex) {}
~PerfDataReader()157 virtual ~PerfDataReader() {}
158
GetDataType() const159 std::string GetDataType() const { return GetDataType(*reader_); }
160
AddCallback(const AutoFDOBinaryCallback & callback)161 void AddCallback(const AutoFDOBinaryCallback& callback) { autofdo_callback_ = callback; }
AddCallback(const ETMBinaryCallback & callback)162 void AddCallback(const ETMBinaryCallback& callback) { etm_binary_callback_ = callback; }
AddCallback(const LBRDataCallback & callback)163 void AddCallback(const LBRDataCallback& callback) { lbr_data_callback_ = callback; }
164
Read()165 virtual bool Read() {
166 if (exclude_perf_) {
167 const auto& info_map = reader_->GetMetaInfoFeature();
168 if (auto it = info_map.find("recording_process"); it == info_map.end()) {
169 LOG(ERROR) << reader_->FileName() << " doesn't support --exclude-perf";
170 return false;
171 } else {
172 int pid;
173 if (!android::base::ParseInt(it->second, &pid, 0)) {
174 LOG(ERROR) << "invalid recording_process " << it->second << " in " << reader_->FileName();
175 return false;
176 }
177 exclude_pid_ = pid;
178 }
179 }
180
181 if (!reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
182 return false;
183 }
184 if (reader_->HasFeature(PerfFileFormat::FEAT_INIT_MAP)) {
185 if (!reader_->ReadInitMapFeature([this](auto r) { return ProcessRecord(*r); })) {
186 return false;
187 }
188 }
189 if (!reader_->ReadDataSection([this](auto r) { return ProcessRecord(*r); })) {
190 return false;
191 }
192 return PostProcess();
193 }
194
195 protected:
196 virtual bool ProcessRecord(Record& r) = 0;
197 virtual bool PostProcess() = 0;
198
ProcessAutoFDOBinaryInfo()199 void ProcessAutoFDOBinaryInfo() {
200 for (auto& p : autofdo_binary_map_) {
201 const Dso* dso = p.first;
202 AutoFDOBinaryInfo& binary = p.second;
203 binary.executable_segments = GetExecutableSegments(dso);
204 autofdo_callback_(BinaryKey(dso, 0), binary);
205 }
206 }
207
208 const std::string data_type_;
209 std::unique_ptr<RecordFileReader> reader_;
210 bool exclude_perf_;
211 BinaryFilter binary_filter_;
212
213 std::optional<int> exclude_pid_;
214 ThreadTree thread_tree_;
215 AutoFDOBinaryCallback autofdo_callback_;
216 ETMBinaryCallback etm_binary_callback_;
217 LBRDataCallback lbr_data_callback_;
218 // Store results for AutoFDO.
219 std::unordered_map<const Dso*, AutoFDOBinaryInfo> autofdo_binary_map_;
220 };
221
222 class ETMThreadTreeWithFilter : public ETMThreadTree {
223 public:
ETMThreadTreeWithFilter(ThreadTree & thread_tree,std::optional<int> & exclude_pid,const std::vector<std::unique_ptr<RegEx>> & exclude_process_names)224 ETMThreadTreeWithFilter(ThreadTree& thread_tree, std::optional<int>& exclude_pid,
225 const std::vector<std::unique_ptr<RegEx>>& exclude_process_names)
226 : thread_tree_(thread_tree),
227 exclude_pid_(exclude_pid),
228 exclude_process_names_(exclude_process_names) {}
229
DisableThreadExitRecords()230 void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
231
FindThread(int tid)232 const ThreadEntry* FindThread(int tid) override {
233 const ThreadEntry* thread = thread_tree_.FindThread(tid);
234 if (thread != nullptr && ShouldExcludePid(thread->pid)) {
235 return nullptr;
236 }
237 return thread;
238 }
239
GetKernelMaps()240 const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
241
242 private:
ShouldExcludePid(int pid)243 bool ShouldExcludePid(int pid) {
244 if (exclude_pid_ && pid == exclude_pid_) {
245 return true;
246 }
247 if (!exclude_process_names_.empty()) {
248 const ThreadEntry* process = thread_tree_.FindThread(pid);
249 if (process != nullptr) {
250 for (const auto& regex : exclude_process_names_) {
251 if (regex->Search(process->comm)) {
252 return true;
253 }
254 }
255 }
256 }
257 return false;
258 }
259
260 ThreadTree& thread_tree_;
261 std::optional<int>& exclude_pid_;
262 const std::vector<std::unique_ptr<RegEx>>& exclude_process_names_;
263 };
264
265 // Read perf.data with ETM data and generate AutoFDO or branch list data.
266 class ETMPerfDataReader : public PerfDataReader {
267 public:
ETMPerfDataReader(std::unique_ptr<RecordFileReader> reader,bool exclude_perf,const std::vector<std::unique_ptr<RegEx>> & exclude_process_names,const RegEx * binary_name_regex,ETMDumpOption etm_dump_option)268 ETMPerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf,
269 const std::vector<std::unique_ptr<RegEx>>& exclude_process_names,
270 const RegEx* binary_name_regex, ETMDumpOption etm_dump_option)
271 : PerfDataReader(std::move(reader), exclude_perf, binary_name_regex),
272 etm_dump_option_(etm_dump_option),
273 etm_thread_tree_(thread_tree_, exclude_pid_, exclude_process_names) {}
274
Read()275 bool Read() override {
276 if (reader_->HasFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST)) {
277 return ProcessETMBranchListFeature();
278 }
279 return PerfDataReader::Read();
280 }
281
282 private:
ProcessRecord(Record & r)283 bool ProcessRecord(Record& r) override {
284 thread_tree_.Update(r);
285 if (r.type() == PERF_RECORD_AUXTRACE_INFO) {
286 etm_decoder_ = ETMDecoder::Create(static_cast<AuxTraceInfoRecord&>(r), etm_thread_tree_);
287 if (!etm_decoder_) {
288 return false;
289 }
290 etm_decoder_->EnableDump(etm_dump_option_);
291 if (autofdo_callback_) {
292 etm_decoder_->RegisterCallback(
293 [this](const ETMInstrRange& range) { ProcessInstrRange(range); });
294 } else if (etm_binary_callback_) {
295 etm_decoder_->RegisterCallback(
296 [this](const ETMBranchList& branch) { ProcessETMBranchList(branch); });
297 }
298 } else if (r.type() == PERF_RECORD_AUX) {
299 AuxRecord& aux = static_cast<AuxRecord&>(r);
300 if (aux.data->aux_size > SIZE_MAX) {
301 LOG(ERROR) << "invalid aux size";
302 return false;
303 }
304 size_t aux_size = aux.data->aux_size;
305 if (aux_size > 0) {
306 bool error = false;
307 if (!reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, aux_size, aux_data_buffer_,
308 error)) {
309 return !error;
310 }
311 if (!etm_decoder_) {
312 LOG(ERROR) << "ETMDecoder isn't created";
313 return false;
314 }
315 return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size, !aux.Unformatted(),
316 aux.Cpu());
317 }
318 } else if (r.type() == PERF_RECORD_MMAP && r.InKernel()) {
319 auto& mmap_r = static_cast<MmapRecord&>(r);
320 if (android::base::StartsWith(mmap_r.filename, DEFAULT_KERNEL_MMAP_NAME)) {
321 kernel_map_start_addr_ = mmap_r.data->addr;
322 }
323 }
324 return true;
325 }
326
PostProcess()327 bool PostProcess() override {
328 if (etm_decoder_ && !etm_decoder_->FinishData()) {
329 return false;
330 }
331 if (autofdo_callback_) {
332 ProcessAutoFDOBinaryInfo();
333 } else if (etm_binary_callback_) {
334 ProcessETMBinary();
335 }
336 return true;
337 }
338
ProcessETMBranchListFeature()339 bool ProcessETMBranchListFeature() {
340 if (exclude_perf_) {
341 LOG(WARNING) << "--exclude-perf has no effect on perf.data with etm branch list";
342 }
343 if (autofdo_callback_) {
344 LOG(ERROR) << "convert to autofdo format isn't support on perf.data with etm branch list";
345 return false;
346 }
347 CHECK(etm_binary_callback_);
348 std::string s;
349 if (!reader_->ReadFeatureSection(PerfFileFormat::FEAT_ETM_BRANCH_LIST, &s)) {
350 return false;
351 }
352 ETMBinaryMap binary_map;
353 if (!StringToETMBinaryMap(s, binary_map)) {
354 return false;
355 }
356 for (auto& [key, binary] : binary_map) {
357 if (!binary_filter_.Filter(key.path)) {
358 continue;
359 }
360 etm_binary_callback_(key, binary);
361 }
362 return true;
363 }
364
ProcessInstrRange(const ETMInstrRange & instr_range)365 void ProcessInstrRange(const ETMInstrRange& instr_range) {
366 if (!binary_filter_.Filter(instr_range.dso)) {
367 return;
368 }
369
370 autofdo_binary_map_[instr_range.dso].AddInstrRange(instr_range);
371 }
372
ProcessETMBranchList(const ETMBranchList & branch_list)373 void ProcessETMBranchList(const ETMBranchList& branch_list) {
374 if (!binary_filter_.Filter(branch_list.dso)) {
375 return;
376 }
377
378 auto& branch_map = etm_binary_map_[branch_list.dso].branch_map;
379 ++branch_map[branch_list.addr][branch_list.branch];
380 }
381
ProcessETMBinary()382 void ProcessETMBinary() {
383 for (auto& p : etm_binary_map_) {
384 Dso* dso = p.first;
385 ETMBinary& binary = p.second;
386 binary.dso_type = dso->type();
387 BinaryKey key(dso, 0);
388 if (binary.dso_type == DSO_KERNEL) {
389 if (kernel_map_start_addr_ == 0) {
390 LOG(WARNING) << "Can't convert kernel ip addresses without kernel start addr. So remove "
391 "branches for the kernel.";
392 continue;
393 }
394 if (dso->GetDebugFilePath() == dso->Path()) {
395 // vmlinux isn't available. We still use kernel ip addr. Put kernel start addr in proto
396 // for address conversion later.
397 key.kernel_start_addr = kernel_map_start_addr_;
398 }
399 }
400 etm_binary_callback_(key, binary);
401 }
402 }
403
404 ETMDumpOption etm_dump_option_;
405 ETMThreadTreeWithFilter etm_thread_tree_;
406 std::vector<uint8_t> aux_data_buffer_;
407 std::unique_ptr<ETMDecoder> etm_decoder_;
408 uint64_t kernel_map_start_addr_ = 0;
409 // Store etm branch list data.
410 std::unordered_map<Dso*, ETMBinary> etm_binary_map_;
411 };
412
ConvertLBRDataToAutoFDO(const LBRData & lbr_data)413 static std::optional<std::vector<AutoFDOBinaryInfo>> ConvertLBRDataToAutoFDO(
414 const LBRData& lbr_data) {
415 std::vector<AutoFDOBinaryInfo> binaries(lbr_data.binaries.size());
416 for (const LBRSample& sample : lbr_data.samples) {
417 if (sample.binary_id != 0) {
418 if (sample.binary_id > binaries.size()) {
419 LOG(ERROR) << "binary_id out of range";
420 return std::nullopt;
421 }
422 binaries[sample.binary_id - 1].AddAddress(sample.vaddr_in_file);
423 }
424 for (size_t i = 0; i < sample.branches.size(); ++i) {
425 const LBRBranch& branch = sample.branches[i];
426 if (branch.from_binary_id == 0) {
427 continue;
428 }
429 if (branch.from_binary_id > binaries.size()) {
430 LOG(ERROR) << "binary_id out of range";
431 return std::nullopt;
432 }
433 if (branch.from_binary_id == branch.to_binary_id) {
434 binaries[branch.from_binary_id - 1].AddBranch(branch.from_vaddr_in_file,
435 branch.to_vaddr_in_file);
436 }
437 if (i > 0 && branch.from_binary_id == sample.branches[i - 1].to_binary_id) {
438 uint64_t begin = sample.branches[i - 1].to_vaddr_in_file;
439 uint64_t end = branch.from_vaddr_in_file;
440 // Use the same logic to skip bogus LBR data as AutoFDO.
441 if (end < begin || end - begin > (1 << 20)) {
442 continue;
443 }
444 binaries[branch.from_binary_id - 1].AddRange(begin, end);
445 }
446 }
447 }
448 return binaries;
449 }
450
451 class LBRPerfDataReader : public PerfDataReader {
452 public:
LBRPerfDataReader(std::unique_ptr<RecordFileReader> reader,bool exclude_perf,const RegEx * binary_name_regex)453 LBRPerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf,
454 const RegEx* binary_name_regex)
455 : PerfDataReader(std::move(reader), exclude_perf, binary_name_regex) {}
456
457 private:
ProcessRecord(Record & r)458 bool ProcessRecord(Record& r) override {
459 thread_tree_.Update(r);
460 if (r.type() == PERF_RECORD_SAMPLE) {
461 auto& sr = static_cast<SampleRecord&>(r);
462 ThreadEntry* thread = thread_tree_.FindThread(sr.tid_data.tid);
463 if (thread == nullptr) {
464 return true;
465 }
466 auto& stack = sr.branch_stack_data;
467 lbr_data_.samples.resize(lbr_data_.samples.size() + 1);
468 LBRSample& sample = lbr_data_.samples.back();
469 std::pair<uint32_t, uint64_t> binary_addr = IpToBinaryAddr(*thread, sr.ip_data.ip);
470 sample.binary_id = binary_addr.first;
471 bool has_valid_binary_id = sample.binary_id != 0;
472 sample.vaddr_in_file = binary_addr.second;
473 sample.branches.resize(stack.stack_nr);
474 for (size_t i = 0; i < stack.stack_nr; ++i) {
475 uint64_t from_ip = stack.stack[i].from;
476 uint64_t to_ip = stack.stack[i].to;
477 LBRBranch& branch = sample.branches[i];
478 binary_addr = IpToBinaryAddr(*thread, from_ip);
479 branch.from_binary_id = binary_addr.first;
480 branch.from_vaddr_in_file = binary_addr.second;
481 binary_addr = IpToBinaryAddr(*thread, to_ip);
482 branch.to_binary_id = binary_addr.first;
483 branch.to_vaddr_in_file = binary_addr.second;
484 if (branch.from_binary_id != 0 || branch.to_binary_id != 0) {
485 has_valid_binary_id = true;
486 }
487 }
488 if (!has_valid_binary_id) {
489 lbr_data_.samples.pop_back();
490 }
491 }
492 return true;
493 }
494
PostProcess()495 bool PostProcess() override {
496 if (autofdo_callback_) {
497 std::optional<std::vector<AutoFDOBinaryInfo>> binaries = ConvertLBRDataToAutoFDO(lbr_data_);
498 if (!binaries) {
499 return false;
500 }
501 for (const auto& [dso, binary_id] : dso_map_) {
502 autofdo_binary_map_[dso] = std::move(binaries.value()[binary_id - 1]);
503 }
504 ProcessAutoFDOBinaryInfo();
505 } else if (lbr_data_callback_) {
506 lbr_data_callback_(lbr_data_);
507 }
508 return true;
509 }
510
IpToBinaryAddr(ThreadEntry & thread,uint64_t ip)511 std::pair<uint32_t, uint64_t> IpToBinaryAddr(ThreadEntry& thread, uint64_t ip) {
512 const MapEntry* map = thread_tree_.FindMap(&thread, ip);
513 Dso* dso = map->dso;
514 if (thread_tree_.IsUnknownDso(dso) || !binary_filter_.Filter(dso)) {
515 return std::make_pair(0, 0);
516 }
517 uint32_t binary_id = GetBinaryId(dso);
518 uint64_t vaddr_in_file = dso->IpToVaddrInFile(ip, map->start_addr, map->pgoff);
519 return std::make_pair(binary_id, vaddr_in_file);
520 }
521
GetBinaryId(const Dso * dso)522 uint32_t GetBinaryId(const Dso* dso) {
523 if (auto it = dso_map_.find(dso); it != dso_map_.end()) {
524 return it->second;
525 }
526 lbr_data_.binaries.emplace_back(dso, 0);
527 uint32_t binary_id = static_cast<uint32_t>(lbr_data_.binaries.size());
528 dso_map_[dso] = binary_id;
529 return binary_id;
530 }
531
532 LBRData lbr_data_;
533 // Map from dso to binary_id in lbr_data_.
534 std::unordered_map<const Dso*, uint32_t> dso_map_;
535 };
536
537 // Read a protobuf file specified by branch_list.proto.
538 class BranchListReader {
539 public:
BranchListReader(std::string_view filename,const RegEx * binary_name_regex)540 BranchListReader(std::string_view filename, const RegEx* binary_name_regex)
541 : filename_(filename), binary_filter_(binary_name_regex) {}
542
AddCallback(const ETMBinaryCallback & callback)543 void AddCallback(const ETMBinaryCallback& callback) { etm_binary_callback_ = callback; }
AddCallback(const LBRDataCallback & callback)544 void AddCallback(const LBRDataCallback& callback) { lbr_data_callback_ = callback; }
545
Read()546 bool Read() {
547 auto reader = BranchListProtoReader::CreateForFile(filename_);
548 if (!reader) {
549 return false;
550 }
551 ETMBinaryMap etm_data;
552 LBRData lbr_data;
553 if (!reader->Read(etm_data, lbr_data)) {
554 return false;
555 }
556 if (etm_binary_callback_ && !etm_data.empty()) {
557 ProcessETMData(etm_data);
558 }
559 if (lbr_data_callback_ && !lbr_data.samples.empty()) {
560 ProcessLBRData(lbr_data);
561 }
562 return true;
563 }
564
565 private:
ProcessETMData(ETMBinaryMap & etm_data)566 void ProcessETMData(ETMBinaryMap& etm_data) {
567 for (auto& [key, binary] : etm_data) {
568 if (!binary_filter_.Filter(key.path)) {
569 continue;
570 }
571 etm_binary_callback_(key, binary);
572 }
573 }
574
ProcessLBRData(LBRData & lbr_data)575 void ProcessLBRData(LBRData& lbr_data) {
576 // 1. Check if we need to remove binaries.
577 std::vector<uint32_t> new_ids(lbr_data.binaries.size());
578 uint32_t next_id = 1;
579
580 for (size_t i = 0; i < lbr_data.binaries.size(); ++i) {
581 if (!binary_filter_.Filter(lbr_data.binaries[i].path)) {
582 new_ids[i] = 0;
583 } else {
584 new_ids[i] = next_id++;
585 }
586 }
587
588 if (next_id <= lbr_data.binaries.size()) {
589 // 2. Modify lbr_data.binaries.
590 for (size_t i = 0; i < lbr_data.binaries.size(); ++i) {
591 if (new_ids[i] != 0) {
592 size_t new_pos = new_ids[i] - 1;
593 lbr_data.binaries[new_pos] = lbr_data.binaries[i];
594 }
595 }
596 lbr_data.binaries.resize(next_id - 1);
597
598 // 3. Modify lbr_data.samples.
599 auto convert_id = [&](uint32_t& binary_id) {
600 if (binary_id != 0) {
601 binary_id = (binary_id <= new_ids.size()) ? new_ids[binary_id - 1] : 0;
602 }
603 };
604 std::vector<LBRSample> new_samples;
605 for (LBRSample& sample : lbr_data.samples) {
606 convert_id(sample.binary_id);
607 bool has_valid_binary_id = sample.binary_id != 0;
608 for (LBRBranch& branch : sample.branches) {
609 convert_id(branch.from_binary_id);
610 convert_id(branch.to_binary_id);
611 if (branch.from_binary_id != 0 || branch.to_binary_id != 0) {
612 has_valid_binary_id = true;
613 }
614 }
615 if (has_valid_binary_id) {
616 new_samples.emplace_back(std::move(sample));
617 }
618 }
619 lbr_data.samples = std::move(new_samples);
620 }
621 lbr_data_callback_(lbr_data);
622 }
623
624 const std::string filename_;
625 BinaryFilter binary_filter_;
626 ETMBinaryCallback etm_binary_callback_;
627 LBRDataCallback lbr_data_callback_;
628 };
629
630 // Convert ETMBinary into AutoFDOBinaryInfo.
631 class ETMBranchListToAutoFDOConverter {
632 public:
Convert(const BinaryKey & key,ETMBinary & binary)633 std::unique_ptr<AutoFDOBinaryInfo> Convert(const BinaryKey& key, ETMBinary& binary) {
634 BuildId build_id = key.build_id;
635 std::unique_ptr<Dso> dso = Dso::CreateDsoWithBuildId(binary.dso_type, key.path, build_id);
636 if (!dso || !CheckBuildId(dso.get(), key.build_id)) {
637 return nullptr;
638 }
639 std::unique_ptr<AutoFDOBinaryInfo> autofdo_binary(new AutoFDOBinaryInfo);
640 autofdo_binary->executable_segments = GetExecutableSegments(dso.get());
641
642 if (dso->type() == DSO_KERNEL) {
643 CHECK_EQ(key.kernel_start_addr, 0);
644 }
645
646 auto process_instr_range = [&](const ETMInstrRange& range) {
647 autofdo_binary->AddInstrRange(range);
648 };
649
650 auto result = ConvertETMBranchMapToInstrRanges(dso.get(), binary.GetOrderedBranchMap(),
651 process_instr_range);
652 if (!result.ok()) {
653 LOG(WARNING) << "failed to build instr ranges for binary " << dso->Path() << ": "
654 << result.error();
655 return nullptr;
656 }
657 return autofdo_binary;
658 }
659
660 private:
CheckBuildId(Dso * dso,const BuildId & expected_build_id)661 bool CheckBuildId(Dso* dso, const BuildId& expected_build_id) {
662 if (expected_build_id.IsEmpty()) {
663 return true;
664 }
665 BuildId build_id;
666 return GetBuildIdFromDsoPath(dso->GetDebugFilePath(), &build_id) &&
667 build_id == expected_build_id;
668 }
669 };
670
671 // Write instruction ranges to a file in AutoFDO text format.
672 class AutoFDOWriter {
673 public:
AddAutoFDOBinary(const BinaryKey & key,AutoFDOBinaryInfo & binary)674 void AddAutoFDOBinary(const BinaryKey& key, AutoFDOBinaryInfo& binary) {
675 auto it = binary_map_.find(key);
676 if (it == binary_map_.end()) {
677 binary_map_[key] = std::move(binary);
678 } else {
679 it->second.Merge(binary);
680 }
681 }
682
WriteAutoFDO(const std::string & output_filename)683 bool WriteAutoFDO(const std::string& output_filename) {
684 std::unique_ptr<FILE, decltype(&fclose)> output_fp(fopen(output_filename.c_str(), "w"), fclose);
685 if (!output_fp) {
686 PLOG(ERROR) << "failed to write to " << output_filename;
687 return false;
688 }
689 // autofdo_binary_map is used to store instruction ranges, which can have a large amount. And
690 // it has a larger access time (instruction ranges * executed time). So it's better to use
691 // unorder_maps to speed up access time. But we also want a stable output here, to compare
692 // output changes result from code changes. So generate a sorted output here.
693 std::vector<BinaryKey> keys;
694 for (auto& p : binary_map_) {
695 keys.emplace_back(p.first);
696 }
697 std::sort(keys.begin(), keys.end(),
698 [](const BinaryKey& key1, const BinaryKey& key2) { return key1.path < key2.path; });
699 if (keys.size() > 1) {
700 fprintf(output_fp.get(),
701 "// Please split this file. AutoFDO only accepts profile for one binary.\n");
702 }
703 for (const auto& key : keys) {
704 const AutoFDOBinaryInfo& binary = binary_map_[key];
705 // AutoFDO text format needs file_offsets instead of virtual addrs in a binary. So convert
706 // vaddrs to file offsets.
707
708 // Write range_count_map. Sort the output by addrs.
709 std::vector<std::pair<AddrPair, uint64_t>> range_counts;
710 for (std::pair<AddrPair, uint64_t> p : binary.range_count_map) {
711 std::optional<uint64_t> start_offset = binary.VaddrToOffset(p.first.first);
712 std::optional<uint64_t> end_offset = binary.VaddrToOffset(p.first.second);
713 if (start_offset && end_offset) {
714 p.first.first = start_offset.value();
715 p.first.second = end_offset.value();
716 range_counts.emplace_back(p);
717 }
718 }
719 std::sort(range_counts.begin(), range_counts.end());
720 fprintf(output_fp.get(), "%zu\n", range_counts.size());
721 for (const auto& p : range_counts) {
722 fprintf(output_fp.get(), "%" PRIx64 "-%" PRIx64 ":%" PRIu64 "\n", p.first.first,
723 p.first.second, p.second);
724 }
725
726 // Write addr_count_map. Sort the output by addrs.
727 std::vector<std::pair<uint64_t, uint64_t>> address_counts;
728 for (std::pair<uint64_t, uint64_t> p : binary.address_count_map) {
729 std::optional<uint64_t> offset = binary.VaddrToOffset(p.first);
730 if (offset) {
731 p.first = offset.value();
732 address_counts.emplace_back(p);
733 }
734 }
735 std::sort(address_counts.begin(), address_counts.end());
736 fprintf(output_fp.get(), "%zu\n", address_counts.size());
737 for (const auto& p : address_counts) {
738 fprintf(output_fp.get(), "%" PRIx64 ":%" PRIu64 "\n", p.first, p.second);
739 }
740
741 // Write branch_count_map. Sort the output by addrs.
742 std::vector<std::pair<AddrPair, uint64_t>> branch_counts;
743 for (std::pair<AddrPair, uint64_t> p : binary.branch_count_map) {
744 std::optional<uint64_t> from_offset = binary.VaddrToOffset(p.first.first);
745 std::optional<uint64_t> to_offset = binary.VaddrToOffset(p.first.second);
746 if (from_offset) {
747 p.first.first = from_offset.value();
748 p.first.second = to_offset ? to_offset.value() : 0;
749 branch_counts.emplace_back(p);
750 }
751 }
752 std::sort(branch_counts.begin(), branch_counts.end());
753 fprintf(output_fp.get(), "%zu\n", branch_counts.size());
754 for (const auto& p : branch_counts) {
755 fprintf(output_fp.get(), "%" PRIx64 "->%" PRIx64 ":%" PRIu64 "\n", p.first.first,
756 p.first.second, p.second);
757 }
758
759 // Write the binary path in comment.
760 fprintf(output_fp.get(), "// build_id: %s\n", key.build_id.ToString().c_str());
761 fprintf(output_fp.get(), "// %s\n\n", key.path.c_str());
762 }
763 return true;
764 }
765
766 // Write bolt profile in format documented in
767 // https://github.com/llvm/llvm-project/blob/main/bolt/include/bolt/Profile/DataAggregator.h#L372.
WriteBolt(const std::string & output_filename)768 bool WriteBolt(const std::string& output_filename) {
769 std::unique_ptr<FILE, decltype(&fclose)> output_fp(fopen(output_filename.c_str(), "w"), fclose);
770 if (!output_fp) {
771 PLOG(ERROR) << "failed to write to " << output_filename;
772 return false;
773 }
774 // autofdo_binary_map is used to store instruction ranges, which can have a large amount. And
775 // it has a larger access time (instruction ranges * executed time). So it's better to use
776 // unorder_maps to speed up access time. But we also want a stable output here, to compare
777 // output changes result from code changes. So generate a sorted output here.
778 std::vector<BinaryKey> keys;
779 for (auto& p : binary_map_) {
780 keys.emplace_back(p.first);
781 }
782 std::sort(keys.begin(), keys.end(),
783 [](const BinaryKey& key1, const BinaryKey& key2) { return key1.path < key2.path; });
784 if (keys.size() > 1) {
785 fprintf(output_fp.get(),
786 "// Please split this file. BOLT only accepts profile for one binary.\n");
787 }
788
789 for (const auto& key : keys) {
790 const AutoFDOBinaryInfo& binary = binary_map_[key];
791 // Write range_count_map. Sort the output by addrs.
792 std::vector<std::pair<AddrPair, uint64_t>> range_counts;
793 for (const auto& p : binary.range_count_map) {
794 range_counts.emplace_back(p);
795 }
796 std::sort(range_counts.begin(), range_counts.end());
797 for (const auto& p : range_counts) {
798 fprintf(output_fp.get(), "F %" PRIx64 " %" PRIx64 " %" PRIu64 "\n", p.first.first,
799 p.first.second, p.second);
800 }
801
802 // Write branch_count_map. Sort the output by addrs.
803 std::vector<std::pair<AddrPair, uint64_t>> branch_counts;
804 for (const auto& p : binary.branch_count_map) {
805 branch_counts.emplace_back(p);
806 }
807 std::sort(branch_counts.begin(), branch_counts.end());
808 for (const auto& p : branch_counts) {
809 fprintf(output_fp.get(), "B %" PRIx64 " %" PRIx64 " %" PRIu64 " 0\n", p.first.first,
810 p.first.second, p.second);
811 }
812
813 // Write the binary path in comment.
814 fprintf(output_fp.get(), "// build_id: %s\n", key.build_id.ToString().c_str());
815 fprintf(output_fp.get(), "// %s\n", key.path.c_str());
816 }
817 return true;
818 }
819
820 private:
821 std::unordered_map<BinaryKey, AutoFDOBinaryInfo, BinaryKeyHash> binary_map_;
822 };
823
824 // Merge branch list data.
825 struct BranchListMerger {
AddETMBinarysimpleperf::__anon245dd9b60111::BranchListMerger826 void AddETMBinary(const BinaryKey& key, ETMBinary& binary) {
827 if (auto it = etm_data_.find(key); it != etm_data_.end()) {
828 it->second.Merge(binary);
829 } else {
830 etm_data_[key] = std::move(binary);
831 }
832 }
833
AddLBRDatasimpleperf::__anon245dd9b60111::BranchListMerger834 void AddLBRData(LBRData& lbr_data) {
835 // 1. Merge binaries.
836 std::vector<uint32_t> new_ids(lbr_data.binaries.size());
837 for (size_t i = 0; i < lbr_data.binaries.size(); i++) {
838 const BinaryKey& key = lbr_data.binaries[i];
839 if (auto it = lbr_binary_id_map_.find(key); it != lbr_binary_id_map_.end()) {
840 new_ids[i] = it->second;
841 } else {
842 uint32_t next_id = static_cast<uint32_t>(lbr_binary_id_map_.size()) + 1;
843 new_ids[i] = next_id;
844 lbr_binary_id_map_[key] = next_id;
845 lbr_data_.binaries.emplace_back(key);
846 }
847 }
848
849 // 2. Merge samples.
850 auto convert_id = [&](uint32_t& binary_id) {
851 if (binary_id != 0) {
852 binary_id = (binary_id <= new_ids.size()) ? new_ids[binary_id - 1] : 0;
853 }
854 };
855
856 for (LBRSample& sample : lbr_data.samples) {
857 convert_id(sample.binary_id);
858 for (LBRBranch& branch : sample.branches) {
859 convert_id(branch.from_binary_id);
860 convert_id(branch.to_binary_id);
861 }
862 lbr_data_.samples.emplace_back(std::move(sample));
863 }
864 }
865
Mergesimpleperf::__anon245dd9b60111::BranchListMerger866 void Merge(BranchListMerger& other) {
867 for (auto& p : other.GetETMData()) {
868 AddETMBinary(p.first, p.second);
869 }
870 AddLBRData(other.GetLBRData());
871 }
872
GetETMDatasimpleperf::__anon245dd9b60111::BranchListMerger873 ETMBinaryMap& GetETMData() { return etm_data_; }
874
GetLBRDatasimpleperf::__anon245dd9b60111::BranchListMerger875 LBRData& GetLBRData() { return lbr_data_; }
876
877 private:
878 ETMBinaryMap etm_data_;
879 LBRData lbr_data_;
880 std::unordered_map<BinaryKey, uint32_t, BinaryKeyHash> lbr_binary_id_map_;
881 };
882
883 // Read multiple branch list files and merge them using BranchListMerger.
884 class BranchListMergedReader {
885 public:
BranchListMergedReader(bool allow_mismatched_build_id,const RegEx * binary_name_regex,size_t jobs)886 BranchListMergedReader(bool allow_mismatched_build_id, const RegEx* binary_name_regex,
887 size_t jobs)
888 : allow_mismatched_build_id_(allow_mismatched_build_id),
889 binary_name_regex_(binary_name_regex),
890 jobs_(jobs) {}
891
Read(const std::vector<std::string> & input_filenames)892 std::unique_ptr<BranchListMerger> Read(const std::vector<std::string>& input_filenames) {
893 std::mutex input_file_mutex;
894 size_t input_file_index = 0;
895 auto get_input_file = [&]() -> std::string_view {
896 std::lock_guard<std::mutex> guard(input_file_mutex);
897 if (input_file_index == input_filenames.size()) {
898 return "";
899 }
900 if ((input_file_index + 1) % 100 == 0) {
901 LOG(VERBOSE) << "Read input file " << (input_file_index + 1) << "/"
902 << input_filenames.size();
903 }
904 return input_filenames[input_file_index++];
905 };
906
907 std::atomic_size_t failed_to_read_count = 0;
908 size_t thread_count = std::min(jobs_, input_filenames.size()) - 1;
909 std::vector<BranchListMerger> thread_mergers(thread_count);
910 std::vector<std::unique_ptr<std::thread>> threads;
911
912 for (size_t i = 0; i < thread_count; i++) {
913 threads.emplace_back(new std::thread([&, i]() {
914 ReadInThreadFunction(get_input_file, thread_mergers[i], failed_to_read_count);
915 }));
916 }
917 auto merger = std::make_unique<BranchListMerger>();
918 ReadInThreadFunction(get_input_file, *merger, failed_to_read_count);
919 for (size_t i = 0; i < thread_count; i++) {
920 threads[i]->join();
921 merger->Merge(thread_mergers[i]);
922 }
923 if (failed_to_read_count == input_filenames.size()) {
924 LOG(ERROR) << "No valid input file";
925 return nullptr;
926 }
927 return merger;
928 }
929
930 private:
ReadInThreadFunction(const std::function<std::string_view ()> & get_input_file,BranchListMerger & merger,std::atomic_size_t & failed_to_read_count)931 void ReadInThreadFunction(const std::function<std::string_view()>& get_input_file,
932 BranchListMerger& merger, std::atomic_size_t& failed_to_read_count) {
933 auto etm_callback = [&](const BinaryKey& key, ETMBinary& binary) {
934 BinaryKey new_key = key;
935 if (allow_mismatched_build_id_) {
936 new_key.build_id = BuildId();
937 }
938 if (binary.dso_type == DsoType::DSO_KERNEL) {
939 ModifyBranchMapForKernel(new_key, binary);
940 }
941 merger.AddETMBinary(new_key, binary);
942 };
943 auto lbr_callback = [&](LBRData& lbr_data) {
944 if (allow_mismatched_build_id_) {
945 for (BinaryKey& key : lbr_data.binaries) {
946 key.build_id = BuildId();
947 }
948 }
949 merger.AddLBRData(lbr_data);
950 };
951 while (true) {
952 std::string_view input_file = get_input_file();
953 if (input_file.empty()) {
954 break;
955 }
956 BranchListReader reader(input_file, binary_name_regex_);
957 reader.AddCallback(etm_callback);
958 reader.AddCallback(lbr_callback);
959 if (!reader.Read()) {
960 failed_to_read_count++;
961 }
962 }
963 }
964
ModifyBranchMapForKernel(BinaryKey & key,ETMBinary & binary)965 void ModifyBranchMapForKernel(BinaryKey& key, ETMBinary& binary) {
966 if (key.kernel_start_addr == 0) {
967 // vmlinux has been provided when generating branch lists. Addresses in branch lists are
968 // already vaddrs in vmlinux.
969 return;
970 }
971 {
972 std::lock_guard<std::mutex> guard(kernel_dso_mutex_);
973 if (!kernel_dso_) {
974 BuildId build_id = key.build_id;
975 kernel_dso_ = Dso::CreateDsoWithBuildId(binary.dso_type, key.path, build_id);
976 if (!kernel_dso_) {
977 return;
978 }
979 // Call IpToVaddrInFile once to initialize kernel start addr from vmlinux.
980 kernel_dso_->IpToVaddrInFile(0, key.kernel_start_addr, 0);
981 }
982 }
983 // Addresses are still kernel ip addrs in memory. Need to convert them to vaddrs in vmlinux.
984 UnorderedETMBranchMap new_branch_map;
985 for (auto& p : binary.branch_map) {
986 uint64_t vaddr_in_file = kernel_dso_->IpToVaddrInFile(p.first, key.kernel_start_addr, 0);
987 new_branch_map[vaddr_in_file] = std::move(p.second);
988 }
989 binary.branch_map = std::move(new_branch_map);
990 key.kernel_start_addr = 0;
991 }
992
993 const bool allow_mismatched_build_id_;
994 const RegEx* binary_name_regex_;
995 size_t jobs_;
996 std::unique_ptr<Dso> kernel_dso_;
997 std::mutex kernel_dso_mutex_;
998 };
999
1000 // Write branch lists to a protobuf file specified by branch_list.proto.
WriteBranchListFile(const std::string & output_filename,const ETMBinaryMap & etm_data,const LBRData & lbr_data,bool compress)1001 static bool WriteBranchListFile(const std::string& output_filename, const ETMBinaryMap& etm_data,
1002 const LBRData& lbr_data, bool compress) {
1003 auto writer = BranchListProtoWriter::CreateForFile(output_filename, compress);
1004 if (!writer) {
1005 return false;
1006 }
1007 if (!etm_data.empty()) {
1008 return writer->Write(etm_data);
1009 }
1010 if (!lbr_data.samples.empty()) {
1011 return writer->Write(lbr_data);
1012 }
1013 // Don't produce empty output file.
1014 LOG(INFO) << "Skip empty output file.";
1015 unlink(output_filename.c_str());
1016 return true;
1017 }
1018
1019 class InjectCommand : public Command {
1020 public:
InjectCommand()1021 InjectCommand()
1022 : Command("inject", "parse etm instruction tracing data",
1023 // clang-format off
1024 "Usage: simpleperf inject [options]\n"
1025 "--binary binary_name Generate data only for binaries matching binary_name regex.\n"
1026 "-i file1,file2,... Input files. Default is perf.data. Support below formats:\n"
1027 " 1. perf.data generated by recording cs-etm event type.\n"
1028 " 2. branch_list file generated by `inject --output branch-list`.\n"
1029 " If a file name starts with @, it contains a list of input files.\n"
1030 "-o <file> output file. Default is perf_inject.data.\n"
1031 "--output <format> Select output file format:\n"
1032 " autofdo -- text format accepted by TextSampleReader\n"
1033 " of AutoFDO\n"
1034 " bolt -- text format accepted by `perf2bolt --pa`\n"
1035 " branch-list -- protobuf file in etm_branch_list.proto\n"
1036 " Default is autofdo.\n"
1037 "--dump-etm type1,type2,... Dump etm data. A type is one of raw, packet and element.\n"
1038 "--exclude-perf Exclude trace data for the recording process.\n"
1039 "--exclude-process-name process_name_regex Exclude data for processes with name containing\n"
1040 " the regular expression.\n"
1041 "--symdir <dir> Look for binaries in a directory recursively.\n"
1042 "--allow-mismatched-build-id Allow mismatched build ids when searching for debug binaries.\n"
1043 "-j <jobs> Use multiple threads to process branch list files.\n"
1044 "-z Compress branch-list output\n"
1045 "--dump <file> Dump a branch list file.\n"
1046 "\n"
1047 "Examples:\n"
1048 "1. Generate autofdo text output.\n"
1049 "$ simpleperf inject -i perf.data -o autofdo.txt --output autofdo\n"
1050 "\n"
1051 "2. Generate branch list proto, then convert to autofdo text.\n"
1052 "$ simpleperf inject -i perf.data -o branch_list.data --output branch-list\n"
1053 "$ simpleperf inject -i branch_list.data -o autofdo.txt --output autofdo\n"
1054 // clang-format on
1055 ) {}
1056
Run(const std::vector<std::string> & args)1057 bool Run(const std::vector<std::string>& args) override {
1058 GOOGLE_PROTOBUF_VERIFY_VERSION;
1059 if (!ParseOptions(args)) {
1060 return false;
1061 }
1062 if (!dump_branch_list_file_.empty()) {
1063 return DumpBranchListFile(dump_branch_list_file_);
1064 }
1065
1066 CHECK(!input_filenames_.empty());
1067 if (IsPerfDataFile(input_filenames_[0])) {
1068 switch (output_format_) {
1069 case OutputFormat::AutoFDO:
1070 [[fallthrough]];
1071 case OutputFormat::BOLT:
1072 return ConvertPerfDataToAutoFDO();
1073 case OutputFormat::BranchList:
1074 return ConvertPerfDataToBranchList();
1075 }
1076 } else {
1077 switch (output_format_) {
1078 case OutputFormat::AutoFDO:
1079 [[fallthrough]];
1080 case OutputFormat::BOLT:
1081 return ConvertBranchListToAutoFDO();
1082 case OutputFormat::BranchList:
1083 return ConvertBranchListToBranchList();
1084 }
1085 }
1086 }
1087
1088 private:
ParseOptions(const std::vector<std::string> & args)1089 bool ParseOptions(const std::vector<std::string>& args) {
1090 const OptionFormatMap option_formats = {
1091 {"--allow-mismatched-build-id", {OptionValueType::NONE, OptionType::SINGLE}},
1092 {"--binary", {OptionValueType::STRING, OptionType::SINGLE}},
1093 {"--dump", {OptionValueType::STRING, OptionType::SINGLE}},
1094 {"--dump-etm", {OptionValueType::STRING, OptionType::SINGLE}},
1095 {"--exclude-perf", {OptionValueType::NONE, OptionType::SINGLE}},
1096 {"--exclude-process-name", {OptionValueType::STRING, OptionType::MULTIPLE}},
1097 {"-i", {OptionValueType::STRING, OptionType::MULTIPLE}},
1098 {"-j", {OptionValueType::UINT, OptionType::SINGLE}},
1099 {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
1100 {"--output", {OptionValueType::STRING, OptionType::SINGLE}},
1101 {"--symdir", {OptionValueType::STRING, OptionType::MULTIPLE}},
1102 {"-z", {OptionValueType::NONE, OptionType::SINGLE}},
1103 };
1104 OptionValueMap options;
1105 std::vector<std::pair<OptionName, OptionValue>> ordered_options;
1106 if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
1107 return false;
1108 }
1109
1110 if (options.PullBoolValue("--allow-mismatched-build-id")) {
1111 allow_mismatched_build_id_ = true;
1112 Dso::AllowMismatchedBuildId();
1113 }
1114 if (auto value = options.PullValue("--binary"); value) {
1115 binary_name_regex_ = RegEx::Create(value->str_value);
1116 if (binary_name_regex_ == nullptr) {
1117 return false;
1118 }
1119 }
1120 options.PullStringValue("--dump", &dump_branch_list_file_);
1121 if (auto value = options.PullValue("--dump-etm"); value) {
1122 if (!ParseEtmDumpOption(value->str_value, &etm_dump_option_)) {
1123 return false;
1124 }
1125 }
1126 exclude_perf_ = options.PullBoolValue("--exclude-perf");
1127 for (const std::string& value : options.PullStringValues("--exclude-process-name")) {
1128 std::unique_ptr<RegEx> regex = RegEx::Create(value);
1129 if (regex == nullptr) {
1130 return false;
1131 }
1132 exclude_process_names_.emplace_back(std::move(regex));
1133 }
1134
1135 for (const OptionValue& value : options.PullValues("-i")) {
1136 std::vector<std::string> files = android::base::Split(value.str_value, ",");
1137 for (std::string& file : files) {
1138 if (android::base::StartsWith(file, "@")) {
1139 if (!ReadFileList(file.substr(1), &input_filenames_)) {
1140 return false;
1141 }
1142 } else {
1143 input_filenames_.emplace_back(file);
1144 }
1145 }
1146 }
1147 if (input_filenames_.empty()) {
1148 input_filenames_.emplace_back("perf.data");
1149 }
1150 if (!options.PullUintValue("-j", &jobs_, 1)) {
1151 return false;
1152 }
1153 options.PullStringValue("-o", &output_filename_);
1154 if (auto value = options.PullValue("--output"); value) {
1155 const std::string& output = value->str_value;
1156 if (output == "autofdo") {
1157 output_format_ = OutputFormat::AutoFDO;
1158 } else if (output == "bolt") {
1159 output_format_ = OutputFormat::BOLT;
1160 } else if (output == "branch-list") {
1161 output_format_ = OutputFormat::BranchList;
1162 } else {
1163 LOG(ERROR) << "unknown format in --output option: " << output;
1164 return false;
1165 }
1166 }
1167 if (std::vector<OptionValue> values = options.PullValues("--symdir"); !values.empty()) {
1168 for (const OptionValue& value : values) {
1169 if (!Dso::AddSymbolDir(value.str_value)) {
1170 return false;
1171 }
1172 }
1173 // Symbol dirs are cleaned when Dso count is decreased to zero, which can happen between
1174 // processing input files. To make symbol dirs always available, create a placeholder dso to
1175 // prevent cleaning from happening.
1176 placeholder_dso_ = Dso::CreateDso(DSO_UNKNOWN_FILE, "unknown");
1177 }
1178 compress_ = options.PullBoolValue("-z");
1179 CHECK(options.values.empty());
1180 return true;
1181 }
1182
ReadFileList(const std::string & path,std::vector<std::string> * file_list)1183 bool ReadFileList(const std::string& path, std::vector<std::string>* file_list) {
1184 std::string data;
1185 if (!android::base::ReadFileToString(path, &data)) {
1186 PLOG(ERROR) << "failed to read " << path;
1187 return false;
1188 }
1189 std::vector<std::string> tokens = android::base::Tokenize(data, " \t\n\r");
1190 file_list->insert(file_list->end(), tokens.begin(), tokens.end());
1191 return true;
1192 }
1193
ReadPerfDataFiles(const std::function<void (PerfDataReader &)> reader_callback)1194 bool ReadPerfDataFiles(const std::function<void(PerfDataReader&)> reader_callback) {
1195 if (input_filenames_.empty()) {
1196 return true;
1197 }
1198
1199 std::string expected_data_type;
1200 for (const auto& filename : input_filenames_) {
1201 std::unique_ptr<RecordFileReader> file_reader = RecordFileReader::CreateInstance(filename);
1202 if (!file_reader) {
1203 return false;
1204 }
1205 std::string data_type = PerfDataReader::GetDataType(*file_reader);
1206 if (expected_data_type.empty()) {
1207 expected_data_type = data_type;
1208 } else if (expected_data_type != data_type) {
1209 LOG(ERROR) << "files have different data type: " << input_filenames_[0] << ", " << filename;
1210 return false;
1211 }
1212 std::unique_ptr<PerfDataReader> reader;
1213 if (data_type == "etm") {
1214 reader.reset(new ETMPerfDataReader(std::move(file_reader), exclude_perf_,
1215 exclude_process_names_, binary_name_regex_.get(),
1216 etm_dump_option_));
1217 } else if (data_type == "lbr") {
1218 reader.reset(
1219 new LBRPerfDataReader(std::move(file_reader), exclude_perf_, binary_name_regex_.get()));
1220 } else {
1221 LOG(ERROR) << "unsupported data type " << data_type << " in " << filename;
1222 return false;
1223 }
1224 reader_callback(*reader);
1225 if (!reader->Read()) {
1226 return false;
1227 }
1228 }
1229 return true;
1230 }
1231
ConvertPerfDataToAutoFDO()1232 bool ConvertPerfDataToAutoFDO() {
1233 AutoFDOWriter autofdo_writer;
1234 auto afdo_callback = [&](const BinaryKey& key, AutoFDOBinaryInfo& binary) {
1235 autofdo_writer.AddAutoFDOBinary(key, binary);
1236 };
1237 auto reader_callback = [&](PerfDataReader& reader) { reader.AddCallback(afdo_callback); };
1238 if (!ReadPerfDataFiles(reader_callback)) {
1239 return false;
1240 }
1241 if (output_format_ == OutputFormat::AutoFDO) {
1242 return autofdo_writer.WriteAutoFDO(output_filename_);
1243 }
1244 CHECK(output_format_ == OutputFormat::BOLT);
1245 return autofdo_writer.WriteBolt(output_filename_);
1246 }
1247
ConvertPerfDataToBranchList()1248 bool ConvertPerfDataToBranchList() {
1249 BranchListMerger merger;
1250 auto etm_callback = [&](const BinaryKey& key, ETMBinary& binary) {
1251 merger.AddETMBinary(key, binary);
1252 };
1253 auto lbr_callback = [&](LBRData& lbr_data) { merger.AddLBRData(lbr_data); };
1254
1255 auto reader_callback = [&](PerfDataReader& reader) {
1256 reader.AddCallback(etm_callback);
1257 reader.AddCallback(lbr_callback);
1258 };
1259 if (!ReadPerfDataFiles(reader_callback)) {
1260 return false;
1261 }
1262 return WriteBranchListFile(output_filename_, merger.GetETMData(), merger.GetLBRData(),
1263 compress_);
1264 }
1265
ConvertBranchListToAutoFDO()1266 bool ConvertBranchListToAutoFDO() {
1267 // Step1 : Merge branch lists from all input files.
1268 BranchListMergedReader reader(allow_mismatched_build_id_, binary_name_regex_.get(), jobs_);
1269 std::unique_ptr<BranchListMerger> merger = reader.Read(input_filenames_);
1270 if (!merger) {
1271 return false;
1272 }
1273
1274 // Step2: Convert ETMBinary and LBRData to AutoFDOBinaryInfo.
1275 AutoFDOWriter autofdo_writer;
1276 ETMBranchListToAutoFDOConverter converter;
1277 for (auto& p : merger->GetETMData()) {
1278 const BinaryKey& key = p.first;
1279 ETMBinary& binary = p.second;
1280 std::unique_ptr<AutoFDOBinaryInfo> autofdo_binary = converter.Convert(key, binary);
1281 if (autofdo_binary) {
1282 // Create new BinaryKey with kernel_start_addr = 0. Because AutoFDO output doesn't care
1283 // kernel_start_addr.
1284 autofdo_writer.AddAutoFDOBinary(BinaryKey(key.path, key.build_id), *autofdo_binary);
1285 }
1286 }
1287 if (!merger->GetLBRData().samples.empty()) {
1288 LBRData& lbr_data = merger->GetLBRData();
1289 std::optional<std::vector<AutoFDOBinaryInfo>> binaries = ConvertLBRDataToAutoFDO(lbr_data);
1290 if (!binaries) {
1291 return false;
1292 }
1293 for (size_t i = 0; i < binaries.value().size(); ++i) {
1294 BinaryKey& key = lbr_data.binaries[i];
1295 AutoFDOBinaryInfo& binary = binaries.value()[i];
1296 std::unique_ptr<Dso> dso = Dso::CreateDsoWithBuildId(DSO_ELF_FILE, key.path, key.build_id);
1297 if (!dso) {
1298 continue;
1299 }
1300 binary.executable_segments = GetExecutableSegments(dso.get());
1301 autofdo_writer.AddAutoFDOBinary(key, binary);
1302 }
1303 }
1304
1305 // Step3: Write AutoFDOBinaryInfo.
1306 if (output_format_ == OutputFormat::AutoFDO) {
1307 return autofdo_writer.WriteAutoFDO(output_filename_);
1308 }
1309 CHECK(output_format_ == OutputFormat::BOLT);
1310 return autofdo_writer.WriteBolt(output_filename_);
1311 }
1312
ConvertBranchListToBranchList()1313 bool ConvertBranchListToBranchList() {
1314 // Step1 : Merge branch lists from all input files.
1315 BranchListMergedReader reader(allow_mismatched_build_id_, binary_name_regex_.get(), jobs_);
1316 std::unique_ptr<BranchListMerger> merger = reader.Read(input_filenames_);
1317 if (!merger) {
1318 return false;
1319 }
1320 // Step2: Write ETMBinary.
1321 return WriteBranchListFile(output_filename_, merger->GetETMData(), merger->GetLBRData(),
1322 compress_);
1323 }
1324
1325 std::unique_ptr<RegEx> binary_name_regex_;
1326 bool exclude_perf_ = false;
1327 std::vector<std::unique_ptr<RegEx>> exclude_process_names_;
1328 std::vector<std::string> input_filenames_;
1329 std::string output_filename_ = "perf_inject.data";
1330 OutputFormat output_format_ = OutputFormat::AutoFDO;
1331 ETMDumpOption etm_dump_option_;
1332 bool compress_ = false;
1333 bool allow_mismatched_build_id_ = false;
1334 size_t jobs_ = 1;
1335 std::string dump_branch_list_file_;
1336
1337 std::unique_ptr<Dso> placeholder_dso_;
1338 };
1339
1340 } // namespace
1341
RegisterInjectCommand()1342 void RegisterInjectCommand() {
1343 return RegisterCommand("inject", [] { return std::unique_ptr<Command>(new InjectCommand); });
1344 }
1345
1346 } // namespace simpleperf
1347