blob: 649cd22dd617f178a7c9df236572e5296fbbde83 [file] [log] [blame]
/*
* 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 "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/flat_hash_map.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 "src/trace_processor/importers/proto/trace.descriptor.h"
#include "src/trace_processor/util/proto_profiler.h"
#include "protos/third_party/pprof/profile.pbzero.h"
namespace perfetto {
namespace protoprofile {
namespace {
class PprofProfileComputer {
public:
std::string Compute(const uint8_t* ptr,
size_t size,
const std::string& message_type,
trace_processor::DescriptorPool* pool);
private:
int InternString(const std::string& str);
int InternLocation(const std::string& str);
// Interned strings:
std::vector<std::string> strings_;
base::FlatHashMap<std::string, int> string_to_id_;
// Interned 'locations', each location is a single frame of the stack.
base::FlatHashMap<std::string, int> locations_;
};
int PprofProfileComputer::InternString(const std::string& s) {
auto val = string_to_id_.Find(s);
if (val) {
return *val;
}
strings_.push_back(s);
int id = static_cast<int>(strings_.size() - 1);
string_to_id_[s] = id;
return id;
}
int PprofProfileComputer::InternLocation(const std::string& s) {
auto val = locations_.Find(s);
if (val) {
return *val;
}
int id = static_cast<int>(locations_.size()) + 1;
locations_[s] = id;
return id;
}
std::string PprofProfileComputer::Compute(
const uint8_t* ptr,
size_t size,
const std::string& message_type,
trace_processor::DescriptorPool* pool) {
PERFETTO_CHECK(InternString("") == 0);
trace_processor::util::SizeProfileComputer computer(pool, message_type);
computer.Reset(ptr, size);
using PathToSamplesMap = std::unordered_map<
trace_processor::util::SizeProfileComputer::FieldPath,
std::vector<size_t>,
trace_processor::util::SizeProfileComputer::FieldPathHasher>;
PathToSamplesMap field_path_to_samples;
for (auto sample = computer.GetNext(); sample; sample = computer.GetNext()) {
field_path_to_samples[computer.GetPath()].push_back(*sample);
}
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 field path we've seen write out the stats:
for (auto& entry : field_path_to_samples) {
std::vector<std::string> field_path;
for (const auto& field : entry.first) {
if (field.has_field_name())
field_path.push_back(field.field_name());
field_path.push_back(field.type_name());
}
std::vector<size_t>& samples = entry.second;
protozero::PackedVarInt location_ids;
auto* sample = profile->add_sample();
for (auto loc_it = field_path.rbegin(); loc_it != field_path.rend();
++loc_it) {
location_ids.Append(InternLocation(*loc_it));
}
sample->set_location_id(location_ids);
std::sort(samples.begin(), samples.end());
size_t count = samples.size();
size_t total_size = 0;
size_t max_size = samples[count - 1];
size_t min_size = samples[0];
size_t median_size = samples[count / 2];
for (size_t i = 0; i < count; ++i)
total_size += 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 (auto it = locations_.GetIterator(); it; ++it) {
auto* location = profile->add_location();
location->set_id(static_cast<uint64_t>(it.value()));
auto* line = location->add_line();
line->set_function_id(static_cast<uint64_t>(it.value()));
auto* function = profile->add_function();
function->set_id(static_cast<uint64_t>(it.value()));
function->set_name(InternString(it.key()));
}
// Finally the string table. We intern more strings above, so this has to be
// last.
for (size_t i = 0; i < strings_.size(); i++) {
profile->add_string_table(strings_[i]);
}
return profile.SerializeAsString();
}
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);
trace_processor::DescriptorPool pool;
base::Status status = pool.AddFromFileDescriptorSet(kTraceDescriptor.data(),
kTraceDescriptor.size());
if (!status.ok()) {
PERFETTO_ELOG("Could not add Trace proto descriptor: %s",
status.c_message());
return 1;
}
const uint8_t* start = reinterpret_cast<const uint8_t*>(s.data());
size_t size = s.size();
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;
}
PprofProfileComputer computer;
std::string out =
computer.Compute(start, size, ".perfetto.protos.Trace", &pool);
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);
}