/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include "command.h" #include "event_attr.h" #include "record_file.h" #include "thread_tree.h" #include "utils.h" namespace simpleperf { namespace { class MergedFileFeature { public: MergedFileFeature(FileFeature& file) : path_(file.path), type_(file.type), min_vaddr_(file.min_vaddr), file_offset_of_min_vaddr_(file.file_offset_of_min_vaddr), dex_file_offsets_(std::move(file.dex_file_offsets)) { for (auto& symbol : file.symbols) { symbol_map_.emplace(symbol.addr, std::move(symbol)); } } bool Merge(FileFeature& file) { if (file.type != type_ || file.min_vaddr != min_vaddr_ || file.file_offset_of_min_vaddr != file_offset_of_min_vaddr_ || file.dex_file_offsets != dex_file_offsets_) { return false; } for (auto& symbol : file.symbols) { auto it = symbol_map_.lower_bound(symbol.addr); if (it != symbol_map_.end()) { const auto& found = it->second; if (found.addr == symbol.addr && found.len == symbol.len && strcmp(found.Name(), symbol.Name()) == 0) { // The symbol already exists in symbol_map. continue; } if (symbol.addr + symbol.len > found.addr) { // an address conflict with the next symbol return false; } } if (it != symbol_map_.begin()) { --it; if (it->second.addr + it->second.len > symbol.addr) { // an address conflict with the previous symbol return false; } } symbol_map_.emplace(symbol.addr, std::move(symbol)); } return true; } void ToFileFeature(FileFeature* file) const { file->path = path_; file->type = type_; file->min_vaddr = min_vaddr_; file->file_offset_of_min_vaddr = file_offset_of_min_vaddr_; file->symbol_ptrs.clear(); for (const auto& [_, symbol] : symbol_map_) { file->symbol_ptrs.emplace_back(&symbol); } file->dex_file_offsets = dex_file_offsets_; } private: std::string path_; DsoType type_; uint64_t min_vaddr_; uint64_t file_offset_of_min_vaddr_; std::map symbol_map_; std::vector dex_file_offsets_; DISALLOW_COPY_AND_ASSIGN(MergedFileFeature); }; class MergeCommand : public Command { public: MergeCommand() : Command("merge", "merge multiple perf.data into one", // clang-format off "Usage: simpleperf merge [options]\n" " Merge multiple perf.data into one. The input files should be recorded on the same\n" " device using the same event types.\n" "-i ,,... Input recording files separated by comma\n" "-o output recording file\n" "\n" "Examples:\n" "$ simpleperf merge -i perf1.data,perf2.data -o perf.data\n" // clang-format on ) {} bool Run(const std::vector& args) override { // 1. Parse options. if (!ParseOptions(args)) { return false; } // 2. Open input files and check if they are mergeable. for (const auto& file : input_files_) { readers_.emplace_back(RecordFileReader::CreateInstance(file)); if (!readers_.back()) { return false; } } if (!IsMergeable()) { return false; } // 3. Merge files. writer_ = RecordFileWriter::CreateInstance(output_file_); if (!writer_) { return false; } if (!MergeAttrSection() || !MergeDataSection() || !MergeFeatureSection()) { return false; } return writer_->Close(); } private: bool ParseOptions(const std::vector& args) { const OptionFormatMap option_formats = { {"-i", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"-o", {OptionValueType::STRING, OptionType::SINGLE}}, }; OptionValueMap options; std::vector> ordered_options; if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) { return false; } for (const OptionValue& value : options.PullValues("-i")) { auto files = android::base::Split(*value.str_value, ","); input_files_.insert(input_files_.end(), files.begin(), files.end()); } options.PullStringValue("-o", &output_file_); CHECK(options.values.empty()); if (input_files_.empty()) { LOG(ERROR) << "missing input files"; return false; } if (output_file_.empty()) { LOG(ERROR) << "missing output file"; return false; } return true; } bool IsMergeable() { return CheckFeatureSection() && CheckAttrSection(); } // Check feature sections to know if the recording environments are the same. bool CheckFeatureSection() { auto get_arch = [](std::unique_ptr& reader) { return reader->ReadFeatureString(PerfFileFormat::FEAT_ARCH); }; auto get_kernel_version = [](std::unique_ptr& reader) { return reader->ReadFeatureString(PerfFileFormat::FEAT_OSRELEASE); }; auto get_meta_info = [](std::unique_ptr& reader, const char* key) { auto it = reader->GetMetaInfoFeature().find(key); return it == reader->GetMetaInfoFeature().end() ? "" : it->second; }; auto get_simpleperf_version = [&](std::unique_ptr& reader) { return get_meta_info(reader, "simpleperf_version"); }; auto get_trace_offcpu = [&](std::unique_ptr& reader) { return get_meta_info(reader, "trace_offcpu"); }; auto get_event_types = [&](std::unique_ptr& reader) { std::string s = get_meta_info(reader, "event_type_info"); std::vector v = android::base::Split(s, "\n"); std::sort(v.begin(), v.end()); return android::base::Join(v, ";"); }; auto get_android_device = [&](std::unique_ptr& reader) { return get_meta_info(reader, "product_props"); }; auto get_android_version = [&](std::unique_ptr& reader) { return get_meta_info(reader, "android_version"); }; auto get_app_package_name = [&](std::unique_ptr& reader) { return get_meta_info(reader, "app_package_name"); }; auto get_clockid = [&](std::unique_ptr& reader) { return get_meta_info(reader, "clockid"); }; auto get_used_features = [](std::unique_ptr& reader) { std::string s; for (const auto& [key, _] : reader->FeatureSectionDescriptors()) { s += std::to_string(key) + ","; } return s; }; using value_func_t = std::function&)>; std::vector> check_entries = { std::make_pair("arch", get_arch), std::make_pair("kernel_version", get_kernel_version), std::make_pair("simpleperf_version", get_simpleperf_version), std::make_pair("trace_offcpu", get_trace_offcpu), std::make_pair("event_types", get_event_types), std::make_pair("android_device", get_android_device), std::make_pair("android_version", get_android_version), std::make_pair("app_package_name", get_app_package_name), std::make_pair("clockid", get_clockid), std::make_pair("used_features", get_used_features), }; for (const auto& [name, get_value] : check_entries) { std::string value0 = get_value(readers_[0]); for (size_t i = 1; i < readers_.size(); i++) { std::string value = get_value(readers_[i]); if (value != value0) { LOG(ERROR) << input_files_[0] << " and " << input_files_[i] << " are not mergeable for " << name << " difference: " << value0 << " vs " << value; return false; } } } if (readers_[0]->HasFeature(PerfFileFormat::FEAT_AUXTRACE)) { LOG(ERROR) << "merging of recording files with auxtrace feature isn't supported"; return false; } return true; } // Check attr sections to know if recorded event types are the same. bool CheckAttrSection() { const EventAttrIds& attrs0 = readers_[0]->AttrSection(); for (size_t i = 1; i < readers_.size(); i++) { const EventAttrIds& attrs = readers_[i]->AttrSection(); if (attrs.size() != attrs0.size()) { LOG(ERROR) << input_files_[0] << " and " << input_files_[i] << " are not mergeable for recording different event types"; return false; } for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) { if (attrs[attr_id].attr != attrs0[attr_id].attr) { LOG(ERROR) << input_files_[0] << " and " << input_files_[i] << " are not mergeable for recording different event types"; return false; } } } return true; } bool MergeAttrSection() { return writer_->WriteAttrSection(readers_[0]->AttrSection()); } bool MergeDataSection() { for (size_t i = 0; i < readers_.size(); i++) { if (i != 0) { if (!WriteGapInDataSection(i - 1, i)) { return false; } } auto callback = [this](std::unique_ptr record) { return ProcessRecord(record.get()); }; if (!readers_[i]->ReadDataSection(callback)) { return false; } } return true; } bool ProcessRecord(Record* record) { return writer_->WriteRecord(*record); } bool WriteGapInDataSection(size_t prev_reader_id, size_t next_reader_id) { // MergeAttrSection() only maps event_ids in readers_[0] to event attrs. So we need to // map event_ids in readers_[next_read_id] to event attrs. The map info is put into an // EventIdRecord. const std::unordered_map& cur_map = readers_[prev_reader_id]->EventIdMap(); const EventAttrIds& attrs = readers_[next_reader_id]->AttrSection(); std::vector event_id_data; for (size_t attr_id = 0; attr_id < attrs.size(); attr_id++) { for (size_t event_id : attrs[attr_id].ids) { if (auto it = cur_map.find(event_id); it == cur_map.end() || it->second != attr_id) { event_id_data.push_back(attr_id); event_id_data.push_back(event_id); } } } if (!event_id_data.empty()) { EventIdRecord record(event_id_data); if (!ProcessRecord(&record)) { return false; } } return true; } bool MergeFeatureSection() { std::vector features; for (const auto& [key, _] : readers_[0]->FeatureSectionDescriptors()) { features.push_back(key); } if (!writer_->BeginWriteFeatures(features.size())) { return false; } for (int feature : features) { if (feature == PerfFileFormat::FEAT_OSRELEASE || feature == PerfFileFormat::FEAT_ARCH || feature == PerfFileFormat::FEAT_BRANCH_STACK || feature == PerfFileFormat::FEAT_META_INFO || feature == PerfFileFormat::FEAT_CMDLINE) { std::vector data; if (!readers_[0]->ReadFeatureSection(feature, &data) || !writer_->WriteFeature(feature, data.data(), data.size())) { return false; } } else if (feature == PerfFileFormat::FEAT_BUILD_ID) { WriteBuildIdFeature(); } else if (feature == PerfFileFormat::FEAT_FILE || feature == PerfFileFormat::FEAT_FILE2) { WriteFileFeature(); } else { LOG(WARNING) << "Drop feature " << feature << ", which isn't supported in the merge cmd."; } } return writer_->EndWriteFeatures(); } bool WriteBuildIdFeature() { std::map build_ids; std::unordered_set files_to_drop; for (auto& reader : readers_) { for (auto& record : reader->ReadBuildIdFeature()) { auto it = build_ids.find(record.filename); if (it == build_ids.end()) { build_ids.emplace(record.filename, std::move(record)); } else if (it->second.build_id != record.build_id) { if (files_to_drop.count(record.filename) == 0) { files_to_drop.emplace(record.filename); LOG(WARNING) << record.filename << " has different build ids in different record files. So drop its build ids."; } } } } std::vector records; for (auto& [filename, record] : build_ids) { if (files_to_drop.count(filename) == 0) { records.emplace_back(std::move(record)); } } return writer_->WriteBuildIdFeature(records); } bool WriteFileFeature() { std::map file_map; std::unordered_set files_to_drop; // Read file features. for (auto& reader : readers_) { FileFeature file; uint64_t read_pos = 0; bool error = false; while (reader->ReadFileFeature(read_pos, file, error)) { if (files_to_drop.count(file.path) != 0) { continue; } if (auto it = file_map.find(file.path); it == file_map.end()) { file_map.emplace(file.path, file); } else if (!it->second.Merge(file)) { LOG(WARNING) << file.path << " has address-conflict symbols in different record files. So drop its symbols."; files_to_drop.emplace(file.path); } } if (error) { return false; } } // Write file features. for (const auto& [file_path, file] : file_map) { if (files_to_drop.count(file_path) != 0) { continue; } FileFeature file_feature; file.ToFileFeature(&file_feature); if (!writer_->WriteFileFeature(file_feature)) { return false; } } return true; } std::vector input_files_; std::vector> readers_; std::string output_file_; std::unique_ptr writer_; }; } // namespace void RegisterMergeCommand() { return RegisterCommand("merge", [] { return std::unique_ptr(new MergeCommand); }); } } // namespace simpleperf