/*
* Copyright (c) 2016, Google Inc.
* All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "builder.h"
#include <fcntl.h>
#include <unistd.h>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <iostream>
#include "google/protobuf/io/gzip_stream.h"
#include "google/protobuf/io/zero_copy_stream_impl.h"
using google::protobuf::io::StringOutputStream;
using google::protobuf::io::GzipOutputStream;
using google::protobuf::io::FileOutputStream;
using google::protobuf::RepeatedField;
namespace perftools {
namespace profiles {
Builder::Builder() : profile_(new Profile()) {
// string_table[0] must be ""
profile_->add_string_table("");
}
int64 Builder::StringId(const char *str) {
if (str == nullptr || !str[0]) {
return 0;
}
const int64 index = profile_->string_table_size();
const auto inserted = strings_.emplace(str, index);
if (!inserted.second) {
// Failed to insert -- use existing id.
return inserted.first->second;
}
profile_->add_string_table(inserted.first->first);
return index;
}
uint64 Builder::FunctionId(const char *name, const char *system_name,
const char *file, int64 start_line) {
int64 name_index = StringId(name);
int64 system_name_index = StringId(system_name);
int64 file_index = StringId(file);
Function fn(name_index, system_name_index, file_index, start_line);
int64 index = profile_->function_size() + 1;
const auto inserted = functions_.insert(std::make_pair(fn, index));
const bool insert_successful = inserted.second;
if (!insert_successful) {
const auto existing_function = inserted.first;
return existing_function->second;
}
auto function = profile_->add_function();
function->set_id(index);
function->set_name(name_index);
function->set_system_name(system_name_index);
function->set_filename(file_index);
function->set_start_line(start_line);
return index;
}
bool Builder::Emit(string *output) {
*output = "";
if (!profile_ || !Finalize()) {
return false;
}
return Marshal(*profile_, output);
}
bool Builder::Marshal(const Profile &profile, string *output) {
*output = "";
StringOutputStream stream(output);
GzipOutputStream gzip_stream(&stream);
if (!profile.SerializeToZeroCopyStream(&gzip_stream)) {
std::cerr << "Failed to serialize to gzip stream";
return false;
}
return gzip_stream.Close();
}
bool Builder::MarshalToFile(const Profile &profile, int fd) {
FileOutputStream stream(fd);
GzipOutputStream gzip_stream(&stream);
if (!profile.SerializeToZeroCopyStream(&gzip_stream)) {
std::cerr << "Failed to serialize to gzip stream";
return false;
}
return gzip_stream.Close();
}
bool Builder::MarshalToFile(const Profile &profile, const char *filename) {
int fd =
TEMP_FAILURE_RETRY(open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0444));
if (fd == -1) {
return false;
}
int ret = MarshalToFile(profile, fd);
close(fd);
return ret;
}
// Returns a bool indicating if the profile is valid. It logs any
// errors it encounters.
bool Builder::CheckValid(const Profile &profile) {
std::unordered_set<uint64> mapping_ids;
for (const auto &mapping : profile.mapping()) {
const int64 id = mapping.id();
if (id != 0) {
const bool insert_successful = mapping_ids.insert(id).second;
if (!insert_successful) {
std::cerr << "Duplicate mapping id: " << id;
return false;
}
}
}
std::unordered_set<uint64> function_ids;
for (const auto &function : profile.function()) {
const int64 id = function.id();
if (id != 0) {
const bool insert_successful = function_ids.insert(id).second;
if (!insert_successful) {
std::cerr << "Duplicate function id: " << id;
return false;
}
}
}
std::unordered_set<uint64> location_ids;
for (const auto &location : profile.location()) {
const int64 id = location.id();
if (id != 0) {
const bool insert_successful = location_ids.insert(id).second;
if (!insert_successful) {
std::cerr << "Duplicate location id: " << id;
return false;
}
}
const int64 mapping_id = location.mapping_id();
if (mapping_id != 0 && mapping_ids.count(mapping_id) == 0) {
std::cerr << "Missing mapping " << mapping_id << " from location " << id;
return false;
}
for (const auto &line : location.line()) {
int64 function_id = line.function_id();
if (function_id != 0 && function_ids.count(function_id) == 0) {
std::cerr << "Missing function " << function_id;
return false;
}
}
}
int sample_type_len = profile.sample_type_size();
if (sample_type_len == 0) {
std::cerr << "No sample type specified";
return false;
}
for (const auto &sample : profile.sample()) {
if (sample.value_size() != sample_type_len) {
std::cerr << "Found sample with " << sample.value_size()
<< " values, expecting " << sample_type_len;
return false;
}
for (uint64 location_id : sample.location_id()) {
if (location_id == 0) {
std::cerr << "Sample referencing location_id=0";
return false;
}
if (location_ids.count(location_id) == 0) {
std::cerr << "Missing location " << location_id;
return false;
}
}
for (const auto &label : sample.label()) {
int64 str = label.str();
int64 num = label.num();
if (str != 0 && num != 0) {
std::cerr << "One of str/num must be unset, got " << str << "," << num;
return false;
}
}
}
return true;
}
// Finalizes the profile for serialization.
// - Creates missing locations for unsymbolized profiles.
// - Associates locations to the corresponding mappings.
bool Builder::Finalize() {
if (profile_->location_size() == 0) {
std::unordered_map<uint64, uint64> address_to_id;
for (auto &sample : *profile_->mutable_sample()) {
// Copy sample locations into a temp vector, and then clear and
// repopulate it with the corresponding location IDs.
const RepeatedField<uint64> addresses = sample.location_id();
sample.clear_location_id();
for (uint64 address : addresses) {
int64 index = address_to_id.size() + 1;
const auto inserted = address_to_id.emplace(address, index);
if (inserted.second) {
auto loc = profile_->add_location();
loc->set_id(index);
loc->set_address(address);
}
sample.add_location_id(inserted.first->second);
}
}
}
// Look up location address on mapping ranges.
if (profile_->mapping_size() > 0) {
std::map<uint64, std::pair<uint64, uint64> > mapping_map;
for (const auto &mapping : profile_->mapping()) {
mapping_map[mapping.memory_start()] =
std::make_pair(mapping.memory_limit(), mapping.id());
}
for (auto &loc : *profile_->mutable_location()) {
if (loc.address() != 0 && loc.mapping_id() == 0) {
auto mapping = mapping_map.upper_bound(loc.address());
if (mapping == mapping_map.begin()) {
// Address landed before the first mapping
continue;
}
mapping--;
uint64 limit = mapping->second.first;
uint64 id = mapping->second.second;
if (loc.address() <= limit) {
loc.set_mapping_id(id);
}
}
}
}
return CheckValid(*profile_);
}
} // namespace profiles
} // namespace perftools