| /* |
| * 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 |