You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
426 lines
14 KiB
426 lines
14 KiB
/*
|
|
* 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() {
|
|
std::vector<EventAttrWithId> attrs0 = readers_[0]->AttrSection();
|
|
for (size_t i = 1; i < readers_.size(); i++) {
|
|
std::vector<EventAttrWithId> 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 (memcmp(attrs[attr_id].attr, attrs0[attr_id].attr, sizeof(perf_event_attr)) != 0) {
|
|
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();
|
|
std::vector<EventAttrWithId> 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) {
|
|
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;
|
|
size_t read_pos = 0;
|
|
while (reader->ReadFileFeature(read_pos, &file)) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
// 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
|