| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromiumos-wide-profiling/perf_reader.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/time.h> |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/macros.h" |
| |
| #include "chromiumos-wide-profiling/binary_data_utils.h" |
| #include "chromiumos-wide-profiling/buffer_reader.h" |
| #include "chromiumos-wide-profiling/buffer_writer.h" |
| #include "chromiumos-wide-profiling/compat/string.h" |
| #include "chromiumos-wide-profiling/file_reader.h" |
| #include "chromiumos-wide-profiling/file_utils.h" |
| #include "chromiumos-wide-profiling/perf_data_structures.h" |
| #include "chromiumos-wide-profiling/perf_data_utils.h" |
| #include "chromiumos-wide-profiling/sample_info_reader.h" |
| |
| namespace quipper { |
| |
| using PerfEvent = PerfDataProto_PerfEvent; |
| using SampleInfo = PerfDataProto_SampleInfo; |
| using StringAndMd5sumPrefix = |
| PerfDataProto_StringMetadata_StringAndMd5sumPrefix; |
| |
| namespace { |
| |
| // The type of the storage size of a string, prefixed before each string field |
| // in raw data. |
| typedef u32 string_size_type; |
| |
| // The type of the number of string data, found in the command line metadata in |
| // the perf data file. |
| typedef u32 num_string_data_type; |
| |
| // Types of the event desc fields that are not found in other structs. |
| typedef u32 event_desc_num_events; |
| typedef u32 event_desc_attr_size; |
| typedef u32 event_desc_num_unique_ids; |
| |
| // The type of the number of nodes field in NUMA topology. |
| typedef u32 numa_topology_num_nodes_type; |
| |
| // A mask that is applied to |metadata_mask()| in order to get a mask for |
| // only the metadata supported by quipper. |
| const uint32_t kSupportedMetadataMask = |
| 1 << HEADER_TRACING_DATA | |
| 1 << HEADER_BUILD_ID | |
| 1 << HEADER_HOSTNAME | |
| 1 << HEADER_OSRELEASE | |
| 1 << HEADER_VERSION | |
| 1 << HEADER_ARCH | |
| 1 << HEADER_NRCPUS | |
| 1 << HEADER_CPUDESC | |
| 1 << HEADER_CPUID | |
| 1 << HEADER_TOTAL_MEM | |
| 1 << HEADER_CMDLINE | |
| 1 << HEADER_EVENT_DESC | |
| 1 << HEADER_CPU_TOPOLOGY | |
| 1 << HEADER_NUMA_TOPOLOGY | |
| 1 << HEADER_BRANCH_STACK; |
| |
| // By default, the build ID event has PID = -1. |
| const uint32_t kDefaultBuildIDEventPid = static_cast<uint32_t>(-1); |
| |
| // Eight bits in a byte. |
| size_t BytesToBits(size_t num_bytes) { |
| return num_bytes * 8; |
| } |
| |
| u8 ReverseByte(u8 x) { |
| x = (x & 0xf0) >> 4 | (x & 0x0f) << 4; // exchange nibbles |
| x = (x & 0xcc) >> 2 | (x & 0x33) << 2; // exchange pairs |
| x = (x & 0xaa) >> 1 | (x & 0x55) << 1; // exchange neighbors |
| return x; |
| } |
| |
| // If field points to the start of a bitfield padded to len bytes, this |
| // performs an endian swap of the bitfield, assuming the compiler that produced |
| // it conforms to the same ABI (bitfield layout is not completely specified by |
| // the language). |
| void SwapBitfieldOfBits(u8* field, size_t len) { |
| for (size_t i = 0; i < len; i++) { |
| field[i] = ReverseByte(field[i]); |
| } |
| } |
| |
| // The code currently assumes that the compiler will not add any padding to the |
| // various structs. These CHECKs make sure that this is true. |
| void CheckNoEventHeaderPadding() { |
| perf_event_header header; |
| CHECK_EQ(sizeof(header), |
| sizeof(header.type) + sizeof(header.misc) + sizeof(header.size)); |
| } |
| |
| void CheckNoPerfEventAttrPadding() { |
| perf_event_attr attr; |
| CHECK_EQ(sizeof(attr), |
| (reinterpret_cast<u64>(&attr.__reserved_2) - |
| reinterpret_cast<u64>(&attr)) + |
| sizeof(attr.__reserved_2)); |
| } |
| |
| void CheckNoEventTypePadding() { |
| perf_trace_event_type event_type; |
| CHECK_EQ(sizeof(event_type), |
| sizeof(event_type.event_id) + sizeof(event_type.name)); |
| } |
| |
| void CheckNoBuildIDEventPadding() { |
| build_id_event event; |
| CHECK_EQ(sizeof(event), |
| sizeof(event.header.type) + sizeof(event.header.misc) + |
| sizeof(event.header.size) + sizeof(event.pid) + |
| sizeof(event.build_id)); |
| } |
| |
| // Creates a new build ID event with the given build ID string, filename, and |
| // misc value. |
| malloced_unique_ptr<build_id_event> CreateBuildIDEvent(const string& build_id, |
| const string& filename, |
| uint16_t misc) { |
| size_t filename_len = GetUint64AlignedStringLength(filename); |
| size_t size = sizeof(struct build_id_event) + filename_len; |
| malloced_unique_ptr<build_id_event> event(CallocMemoryForBuildID(size)); |
| event->header.type = HEADER_BUILD_ID; |
| event->header.misc = misc; |
| event->header.size = size; |
| |
| event->pid = kDefaultBuildIDEventPid; |
| snprintf(event->filename, filename_len, "%s", filename.c_str()); |
| memset(event->filename + filename.size(), 0, filename_len - filename.size()); |
| |
| HexStringToRawData(build_id, event->build_id, sizeof(event->build_id)); |
| return event; |
| } |
| |
| // Given a string, returns the total size required to store the string in perf |
| // data, including a preceding length field and extra padding to align the |
| // string + null terminator to a multiple of uint64s. |
| size_t ExpectedStorageSizeOf(const string& str) { |
| return sizeof(string_size_type) + GetUint64AlignedStringLength(str); |
| } |
| |
| // Reads a perf_event_header from |data| and performs byte swapping if |
| // necessary. Returns true on success, or false if there was an error. |
| bool ReadPerfEventHeader(DataReader* data, struct perf_event_header* header) { |
| if (!data->ReadData(sizeof(struct perf_event_header), header)) { |
| LOG(ERROR) << "Error reading perf event header."; |
| return false; |
| } |
| if (data->is_cross_endian()) { |
| ByteSwap(&header->type); |
| ByteSwap(&header->size); |
| ByteSwap(&header->misc); |
| } |
| return true; |
| } |
| |
| // Reads a perf_file_section from |data| and performs byte swapping if |
| // necessary. Returns true on success, or false if there was an error. |
| bool ReadPerfFileSection(DataReader* data, struct perf_file_section* section) { |
| if (!data->ReadData(sizeof(*section), section)) { |
| LOG(ERROR) << "Error reading perf file section info."; |
| return false; |
| } |
| if (data->is_cross_endian()) { |
| ByteSwap(§ion->offset); |
| ByteSwap(§ion->size); |
| } |
| return true; |
| } |
| |
| // Returns true if |e1| has an earlier timestamp than |e2|. Used to sort an |
| // array of events. |
| bool CompareEventTimes(const PerfEvent& e1, const PerfEvent& e2) { |
| return GetTimeFromPerfEvent(e1) < GetTimeFromPerfEvent(e2); |
| } |
| |
| } // namespace |
| |
| PerfReader::PerfReader() : is_cross_endian_(false) { |
| // The metadata mask is stored in |proto_|. It should be initialized to 0 |
| // since it is used heavily. |
| proto_.add_metadata_mask(0); |
| } |
| |
| PerfReader::~PerfReader() {} |
| |
| bool PerfReader::Serialize(PerfDataProto* perf_data_proto) const { |
| perf_data_proto->CopyFrom(proto_); |
| |
| // Add a timestamp_sec to the protobuf. |
| struct timeval timestamp_sec; |
| if (!gettimeofday(×tamp_sec, NULL)) |
| perf_data_proto->set_timestamp_sec(timestamp_sec.tv_sec); |
| return true; |
| } |
| |
| bool PerfReader::Deserialize(const PerfDataProto& perf_data_proto) { |
| proto_.CopyFrom(perf_data_proto); |
| |
| // Iterate through all attrs and create a SampleInfoReader for each of them. |
| // This is necessary for writing the proto representation of perf data to raw |
| // data. |
| for (const auto& stored_attr : proto_.file_attrs()) { |
| PerfFileAttr attr; |
| serializer_.DeserializePerfFileAttr(stored_attr, &attr); |
| serializer_.CreateSampleInfoReader(attr, false /* read_cross_endian */); |
| } |
| return true; |
| } |
| |
| bool PerfReader::ReadFile(const string& filename) { |
| FileReader reader(filename); |
| if (!reader.IsOpen()) { |
| LOG(ERROR) << "Unable to open file " << filename; |
| return false; |
| } |
| return ReadFromData(&reader); |
| } |
| |
| bool PerfReader::ReadFromVector(const std::vector<char>& data) { |
| return ReadFromPointer(data.data(), data.size()); |
| } |
| |
| bool PerfReader::ReadFromString(const string& str) { |
| return ReadFromPointer(str.data(), str.size()); |
| } |
| |
| bool PerfReader::ReadFromPointer(const char* data, size_t size) { |
| BufferReader buffer(data, size); |
| return ReadFromData(&buffer); |
| } |
| |
| bool PerfReader::ReadFromData(DataReader* data) { |
| if (data->size() == 0) { |
| LOG(ERROR) << "Input data is empty!"; |
| return false; |
| } |
| if (!ReadHeader(data)) |
| return false; |
| |
| // Check if it is normal perf data. |
| if (header_.size == sizeof(header_)) { |
| DVLOG(1) << "Perf data is in normal format."; |
| |
| // Make sure sections are within the size of the file. This check prevents |
| // more obscure messages later when attempting to read from one of these |
| // sections. |
| if (header_.attrs.offset + header_.attrs.size > data->size()) { |
| LOG(ERROR) << "Header says attrs section ends at " |
| << header_.attrs.offset + header_.attrs.size |
| << " bytes, which is larger than perf data size of " |
| << data->size() << " bytes."; |
| return false; |
| } |
| if (header_.data.offset + header_.data.size > data->size()) { |
| LOG(ERROR) << "Header says data section ends at " |
| << header_.data.offset + header_.data.size |
| << " bytes, which is larger than perf data size of " |
| << data->size() << " bytes."; |
| return false; |
| } |
| if (header_.event_types.offset + header_.event_types.size > data->size()) { |
| LOG(ERROR) << "Header says event_types section ends at " |
| << header_.event_types.offset + header_.event_types.size |
| << " bytes, which is larger than perf data size of " |
| << data->size() << " bytes."; |
| return false; |
| } |
| |
| if (!get_metadata_mask_bit(HEADER_EVENT_DESC)) { |
| // Prefer to read attrs and event names from HEADER_EVENT_DESC metadata if |
| // available. event_types section of perf.data is obsolete, but use it as |
| // a fallback: |
| if (!(ReadAttrsSection(data) && ReadEventTypesSection(data))) |
| return false; |
| } |
| |
| if (!(ReadMetadata(data) && ReadDataSection(data))) |
| return false; |
| |
| // We can construct HEADER_EVENT_DESC from attrs and event types. |
| // NB: Can't set this before ReadMetadata(), or it may misread the metadata. |
| if (!event_types().empty()) |
| set_metadata_mask_bit(HEADER_EVENT_DESC); |
| |
| return true; |
| } |
| |
| // Otherwise it is piped data. |
| if (piped_header_.size != sizeof(piped_header_)) { |
| LOG(ERROR) << "Expecting piped data format, but header size " |
| << piped_header_.size << " does not match expected size " |
| << sizeof(piped_header_); |
| return false; |
| } |
| |
| return ReadPipedData(data); |
| } |
| |
| bool PerfReader::WriteFile(const string& filename) { |
| std::vector<char> data; |
| return WriteToVector(&data) && BufferToFile(filename, data); |
| } |
| |
| bool PerfReader::WriteToVector(std::vector<char>* data) { |
| data->resize(GetSize()); |
| return WriteToPointerWithoutCheckingSize(&data->at(0), data->size()); |
| } |
| |
| bool PerfReader::WriteToString(string* str) { |
| str->resize(GetSize()); |
| return WriteToPointerWithoutCheckingSize(&str->at(0), str->size()); |
| } |
| |
| bool PerfReader::WriteToPointer(char* buffer, size_t size) { |
| size_t required_size = GetSize(); |
| if (size < required_size) { |
| LOG(ERROR) << "Buffer is too small - buffer size is " << size |
| << " and required size is " << required_size; |
| return false; |
| } |
| return WriteToPointerWithoutCheckingSize(buffer, size); |
| } |
| |
| bool PerfReader::WriteToPointerWithoutCheckingSize(char* buffer, size_t size) { |
| BufferWriter data(buffer, size); |
| struct perf_file_header header; |
| GenerateHeader(&header); |
| |
| return |
| WriteHeader(header, &data) && |
| WriteAttrs(header, &data) && |
| WriteData(header, &data) && |
| WriteMetadata(header, &data); |
| } |
| |
| size_t PerfReader::GetSize() const { |
| struct perf_file_header header; |
| GenerateHeader(&header); |
| |
| size_t total_size = 0; |
| total_size += header.size; |
| total_size += header.attrs.size; |
| total_size += header.event_types.size; |
| total_size += header.data.size; |
| // Add the ID info, whose size is not explicitly included in the header. |
| for (const auto& attr : proto_.file_attrs()) { |
| total_size += |
| attr.ids_size() * sizeof(decltype(PerfFileAttr::ids)::value_type); |
| } |
| |
| // Additional info about metadata. See WriteMetadata for explanation. |
| total_size += GetNumSupportedMetadata() * sizeof(struct perf_file_section); |
| |
| // Add the sizes of the various metadata. |
| total_size += tracing_data().size(); |
| total_size += GetBuildIDMetadataSize(); |
| total_size += GetStringMetadataSize(); |
| total_size += GetUint32MetadataSize(); |
| total_size += GetUint64MetadataSize(); |
| total_size += GetEventDescMetadataSize(); |
| total_size += GetCPUTopologyMetadataSize(); |
| total_size += GetNUMATopologyMetadataSize(); |
| return total_size; |
| } |
| |
| void PerfReader::GenerateHeader(struct perf_file_header* header) const { |
| // This is the order of the input perf file contents in normal mode: |
| // 1. Header |
| // 2. Attribute IDs (pointed to by attr.ids.offset) |
| // 3. Attributes |
| // 4. Event types |
| // 5. Data |
| // 6. Metadata |
| |
| // Compute offsets in the above order. |
| CheckNoEventHeaderPadding(); |
| memset(header, 0, sizeof(*header)); |
| header->magic = kPerfMagic; |
| header->size = sizeof(*header); |
| header->attr_size = sizeof(perf_file_attr); |
| header->attrs.size = header->attr_size * attrs().size(); |
| for (const PerfEvent& event : proto_.events()) |
| header->data.size += event.header().size(); |
| // Do not use the event_types section. Use EVENT_DESC metadata instead. |
| header->event_types.size = 0; |
| |
| u64 current_offset = 0; |
| current_offset += header->size; |
| for (const auto& attr : proto_.file_attrs()) { |
| current_offset += |
| sizeof(decltype(PerfFileAttr::ids)::value_type) * attr.ids_size(); |
| } |
| header->attrs.offset = current_offset; |
| current_offset += header->attrs.size; |
| header->event_types.offset = current_offset; |
| current_offset += header->event_types.size; |
| |
| header->data.offset = current_offset; |
| |
| // Construct the header feature bits. |
| memset(&header->adds_features, 0, sizeof(header->adds_features)); |
| // The following code makes the assumption that all feature bits are in the |
| // first word of |adds_features|. If the perf data format changes and the |
| // assumption is no longer valid, this CHECK will fail, at which point the |
| // below code needs to be updated. For now, sticking to that assumption keeps |
| // the code simple. |
| // This assumption is also used when reading metadata, so that code |
| // will also have to be updated if this CHECK starts to fail. |
| CHECK_LE(static_cast<size_t>(HEADER_LAST_FEATURE), |
| BytesToBits(sizeof(header->adds_features[0]))); |
| header->adds_features[0] |= metadata_mask() & kSupportedMetadataMask; |
| } |
| |
| bool PerfReader::InjectBuildIDs( |
| const std::map<string, string>& filenames_to_build_ids) { |
| set_metadata_mask_bit(HEADER_BUILD_ID); |
| std::set<string> updated_filenames; |
| // Inject new build ID's for existing build ID events. |
| for (auto& build_id : *proto_.mutable_build_ids()) { |
| auto find_result = filenames_to_build_ids.find(build_id.filename()); |
| if (find_result == filenames_to_build_ids.end()) |
| continue; |
| |
| // TODO(sque): Should build IDs passed into this function be in binary |
| // format? That would greatly simplify this section. |
| const string& build_id_string = find_result->second; |
| const int kHexCharsPerByte = 2; |
| std::vector<uint8_t> |
| build_id_data(build_id_string.size() / kHexCharsPerByte); |
| if (!HexStringToRawData(build_id_string, build_id_data.data(), |
| build_id_data.size())) { |
| LOG(ERROR) << "Could not convert hex string to raw data: " |
| << build_id_string; |
| return false; |
| } |
| build_id.set_build_id_hash(build_id_data.data(), build_id_data.size()); |
| |
| updated_filenames.insert(build_id.filename()); |
| } |
| |
| // For files with no existing build ID events, create new build ID events. |
| // This requires a lookup of all MMAP's to determine the |misc| field of each |
| // build ID event. |
| std::map<string, uint16_t> filename_to_misc; |
| for (const PerfEvent& event : proto_.events()) { |
| if (event.header().type() == PERF_RECORD_MMAP || |
| event.header().type() == PERF_RECORD_MMAP2) { |
| filename_to_misc[event.mmap_event().filename()] = event.header().misc(); |
| } |
| } |
| |
| std::map<string, string>::const_iterator it; |
| for (it = filenames_to_build_ids.begin(); |
| it != filenames_to_build_ids.end(); |
| ++it) { |
| const string& filename = it->first; |
| if (updated_filenames.find(filename) != updated_filenames.end()) |
| continue; |
| |
| // Determine the misc field. |
| uint16_t new_misc = PERF_RECORD_MISC_KERNEL; |
| std::map<string, uint16_t>::const_iterator misc_iter = |
| filename_to_misc.find(filename); |
| if (misc_iter != filename_to_misc.end()) |
| new_misc = misc_iter->second; |
| |
| string build_id = it->second; |
| malloced_unique_ptr<build_id_event> event = |
| CreateBuildIDEvent(build_id, filename, new_misc); |
| if (!serializer_.SerializeBuildIDEvent(event, proto_.add_build_ids())) { |
| LOG(ERROR) << "Could not serialize build ID event with ID " << build_id; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PerfReader::Localize( |
| const std::map<string, string>& build_ids_to_filenames) { |
| std::map<string, string> filename_map; |
| for (auto& build_id : *proto_.mutable_build_ids()) { |
| string build_id_string = RawDataToHexString(build_id.build_id_hash()); |
| auto find_result = build_ids_to_filenames.find(build_id_string); |
| if (find_result == build_ids_to_filenames.end()) |
| continue; |
| const string& new_filename = find_result->second; |
| filename_map[build_id.filename()] = new_filename; |
| } |
| |
| return LocalizeUsingFilenames(filename_map); |
| } |
| |
| bool PerfReader::LocalizeUsingFilenames( |
| const std::map<string, string>& filename_map) { |
| LocalizeMMapFilenames(filename_map); |
| for (auto& build_id : *proto_.mutable_build_ids()) { |
| auto find_result = filename_map.find(build_id.filename()); |
| if (find_result != filename_map.end()) |
| build_id.set_filename(find_result->second); |
| } |
| return true; |
| } |
| |
| void PerfReader::GetFilenames(std::vector<string>* filenames) const { |
| std::set<string> filename_set; |
| GetFilenamesAsSet(&filename_set); |
| filenames->clear(); |
| filenames->insert(filenames->begin(), filename_set.begin(), |
| filename_set.end()); |
| } |
| |
| void PerfReader::GetFilenamesAsSet(std::set<string>* filenames) const { |
| filenames->clear(); |
| for (const PerfEvent& event : proto_.events()) { |
| if (event.header().type() == PERF_RECORD_MMAP || |
| event.header().type() == PERF_RECORD_MMAP2) { |
| filenames->insert(event.mmap_event().filename()); |
| } |
| } |
| } |
| |
| void PerfReader::GetFilenamesToBuildIDs( |
| std::map<string, string>* filenames_to_build_ids) const { |
| filenames_to_build_ids->clear(); |
| for (const auto& build_id : proto_.build_ids()) { |
| string build_id_string = RawDataToHexString(build_id.build_id_hash()); |
| PerfizeBuildIDString(&build_id_string); |
| (*filenames_to_build_ids)[build_id.filename()] = build_id_string; |
| } |
| } |
| |
| void PerfReader::MaybeSortEventsByTime() { |
| // Events can not be sorted by time if PERF_SAMPLE_TIME is not set in |
| // attr.sample_type for all attrs. |
| for (const auto& attr : attrs()) { |
| if (!(attr.attr().sample_type() & PERF_SAMPLE_TIME)) { |
| return; |
| } |
| } |
| |
| // Sort the events based on timestamp. |
| std::stable_sort(proto_.mutable_events()->begin(), |
| proto_.mutable_events()->end(), |
| CompareEventTimes); |
| } |
| |
| bool PerfReader::ReadHeader(DataReader* data) { |
| CheckNoEventHeaderPadding(); |
| // The header is the first thing to be read. Don't use SeekSet(0) because it |
| // doesn't make sense for piped files. Instead, verify that the reader points |
| // to the start of the data. |
| CHECK_EQ(0U, data->Tell()); |
| if (!data->ReadUint64(&piped_header_.magic)) { |
| LOG(ERROR) << "Error reading header magic number."; |
| return false; |
| } |
| |
| if (piped_header_.magic != kPerfMagic && |
| piped_header_.magic != bswap_64(kPerfMagic)) { |
| LOG(ERROR) << "Read wrong magic. Expected: 0x" << std::hex << kPerfMagic |
| << " or 0x" << std::hex << bswap_64(kPerfMagic) |
| << " Got: 0x" << std::hex << piped_header_.magic; |
| return false; |
| } |
| is_cross_endian_ = (piped_header_.magic != kPerfMagic); |
| data->set_is_cross_endian(is_cross_endian_); |
| |
| if (!data->ReadUint64(&piped_header_.size)) { |
| LOG(ERROR) << "Error reading header size."; |
| return false; |
| } |
| |
| CHECK_EQ(data->Tell(), sizeof(piped_header_)); |
| |
| // Header can be a piped header. |
| if (piped_header_.size == sizeof(piped_header_)) |
| return true; |
| |
| // Read as a non-piped header. |
| if (!data->ReadUint64(&header_.attr_size)) { |
| LOG(ERROR) << "Error reading header::attr_size."; |
| return false; |
| } |
| if (!ReadPerfFileSection(data, &header_.attrs) || |
| !ReadPerfFileSection(data, &header_.data) || |
| !ReadPerfFileSection(data, &header_.event_types)) { |
| LOG(ERROR) << "Error reading header file section info."; |
| return false; |
| } |
| |
| const size_t features_size = sizeof(header_.adds_features); |
| CHECK_EQ(data->Tell(), sizeof(header_) - features_size); |
| |
| if (!data->ReadData(features_size, header_.adds_features)) { |
| LOG(ERROR) << "Error reading header::adds_features."; |
| return false; |
| } |
| proto_.set_metadata_mask(0, header_.adds_features[0]); |
| |
| // Byte-swapping |adds_features| is tricky. It is defined as an array of |
| // unsigned longs, which can vary between architectures. However, the overall |
| // size of the array in bytes is fixed. |
| // |
| // According to perf's perf_file_header__read() function, the hostname feature |
| // should always be set. Try byte-swapping as uint64s first and check the |
| // hostname bit. If it's not set, then try swapping as uint32s. This is |
| // similar to the algorithm used in perf. |
| if (data->is_cross_endian()) { |
| static_assert(sizeof(header_.adds_features[0]) == sizeof(uint32_t) || |
| sizeof(header_.adds_features[0]) == sizeof(uint64_t), |
| "|header_.adds_features| must be defined as an array of " |
| "either 32-bit or 64-bit words."); |
| |
| uint64_t features64 = 0; |
| // Some compilers will complain if we directly cast |header_.adds_features| |
| // to a uint64_t*. Instead, determine the first uint64_t without using |
| // pointer aliasing. |
| if (sizeof(header_.adds_features[0]) == sizeof(uint64_t)) { |
| features64 = bswap_64(header_.adds_features[0]); |
| } else { |
| // If the native |adds_features| is composed of 32-bit words, swap the |
| // byte order of each word and then swap their positions to create a |
| // 64-bit word. |
| features64 = |
| static_cast<uint64_t>(bswap_32(header_.adds_features[0])) << 32; |
| features64 |= bswap_32(header_.adds_features[1]); |
| } |
| if (features64 & (1 << HEADER_HOSTNAME)) { |
| for (size_t i = 0; i < features_size / sizeof(uint64_t); ++i) |
| ByteSwap(reinterpret_cast<uint64_t*>(header_.adds_features) + i); |
| } else { |
| for (size_t i = 0; i < features_size / sizeof(uint32_t); ++i) |
| ByteSwap(reinterpret_cast<uint32_t*>(header_.adds_features) + i); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool PerfReader::ReadAttrsSection(DataReader* data) { |
| size_t num_attrs = header_.attrs.size / header_.attr_size; |
| if (header_.attrs.size % header_.attr_size != 0) { |
| LOG(ERROR) << "Total size of attrs " << header_.attrs.size |
| << " is not a multiple of attr size " << header_.attr_size; |
| } |
| data->SeekSet(header_.attrs.offset); |
| for (size_t i = 0; i < num_attrs; i++) { |
| if (!ReadAttr(data)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool PerfReader::ReadAttr(DataReader* data) { |
| PerfFileAttr attr; |
| if (!ReadEventAttr(data, &attr.attr)) |
| return false; |
| |
| perf_file_section ids; |
| if (!ReadPerfFileSection(data, &ids)) |
| return false; |
| |
| // The ID may be stored at a different location in the file than where we're |
| // currently reading. |
| size_t saved_offset = data->Tell(); |
| data->SeekSet(ids.offset); |
| |
| size_t num_ids = ids.size / sizeof(decltype(attr.ids)::value_type); |
| if (!ReadUniqueIDs(data, num_ids, &attr.ids)) |
| return false; |
| data->SeekSet(saved_offset); |
| AddPerfFileAttr(attr); |
| |
| return true; |
| } |
| |
| bool PerfReader::ReadEventAttr(DataReader* data, perf_event_attr* attr) { |
| CheckNoPerfEventAttrPadding(); |
| *attr = {0}; |
| |
| static_assert( |
| offsetof(struct perf_event_attr, size) == sizeof(perf_event_attr::type), |
| "type and size should be the first to fields of perf_event_attr"); |
| |
| if (!data->ReadUint32(&attr->type) || !data->ReadUint32(&attr->size)) { |
| LOG(ERROR) << "Error reading event attr type and size."; |
| return false; |
| } |
| |
| // Now read the rest of the attr struct. |
| const size_t attr_offset = sizeof(attr->type) + sizeof(attr->size); |
| const size_t attr_readable_size = |
| std::min(static_cast<size_t>(attr->size), sizeof(*attr)); |
| if (!data->ReadDataValue(attr_readable_size - attr_offset, "attribute", |
| reinterpret_cast<char*>(attr) + attr_offset)) { |
| return false; |
| } |
| data->SeekSet(data->Tell() + attr->size - attr_readable_size); |
| |
| if (data->is_cross_endian()) { |
| // Depending on attr->size, some of these might not have actually been |
| // read. This is okay: they are zero. |
| ByteSwap(&attr->type); |
| ByteSwap(&attr->size); |
| ByteSwap(&attr->config); |
| ByteSwap(&attr->sample_period); |
| ByteSwap(&attr->sample_type); |
| ByteSwap(&attr->read_format); |
| |
| // NB: This will also reverse precise_ip : 2 as if it was two fields: |
| auto *const bitfield_start = &attr->read_format + 1; |
| SwapBitfieldOfBits(reinterpret_cast<u8*>(bitfield_start), sizeof(u64)); |
| // ... So swap it back: |
| const auto tmp = attr->precise_ip; |
| attr->precise_ip = (tmp & 0x2) >> 1 | (tmp & 0x1) << 1; |
| |
| ByteSwap(&attr->wakeup_events); // union with wakeup_watermark |
| ByteSwap(&attr->bp_type); |
| ByteSwap(&attr->bp_addr); // union with config1 |
| ByteSwap(&attr->bp_len); // union with config2 |
| ByteSwap(&attr->branch_sample_type); |
| ByteSwap(&attr->sample_regs_user); |
| ByteSwap(&attr->sample_stack_user); |
| } |
| |
| // The actual perf_event_attr data size might be different from the size of |
| // the struct definition. Check against perf_event_attr's |size| field. |
| attr->size = sizeof(*attr); |
| |
| return true; |
| } |
| |
| bool PerfReader::ReadUniqueIDs(DataReader* data, size_t num_ids, |
| std::vector<u64>* ids) { |
| ids->resize(num_ids); |
| for (u64& id : *ids) { |
| if (!data->ReadUint64(&id)) { |
| LOG(ERROR) << "Error reading unique ID."; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PerfReader::ReadEventTypesSection(DataReader* data) { |
| int num_event_types = |
| header_.event_types.size / sizeof(struct perf_trace_event_type); |
| if (num_event_types == 0) { |
| // Not available. |
| return true; |
| } |
| CHECK_EQ(proto_.file_attrs().size(), num_event_types); |
| CHECK_EQ(sizeof(perf_trace_event_type) * num_event_types, |
| header_.event_types.size); |
| data->SeekSet(header_.event_types.offset); |
| for (int i = 0; i < num_event_types; ++i) { |
| if (!ReadEventType(data, i, 0)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool PerfReader::ReadEventType(DataReader* data, int attr_idx, |
| size_t event_size) { |
| CheckNoEventTypePadding(); |
| decltype(perf_trace_event_type::event_id) event_id; |
| |
| if (!data->ReadUint64(&event_id)) { |
| LOG(ERROR) << "Error reading event ID."; |
| return false; |
| } |
| |
| size_t event_name_len; |
| if (event_size == 0) { // Not in an event. |
| event_name_len = sizeof(perf_trace_event_type::name); |
| } else { |
| event_name_len = event_size - sizeof(perf_event_header) - sizeof(event_id); |
| } |
| |
| PerfFileAttr attr; |
| if (!data->ReadString(event_name_len, &attr.name)) { |
| LOG(ERROR) << "Not enough data left in data to read event name."; |
| return false; |
| } |
| |
| if (attr_idx >= proto_.file_attrs().size()) { |
| LOG(ERROR) << "Too many event types, or attrs not read yet!"; |
| return false; |
| } |
| if (event_id != proto_.file_attrs(attr_idx).attr().config()) { |
| LOG(ERROR) << "event_id for perf_trace_event_type (" << event_id << ") " |
| << "does not match attr.config (" |
| << proto_.file_attrs(attr_idx).attr().config() << ")"; |
| return false; |
| } |
| attr.attr.config = proto_.file_attrs(attr_idx).attr().config(); |
| |
| serializer_.SerializePerfEventType(attr, proto_.add_event_types()); |
| return true; |
| } |
| |
| bool PerfReader::ReadDataSection(DataReader* data) { |
| u64 data_remaining_bytes = header_.data.size; |
| data->SeekSet(header_.data.offset); |
| while (data_remaining_bytes != 0) { |
| // Read the header to determine the size of the event. |
| perf_event_header header; |
| if (!ReadPerfEventHeader(data, &header)) { |
| LOG(ERROR) << "Error reading event header from data section."; |
| return false; |
| } |
| |
| // Read the rest of the event data. |
| malloced_unique_ptr<event_t> event(CallocMemoryForEvent(header.size)); |
| event->header = header; |
| if (!data->ReadDataValue(event->header.size - sizeof(event->header), |
| "rest of event", &event->header + 1)) { |
| return false; |
| } |
| // TODO(sque): Find some way to combine this with the serialization below. |
| MaybeSwapEventFields(event.get(), data->is_cross_endian()); |
| |
| // We must have a valid way to read sample info before reading perf events. |
| CHECK(serializer_.SampleInfoReaderAvailable()); |
| |
| // Serialize the event to protobuf form. |
| PerfEvent* proto_event = proto_.add_events(); |
| if (!serializer_.SerializeEvent(event, proto_event)) |
| return false; |
| |
| data_remaining_bytes -= event->header.size; |
| } |
| |
| DLOG(INFO) << "Number of events stored: "<< proto_.events_size(); |
| return true; |
| } |
| |
| bool PerfReader::ReadMetadata(DataReader* data) { |
| // Metadata comes after the event data. |
| data->SeekSet(header_.data.offset + header_.data.size); |
| |
| // Read the (offset, size) pairs of all the metadata elements. Note that this |
| // takes into account all present metadata types, not just the ones included |
| // in |kSupportedMetadataMask|. If a metadata type is not supported, it is |
| // skipped over. |
| std::vector<struct perf_file_section> sections(GetNumBits(metadata_mask())); |
| for (struct perf_file_section& section : sections) { |
| if (!ReadPerfFileSection(data, §ion)) { |
| LOG(ERROR) << "Error reading metadata entry info."; |
| return false; |
| } |
| } |
| |
| auto section_iter = sections.begin(); |
| for (u32 type = HEADER_FIRST_FEATURE; type != HEADER_LAST_FEATURE; ++type) { |
| if (!get_metadata_mask_bit(type)) |
| continue; |
| data->SeekSet(section_iter->offset); |
| u64 size = section_iter->size; |
| |
| switch (type) { |
| case HEADER_TRACING_DATA: |
| if (!ReadTracingMetadata(data, size)) |
| return false; |
| break; |
| case HEADER_BUILD_ID: |
| if (!ReadBuildIDMetadata(data, size)) |
| return false; |
| break; |
| case HEADER_HOSTNAME: |
| if (!ReadSingleStringMetadata( |
| data, size, |
| proto_.mutable_string_metadata()->mutable_hostname())) { |
| return false; |
| } |
| break; |
| case HEADER_OSRELEASE: |
| if (!ReadSingleStringMetadata( |
| data, size, |
| proto_.mutable_string_metadata()->mutable_kernel_version())) { |
| return false; |
| } |
| break; |
| case HEADER_VERSION: |
| if (!ReadSingleStringMetadata( |
| data, size, |
| proto_.mutable_string_metadata()->mutable_perf_version())) { |
| return false; |
| } |
| break; |
| case HEADER_ARCH: |
| if (!ReadSingleStringMetadata( |
| data, size, |
| proto_.mutable_string_metadata()->mutable_architecture())) { |
| return false; |
| } |
| break; |
| case HEADER_CPUDESC: |
| if (!ReadSingleStringMetadata( |
| data, size, |
| proto_.mutable_string_metadata()->mutable_cpu_description())) { |
| return false; |
| } |
| break; |
| case HEADER_CPUID: |
| if (!ReadSingleStringMetadata( |
| data, size, |
| proto_.mutable_string_metadata()->mutable_cpu_id())) { |
| return false; |
| } |
| break; |
| case HEADER_CMDLINE: |
| { |
| auto* string_metadata = proto_.mutable_string_metadata(); |
| if (!ReadRepeatedStringMetadata( |
| data, size, |
| string_metadata->mutable_perf_command_line_token(), |
| string_metadata->mutable_perf_command_line_whole())) { |
| return false; |
| } |
| break; |
| } |
| case HEADER_NRCPUS: |
| if (!ReadUint32Metadata(data, type, size)) |
| return false; |
| break; |
| case HEADER_TOTAL_MEM: |
| if (!ReadUint64Metadata(data, type, size)) |
| return false; |
| break; |
| case HEADER_EVENT_DESC: |
| if (!ReadEventDescMetadata(data, type, size)) |
| return false; |
| break; |
| case HEADER_CPU_TOPOLOGY: |
| if (!ReadCPUTopologyMetadata(data, type, size)) |
| return false; |
| break; |
| case HEADER_NUMA_TOPOLOGY: |
| if (!ReadNUMATopologyMetadata(data, type, size)) |
| return false; |
| break; |
| case HEADER_BRANCH_STACK: |
| break; |
| default: |
| LOG(INFO) << "Unsupported metadata type, skipping: " << type; |
| break; |
| } |
| ++section_iter; |
| } |
| |
| return true; |
| } |
| |
| bool PerfReader::ReadBuildIDMetadata(DataReader* data, size_t size) { |
| CheckNoBuildIDEventPadding(); |
| while (size > 0) { |
| // Make sure there is enough data for everything but the filename. |
| perf_event_header build_id_header; |
| if (!ReadPerfEventHeader(data, &build_id_header)) { |
| LOG(ERROR) << "Error reading build ID header."; |
| return false; |
| } |
| |
| if (!ReadBuildIDMetadataWithoutHeader(data, build_id_header)) |
| return false; |
| size -= build_id_header.size; |
| } |
| |
| return true; |
| } |
| |
| bool PerfReader::ReadBuildIDMetadataWithoutHeader( |
| DataReader* data, const perf_event_header& header) { |
| // Allocate memory for the event. |
| malloced_unique_ptr<build_id_event> |
| event(CallocMemoryForBuildID(header.size)); |
| event->header = header; |
| |
| // Make sure there is enough data for the rest of the event. |
| if (!data->ReadDataValue(header.size - sizeof(header), |
| "rest of build ID event", &event->header + 1)) { |
| LOG(ERROR) << "Not enough bytes to read build id event"; |
| return false; |
| } |
| if (data->is_cross_endian()) |
| ByteSwap(&event->pid); |
| |
| // Perf tends to use more space than necessary, so fix the size. |
| event->header.size = |
| sizeof(*event) + GetUint64AlignedStringLength(event->filename); |
| |
| if (!serializer_.SerializeBuildIDEvent(event, proto_.add_build_ids())) { |
| LOG(ERROR) << "Could not serialize build ID event with ID " |
| << RawDataToHexString(event->build_id, sizeof(event->build_id)); |
| return false; |
| } |
| return true; |
| } |
| |
| bool PerfReader::ReadSingleStringMetadata( |
| DataReader* data, |
| size_t max_readable_size, |
| StringAndMd5sumPrefix* dest) const { |
| // If a string metadata field is present but empty, it can have a size of 0, |
| // in which case there is nothing to be read. |
| string single_string; |
| if (max_readable_size && !data->ReadStringWithSizeFromData(&single_string)) |
| return false; |
| dest->set_value(single_string); |
| dest->set_value_md5_prefix(Md5Prefix(single_string)); |
| return true; |
| } |
| |
| bool PerfReader::ReadRepeatedStringMetadata( |
| DataReader* data, |
| size_t max_readable_size, |
| RepeatedPtrField<StringAndMd5sumPrefix>* dest_array, |
| StringAndMd5sumPrefix* dest_single) const { |
| num_string_data_type count = 1; |
| if (!data->ReadUint32(&count)) { |
| LOG(ERROR) << "Error reading string count."; |
| return false; |
| } |
| size_t size_read = sizeof(count); |
| |
| string full_string; |
| while (count-- > 0 && size_read < max_readable_size) { |
| StringAndMd5sumPrefix* new_entry = dest_array->Add(); |
| size_t offset = data->Tell(); |
| if (!ReadSingleStringMetadata(data, max_readable_size - size_read, |
| new_entry)) { |
| return false; |
| } |
| |
| if (!full_string.empty()) |
| full_string += " "; |
| full_string += new_entry->value(); |
| |
| size_read += data->Tell() - offset; |
| } |
| |
| dest_single->set_value(full_string); |
| dest_single->set_value_md5_prefix(Md5Prefix(full_string)); |
| return true; |
| } |
| |
| bool PerfReader::ReadUint32Metadata(DataReader* data, u32 type, size_t size) { |
| PerfUint32Metadata uint32_data; |
| uint32_data.type = type; |
| |
| while (size > 0) { |
| uint32_t item; |
| if (!data->ReadUint32(&item)) { |
| LOG(ERROR) << "Error reading uint32 metadata"; |
| return false; |
| } |
| |
| uint32_data.data.push_back(item); |
| size -= sizeof(item); |
| } |
| |
| serializer_.SerializeSingleUint32Metadata(uint32_data, |
| proto_.add_uint32_metadata()); |
| return true; |
| } |
| |
| bool PerfReader::ReadUint64Metadata(DataReader* data, u32 type, size_t size) { |
| PerfUint64Metadata uint64_data; |
| uint64_data.type = type; |
| |
| while (size > 0) { |
| uint64_t item; |
| if (!data->ReadUint64(&item)) { |
| LOG(ERROR) << "Error reading uint64 metadata"; |
| return false; |
| } |
| |
| uint64_data.data.push_back(item); |
| size -= sizeof(item); |
| } |
| |
| serializer_.SerializeSingleUint64Metadata(uint64_data, |
| proto_.add_uint64_metadata()); |
| return true; |
| } |
| |
| // TODO(cwp-team): Move this to match the order in the header file. |
| bool PerfReader::ReadEventDescMetadata(DataReader* data, u32 type, |
| size_t size) { |
| // Structure: |
| // u32 nr_events |
| // u32 sizeof(perf_event_attr) |
| // foreach event (nr_events): |
| // struct perf_event_attr |
| // u32 nr_ids |
| // event name (len & string, 64-bit padded) |
| // u64 ids[nr_ids] |
| |
| u32 nr_events; |
| if (!data->ReadUint32(&nr_events)) { |
| LOG(ERROR) << "Error reading event_desc nr_events."; |
| return false; |
| } |
| |
| u32 attr_size; |
| if (!data->ReadUint32(&attr_size)) { |
| LOG(ERROR) << "Error reading event_desc attr_size."; |
| return false; |
| } |
| |
| proto_.clear_file_attrs(); |
| proto_.mutable_file_attrs()->Reserve(nr_events); |
| |
| for (u32 i = 0; i < nr_events; i++) { |
| PerfFileAttr attr; |
| if (!ReadEventAttr(data, &attr.attr)) |
| return false; |
| |
| u32 nr_ids; |
| if (!data->ReadUint32(&nr_ids)) { |
| LOG(ERROR) << "Error reading event_desc nr_ids."; |
| return false; |
| } |
| |
| if (!data->ReadStringWithSizeFromData(&attr.name)) |
| return false; |
| // TODO(sque): Read directly into proto_.file_attrs. |
| std::vector<u64> &ids = attr.ids; |
| ids.resize(nr_ids); |
| for (u64& id : ids) { |
| if (!data->ReadUint64(&id)) { |
| LOG(ERROR) << "Error reading ID value for attr #" << i; |
| return false; |
| } |
| } |
| AddPerfFileAttr(attr); |
| // The EVENT_DESC metadata is the newer replacement for the older event type |
| // fields. In the protobuf, both types of data are stored in the |
| // |event_types| field. |
| serializer_.SerializePerfEventType(attr, proto_.add_event_types()); |
| } |
| return true; |
| } |
| |
| bool PerfReader::ReadCPUTopologyMetadata(DataReader* data, u32 type, |
| size_t size) { |
| num_siblings_type num_core_siblings; |
| if (!data->ReadUint32(&num_core_siblings)) { |
| LOG(ERROR) << "Error reading num core siblings."; |
| return false; |
| } |
| |
| PerfCPUTopologyMetadata cpu_topology; |
| cpu_topology.core_siblings.resize(num_core_siblings); |
| for (size_t i = 0; i < num_core_siblings; ++i) { |
| if (!data->ReadStringWithSizeFromData(&cpu_topology.core_siblings[i])) |
| return false; |
| } |
| |
| num_siblings_type num_thread_siblings; |
| if (!data->ReadUint32(&num_thread_siblings)) { |
| LOG(ERROR) << "Error reading num core siblings."; |
| return false; |
| } |
| |
| cpu_topology.thread_siblings.resize(num_thread_siblings); |
| for (size_t i = 0; i < num_thread_siblings; ++i) { |
| if (!data->ReadStringWithSizeFromData(&cpu_topology.thread_siblings[i])) |
| return false; |
| } |
| |
| serializer_.SerializeCPUTopologyMetadata(cpu_topology, |
| proto_.mutable_cpu_topology()); |
| return true; |
| } |
| |
| bool PerfReader::ReadNUMATopologyMetadata(DataReader* data, |
| u32 type, |
| size_t size) { |
| numa_topology_num_nodes_type num_nodes; |
| if (!data->ReadUint32(&num_nodes)) { |
| LOG(ERROR) << "Error reading NUMA topology num nodes."; |
| return false; |
| } |
| |
| for (size_t i = 0; i < num_nodes; ++i) { |
| PerfNodeTopologyMetadata node; |
| if (!data->ReadUint32(&node.id) || |
| !data->ReadUint64(&node.total_memory) || |
| !data->ReadUint64(&node.free_memory) || |
| !data->ReadStringWithSizeFromData(&node.cpu_list)) { |
| LOG(ERROR) << "Error reading NUMA topology info for node #" << i; |
| return false; |
| } |
| serializer_.SerializeNodeTopologyMetadata(node, proto_.add_numa_topology()); |
| } |
| return true; |
| } |
| |
| // TODO(cwp-team): Move this to match the order in the header file. |
| bool PerfReader::ReadTracingMetadata(DataReader* data, size_t size) { |
| std::vector<char> tracing_data(size); |
| if (!data->ReadDataValue(tracing_data.size(), "tracing_data", |
| tracing_data.data())) { |
| return false; |
| } |
| serializer_.SerializeTracingMetadata(tracing_data, &proto_); |
| return true; |
| } |
| |
| bool PerfReader::ReadPipedData(DataReader* data) { |
| // The piped data comes right after the file header. |
| CHECK_EQ(piped_header_.size, data->Tell()); |
| bool result = true; |
| int num_event_types = 0; |
| |
| CheckNoEventHeaderPadding(); |
| |
| while (result && data->Tell() < data->size()) { |
| perf_event_header header; |
| if (!ReadPerfEventHeader(data, &header)) { |
| LOG(ERROR) << "Error reading event header."; |
| break; |
| } |
| |
| if (header.size == 0) { |
| // Avoid an infinite loop. |
| LOG(ERROR) << "Event size is zero. Type: " << header.type; |
| return false; |
| } |
| |
| // Compute the size of the post-header part of the event data. |
| size_t size_without_header = header.size - sizeof(header); |
| |
| if (header.type < PERF_RECORD_MAX) { |
| // Allocate space for an event struct based on the size in the header. |
| // Don't blindly allocate the entire event_t because it is a |
| // variable-sized type that may include data beyond what's nominally |
| // declared in its definition. |
| malloced_unique_ptr<event_t> event(CallocMemoryForEvent(header.size)); |
| event->header = header; |
| |
| // Read the rest of the event data. |
| if (!data->ReadDataValue(size_without_header, "rest of piped event", |
| &event->header + 1)) { |
| break; |
| } |
| MaybeSwapEventFields(event.get(), data->is_cross_endian()); |
| |
| // Serialize the event to protobuf form. |
| PerfEvent* proto_event = proto_.add_events(); |
| if (!serializer_.SerializeEvent(event, proto_event)) |
| return false; |
| |
| continue; |
| } |
| |
| switch (header.type) { |
| case PERF_RECORD_HEADER_ATTR: |
| result = ReadAttrEventBlock(data, size_without_header); |
| break; |
| case PERF_RECORD_HEADER_EVENT_TYPE: |
| result = ReadEventType(data, num_event_types++, header.size); |
| break; |
| case PERF_RECORD_HEADER_EVENT_DESC: |
| set_metadata_mask_bit(HEADER_EVENT_DESC); |
| result = ReadEventDescMetadata(data, HEADER_EVENT_DESC, |
| size_without_header); |
| break; |
| case PERF_RECORD_HEADER_TRACING_DATA: |
| set_metadata_mask_bit(HEADER_TRACING_DATA); |
| { |
| // TRACING_DATA's header.size is a lie. It is the size of only the event |
| // struct. The size of the data is in the event struct, and followed |
| // immediately by the tracing header data. |
| decltype(tracing_data_event::size) size = 0; |
| if (!data->ReadUint32(&size)) { |
| LOG(ERROR) << "Error reading tracing data size."; |
| return false; |
| } |
| result = ReadTracingMetadata(data, size); |
| } |
| break; |
| case PERF_RECORD_HEADER_BUILD_ID: |
| set_metadata_mask_bit(HEADER_BUILD_ID); |
| result = ReadBuildIDMetadataWithoutHeader(data, header); |
| break; |
| case PERF_RECORD_HEADER_HOSTNAME: |
| set_metadata_mask_bit(HEADER_HOSTNAME); |
| result = ReadSingleStringMetadata( |
| data, size_without_header, |
| proto_.mutable_string_metadata()->mutable_hostname()); |
| break; |
| case PERF_RECORD_HEADER_OSRELEASE: |
| set_metadata_mask_bit(HEADER_OSRELEASE); |
| result = ReadSingleStringMetadata( |
| data, size_without_header, |
| proto_.mutable_string_metadata()->mutable_kernel_version()); |
| break; |
| case PERF_RECORD_HEADER_VERSION: |
| set_metadata_mask_bit(HEADER_VERSION); |
| result = ReadSingleStringMetadata( |
| data, size_without_header, |
| proto_.mutable_string_metadata()->mutable_perf_version()); |
| break; |
| case PERF_RECORD_HEADER_ARCH: |
| set_metadata_mask_bit(HEADER_ARCH); |
| result = ReadSingleStringMetadata( |
| data, size_without_header, |
| proto_.mutable_string_metadata()->mutable_architecture()); |
| break; |
| case PERF_RECORD_HEADER_CPUDESC: |
| set_metadata_mask_bit(HEADER_CPUDESC); |
| result = ReadSingleStringMetadata( |
| data, size_without_header, |
| proto_.mutable_string_metadata()->mutable_cpu_description()); |
| break; |
| case PERF_RECORD_HEADER_CPUID: |
| set_metadata_mask_bit(HEADER_CPUID); |
| result = ReadSingleStringMetadata( |
| data, size_without_header, |
| proto_.mutable_string_metadata()->mutable_cpu_id()); |
| break; |
| case PERF_RECORD_HEADER_CMDLINE: |
| { |
| set_metadata_mask_bit(HEADER_CMDLINE); |
| auto* string_metadata = proto_.mutable_string_metadata(); |
| result = ReadRepeatedStringMetadata( |
| data, size_without_header, |
| string_metadata->mutable_perf_command_line_token(), |
| string_metadata->mutable_perf_command_line_whole()); |
| break; |
| } |
| case PERF_RECORD_HEADER_NRCPUS: |
| set_metadata_mask_bit(HEADER_NRCPUS); |
| result = ReadUint32Metadata(data, HEADER_NRCPUS, size_without_header); |
| break; |
| case PERF_RECORD_HEADER_TOTAL_MEM: |
| set_metadata_mask_bit(HEADER_TOTAL_MEM); |
| result = ReadUint64Metadata(data, HEADER_TOTAL_MEM, size_without_header); |
| break; |
| case PERF_RECORD_HEADER_CPU_TOPOLOGY: |
| set_metadata_mask_bit(HEADER_CPU_TOPOLOGY); |
| result = ReadCPUTopologyMetadata(data, HEADER_CPU_TOPOLOGY, |
| size_without_header); |
| break; |
| case PERF_RECORD_HEADER_NUMA_TOPOLOGY: |
| set_metadata_mask_bit(HEADER_NUMA_TOPOLOGY); |
| result = ReadNUMATopologyMetadata(data, HEADER_NUMA_TOPOLOGY, |
| size_without_header); |
| break; |
| default: |
| // For unsupported event types, log a warning only if the type is an |
| // unknown type. |
| if (header.type < PERF_RECORD_USER_TYPE_START || |
| header.type >= PERF_RECORD_HEADER_MAX) { |
| LOG(WARNING) << "Unknown event type: " << header.type; |
| } |
| // Skip over the data in this event. |
| data->SeekSet(data->Tell() + size_without_header); |
| break; |
| } |
| } |
| |
| if (!result) |
| return false; |
| |
| // The PERF_RECORD_HEADER_EVENT_TYPE events are obsolete, but if present |
| // and PERF_RECORD_HEADER_EVENT_DESC metadata events are not, we should use |
| // them. Otherwise, we should use prefer the _EVENT_DESC data. |
| if (!get_metadata_mask_bit(HEADER_EVENT_DESC) && |
| num_event_types == proto_.file_attrs().size()) { |
| // We can construct HEADER_EVENT_DESC: |
| set_metadata_mask_bit(HEADER_EVENT_DESC); |
| } |
| |
| return result; |
| } |
| |
| bool PerfReader::WriteHeader(const struct perf_file_header& header, |
| DataWriter* data) const { |
| CheckNoEventHeaderPadding(); |
| size_t size = sizeof(header); |
| return data->WriteDataValue(&header, size, "file header"); |
| } |
| |
| bool PerfReader::WriteAttrs(const struct perf_file_header& header, |
| DataWriter* data) const { |
| CheckNoPerfEventAttrPadding(); |
| const size_t id_offset = header.size; |
| CHECK_EQ(id_offset, data->Tell()); |
| |
| std::vector<struct perf_file_section> id_sections; |
| id_sections.reserve(attrs().size()); |
| for (const auto& attr : proto_.file_attrs()) { |
| size_t section_size = |
| attr.ids_size() * sizeof(decltype(PerfFileAttr::ids)::value_type); |
| id_sections.push_back(perf_file_section{data->Tell(), section_size}); |
| for (const uint64_t& id : attr.ids()) { |
| if (!data->WriteDataValue(&id, sizeof(id), "ID info")) |
| return false; |
| } |
| } |
| |
| CHECK_EQ(header.attrs.offset, data->Tell()); |
| for (int i = 0; i < attrs().size(); i++) { |
| const struct perf_file_section& id_section = id_sections[i]; |
| PerfFileAttr attr; |
| serializer_.DeserializePerfFileAttr(proto_.file_attrs(i), &attr); |
| if (!data->WriteDataValue(&attr.attr, sizeof(attr.attr), "attribute") || |
| !data->WriteDataValue(&id_section, sizeof(id_section), "ID section")) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PerfReader::WriteData(const struct perf_file_header& header, |
| DataWriter* data) const { |
| // No need to CHECK anything if no event data is being written. |
| if (proto_.events().empty()) |
| return true; |
| |
| CHECK(serializer_.SampleInfoReaderAvailable()); |
| CHECK_EQ(header.data.offset, data->Tell()); |
| for (const PerfEvent& proto_event : proto_.events()) { |
| malloced_unique_ptr<event_t> event; |
| // The nominal size given by |proto_event| may not be correct, as the |
| // contents may have changed since the PerfEvent was created. Use the size |
| // in the event_t returned by PerfSerializer::DeserializeEvent(). |
| if (!serializer_.DeserializeEvent(proto_event, &event) || |
| !data->WriteDataValue(event.get(), event->header.size, "event data")) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PerfReader::WriteMetadata(const struct perf_file_header& header, |
| DataWriter* data) const { |
| const size_t header_offset = header.data.offset + header.data.size; |
| CHECK_EQ(header_offset, data->Tell()); |
| |
| // There is one header for each feature pointing to the metadata for that |
| // feature. If a feature has no metadata, the size field is zero. |
| const size_t headers_size = |
| GetNumSupportedMetadata() * sizeof(perf_file_section); |
| const size_t metadata_offset = header_offset + headers_size; |
| data->SeekSet(metadata_offset); |
| |
| // Record the new metadata offsets and sizes in this vector of info entries. |
| std::vector<struct perf_file_section> metadata_sections; |
| metadata_sections.reserve(GetNumSupportedMetadata()); |
| |
| // For less verbose access to string metadata fields. |
| const auto& string_metadata = proto_.string_metadata(); |
| |
| for (u32 type = HEADER_FIRST_FEATURE; type != HEADER_LAST_FEATURE; ++type) { |
| if ((header.adds_features[0] & (1 << type)) == 0) |
| continue; |
| |
| struct perf_file_section header_entry; |
| header_entry.offset = data->Tell(); |
| // Write actual metadata to address metadata_offset |
| switch (type) { |
| case HEADER_TRACING_DATA: |
| if (!data->WriteDataValue(tracing_data().data(), tracing_data().size(), |
| "tracing data")) { |
| return false; |
| } |
| break; |
| case HEADER_BUILD_ID: |
| if (!WriteBuildIDMetadata(type, data)) |
| return false; |
| break; |
| case HEADER_HOSTNAME: |
| if (!WriteSingleStringMetadata(string_metadata.hostname(), data)) |
| return false; |
| break; |
| case HEADER_OSRELEASE: |
| if (!WriteSingleStringMetadata(string_metadata.kernel_version(), data)) |
| return false; |
| break; |
| case HEADER_VERSION: |
| if (!WriteSingleStringMetadata(string_metadata.perf_version(), data)) |
| return false; |
| break; |
| case HEADER_ARCH: |
| if (!WriteSingleStringMetadata(string_metadata.architecture(), data)) |
| return false; |
| break; |
| case HEADER_CPUDESC: |
| if (!WriteSingleStringMetadata(string_metadata.cpu_description(), data)) |
| return false; |
| break; |
| case HEADER_CPUID: |
| if (!WriteSingleStringMetadata(string_metadata.cpu_id(), data)) |
| return false; |
| break; |
| case HEADER_CMDLINE: |
| if (!WriteRepeatedStringMetadata( |
| string_metadata.perf_command_line_token(), |
| data)) { |
| return false; |
| } |
| break; |
| case HEADER_NRCPUS: |
| if (!WriteUint32Metadata(type, data)) |
| return false; |
| break; |
| case HEADER_TOTAL_MEM: |
| if (!WriteUint64Metadata(type, data)) |
| return false; |
| break; |
| case HEADER_EVENT_DESC: |
| if (!WriteEventDescMetadata(type, data)) |
| return false; |
| break; |
| case HEADER_CPU_TOPOLOGY: |
| if (!WriteCPUTopologyMetadata(type, data)) |
| return false; |
| break; |
| case HEADER_NUMA_TOPOLOGY: |
| if (!WriteNUMATopologyMetadata(type, data)) |
| return false; |
| break; |
| case HEADER_BRANCH_STACK: |
| break; |
| default: LOG(ERROR) << "Unsupported metadata type: " << type; |
| return false; |
| } |
| |
| // Compute the size of the metadata that was just written. This is reflected |
| // in how much the data write pointer has moved. |
| header_entry.size = data->Tell() - header_entry.offset; |
| metadata_sections.push_back(header_entry); |
| } |
| // Make sure we have recorded the right number of entries. |
| CHECK_EQ(GetNumSupportedMetadata(), metadata_sections.size()); |
| |
| // Now write the metadata offset and size info back to the metadata headers. |
| size_t old_offset = data->Tell(); |
| data->SeekSet(header_offset); |
| if (!data->WriteDataValue(metadata_sections.data(), headers_size, |
| "metadata section info")) { |
| return false; |
| } |
| // Make sure the write pointer now points to the end of the metadata headers |
| // and hence the beginning of the actual metadata. |
| CHECK_EQ(metadata_offset, data->Tell()); |
| data->SeekSet(old_offset); |
| |
| return true; |
| } |
| |
| bool PerfReader::WriteBuildIDMetadata(u32 type, DataWriter* data) const { |
| CheckNoBuildIDEventPadding(); |
| for (const auto& build_id : proto_.build_ids()) { |
| malloced_unique_ptr<build_id_event> event; |
| if (!serializer_.DeserializeBuildIDEvent(build_id, &event)) { |
| LOG(ERROR) << "Could not deserialize build ID event with build ID " |
| << RawDataToHexString(build_id.build_id_hash()); |
| return false; |
| } |
| if (!data->WriteDataValue(event.get(), event->header.size, |
| "Build ID metadata")) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PerfReader::WriteSingleStringMetadata(const StringAndMd5sumPrefix& src, |
| DataWriter* data) const { |
| return data->WriteStringWithSizeToData(src.value()); |
| } |
| |
| bool PerfReader::WriteRepeatedStringMetadata( |
| const RepeatedPtrField<StringAndMd5sumPrefix>& src_array, |
| DataWriter* data) const { |
| num_string_data_type num_strings = src_array.size(); |
| if (!data->WriteDataValue(&num_strings, sizeof(num_strings), |
| "number of string metadata")) { |
| return false; |
| } |
| for (const auto& src_entry : src_array) { |
| if (!data->WriteStringWithSizeToData(src_entry.value())) |
| return false; |
| } |
| return true; |
| } |
| |
| bool PerfReader::WriteUint32Metadata(u32 type, DataWriter* data) const { |
| for (const auto& metadata : proto_.uint32_metadata()) { |
| if (metadata.type() != type) |
| continue; |
| PerfUint32Metadata local_metadata; |
| serializer_.DeserializeSingleUint32Metadata(metadata, &local_metadata); |
| const std::vector<uint32_t>& raw_data = local_metadata.data; |
| return data->WriteDataValue(raw_data.data(), |
| raw_data.size() * sizeof(uint32_t), |
| "uint32_t metadata"); |
| } |
| LOG(ERROR) << "Uint32 metadata of type " << type << " not present"; |
| return false; |
| } |
| |
| bool PerfReader::WriteUint64Metadata(u32 type, DataWriter* data) const { |
| for (const auto& metadata : proto_.uint64_metadata()) { |
| if (metadata.type() != type) |
| continue; |
| PerfUint64Metadata local_metadata; |
| serializer_.DeserializeSingleUint64Metadata(metadata, &local_metadata); |
| const std::vector<uint64_t>& raw_data = local_metadata.data; |
| return data->WriteDataValue(raw_data.data(), |
| raw_data.size() * sizeof(uint64_t), |
| "uint32_t metadata"); |
| } |
| LOG(ERROR) << "Uint64 metadata of type " << type << " not present"; |
| return false; |
| } |
| |
| bool PerfReader::WriteEventDescMetadata(u32 type, DataWriter* data) const { |
| CheckNoPerfEventAttrPadding(); |
| |
| if (attrs().size() > event_types().size()) { |
| LOG(ERROR) << "Number of attrs (" << attrs().size() << ") cannot exceed " |
| << "number of event types (" << event_types().size() << ")"; |
| return false; |
| } |
| |
| event_desc_num_events num_events = proto_.file_attrs().size(); |
| if (!data->WriteDataValue(&num_events, sizeof(num_events), |
| "event_desc num_events")) { |
| return false; |
| } |
| event_desc_attr_size attr_size = sizeof(perf_event_attr); |
| if (!data->WriteDataValue(&attr_size, sizeof(attr_size), |
| "event_desc attr_size")) { |
| return false; |
| } |
| |
| for (int i = 0; i < attrs().size(); ++i) { |
| const auto& stored_attr = attrs().Get(i); |
| PerfFileAttr attr; |
| serializer_.DeserializePerfFileAttr(stored_attr, &attr); |
| if (!serializer_.DeserializePerfEventType(proto_.event_types(i), &attr)) |
| return false; |
| |
| if (!data->WriteDataValue(&attr.attr, sizeof(attr.attr), |
| "event_desc attribute")) { |
| return false; |
| } |
| |
| event_desc_num_unique_ids num_ids = attr.ids.size(); |
| if (!data->WriteDataValue(&num_ids, sizeof(num_ids), |
| "event_desc num_unique_ids")) { |
| return false; |
| } |
| |
| if (!data->WriteStringWithSizeToData(attr.name)) |
| return false; |
| |
| if (!data->WriteDataValue(attr.ids.data(), num_ids * sizeof(attr.ids[0]), |
| "event_desc unique_ids")) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PerfReader::WriteCPUTopologyMetadata(u32 type, DataWriter* data) const { |
| PerfCPUTopologyMetadata cpu_topology; |
| serializer_.DeserializeCPUTopologyMetadata(proto_.cpu_topology(), |
| &cpu_topology); |
| |
| std::vector<string>& cores = cpu_topology.core_siblings; |
| num_siblings_type num_cores = cores.size(); |
| if (!data->WriteDataValue(&num_cores, sizeof(num_cores), "num cores")) |
| return false; |
| for (string& core_name : cores) { |
| if (!data->WriteStringWithSizeToData(core_name)) |
| return false; |
| } |
| |
| std::vector<string>& threads = cpu_topology.thread_siblings; |
| num_siblings_type num_threads = threads.size(); |
| if (!data->WriteDataValue(&num_threads, sizeof(num_threads), "num threads")) |
| return false; |
| for (string& thread_name : threads) { |
| if (!data->WriteStringWithSizeToData(thread_name)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool PerfReader::WriteNUMATopologyMetadata(u32 type, DataWriter* data) const { |
| numa_topology_num_nodes_type num_nodes = proto_.numa_topology().size(); |
| if (!data->WriteDataValue(&num_nodes, sizeof(num_nodes), "num nodes")) |
| return false; |
| |
| for (const auto& node_proto : proto_.numa_topology()) { |
| PerfNodeTopologyMetadata node; |
| serializer_.DeserializeNodeTopologyMetadata(node_proto, &node); |
| |
| if (!data->WriteDataValue(&node.id, sizeof(node.id), "node id") || |
| !data->WriteDataValue(&node.total_memory, sizeof(node.total_memory), |
| "node total memory") || |
| !data->WriteDataValue(&node.free_memory, sizeof(node.free_memory), |
| "node free memory") || |
| !data->WriteStringWithSizeToData(node.cpu_list)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PerfReader::ReadAttrEventBlock(DataReader* data, size_t size) { |
| const size_t initial_offset = data->Tell(); |
| PerfFileAttr attr; |
| if (!ReadEventAttr(data, &attr.attr)) |
| return false; |
| |
| // attr.attr.size has been upgraded to the current size of perf_event_attr. |
| const size_t actual_attr_size = data->Tell() - initial_offset; |
| |
| const size_t num_ids = |
| (size - actual_attr_size) / sizeof(decltype(attr.ids)::value_type); |
| if (!ReadUniqueIDs(data, num_ids, &attr.ids)) |
| return false; |
| |
| // Event types are found many times in the perf data file. |
| // Only add this event type if it is not already present. |
| for (const auto& stored_attr : proto_.file_attrs()) { |
| if (stored_attr.ids(0) == attr.ids[0]) |
| return true; |
| } |
| AddPerfFileAttr(attr); |
| return true; |
| } |
| |
| void PerfReader::MaybeSwapEventFields(event_t* event, bool is_cross_endian) { |
| if (!is_cross_endian) |
| return; |
| uint32_t type = event->header.type; |
| switch (type) { |
| case PERF_RECORD_SAMPLE: |
| break; |
| case PERF_RECORD_MMAP: |
| ByteSwap(&event->mmap.pid); |
| ByteSwap(&event->mmap.tid); |
| ByteSwap(&event->mmap.start); |
| ByteSwap(&event->mmap.len); |
| ByteSwap(&event->mmap.pgoff); |
| break; |
| case PERF_RECORD_MMAP2: |
| ByteSwap(&event->mmap2.pid); |
| ByteSwap(&event->mmap2.tid); |
| ByteSwap(&event->mmap2.start); |
| ByteSwap(&event->mmap2.len); |
| ByteSwap(&event->mmap2.pgoff); |
| ByteSwap(&event->mmap2.maj); |
| ByteSwap(&event->mmap2.min); |
| ByteSwap(&event->mmap2.ino); |
| ByteSwap(&event->mmap2.ino_generation); |
| ByteSwap(&event->mmap2.prot); |
| ByteSwap(&event->mmap2.flags); |
| break; |
| case PERF_RECORD_FORK: |
| case PERF_RECORD_EXIT: |
| ByteSwap(&event->fork.pid); |
| ByteSwap(&event->fork.tid); |
| ByteSwap(&event->fork.ppid); |
| ByteSwap(&event->fork.ptid); |
| ByteSwap(&event->fork.time); |
| break; |
| case PERF_RECORD_COMM: |
| ByteSwap(&event->comm.pid); |
| ByteSwap(&event->comm.tid); |
| break; |
| case PERF_RECORD_LOST: |
| ByteSwap(&event->lost.id); |
| ByteSwap(&event->lost.lost); |
| break; |
| case PERF_RECORD_THROTTLE: |
| case PERF_RECORD_UNTHROTTLE: |
| ByteSwap(&event->throttle.time); |
| ByteSwap(&event->throttle.id); |
| ByteSwap(&event->throttle.stream_id); |
| break; |
| case PERF_RECORD_READ: |
| ByteSwap(&event->read.pid); |
| ByteSwap(&event->read.tid); |
| ByteSwap(&event->read.value); |
| ByteSwap(&event->read.time_enabled); |
| ByteSwap(&event->read.time_running); |
| ByteSwap(&event->read.id); |
| break; |
| default: |
| LOG(FATAL) << "Unknown event type: " << type; |
| } |
| |
| // Do not swap the sample info fields that are not explicitly listed in the |
| // struct definition of each event type. Leave that up to SampleInfoReader |
| // within |serializer_|. |
| } |
| |
| size_t PerfReader::GetNumSupportedMetadata() const { |
| return GetNumBits(metadata_mask() & kSupportedMetadataMask); |
| } |
| |
| size_t PerfReader::GetEventDescMetadataSize() const { |
| if (attrs().size() > event_types().size()) { |
| LOG(ERROR) << "Number of attrs (" << attrs().size() << ") cannot exceed " |
| << "number of event types (" << event_types().size() << ")"; |
| return 0; |
| } |
| |
| size_t size = 0; |
| if (get_metadata_mask_bit(HEADER_EVENT_DESC)) { |
| size += sizeof(event_desc_num_events) + sizeof(event_desc_attr_size); |
| for (int i = 0; i < attrs().size(); ++i) { |
| size += sizeof(perf_event_attr); |
| size += sizeof(event_desc_num_unique_ids); |
| size += ExpectedStorageSizeOf(event_types().Get(i).name()); |
| size += attrs().Get(i).ids_size() * |
| sizeof(decltype(PerfFileAttr::ids)::value_type); |
| } |
| } |
| return size; |
| } |
| |
| size_t PerfReader::GetBuildIDMetadataSize() const { |
| size_t size = 0; |
| for (const auto& build_id : proto_.build_ids()) { |
| size += sizeof(build_id_event) + |
| GetUint64AlignedStringLength(build_id.filename()); |
| } |
| return size; |
| } |
| |
| size_t PerfReader::GetStringMetadataSize() const { |
| size_t size = 0; |
| if (string_metadata().has_hostname()) |
| size += ExpectedStorageSizeOf(string_metadata().hostname().value()); |
| if (string_metadata().has_kernel_version()) |
| size += ExpectedStorageSizeOf(string_metadata().kernel_version().value()); |
| if (string_metadata().has_perf_version()) |
| size += ExpectedStorageSizeOf(string_metadata().perf_version().value()); |
| if (string_metadata().has_architecture()) |
| size += ExpectedStorageSizeOf(string_metadata().architecture().value()); |
| if (string_metadata().has_cpu_description()) |
| size += ExpectedStorageSizeOf(string_metadata().cpu_description().value()); |
| if (string_metadata().has_cpu_id()) |
| size += ExpectedStorageSizeOf(string_metadata().cpu_id().value()); |
| |
| if (!string_metadata().perf_command_line_token().empty()) { |
| size += sizeof(num_string_data_type); |
| for (const auto& token : string_metadata().perf_command_line_token()) |
| size += ExpectedStorageSizeOf(token.value()); |
| } |
| return size; |
| } |
| |
| size_t PerfReader::GetUint32MetadataSize() const { |
| size_t size = 0; |
| for (const auto& metadata : proto_.uint32_metadata()) |
| size += metadata.data().size() * sizeof(uint32_t); |
| return size; |
| } |
| |
| size_t PerfReader::GetUint64MetadataSize() const { |
| size_t size = 0; |
| for (const auto& metadata : proto_.uint64_metadata()) |
| size += metadata.data().size() * sizeof(uint64_t); |
| return size; |
| } |
| |
| size_t PerfReader::GetCPUTopologyMetadataSize() const { |
| // Core siblings. |
| size_t size = sizeof(num_siblings_type); |
| for (const string& core_sibling : proto_.cpu_topology().core_siblings()) |
| size += ExpectedStorageSizeOf(core_sibling); |
| |
| // Thread siblings. |
| size += sizeof(num_siblings_type); |
| for (const string& thread_sibling : proto_.cpu_topology().thread_siblings()) |
| size += ExpectedStorageSizeOf(thread_sibling); |
| |
| return size; |
| } |
| |
| size_t PerfReader::GetNUMATopologyMetadataSize() const { |
| size_t size = sizeof(numa_topology_num_nodes_type); |
| for (const auto& node : proto_.numa_topology()) { |
| size += sizeof(node.id()); |
| size += sizeof(node.total_memory()) + sizeof(node.free_memory()); |
| size += ExpectedStorageSizeOf(node.cpu_list()); |
| } |
| return size; |
| } |
| |
| bool PerfReader::NeedsNumberOfStringData(u32 type) const { |
| return type == HEADER_CMDLINE; |
| } |
| |
| bool PerfReader::LocalizeMMapFilenames( |
| const std::map<string, string>& filename_map) { |
| CHECK(serializer_.SampleInfoReaderAvailable()); |
| |
| // Search for mmap/mmap2 events for which the filename needs to be updated. |
| for (PerfEvent& event : *proto_.mutable_events()) { |
| if (event.header().type() != PERF_RECORD_MMAP && |
| event.header().type() != PERF_RECORD_MMAP2) { |
| continue; |
| } |
| const string& filename = event.mmap_event().filename(); |
| const auto it = filename_map.find(filename); |
| if (it == filename_map.end()) // not found |
| continue; |
| |
| const string& new_filename = it->second; |
| size_t old_len = GetUint64AlignedStringLength(filename); |
| size_t new_len = GetUint64AlignedStringLength(new_filename); |
| size_t new_size = event.header().size() - old_len + new_len; |
| |
| event.mutable_mmap_event()->set_filename(new_filename); |
| event.mutable_header()->set_size(new_size); |
| } |
| |
| return true; |
| } |
| |
| void PerfReader::AddPerfFileAttr(const PerfFileAttr& attr) { |
| serializer_.SerializePerfFileAttr(attr, proto_.add_file_attrs()); |
| |
| // Generate a new SampleInfoReader with the new attr. |
| serializer_.CreateSampleInfoReader(attr, is_cross_endian_); |
| } |
| |
| } // namespace quipper |