| /* |
| * Copyright (c) 2016, Google Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * * Neither the name of Google Inc. nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL Google Inc. BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "perf_data_converter.h" |
| |
| #include <map> |
| #include <sstream> |
| #include <vector> |
| |
| #include "builder.h" |
| #include "chromiumos-wide-profiling/perf_data.pb.h" |
| #include "chromiumos-wide-profiling/perf_parser.h" |
| #include "chromiumos-wide-profiling/perf_reader.h" |
| #include "int_compat.h" |
| #include "intervalmap.h" |
| #include "map_util.h" |
| #include "perf_data_handler.h" |
| #include "profile_wrappers.pb.h" |
| #include "string_compat.h" |
| |
| namespace perftools { |
| namespace { |
| |
| typedef perftools::profiles::Profile Profile; |
| |
| typedef perftools::profiles::Builder ProfileBuilder; |
| |
| typedef uint32 Pid; |
| typedef std::vector<std::unique_ptr<Profile>> ProfileVector; |
| typedef std::map<std::vector<uint64>, perftools::profiles::Sample*> SampleMap; |
| typedef std::unordered_map<uint64, perftools::profiles::Location*> LocationMap; |
| typedef IntervalMap<perftools::profiles::Mapping*> MappingMap; |
| |
| enum ExecutionMode { |
| Unknown, |
| HostKernel, |
| HostUser, |
| GuestKernel, |
| GuestUser, |
| Hypervisor |
| }; |
| |
| const char* ExecModeString(ExecutionMode mode) { |
| switch (mode) { |
| case HostKernel: |
| return ExecutionModeHostKernel; |
| case HostUser: |
| return ExecutionModeHostUser; |
| case GuestKernel: |
| return ExecutionModeGuestKernel; |
| case GuestUser: |
| return ExecutionModeGuestUser; |
| case Hypervisor: |
| return ExecutionModeHypervisor; |
| default: |
| std::cerr << "Execution mode not handled: " << mode << std::endl; |
| return ""; |
| } |
| } |
| |
| // It is sufficient to key the caches for Locations and Mappings by PID. |
| // However, when Samples include labels, it is necessary to key their caches |
| // not only by PID, but also by anything their labels may contain, since labels |
| // are also distinguishing features. This struct should contain everything |
| // required to uniquely identify a Sample: if two Samples you consider different |
| // end up with the same SampleKey, you should extend SampleKey til they don't. |
| // |
| // If any of these values are not used as labels, they should be set to 0. |
| struct SampleKey { |
| public: |
| Pid pid; |
| Pid tid; |
| uint64 time_ns; |
| ExecutionMode exec_mode; |
| SampleKey() { |
| pid = 0; |
| tid = 0; |
| time_ns = 0; |
| exec_mode = Unknown; |
| } |
| }; |
| |
| struct SampleKeyEqualityTester { |
| bool operator()(const SampleKey a, const SampleKey b) const { |
| return ((a.pid == b.pid) && (a.tid == b.tid) && (a.time_ns == b.time_ns) && |
| (a.exec_mode == b.exec_mode)); |
| } |
| }; |
| |
| struct SampleKeyHasher { |
| size_t operator()(const SampleKey k) const { |
| return (std::hash<int32>()(k.pid) ^ std::hash<int32>()(k.tid) ^ |
| std::hash<uint64>()(k.time_ns) ^ std::hash<int>()(k.exec_mode)); |
| } |
| }; |
| |
| class PerfDataConverter : public PerfDataHandler { |
| public: |
| explicit PerfDataConverter(const quipper::PerfDataProto& perf_data, |
| uint32 sample_labels = kNoLabels, |
| bool group_by_pids = true) |
| : perf_data_(perf_data), |
| sample_labels_(sample_labels), |
| group_by_pids_(group_by_pids) {} |
| PerfDataConverter(const PerfDataConverter&) = delete; |
| PerfDataConverter& operator=(const PerfDataConverter&) = delete; |
| virtual ~PerfDataConverter() {} |
| |
| ProfileVector Profiles(); |
| |
| // Callbacks for PerfDataHandler |
| virtual void Sample(const PerfDataHandler::SampleContext& sample); |
| virtual void Comm(const CommContext& comm); |
| |
| private: |
| void AddSample(const PerfDataHandler::SampleContext& context, const Pid& pid, |
| const SampleKey& sample_key, const std::vector<uint64>& stack, |
| ProfileBuilder* builder); |
| perftools::profiles::Location* AddLocation(const Pid& pid, uint64 address, |
| ProfileBuilder* builder); |
| void AddMapping(const Pid& pid, uint64 for_ip, |
| const PerfDataHandler::Mapping* smap_in, |
| ProfileBuilder* builder); |
| perftools::profiles::Mapping* LookupMapping(const Pid& pid, uint64 address, |
| ProfileBuilder* builder); |
| |
| bool IncludePidLabels() { return (sample_labels_ & kPidLabel); } |
| bool IncludeTidLabels() { return (sample_labels_ & kTidLabel); } |
| bool IncludeTimestampNsLabels() { |
| return (sample_labels_ & kTimestampNsLabel); |
| } |
| bool IncludeExecutionModeLabels() { |
| return (sample_labels_ & kExecutionModeLabel); |
| } |
| |
| const quipper::PerfDataProto& perf_data_; |
| std::vector<std::unique_ptr<ProfileBuilder>> builders_; |
| |
| std::unordered_map<Pid, ProfileBuilder*> builder_cache_; |
| std::unordered_map<Pid, LocationMap> location_cache_; |
| std::unordered_map<Pid, MappingMap> mapping_cache_; |
| |
| // While Locations and Mappings are per-address-space (=per-process), samples |
| // can be thread-specific. If the requested sample labels include PID and |
| // TID, we'll need to cache separate profile_proto::Samples for samples that |
| // are identical except for TID. Likewise, if the requested sample labels |
| // include timestamp_ns, then we'll need to have separate |
| // profile_proto::Samples for samples that are identical except for timestamp. |
| std::unordered_map<SampleKey, SampleMap, SampleKeyHasher, |
| SampleKeyEqualityTester> |
| sample_cache_; |
| |
| const uint32 sample_labels_; |
| |
| const bool group_by_pids_ = true; |
| }; |
| |
| void PerfDataConverter::AddMapping(const Pid& pid, uint64 for_ip, |
| const PerfDataHandler::Mapping* smap, |
| ProfileBuilder* builder) { |
| if (builder == nullptr) { |
| std::cerr << "Cannot add mapping to null builder." << std::endl; |
| abort(); |
| } |
| MappingMap* mapmap = FindOrNull(&mapping_cache_, pid); |
| if (mapmap == nullptr) { |
| mapping_cache_[pid] = MappingMap(); |
| mapmap = &mapping_cache_[pid]; |
| } |
| if (smap == nullptr) { |
| return; |
| } |
| |
| perftools::profiles::Mapping* mapping = nullptr; |
| if (!mapmap->Lookup(for_ip, &mapping)) { |
| Profile* profile = builder->mutable_profile(); |
| auto mapping = profile->add_mapping(); |
| mapping->set_id(profile->mapping_size()); |
| mapping->set_memory_start(smap->start); |
| mapping->set_memory_limit(smap->limit); |
| mapping->set_file_offset(smap->file_offset); |
| if (smap->build_id != nullptr && !smap->build_id->empty()) { |
| mapping->set_build_id(builder->StringId(smap->build_id->c_str())); |
| } |
| if (smap->filename != nullptr && !smap->filename->empty()) { |
| mapping->set_filename(builder->StringId(smap->filename->c_str())); |
| } else if (smap->filename_md5_prefix != 0) { |
| std::stringstream filename; |
| filename << std::hex << smap->filename_md5_prefix; |
| mapping->set_filename(builder->StringId(filename.str().c_str())); |
| } |
| if (mapping->memory_start() >= mapping->memory_limit()) { |
| std::cerr << "The start of the mapping must be strictly less than its" |
| << "limit in file: " << mapping->filename() << std::endl |
| << "Start: " << mapping->memory_start() << std::endl |
| << "Limit: " << mapping->memory_limit() << std::endl; |
| abort(); |
| } |
| mapmap->Set(mapping->memory_start(), mapping->memory_limit(), mapping); |
| } |
| } |
| |
| void PerfDataConverter::AddSample(const PerfDataHandler::SampleContext& context, |
| const Pid& pid, const SampleKey& sample_key, |
| const std::vector<uint64>& stack, |
| ProfileBuilder* builder) { |
| perftools::profiles::Sample* sample = nullptr; |
| if (const auto* samplep = FindOrNull(&sample_cache_[sample_key], stack)) { |
| sample = *samplep; |
| } |
| |
| if (sample == nullptr) { |
| Profile* profile = builder->mutable_profile(); |
| sample = profile->add_sample(); |
| sample_cache_[sample_key][stack] = sample; |
| for (const auto& addr : stack) { |
| const auto* location = FindPtrOrNull(location_cache_[pid], addr); |
| if (location == nullptr) { |
| location = AddLocation(pid, addr, builder); |
| if (location == nullptr) { |
| std::cerr << "AddLocation failed." << std::endl; |
| abort(); |
| } |
| if (location->address() != addr) { |
| std::cerr << "Added location has inconsistent address." << std::endl |
| << "Expected: " << addr << std::endl |
| << "Found: " << location->address() << std::endl; |
| abort(); |
| } |
| } |
| sample->add_location_id(location->id()); |
| } |
| // Emit any requested labels. |
| if (IncludePidLabels() && context.sample.has_pid()) { |
| auto label = sample->add_label(); |
| label->set_key(builder->StringId(PidLabelKey)); |
| label->set_num(static_cast<int64>(context.sample.pid())); |
| } |
| if (IncludeTidLabels() && context.sample.has_tid()) { |
| auto label = sample->add_label(); |
| label->set_key(builder->StringId(TidLabelKey)); |
| label->set_num(static_cast<int64>(context.sample.tid())); |
| } |
| if (IncludeTimestampNsLabels() && context.sample.has_sample_time_ns()) { |
| auto label = sample->add_label(); |
| label->set_key(builder->StringId(TimestampNsLabelKey)); |
| int64 timestamp_ns_as_int64 = |
| static_cast<int64>(context.sample.sample_time_ns()); |
| label->set_num(timestamp_ns_as_int64); |
| } |
| if (IncludeExecutionModeLabels() && sample_key.exec_mode != Unknown) { |
| auto label = sample->add_label(); |
| label->set_key(builder->StringId(ExecutionModeLabelKey)); |
| label->set_str(builder->StringId(ExecModeString(sample_key.exec_mode))); |
| } |
| // Two values per collected event: the first is sample counts, the second is |
| // event counts (unsampled weight for each sample). |
| for (int event_id = 0; event_id < perf_data_.file_attrs_size(); |
| ++event_id) { |
| sample->add_value(0); |
| sample->add_value(0); |
| } |
| } |
| |
| int64 weight = 1; |
| // If the sample has a period, use that in preference |
| if (context.sample.period() > 0) { |
| weight = context.sample.period(); |
| } else if (context.file_attrs_index >= 0) { |
| uint64 period = |
| perf_data_.file_attrs(context.file_attrs_index).attr().sample_period(); |
| if (period > 0) { |
| // If sampling used a fixed period, use that as the weight. |
| weight = period; |
| } |
| } |
| int event_index = context.file_attrs_index; |
| sample->set_value(2 * event_index, sample->value(2 * event_index) + 1); |
| sample->set_value(2 * event_index + 1, |
| sample->value(2 * event_index + 1) + weight); |
| } |
| |
| perftools::profiles::Mapping* PerfDataConverter::LookupMapping( |
| const Pid& pid, uint64 address, ProfileBuilder* builder) { |
| MappingMap mapmap = mapping_cache_[pid]; |
| perftools::profiles::Mapping* mapping = nullptr; |
| mapmap.Lookup(address, &mapping); |
| return mapping; |
| } |
| |
| perftools::profiles::Location* PerfDataConverter::AddLocation( |
| const Pid& pid, uint64 address, ProfileBuilder* builder) { |
| Profile* profile = builder->mutable_profile(); |
| perftools::profiles::Location* location = profile->add_location(); |
| location->set_id(profile->location_size()); |
| location->set_address(address); |
| |
| auto mapping = LookupMapping(pid, address, builder); |
| if (mapping != nullptr) { |
| location->set_mapping_id(mapping->id()); |
| } else { |
| if (address != 0) { |
| std::cerr << "Found unmapped address: " << address << " in PID " << pid |
| << std::endl; |
| abort(); |
| } |
| } |
| location_cache_[pid][address] = location; |
| return location; |
| } |
| |
| void PerfDataConverter::Comm(const CommContext& comm) { |
| if (comm.comm->pid() == comm.comm->tid()) { |
| // pid==tid means an exec() happened, so clear everything from the |
| // existing pid. |
| |
| Pid pid = comm.comm->pid(); |
| builder_cache_[pid] = nullptr; |
| location_cache_[pid].clear(); |
| mapping_cache_[pid].Clear(); |
| |
| // Every Sample in the cache with this PID gets wiped. |
| for (auto samples_it = sample_cache_.begin(); |
| samples_it != sample_cache_.end(); ++samples_it) { |
| if (samples_it->first.pid == comm.comm->pid()) { |
| samples_it->second.clear(); |
| } |
| } |
| } |
| } |
| |
| void PerfDataConverter::Sample(const PerfDataHandler::SampleContext& sample) { |
| if (sample.file_attrs_index < 0 || |
| sample.file_attrs_index >= perf_data_.file_attrs_size()) { |
| LOG(WARNING) << "out of bounds file_attrs_index: " |
| << sample.file_attrs_index; |
| return; |
| } |
| |
| Pid builder_pid = group_by_pids_ ? sample.sample.pid() : 0; |
| Pid event_pid = sample.sample.pid(); |
| SampleKey sample_key; |
| sample_key.pid = sample.sample.has_pid() ? sample.sample.pid() : 0; |
| sample_key.tid = |
| (IncludeTidLabels() && sample.sample.has_tid()) ? sample.sample.tid() : 0; |
| sample_key.time_ns = |
| (IncludeTimestampNsLabels() && sample.sample.has_sample_time_ns()) |
| ? sample.sample.sample_time_ns() |
| : 0; |
| if (IncludeExecutionModeLabels() && sample.header.has_misc()) { |
| switch (sample.header.misc()) { |
| case PERF_RECORD_MISC_KERNEL: |
| sample_key.exec_mode = HostKernel; |
| break; |
| case PERF_RECORD_MISC_USER: |
| sample_key.exec_mode = HostUser; |
| break; |
| case PERF_RECORD_MISC_GUEST_KERNEL: |
| sample_key.exec_mode = GuestKernel; |
| break; |
| case PERF_RECORD_MISC_GUEST_USER: |
| sample_key.exec_mode = GuestUser; |
| break; |
| case PERF_RECORD_MISC_HYPERVISOR: |
| sample_key.exec_mode = Hypervisor; |
| break; |
| } |
| } |
| ProfileBuilder* builder = FindPtrOrNull(builder_cache_, builder_pid); |
| if (builder == nullptr) { |
| builder = new ProfileBuilder(); |
| builder_cache_[builder_pid] = builder; |
| builders_.emplace_back(builder); |
| Profile* profile = builder->mutable_profile(); |
| |
| int unknown_event_idx = 0; |
| for (int event_idx = 0; event_idx < perf_data_.file_attrs_size(); |
| ++event_idx) { |
| // Come up with an event name for this event. perf.data will usually |
| // contain an event_types section of the same cardinality as its |
| // file_attrs; in this case we can just use the name there. Otherwise |
| // we just give it an anonymous name. |
| string event_name = ""; |
| if (perf_data_.file_attrs_size() == perf_data_.event_types_size()) { |
| const auto& event_type = perf_data_.event_types(event_idx); |
| if (event_type.has_name()) { |
| event_name = event_type.name() + "_"; |
| } |
| } |
| if (event_name == "") { |
| event_name = "event_" + std::to_string(unknown_event_idx++) + "_"; |
| } |
| auto sample_type = profile->add_sample_type(); |
| sample_type->set_type(builder->StringId((event_name + "sample").c_str())); |
| sample_type->set_unit(builder->StringId("count")); |
| sample_type = profile->add_sample_type(); |
| sample_type->set_type(builder->StringId((event_name + "event").c_str())); |
| sample_type->set_unit(builder->StringId("count")); |
| } |
| if (sample.main_mapping == nullptr) { |
| auto fake_main = profile->add_mapping(); |
| fake_main->set_id(profile->mapping_size()); |
| fake_main->set_memory_start(0); |
| fake_main->set_memory_limit(1); |
| } else { |
| AddMapping(event_pid, sample.main_mapping->limit - 1, sample.main_mapping, |
| builder); |
| } |
| } |
| |
| if (!sample.branch_stack.empty()) { |
| std::cerr << "don't know how to handle branch_stack" << std::endl; |
| abort(); |
| } |
| |
| if (sample.branch_stack.empty()) { |
| // Normal sample or has callchain. |
| std::vector<uint64> stack; |
| uint64 ip = sample.sample_mapping != nullptr ? sample.sample.ip() : 0; |
| if (ip != 0) { |
| const auto start = sample.sample_mapping->start; |
| const auto limit = sample.sample_mapping->limit; |
| if (ip < start || ip >= limit) { |
| std::cerr << "IP is out of bound of mapping." << std::endl |
| << "IP: " << ip << std::endl |
| << "Start: " << start << std::endl |
| << "Limit: " << limit << std::endl; |
| } |
| } |
| AddMapping(event_pid, ip, sample.sample_mapping, builder); |
| |
| stack.push_back(ip); // Leaf at stack[0] |
| bool skipped_dup = false; |
| for (const auto& frame : sample.callchain) { |
| if (!skipped_dup && stack.size() == 1 && frame.ip == stack[0]) { |
| skipped_dup = true; |
| // Newer versions of perf_events include the IP at the leaf of |
| // the callchain. |
| continue; |
| } |
| if (frame.mapping == nullptr) { |
| continue; |
| } |
| uint64 frame_ip = frame.ip; |
| // Why <=? Because this is a return address, which should be |
| // preceded by a call (the "real" context.) If we're at the edge |
| // of the mapping, we're really off its edge. |
| if (frame_ip <= frame.mapping->start) { |
| continue; |
| } |
| // these aren't real callchain entries, just hints as to kernel/user |
| // addresses. |
| if (frame_ip >= PERF_CONTEXT_MAX) { |
| continue; |
| } |
| |
| // subtract one so we point to the call instead of the return addr. |
| frame_ip--; |
| if (frame.mapping != nullptr) { |
| AddMapping(event_pid, frame_ip, frame.mapping, builder); |
| } |
| stack.push_back(frame_ip); |
| } |
| AddSample(sample, event_pid, sample_key, stack, builder); |
| } |
| } |
| |
| ProfileVector PerfDataConverter::Profiles() { |
| ProfileVector profiles; |
| for (auto& builder : builders_) { |
| builder->Finalize(); |
| Profile* profile = new Profile(); |
| profile->Swap(builder->mutable_profile()); |
| profiles.emplace_back(std::unique_ptr<Profile>(profile)); |
| } |
| |
| return profiles; |
| } |
| |
| // Returns the provided ProfileVector as a serialized proto. |
| string SerializedProfileList(const ProfileVector& vec) { |
| ProfileList list; |
| for (const auto& profile : vec) { |
| *list.add_profile() = *profile; |
| } |
| |
| string serialized_out; |
| if (!list.SerializeToString(&serialized_out)) { |
| std::cerr << "Failed to serialize ProfileList" << std::endl; |
| abort(); |
| } |
| return serialized_out; |
| } |
| |
| ProfileVector PerfDataProtoToProfileList(quipper::PerfDataProto* perf_data, |
| uint32 sample_labels, |
| bool group_by_pids) { |
| PerfDataConverter converter(*perf_data, sample_labels, group_by_pids); |
| PerfDataHandler::Process(*perf_data, &converter); |
| return converter.Profiles(); |
| } |
| |
| } // namespace |
| |
| ProfileVector RawPerfDataToProfileProto(const void* raw, int raw_size, |
| const void* build_ids, |
| int build_ids_size, |
| uint32 sample_labels, |
| bool group_by_pids) { |
| std::unique_ptr<quipper::PerfReader> reader(new quipper::PerfReader); |
| if (!reader->ReadFromPointer(reinterpret_cast<const char*>(raw), raw_size)) { |
| LOG(ERROR) << "Could not read input perf.data"; |
| return ProfileVector(); |
| } |
| |
| std::unique_ptr<quipper::PerfParser> parser( |
| new quipper::PerfParser(reader.get())); |
| if (!parser->ParseRawEvents()) { |
| LOG(ERROR) << "Could not parse input perf data"; |
| return ProfileVector(); |
| } |
| |
| StringMap build_id_map_pb; |
| if (!build_id_map_pb.ParseFromArray(build_ids, build_ids_size)) { |
| std::cerr << "Unable to parse build ids." << std::endl; |
| abort(); |
| } |
| std::map<string, string> build_id_map; |
| for (const auto& item : build_id_map_pb.key_value()) { |
| build_id_map[item.key()] = item.value(); |
| } |
| reader->InjectBuildIDs(build_id_map); |
| // Perf populates info about the kernel using multiple pathways, |
| // which don't actually all match up how they name kernel data; in |
| // particular, buildids are reported by a different name than the |
| // actual "mmap" info. Normalize these names so our ProfileVector |
| // will match kernel mappings to a buildid. |
| reader->LocalizeUsingFilenames({ |
| {"[kernel.kallsyms]_text", "[kernel.kallsyms]"}, |
| {"[kernel.kallsyms]_stext", "[kernel.kallsyms]"}, |
| }); |
| quipper::PerfDataProto perf_data; |
| if (!reader->Serialize(&perf_data)) { |
| LOG(ERROR) << "Could not serialize perf.data"; |
| return ProfileVector(); |
| } |
| return PerfDataProtoToProfileList(&perf_data, sample_labels, group_by_pids); |
| } |
| |
| ProfileVector SerializedPerfDataProtoToProfileProto( |
| const string& serialized_perf_data, uint32 sample_labels, |
| bool group_by_pids) { |
| quipper::PerfDataProto perf_data; |
| perf_data.ParseFromString(serialized_perf_data); |
| return PerfDataProtoToProfileList(&perf_data, sample_labels, group_by_pids); |
| } |
| |
| // Returns a serialized ProfileList following the semantics of |
| // RawPerfDataToProfileProto |
| string RawPerfDataToSerializedProfileList(const void* raw, int raw_size, |
| const void* build_ids, |
| int build_ids_size, |
| uint32 sample_labels, |
| bool group_by_pids) { |
| const auto& profiles = RawPerfDataToProfileProto( |
| raw, raw_size, build_ids, build_ids_size, sample_labels, group_by_pids); |
| return SerializedProfileList(profiles); |
| } |
| |
| string RawPerfDataUniqueMappedFiles(const void* raw, int raw_size) { |
| std::unique_ptr<quipper::PerfReader> reader(new quipper::PerfReader); |
| if (!reader->ReadFromPointer(reinterpret_cast<const char*>(raw), raw_size)) { |
| LOG(ERROR) << "Could not read input perf.data"; |
| return ""; |
| } |
| std::vector<string> filenames; |
| reader->GetFilenames(&filenames); |
| StringList list; |
| for (const auto& filename : filenames) { |
| list.add_item(filename); |
| } |
| string serialized; |
| if (!list.SerializeToString(&serialized)) { |
| std::cerr << "Failed to serialize StringList" << std::endl; |
| abort(); |
| } |
| return serialized; |
| } |
| |
| string SerializedPerfDataProtoToSerializedProfileList( |
| const string& perf_data_str, uint32 sample_labels) { |
| const auto& profiles = |
| SerializedPerfDataProtoToProfileProto(perf_data_str, sample_labels); |
| return SerializedProfileList(profiles); |
| } |
| |
| } // namespace perftools |