blob: 1bf9833fba6a14456ac7dce124c45f18cdb8ed80 [file] [log] [blame]
/*
* 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 <stdio.h>
#include <memory>
#include <regex>
#include <string>
#include <android-base/macros.h>
#include <android-base/strings.h>
#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<uint64_t, Symbol> symbol_map_;
std::vector<uint64_t> 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 <file1>,<file2>,... Input recording files separated by comma\n"
"-o <file> 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<std::string>& 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<std::string>& args) {
const OptionFormatMap option_formats = {
{"-i", {OptionValueType::STRING, OptionType::MULTIPLE}},
{"-o", {OptionValueType::STRING, OptionType::SINGLE}},
};
OptionValueMap options;
std::vector<std::pair<OptionName, OptionValue>> 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<RecordFileReader>& reader) {
return reader->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
};
auto get_kernel_version = [](std::unique_ptr<RecordFileReader>& reader) {
return reader->ReadFeatureString(PerfFileFormat::FEAT_OSRELEASE);
};
auto get_meta_info = [](std::unique_ptr<RecordFileReader>& reader, const char* key) {
auto it = reader->GetMetaInfoFeature().find(key);
return it == reader->GetMetaInfoFeature().end() ? "" : it->second;
};
auto get_simpleperf_version = [&](std::unique_ptr<RecordFileReader>& reader) {
return get_meta_info(reader, "simpleperf_version");
};
auto get_trace_offcpu = [&](std::unique_ptr<RecordFileReader>& reader) {
return get_meta_info(reader, "trace_offcpu");
};
auto get_event_types = [&](std::unique_ptr<RecordFileReader>& reader) {
std::string s = get_meta_info(reader, "event_type_info");
std::vector<std::string> v = android::base::Split(s, "\n");
std::sort(v.begin(), v.end());
return android::base::Join(v, ";");
};
auto get_android_device = [&](std::unique_ptr<RecordFileReader>& reader) {
return get_meta_info(reader, "product_props");
};
auto get_android_version = [&](std::unique_ptr<RecordFileReader>& reader) {
return get_meta_info(reader, "android_version");
};
auto get_app_package_name = [&](std::unique_ptr<RecordFileReader>& reader) {
return get_meta_info(reader, "app_package_name");
};
auto get_clockid = [&](std::unique_ptr<RecordFileReader>& reader) {
return get_meta_info(reader, "clockid");
};
auto get_used_features = [](std::unique_ptr<RecordFileReader>& reader) {
std::string s;
for (const auto& [key, _] : reader->FeatureSectionDescriptors()) {
s += std::to_string(key) + ",";
}
return s;
};
using value_func_t = std::function<std::string(std::unique_ptr<RecordFileReader>&)>;
std::vector<std::pair<std::string, value_func_t>> 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> 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<uint64_t, size_t>& cur_map = readers_[prev_reader_id]->EventIdMap();
const EventAttrIds& attrs = readers_[next_reader_id]->AttrSection();
std::vector<uint64_t> 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<int> 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<char> 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<std::string, BuildIdRecord> build_ids;
std::unordered_set<std::string> 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<BuildIdRecord> 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<std::string, MergedFileFeature> file_map;
std::unordered_set<std::string> 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<std::string> input_files_;
std::vector<std::unique_ptr<RecordFileReader>> readers_;
std::string output_file_;
std::unique_ptr<RecordFileWriter> writer_;
};
} // namespace
void RegisterMergeCommand() {
return RegisterCommand("merge", [] { return std::unique_ptr<Command>(new MergeCommand); });
}
} // namespace simpleperf