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