1 /*
2 * Copyright (C) 2020 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 <stdio.h>
18
19 #include <memory>
20 #include <regex>
21 #include <string>
22
23 #include <android-base/macros.h>
24 #include <android-base/strings.h>
25
26 #include "command.h"
27 #include "event_attr.h"
28 #include "record_file.h"
29 #include "thread_tree.h"
30 #include "utils.h"
31
32 namespace simpleperf {
33 namespace {
34
35 class MergedFileFeature {
36 public:
MergedFileFeature(FileFeature & file)37 MergedFileFeature(FileFeature& file)
38 : path_(file.path),
39 type_(file.type),
40 min_vaddr_(file.min_vaddr),
41 file_offset_of_min_vaddr_(file.file_offset_of_min_vaddr),
42 dex_file_offsets_(std::move(file.dex_file_offsets)) {
43 for (auto& symbol : file.symbols) {
44 symbol_map_.emplace(symbol.addr, std::move(symbol));
45 }
46 }
47
Merge(FileFeature & file)48 bool Merge(FileFeature& file) {
49 if (file.type != type_ || file.min_vaddr != min_vaddr_ ||
50 file.file_offset_of_min_vaddr != file_offset_of_min_vaddr_ ||
51 file.dex_file_offsets != dex_file_offsets_) {
52 return false;
53 }
54 for (auto& symbol : file.symbols) {
55 auto it = symbol_map_.lower_bound(symbol.addr);
56 if (it != symbol_map_.end()) {
57 const auto& found = it->second;
58 if (found.addr == symbol.addr && found.len == symbol.len &&
59 strcmp(found.Name(), symbol.Name()) == 0) {
60 // The symbol already exists in symbol_map.
61 continue;
62 }
63 if (symbol.addr + symbol.len > found.addr) {
64 // an address conflict with the next symbol
65 return false;
66 }
67 }
68 if (it != symbol_map_.begin()) {
69 --it;
70 if (it->second.addr + it->second.len > symbol.addr) {
71 // an address conflict with the previous symbol
72 return false;
73 }
74 }
75 symbol_map_.emplace(symbol.addr, std::move(symbol));
76 }
77 return true;
78 }
79
ToFileFeature(FileFeature * file) const80 void ToFileFeature(FileFeature* file) const {
81 file->path = path_;
82 file->type = type_;
83 file->min_vaddr = min_vaddr_;
84 file->file_offset_of_min_vaddr = file_offset_of_min_vaddr_;
85 file->symbol_ptrs.clear();
86 for (const auto& [_, symbol] : symbol_map_) {
87 file->symbol_ptrs.emplace_back(&symbol);
88 }
89 file->dex_file_offsets = dex_file_offsets_;
90 }
91
92 private:
93 std::string path_;
94 DsoType type_;
95 uint64_t min_vaddr_;
96 uint64_t file_offset_of_min_vaddr_;
97 std::map<uint64_t, Symbol> symbol_map_;
98 std::vector<uint64_t> dex_file_offsets_;
99
100 DISALLOW_COPY_AND_ASSIGN(MergedFileFeature);
101 };
102
103 class MergeCommand : public Command {
104 public:
MergeCommand()105 MergeCommand()
106 : Command("merge", "merge multiple perf.data into one",
107 // clang-format off
108 "Usage: simpleperf merge [options]\n"
109 " Merge multiple perf.data into one. The input files should be recorded on the same\n"
110 " device using the same event types.\n"
111 "-i <file1>,<file2>,... Input recording files separated by comma\n"
112 "-o <file> output recording file\n"
113 "\n"
114 "Examples:\n"
115 "$ simpleperf merge -i perf1.data,perf2.data -o perf.data\n"
116 // clang-format on
117 ) {}
118
Run(const std::vector<std::string> & args)119 bool Run(const std::vector<std::string>& args) override {
120 // 1. Parse options.
121 if (!ParseOptions(args)) {
122 return false;
123 }
124
125 // 2. Open input files and check if they are mergeable.
126 for (const auto& file : input_files_) {
127 readers_.emplace_back(RecordFileReader::CreateInstance(file));
128 if (!readers_.back()) {
129 return false;
130 }
131 }
132 if (!IsMergeable()) {
133 return false;
134 }
135
136 // 3. Merge files.
137 writer_ = RecordFileWriter::CreateInstance(output_file_);
138 if (!writer_) {
139 return false;
140 }
141 if (!MergeAttrSection() || !MergeDataSection() || !MergeFeatureSection()) {
142 return false;
143 }
144 return writer_->Close();
145 }
146
147 private:
ParseOptions(const std::vector<std::string> & args)148 bool ParseOptions(const std::vector<std::string>& args) {
149 const OptionFormatMap option_formats = {
150 {"-i", {OptionValueType::STRING, OptionType::MULTIPLE}},
151 {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
152 };
153 OptionValueMap options;
154 std::vector<std::pair<OptionName, OptionValue>> ordered_options;
155 if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
156 return false;
157 }
158 for (const OptionValue& value : options.PullValues("-i")) {
159 auto files = android::base::Split(*value.str_value, ",");
160 input_files_.insert(input_files_.end(), files.begin(), files.end());
161 }
162 options.PullStringValue("-o", &output_file_);
163
164 CHECK(options.values.empty());
165
166 if (input_files_.empty()) {
167 LOG(ERROR) << "missing input files";
168 return false;
169 }
170 if (output_file_.empty()) {
171 LOG(ERROR) << "missing output file";
172 return false;
173 }
174 return true;
175 }
176
IsMergeable()177 bool IsMergeable() { return CheckFeatureSection() && CheckAttrSection(); }
178
179 // Check feature sections to know if the recording environments are the same.
CheckFeatureSection()180 bool CheckFeatureSection() {
181 auto get_arch = [](std::unique_ptr<RecordFileReader>& reader) {
182 return reader->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
183 };
184 auto get_kernel_version = [](std::unique_ptr<RecordFileReader>& reader) {
185 return reader->ReadFeatureString(PerfFileFormat::FEAT_OSRELEASE);
186 };
187 auto get_meta_info = [](std::unique_ptr<RecordFileReader>& reader, const char* key) {
188 auto it = reader->GetMetaInfoFeature().find(key);
189 return it == reader->GetMetaInfoFeature().end() ? "" : it->second;
190 };
191 auto get_simpleperf_version = [&](std::unique_ptr<RecordFileReader>& reader) {
192 return get_meta_info(reader, "simpleperf_version");
193 };
194 auto get_trace_offcpu = [&](std::unique_ptr<RecordFileReader>& reader) {
195 return get_meta_info(reader, "trace_offcpu");
196 };
197 auto get_event_types = [&](std::unique_ptr<RecordFileReader>& reader) {
198 std::string s = get_meta_info(reader, "event_type_info");
199 std::vector<std::string> v = android::base::Split(s, "\n");
200 std::sort(v.begin(), v.end());
201 return android::base::Join(v, ";");
202 };
203 auto get_android_device = [&](std::unique_ptr<RecordFileReader>& reader) {
204 return get_meta_info(reader, "product_props");
205 };
206 auto get_android_version = [&](std::unique_ptr<RecordFileReader>& reader) {
207 return get_meta_info(reader, "android_version");
208 };
209 auto get_app_package_name = [&](std::unique_ptr<RecordFileReader>& reader) {
210 return get_meta_info(reader, "app_package_name");
211 };
212 auto get_clockid = [&](std::unique_ptr<RecordFileReader>& reader) {
213 return get_meta_info(reader, "clockid");
214 };
215 auto get_used_features = [](std::unique_ptr<RecordFileReader>& reader) {
216 std::string s;
217 for (const auto& [key, _] : reader->FeatureSectionDescriptors()) {
218 s += std::to_string(key) + ",";
219 }
220 return s;
221 };
222
223 using value_func_t = std::function<std::string(std::unique_ptr<RecordFileReader>&)>;
224 std::vector<std::pair<std::string, value_func_t>> check_entries = {
225 std::make_pair("arch", get_arch),
226 std::make_pair("kernel_version", get_kernel_version),
227 std::make_pair("simpleperf_version", get_simpleperf_version),
228 std::make_pair("trace_offcpu", get_trace_offcpu),
229 std::make_pair("event_types", get_event_types),
230 std::make_pair("android_device", get_android_device),
231 std::make_pair("android_version", get_android_version),
232 std::make_pair("app_package_name", get_app_package_name),
233 std::make_pair("clockid", get_clockid),
234 std::make_pair("used_features", get_used_features),
235 };
236
237 for (const auto& [name, get_value] : check_entries) {
238 std::string value0 = get_value(readers_[0]);
239 for (size_t i = 1; i < readers_.size(); i++) {
240 std::string value = get_value(readers_[i]);
241 if (value != value0) {
242 LOG(ERROR) << input_files_[0] << " and " << input_files_[i] << " are not mergeable for "
243 << name << " difference: " << value0 << " vs " << value;
244 return false;
245 }
246 }
247 }
248
249 if (readers_[0]->HasFeature(PerfFileFormat::FEAT_AUXTRACE)) {
250 LOG(ERROR) << "merging of recording files with auxtrace feature isn't supported";
251 return false;
252 }
253 return true;
254 }
255
256 // Check attr sections to know if recorded event types are the same.
CheckAttrSection()257 bool CheckAttrSection() {
258 std::vector<EventAttrWithId> attrs0 = readers_[0]->AttrSection();
259 for (size_t i = 1; i < readers_.size(); i++) {
260 std::vector<EventAttrWithId> attrs = readers_[i]->AttrSection();
261 if (attrs.size() != attrs0.size()) {
262 LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
263 << " are not mergeable for recording different event types";
264 return false;
265 }
266 for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) {
267 if (memcmp(attrs[attr_id].attr, attrs0[attr_id].attr, sizeof(perf_event_attr)) != 0) {
268 LOG(ERROR) << input_files_[0] << " and " << input_files_[i]
269 << " are not mergeable for recording different event types";
270 return false;
271 }
272 }
273 }
274 return true;
275 }
276
MergeAttrSection()277 bool MergeAttrSection() { return writer_->WriteAttrSection(readers_[0]->AttrSection()); }
278
MergeDataSection()279 bool MergeDataSection() {
280 for (size_t i = 0; i < readers_.size(); i++) {
281 if (i != 0) {
282 if (!WriteGapInDataSection(i - 1, i)) {
283 return false;
284 }
285 }
286 auto callback = [this](std::unique_ptr<Record> record) {
287 return ProcessRecord(record.get());
288 };
289 if (!readers_[i]->ReadDataSection(callback)) {
290 return false;
291 }
292 }
293 return true;
294 }
295
ProcessRecord(Record * record)296 bool ProcessRecord(Record* record) { return writer_->WriteRecord(*record); }
297
WriteGapInDataSection(size_t prev_reader_id,size_t next_reader_id)298 bool WriteGapInDataSection(size_t prev_reader_id, size_t next_reader_id) {
299 // MergeAttrSection() only maps event_ids in readers_[0] to event attrs. So we need to
300 // map event_ids in readers_[next_read_id] to event attrs. The map info is put into an
301 // EventIdRecord.
302 const std::unordered_map<uint64_t, size_t>& cur_map = readers_[prev_reader_id]->EventIdMap();
303 std::vector<EventAttrWithId> attrs = readers_[next_reader_id]->AttrSection();
304 std::vector<uint64_t> event_id_data;
305 for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) {
306 for (size_t event_id : attrs[attr_id].ids) {
307 if (auto it = cur_map.find(event_id); it == cur_map.end() || it->second != attr_id) {
308 event_id_data.push_back(attr_id);
309 event_id_data.push_back(event_id);
310 }
311 }
312 }
313 if (!event_id_data.empty()) {
314 EventIdRecord record(event_id_data);
315 if (!ProcessRecord(&record)) {
316 return false;
317 }
318 }
319 return true;
320 }
321
MergeFeatureSection()322 bool MergeFeatureSection() {
323 std::vector<int> features;
324 for (const auto& [key, _] : readers_[0]->FeatureSectionDescriptors()) {
325 features.push_back(key);
326 }
327 if (!writer_->BeginWriteFeatures(features.size())) {
328 return false;
329 }
330 for (int feature : features) {
331 if (feature == PerfFileFormat::FEAT_OSRELEASE || feature == PerfFileFormat::FEAT_ARCH ||
332 feature == PerfFileFormat::FEAT_BRANCH_STACK ||
333 feature == PerfFileFormat::FEAT_META_INFO || feature == PerfFileFormat::FEAT_CMDLINE) {
334 std::vector<char> data;
335 if (!readers_[0]->ReadFeatureSection(feature, &data) ||
336 !writer_->WriteFeature(feature, data.data(), data.size())) {
337 return false;
338 }
339 } else if (feature == PerfFileFormat::FEAT_BUILD_ID) {
340 WriteBuildIdFeature();
341 } else if (feature == PerfFileFormat::FEAT_FILE) {
342 WriteFileFeature();
343 } else {
344 LOG(WARNING) << "Drop feature " << feature << ", which isn't supported in the merge cmd.";
345 }
346 }
347 return writer_->EndWriteFeatures();
348 }
349
WriteBuildIdFeature()350 bool WriteBuildIdFeature() {
351 std::map<std::string, BuildIdRecord> build_ids;
352 std::unordered_set<std::string> files_to_drop;
353 for (auto& reader : readers_) {
354 for (auto& record : reader->ReadBuildIdFeature()) {
355 auto it = build_ids.find(record.filename);
356 if (it == build_ids.end()) {
357 build_ids.emplace(record.filename, std::move(record));
358 } else if (it->second.build_id != record.build_id) {
359 if (files_to_drop.count(record.filename) == 0) {
360 files_to_drop.emplace(record.filename);
361 LOG(WARNING)
362 << record.filename
363 << " has different build ids in different record files. So drop its build ids.";
364 }
365 }
366 }
367 }
368 std::vector<BuildIdRecord> records;
369 for (auto& [filename, record] : build_ids) {
370 if (files_to_drop.count(filename) == 0) {
371 records.emplace_back(std::move(record));
372 }
373 }
374 return writer_->WriteBuildIdFeature(records);
375 }
376
WriteFileFeature()377 bool WriteFileFeature() {
378 std::map<std::string, MergedFileFeature> file_map;
379 std::unordered_set<std::string> files_to_drop;
380
381 // Read file features.
382 for (auto& reader : readers_) {
383 FileFeature file;
384 size_t read_pos = 0;
385 while (reader->ReadFileFeature(read_pos, &file)) {
386 if (files_to_drop.count(file.path) != 0) {
387 continue;
388 }
389 if (auto it = file_map.find(file.path); it == file_map.end()) {
390 file_map.emplace(file.path, file);
391 } else if (!it->second.Merge(file)) {
392 LOG(WARNING)
393 << file.path
394 << " has address-conflict symbols in different record files. So drop its symbols.";
395 files_to_drop.emplace(file.path);
396 }
397 }
398 }
399 // Write file features.
400 for (const auto& [file_path, file] : file_map) {
401 if (files_to_drop.count(file_path) != 0) {
402 continue;
403 }
404 FileFeature file_feature;
405 file.ToFileFeature(&file_feature);
406 if (!writer_->WriteFileFeature(file_feature)) {
407 return false;
408 }
409 }
410 return true;
411 }
412
413 std::vector<std::string> input_files_;
414 std::vector<std::unique_ptr<RecordFileReader>> readers_;
415 std::string output_file_;
416 std::unique_ptr<RecordFileWriter> writer_;
417 };
418
419 } // namespace
420
RegisterMergeCommand()421 void RegisterMergeCommand() {
422 return RegisterCommand("merge", [] { return std::unique_ptr<Command>(new MergeCommand); });
423 }
424
425 } // namespace simpleperf
426