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.
380 lines
12 KiB
380 lines
12 KiB
/*
|
|
* Copyright (C) 2019 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 <algorithm>
|
|
#include <vector>
|
|
|
|
#include <google/protobuf/compiler/importer.h>
|
|
#include <google/protobuf/dynamic_message.h>
|
|
#include <google/protobuf/io/zero_copy_stream_impl.h>
|
|
|
|
#include "perfetto/ext/base/file_utils.h"
|
|
#include "perfetto/ext/base/scoped_file.h"
|
|
#include "perfetto/protozero/field.h"
|
|
#include "perfetto/protozero/packed_repeated_fields.h"
|
|
#include "perfetto/protozero/proto_decoder.h"
|
|
#include "perfetto/protozero/proto_utils.h"
|
|
#include "perfetto/protozero/scattered_heap_buffer.h"
|
|
|
|
#include "protos/third_party/pprof/profile.pbzero.h"
|
|
|
|
namespace perfetto {
|
|
namespace protoprofile {
|
|
namespace {
|
|
|
|
using protozero::proto_utils::ProtoWireType;
|
|
using ::google::protobuf::Descriptor;
|
|
using ::google::protobuf::DynamicMessageFactory;
|
|
using ::google::protobuf::FieldDescriptor;
|
|
using ::google::protobuf::FileDescriptor;
|
|
using ::google::protobuf::Message;
|
|
using ::google::protobuf::compiler::DiskSourceTree;
|
|
using ::google::protobuf::compiler::Importer;
|
|
using ::google::protobuf::compiler::MultiFileErrorCollector;
|
|
|
|
class MultiFileErrorCollectorImpl : public MultiFileErrorCollector {
|
|
public:
|
|
~MultiFileErrorCollectorImpl() override;
|
|
void AddError(const std::string& filename,
|
|
int line,
|
|
int column,
|
|
const std::string& message) override;
|
|
|
|
void AddWarning(const std::string& filename,
|
|
int line,
|
|
int column,
|
|
const std::string& message) override;
|
|
};
|
|
|
|
MultiFileErrorCollectorImpl::~MultiFileErrorCollectorImpl() = default;
|
|
|
|
void MultiFileErrorCollectorImpl::AddError(const std::string& filename,
|
|
int line,
|
|
int column,
|
|
const std::string& message) {
|
|
PERFETTO_ELOG("Error %s %d:%d: %s", filename.c_str(), line, column,
|
|
message.c_str());
|
|
}
|
|
|
|
void MultiFileErrorCollectorImpl::AddWarning(const std::string& filename,
|
|
int line,
|
|
int column,
|
|
const std::string& message) {
|
|
PERFETTO_ELOG("Error %s %d:%d: %s", filename.c_str(), line, column,
|
|
message.c_str());
|
|
}
|
|
|
|
class SizeProfileComputer {
|
|
public:
|
|
std::string Compute(const uint8_t* ptr,
|
|
size_t size,
|
|
const Descriptor* descriptor);
|
|
|
|
private:
|
|
struct StackInfo {
|
|
std::vector<size_t> samples;
|
|
std::vector<int> locations;
|
|
};
|
|
|
|
void ComputeInner(const uint8_t* ptr,
|
|
size_t size,
|
|
const Descriptor* descriptor);
|
|
void Sample(size_t size);
|
|
int InternString(const std::string& str);
|
|
int InternLocation(const std::string& str);
|
|
size_t GetFieldSize(const protozero::Field& f);
|
|
|
|
// Convert the current stack into a string:
|
|
// {"Foo", "#bar", "Bar", "#baz", "int"} -> "Foo$#bar$Bar$#baz$int$"
|
|
std::string StackToKey();
|
|
|
|
// The current 'stack' we're considering as we parse the protobuf.
|
|
// For example if we're currently looking at the varint field baz which is
|
|
// nested inside message Bar which is in turn a field named bar on the message
|
|
// Foo. Then the stack would be: Foo, #bar, Bar, #baz, int
|
|
// We keep track of both the field names (#bar, #baz) and the field types
|
|
// (Foo, Bar, int) as sometimes we are intrested in which fields are big
|
|
// and sometimes which types are big.
|
|
std::vector<std::string> stack_;
|
|
|
|
// Information about each stack seen. Keyed by a unique string for each stack.
|
|
std::map<std::string, StackInfo> stack_info_;
|
|
|
|
// Interned strings:
|
|
std::vector<std::string> strings_;
|
|
std::map<std::string, int> string_to_id_;
|
|
|
|
// Interned 'locations', each location is a single frame of the stack.
|
|
std::map<std::string, int> locations_;
|
|
};
|
|
|
|
size_t SizeProfileComputer::GetFieldSize(const protozero::Field& f) {
|
|
uint8_t buf[10];
|
|
switch (f.type()) {
|
|
case protozero::proto_utils::ProtoWireType::kVarInt:
|
|
return static_cast<size_t>(
|
|
protozero::proto_utils::WriteVarInt(f.as_uint64(), buf) - buf);
|
|
case protozero::proto_utils::ProtoWireType::kLengthDelimited:
|
|
return f.size();
|
|
case protozero::proto_utils::ProtoWireType::kFixed32:
|
|
return 4;
|
|
case protozero::proto_utils::ProtoWireType::kFixed64:
|
|
return 8;
|
|
}
|
|
PERFETTO_FATAL("unexpected field type"); // for gcc
|
|
}
|
|
|
|
int SizeProfileComputer::InternString(const std::string& s) {
|
|
if (string_to_id_.count(s)) {
|
|
return string_to_id_[s];
|
|
}
|
|
strings_.push_back(s);
|
|
int id = static_cast<int>(strings_.size() - 1);
|
|
string_to_id_[s] = id;
|
|
return id;
|
|
}
|
|
|
|
int SizeProfileComputer::InternLocation(const std::string& s) {
|
|
if (locations_.count(s)) {
|
|
return locations_[s];
|
|
}
|
|
int id = static_cast<int>(locations_.size()) + 1;
|
|
locations_[s] = id;
|
|
return id;
|
|
}
|
|
|
|
std::string SizeProfileComputer::StackToKey() {
|
|
std::string key;
|
|
for (const std::string& part : stack_) {
|
|
key += part;
|
|
key += "$";
|
|
}
|
|
return key;
|
|
}
|
|
|
|
void SizeProfileComputer::Sample(size_t size) {
|
|
const std::string& key = StackToKey();
|
|
|
|
if (!stack_info_.count(key)) {
|
|
StackInfo& info = stack_info_[key];
|
|
info.locations.resize(stack_.size());
|
|
for (size_t i = 0; i < stack_.size(); i++) {
|
|
info.locations[i] = InternLocation(stack_[i]);
|
|
}
|
|
}
|
|
|
|
stack_info_[key].samples.push_back(size);
|
|
}
|
|
|
|
std::string SizeProfileComputer::Compute(const uint8_t* ptr,
|
|
size_t size,
|
|
const Descriptor* descriptor) {
|
|
PERFETTO_CHECK(InternString("") == 0);
|
|
ComputeInner(ptr, size, descriptor);
|
|
protozero::HeapBuffered<third_party::perftools::profiles::pbzero::Profile>
|
|
profile;
|
|
|
|
auto* sample_type = profile->add_sample_type();
|
|
sample_type->set_type(InternString("protos"));
|
|
sample_type->set_unit(InternString("count"));
|
|
|
|
sample_type = profile->add_sample_type();
|
|
sample_type->set_type(InternString("max_size"));
|
|
sample_type->set_unit(InternString("bytes"));
|
|
|
|
sample_type = profile->add_sample_type();
|
|
sample_type->set_type(InternString("min_size"));
|
|
sample_type->set_unit(InternString("bytes"));
|
|
|
|
sample_type = profile->add_sample_type();
|
|
sample_type->set_type(InternString("median"));
|
|
sample_type->set_unit(InternString("bytes"));
|
|
|
|
sample_type = profile->add_sample_type();
|
|
sample_type->set_type(InternString("total_size"));
|
|
sample_type->set_unit(InternString("bytes"));
|
|
|
|
// For each unique stack we've seen write out the stats:
|
|
for (auto& id_info : stack_info_) {
|
|
StackInfo& info = id_info.second;
|
|
|
|
protozero::PackedVarInt location_ids;
|
|
auto* sample = profile->add_sample();
|
|
for (auto it = info.locations.rbegin(); it != info.locations.rend(); ++it) {
|
|
location_ids.Append(static_cast<uint64_t>(*it));
|
|
}
|
|
sample->set_location_id(location_ids);
|
|
|
|
std::sort(info.samples.begin(), info.samples.end());
|
|
size_t count = info.samples.size();
|
|
size_t total_size = 0;
|
|
size_t max_size = info.samples[count - 1];
|
|
size_t min_size = info.samples[0];
|
|
size_t median_size = info.samples[count / 2];
|
|
for (size_t i = 0; i < count; ++i)
|
|
total_size += info.samples[i];
|
|
// These have to be in the same order as the sample types above:
|
|
protozero::PackedVarInt values;
|
|
values.Append(static_cast<int64_t>(count));
|
|
values.Append(static_cast<int64_t>(max_size));
|
|
values.Append(static_cast<int64_t>(min_size));
|
|
values.Append(static_cast<int64_t>(median_size));
|
|
values.Append(static_cast<int64_t>(total_size));
|
|
sample->set_value(values);
|
|
}
|
|
|
|
// The proto profile has a two step mapping where samples are associated with
|
|
// locations which in turn are associated to functions. We don't currently
|
|
// distinguish them so we make a 1:1 mapping between the locations and the
|
|
// functions:
|
|
for (const auto& location_id : locations_) {
|
|
auto* location = profile->add_location();
|
|
location->set_id(static_cast<uint64_t>(location_id.second));
|
|
auto* line = location->add_line();
|
|
line->set_function_id(static_cast<uint64_t>(location_id.second));
|
|
}
|
|
|
|
for (const auto& location_id : locations_) {
|
|
auto* function = profile->add_function();
|
|
function->set_id(static_cast<uint64_t>(location_id.second));
|
|
function->set_name(InternString(location_id.first));
|
|
}
|
|
|
|
// Finally the string table. We intern more strings above, so this has to be
|
|
// last.
|
|
for (int i = 0; i < static_cast<int>(strings_.size()); i++) {
|
|
profile->add_string_table(strings_[static_cast<size_t>(i)]);
|
|
}
|
|
return profile.SerializeAsString();
|
|
}
|
|
|
|
void SizeProfileComputer::ComputeInner(const uint8_t* ptr,
|
|
size_t size,
|
|
const Descriptor* descriptor) {
|
|
size_t overhead = size;
|
|
size_t unknown = 0;
|
|
protozero::ProtoDecoder decoder(ptr, size);
|
|
|
|
stack_.push_back(descriptor->name());
|
|
|
|
// Compute the size of each sub-field of this message, subtracting it
|
|
// from overhead and possible adding it to unknown.
|
|
for (;;) {
|
|
if (decoder.bytes_left() == 0)
|
|
break;
|
|
protozero::Field field = decoder.ReadField();
|
|
if (!field.valid()) {
|
|
PERFETTO_ELOG("Field not valid (can mean field id >1000)");
|
|
break;
|
|
}
|
|
|
|
int id = field.id();
|
|
ProtoWireType type = field.type();
|
|
size_t field_size = GetFieldSize(field);
|
|
|
|
overhead -= field_size;
|
|
const FieldDescriptor* field_descriptor = descriptor->FindFieldByNumber(id);
|
|
if (!field_descriptor) {
|
|
unknown += field_size;
|
|
continue;
|
|
}
|
|
|
|
stack_.push_back("#" + field_descriptor->name());
|
|
bool is_message_type =
|
|
field_descriptor->type() == FieldDescriptor::TYPE_MESSAGE;
|
|
if (type == ProtoWireType::kLengthDelimited && is_message_type) {
|
|
ComputeInner(field.data(), field.size(),
|
|
field_descriptor->message_type());
|
|
} else {
|
|
stack_.push_back(field_descriptor->type_name());
|
|
Sample(field_size);
|
|
stack_.pop_back();
|
|
}
|
|
stack_.pop_back();
|
|
}
|
|
|
|
if (unknown) {
|
|
stack_.push_back("#:unknown:");
|
|
Sample(unknown);
|
|
stack_.pop_back();
|
|
}
|
|
|
|
// Anything not blamed on a child is overhead for this message.
|
|
Sample(overhead);
|
|
stack_.pop_back();
|
|
}
|
|
|
|
int PrintUsage(int, const char** argv) {
|
|
fprintf(stderr, "Usage: %s INPUT_PATH OUTPUT_PATH\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
int Main(int argc, const char** argv) {
|
|
if (argc != 3)
|
|
return PrintUsage(argc, argv);
|
|
|
|
const char* input_path = argv[1];
|
|
const char* output_path = argv[2];
|
|
|
|
base::ScopedFile proto_fd = base::OpenFile(input_path, O_RDONLY);
|
|
if (!proto_fd) {
|
|
PERFETTO_ELOG("Could not open input path (%s)", input_path);
|
|
return 1;
|
|
}
|
|
|
|
std::string s;
|
|
base::ReadFileDescriptor(proto_fd.get(), &s);
|
|
|
|
const Descriptor* descriptor;
|
|
DiskSourceTree dst;
|
|
dst.MapPath("", "");
|
|
MultiFileErrorCollectorImpl mfe;
|
|
Importer importer(&dst, &mfe);
|
|
const FileDescriptor* parsed_file =
|
|
importer.Import("protos/perfetto/trace/trace.proto");
|
|
DynamicMessageFactory dmf;
|
|
descriptor = parsed_file->message_type(0);
|
|
|
|
const uint8_t* start = reinterpret_cast<const uint8_t*>(s.data());
|
|
size_t size = s.size();
|
|
|
|
if (!descriptor) {
|
|
PERFETTO_ELOG("Could not parse trace.proto");
|
|
return 1;
|
|
}
|
|
|
|
base::ScopedFile output_fd =
|
|
base::OpenFile(output_path, O_WRONLY | O_TRUNC | O_CREAT, 0600);
|
|
if (!output_fd) {
|
|
PERFETTO_ELOG("Could not open output path (%s)", output_path);
|
|
return 1;
|
|
}
|
|
SizeProfileComputer computer;
|
|
std::string out = computer.Compute(start, size, descriptor);
|
|
base::WriteAll(output_fd.get(), out.data(), out.size());
|
|
base::FlushFile(output_fd.get());
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace protoprofile
|
|
} // namespace perfetto
|
|
|
|
int main(int argc, const char** argv) {
|
|
return perfetto::protoprofile::Main(argc, argv);
|
|
}
|