linux-perf-data-0.6.0/.cargo_vcs_info.json0000644000000001360000000000100140370ustar { "git": { "sha1": "9d723ba72750fb8039328615987485302abc3ed0" }, "path_in_vcs": "" }linux-perf-data-0.6.0/.gitignore000064400000000000000000000000500072674642500146420ustar 00000000000000/target /fixtures .DS_Store /Cargo.lock linux-perf-data-0.6.0/Cargo.toml0000644000000024150000000000100120370ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "linux-perf-data" version = "0.6.0" authors = ["Markus Stange "] exclude = [ "/.github", "/.vscode", "/tests", ] description = "A parser for the perf.data format. This format is emitted by the Linux perf tool." documentation = "https://docs.rs/linux-perf-data/" readme = "Readme.md" keywords = [ "linux", "perf", "parser", ] categories = [ "development-tools::profiling", "parser-implementations", ] license = "MIT OR Apache-2.0" repository = "https://github.com/mstange/linux-perf-data/" resolver = "2" [dependencies.byteorder] version = "1.4.3" [dependencies.linear-map] version = "1.2.0" [dependencies.linux-perf-event-reader] version = "0.8.0" [dependencies.memchr] version = "2.4.1" [dependencies.thiserror] version = "1.0.30" linux-perf-data-0.6.0/Cargo.toml.orig000064400000000000000000000013360072674642500155510ustar 00000000000000[package] name = "linux-perf-data" version = "0.6.0" edition = "2021" license = "MIT OR Apache-2.0" authors = ["Markus Stange "] categories = ["development-tools::profiling", "parser-implementations"] description = "A parser for the perf.data format. This format is emitted by the Linux perf tool." keywords = ["linux", "perf", "parser"] readme = "Readme.md" documentation = "https://docs.rs/linux-perf-data/" repository = "https://github.com/mstange/linux-perf-data/" exclude = ["/.github", "/.vscode", "/tests"] [dependencies] byteorder = "1.4.3" memchr = "2.4.1" thiserror = "1.0.30" linux-perf-event-reader = "0.8.0" # linux-perf-event-reader = { path = "../linux-perf-event-reader" } linear-map = "1.2.0" linux-perf-data-0.6.0/LICENSE-APACHE000064400000000000000000000251370072674642500146130ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. linux-perf-data-0.6.0/LICENSE-MIT000064400000000000000000000020700072674642500143120ustar 00000000000000Copyright (c) 2018 Markus Stange Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. linux-perf-data-0.6.0/README.md000064400000000000000000000011460072674642500141400ustar 00000000000000# linux-perf-data This crate contains a parser for the perf.data format. These files are created by the Linux `perf` tool. ## License Licensed under either of * Apache License, Version 2.0 ([`LICENSE-APACHE`](./LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([`LICENSE-MIT`](./LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. linux-perf-data-0.6.0/src/build_id_event.rs000064400000000000000000000050200072674642500167650ustar 00000000000000use std::io::Read; use byteorder::{ByteOrder, ReadBytesExt}; use linux_perf_event_reader::{constants::PERF_RECORD_MISC_BUILD_ID_SIZE, PerfEventHeader}; /// Old versions of perf did not write down the length of the build ID. /// Detect the true length by removing 4-byte chunks of zeros from the end. fn detect_build_id_len(build_id_bytes: &[u8]) -> u8 { let mut len = build_id_bytes.len(); const CHUNK_SIZE: usize = 4; for chunk in build_id_bytes.chunks(CHUNK_SIZE).rev() { if chunk.iter().any(|b| *b != 0) { break; } len -= chunk.len(); } len as u8 } /// `build_id_event` /// /// If PERF_RECORD_MISC_KERNEL is set in header.misc, then this /// is the build id for the vmlinux image or a kmod. #[derive(Debug, Clone)] pub struct BuildIdEvent { pub header: PerfEventHeader, pub pid: i32, pub build_id: Vec, pub file_path: Vec, } impl BuildIdEvent { pub fn parse(mut reader: R) -> Result { let header = PerfEventHeader::parse::<_, T>(&mut reader)?; let pid = reader.read_i32::()?; let mut build_id_bytes = [0; 24]; reader.read_exact(&mut build_id_bytes)?; // Followed by file path for the remaining bytes. The total size of the record // is given by header.size. const BYTES_BEFORE_PATH: usize = PerfEventHeader::STRUCT_SIZE + 4 + 24; let path_len = usize::from(header.size).saturating_sub(BYTES_BEFORE_PATH); let mut path_bytes = vec![0; path_len]; reader.read_exact(&mut path_bytes)?; let path_len = memchr::memchr(0, &path_bytes).unwrap_or(path_len); path_bytes.truncate(path_len); let file_path = path_bytes; // If PERF_RECORD_MISC_BUILD_ID_SIZE is set in header.misc, then build_id_bytes[20] // is the length of the build id (<= 20), and build_id_bytes[21..24] are unused. // Otherwise, the length of the build ID is unknown and has to be detected by // removing trailing 4-byte groups of zero bytes. (Usually there will be // exactly one such group, because build IDs are usually 20 bytes long.) let build_id_len = if header.misc & PERF_RECORD_MISC_BUILD_ID_SIZE != 0 { build_id_bytes[20].min(20) } else { detect_build_id_len(&build_id_bytes) }; let build_id = build_id_bytes[..build_id_len as usize].to_owned(); Ok(Self { header, pid, build_id, file_path, }) } } linux-perf-data-0.6.0/src/constants.rs000064400000000000000000000025770072674642500160430ustar 00000000000000// pub const PERF_RECORD_USER_TYPE_START: u32 = 64; pub const PERF_RECORD_HEADER_ATTR: u32 = 64; pub const PERF_RECORD_HEADER_EVENT_TYPE: u32 = 65; pub const PERF_RECORD_HEADER_TRACING_DATA: u32 = 66; pub const PERF_RECORD_HEADER_BUILD_ID: u32 = 67; pub const PERF_RECORD_FINISHED_ROUND: u32 = 68; pub const PERF_RECORD_ID_INDEX: u32 = 69; pub const PERF_RECORD_AUXTRACE_INFO: u32 = 70; pub const PERF_RECORD_AUXTRACE: u32 = 71; pub const PERF_RECORD_AUXTRACE_ERROR: u32 = 72; pub const PERF_RECORD_THREAD_MAP: u32 = 73; pub const PERF_RECORD_CPU_MAP: u32 = 74; pub const PERF_RECORD_STAT_CONFIG: u32 = 75; pub const PERF_RECORD_STAT: u32 = 76; pub const PERF_RECORD_STAT_ROUND: u32 = 77; pub const PERF_RECORD_EVENT_UPDATE: u32 = 78; pub const PERF_RECORD_TIME_CONV: u32 = 79; pub const PERF_RECORD_HEADER_FEATURE: u32 = 80; pub const PERF_RECORD_COMPRESSED: u32 = 81; // pub const SIMPLE_PERF_RECORD_TYPE_START: u32 = 32768; pub const SIMPLE_PERF_RECORD_KERNEL_SYMBOL: u32 = 32769; pub const SIMPLE_PERF_RECORD_DSO: u32 = 32770; pub const SIMPLE_PERF_RECORD_SYMBOL: u32 = 32771; pub const SIMPLE_PERF_RECORD_SPLIT: u32 = 32772; pub const SIMPLE_PERF_RECORD_SPLIT_END: u32 = 32773; pub const SIMPLE_PERF_RECORD_EVENT_ID: u32 = 32774; pub const SIMPLE_PERF_RECORD_CALLCHAIN: u32 = 32775; pub const SIMPLE_PERF_RECORD_UNWINDING_RESULT: u32 = 32776; pub const SIMPLE_PERF_RECORD_TRACING_DATA: u32 = 32777; linux-perf-data-0.6.0/src/dso_key.rs000064400000000000000000000102660072674642500154560ustar 00000000000000use linux_perf_event_reader::CpuMode; /// A canonicalized key which can be used to cross-reference an Mmap record with /// an entry in the perf file's build ID list. /// /// This is needed because the entries sometimes don't have matching path strings. /// /// Examples: /// /// - Mmap path "[kernel.kallsyms]_text" + build ID map entry path "[kernel.kallsyms]" /// - Mmap path "[kernel.kallsyms]_text" + build ID map entry path "/full/path/to/vmlinux" #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum DsoKey { Kernel, GuestKernel, Vdso32, VdsoX32, Vdso64, Vsyscall, KernelModule { /// The name of the kernel module, without file extension, e.g. "snd-seq-device". /// /// We don't store the full path in the key because the name is enough to /// uniquely identify the kernel module. name: String, }, User { /// The file name of the user-space DSO. file_name: String, /// The full path of the user-space DSO. This must be part of the key because /// there could be multiple DSOs with the same file name at different paths. full_path: Vec, }, } impl DsoKey { /// Make a `DsoKey` from a path and a `CpuMode` (which usually comes from a `misc` field). /// /// Returns `None` for things which cannot be detected as a DSO, such as `//anon` mappings. pub fn detect(path: &[u8], cpu_mode: CpuMode) -> Option { if path == b"//anon" || path == b"[stack]" || path == b"[heap]" || path == b"[vvar]" { return None; } if path.starts_with(b"[kernel.kallsyms]") { let dso_key = if cpu_mode == CpuMode::GuestKernel { DsoKey::GuestKernel } else { DsoKey::Kernel }; return Some(dso_key); } if path.starts_with(b"[guest.kernel.kallsyms") { return Some(DsoKey::GuestKernel); } if path == b"[vdso32]" { return Some(DsoKey::Vdso32); } if path == b"[vdsox32]" { return Some(DsoKey::VdsoX32); } if path == b"[vdso]" { // TODO: I think this could also be Vdso32 when recording on a 32 bit machine. return Some(DsoKey::Vdso64); } if path == b"[vsyscall]" { return Some(DsoKey::Vsyscall); } if (cpu_mode == CpuMode::Kernel || cpu_mode == CpuMode::GuestKernel) && path.starts_with(b"[") { return Some(DsoKey::KernelModule { name: String::from_utf8_lossy(path).into(), }); } let filename = if let Some(final_slash_pos) = path.iter().rposition(|b| *b == b'/') { &path[final_slash_pos + 1..] } else { path }; let dso_key = match (cpu_mode, filename.strip_suffix(b".ko")) { (CpuMode::Kernel | CpuMode::GuestKernel, Some(kmod_name)) => { // "/lib/modules/5.13.0-35-generic/kernel/sound/core/snd-seq-device.ko" -> "[snd-seq-device]" let kmod_name = String::from_utf8_lossy(kmod_name); DsoKey::KernelModule { name: format!("[{}]", kmod_name), } } (CpuMode::Kernel, _) => DsoKey::Kernel, (CpuMode::GuestKernel, _) => DsoKey::GuestKernel, (CpuMode::User | CpuMode::GuestUser, _) => DsoKey::User { file_name: String::from_utf8_lossy(filename).into(), full_path: path.to_owned(), }, _ => return None, }; Some(dso_key) } /// The name string for this DSO. This is a short string that you'd want /// to see in a profiler UI, for example. pub fn name(&self) -> &str { match self { DsoKey::Kernel => "[kernel.kallsyms]", // or just "[kernel]"? DsoKey::GuestKernel => "[guest.kernel.kallsyms]", DsoKey::Vdso32 => "[vdso32]", DsoKey::VdsoX32 => "[vdsox32]", DsoKey::Vdso64 => "[vdso]", DsoKey::Vsyscall => "[vsyscall]", DsoKey::KernelModule { name } => name, DsoKey::User { file_name, .. } => file_name, } } } linux-perf-data-0.6.0/src/error.rs000064400000000000000000000051100072674642500151420ustar 00000000000000use std::io; /// The error type used in this crate. #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { /// The data slice was not big enough to read the struct, or we /// were trying to follow an invalid offset to somewhere outside /// of the data bounds. #[error("Read error: {0}")] Read(#[from] ReadError), #[error("I/O error: {0}")] IoError(#[from] io::Error), #[error("Did not recognize magic value {0:?}")] UnrecognizedMagicValue([u8; 8]), #[error("Section size did not fit into usize")] SectionSizeTooBig, #[error("The file declares no perf event attributes, so samples cannot be parsed")] NoAttributes, #[error("The file contains multiple events but attr {0} does not specify IDENTIFIER")] NoIdentifierDespiteMultiEvent(usize), #[error("The file contains multiple events but attr {0} does not agree with attr zero about SAMPLE_ID_ALL")] InconsistentSampleIdAllWithMultiEvent(usize), #[error("The section wasn't big enough to contain the u32 string length")] NotEnoughSpaceForStringLen, #[error("The section wasn't big enough to contain the u32 string list length")] NotEnoughSpaceForStringListLen, #[error("The feature section wasn't big enough")] FeatureSectionTooSmall, #[error("The indicated string length wouldn't fit in the indicated section size")] StringLengthTooLong, #[error("The indicated string list length wouldn't fit into usize")] StringListLengthBiggerThanUsize, #[error("The indicated string length wouldn't fit into usize")] StringLengthBiggerThanUsize, #[error("The string was not valid utf-8")] StringUtf8, #[error("The specified size in the perf event header was smaller than the header itself")] InvalidPerfEventSize, } /// This error indicates that the data slice was not large enough to /// read the respective item. #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum ReadError { #[error("Could not read PerfHeader")] PerfHeader, #[error("Could not read FeatureSection")] FeatureSection, #[error("Could not read BuildIdSection")] BuildIdSection, #[error("Could not read StringLen")] StringLen, #[error("Could not read String")] String, #[error("Could not read NrCpus")] NrCpus, #[error("Could not read AttrsSection")] AttrsSection, #[error("Could not read PerfEventAttr")] PerfEventAttr, #[error("Could not read PerfEventHeader")] PerfEventHeader, #[error("Could not read PerfEvent data")] PerfEventData, } linux-perf-data-0.6.0/src/feature_sections.rs000064400000000000000000000171230072674642500173620ustar 00000000000000use std::io::{Read, Seek, SeekFrom}; use byteorder::{ByteOrder, ReadBytesExt}; use linear_map::LinearMap; use linux_perf_event_reader::PerfEventAttr; use crate::{perf_file::PerfFileSection, Error, ReadError}; /// The number of available and online CPUs. (`nr_cpus`) #[derive(Debug, Clone, Copy)] pub struct NrCpus { /// CPUs not yet onlined pub nr_cpus_available: u32, pub nr_cpus_online: u32, } impl NrCpus { pub const STRUCT_SIZE: usize = 4 + 4; pub fn parse(mut reader: R) -> Result { let nr_cpus_available = reader.read_u32::()?; let nr_cpus_online = reader.read_u32::()?; Ok(Self { nr_cpus_available, nr_cpus_online, }) } } /// The timestamps of the first and last sample. #[derive(Debug, Clone, Copy)] pub struct SampleTimeRange { pub first_sample_time: u64, pub last_sample_time: u64, } impl SampleTimeRange { pub const STRUCT_SIZE: usize = 8 + 8; pub fn parse(mut reader: R) -> Result { let first_sample_time = reader.read_u64::()?; let last_sample_time = reader.read_u64::()?; Ok(Self { first_sample_time, last_sample_time, }) } } pub struct HeaderString; impl HeaderString { /// Parse a string. pub fn parse(mut reader: R) -> Result, std::io::Error> { let len = reader.read_u32::()?; let mut s = vec![0; len as usize]; reader.read_exact(&mut s)?; let actual_len = memchr::memchr(0, &s).unwrap_or(s.len()); s.truncate(actual_len); Ok(String::from_utf8(s).ok()) } } /// A single event attr with name and corresponding event IDs. #[derive(Debug, Clone)] pub struct AttributeDescription { pub attr: PerfEventAttr, pub name: Option, pub event_ids: Vec, } impl AttributeDescription { /// Parse the `HEADER_EVENT_DESC` section of a perf.data file into a Vec of `AttributeDescription` structs. pub fn parse_event_desc_section( mut reader: R, ) -> Result, std::io::Error> { // ```c // struct { // uint32_t nr; /* number of events */ // uint32_t attr_size; /* size of each perf_event_attr */ // struct { // struct perf_event_attr attr; /* size of attr_size */ // uint32_t nr_ids; // struct perf_header_string event_string; // uint64_t ids[nr_ids]; // } events[nr]; /* Variable length records */ // }; // ``` let nr = reader.read_u32::()?; let mut attributes = Vec::with_capacity(nr as usize); let attr_size = reader.read_u32::()?; for _ in 0..nr { let attr = PerfEventAttr::parse::<_, T>(&mut reader, Some(attr_size))?; let nr_ids = reader.read_u32::()?; let event_string = HeaderString::parse::<_, T>(&mut reader)?; let mut ids = Vec::with_capacity(nr_ids as usize); for _ in 0..nr_ids { ids.push(reader.read_u64::()?); } attributes.push(AttributeDescription { attr, name: event_string, event_ids: ids, }); } Ok(attributes) } /// Parse the `event_types` section of a perf.data file into a Vec of `AttributeDescription` structs. /// This section was used in the past but is no longer used. /// Only call this function if event_types_section.size is non-zero. pub fn parse_event_types_section( mut cursor: C, event_types_section: &PerfFileSection, attr_size: u64, ) -> Result, Error> { cursor.seek(SeekFrom::Start(event_types_section.offset))?; // Each entry in the event_types section is a PerfEventAttr followed by a PerfFileSection. let entry_size = attr_size + PerfFileSection::STRUCT_SIZE as u64; let entry_count = event_types_section.size / entry_size; let mut perf_event_event_type_info = Vec::with_capacity(entry_count as usize); for _ in 0..entry_count { let attr = PerfEventAttr::parse::<_, T>(&mut cursor, Some(attr_size as u32)) .map_err(|_| ReadError::PerfEventAttr)?; let event_ids = PerfFileSection::parse::<_, T>(&mut cursor)?; perf_event_event_type_info.push((attr, event_ids)); } // Read the lists of event IDs for each event type. let mut attributes = Vec::new(); for (attr, section) in perf_event_event_type_info { cursor.seek(SeekFrom::Start(section.offset))?; // This section is just a list of u64 event IDs. let id_count = section.size / 8; let mut event_ids = Vec::with_capacity(id_count as usize); for _ in 0..id_count { event_ids.push(cursor.read_u64::()?); } attributes.push(AttributeDescription { attr, name: None, event_ids, }); } Ok(attributes) } /// Parse the `attr` section of a perf.data file into a Vec of `AttributeDescription` structs. /// This section is used as a last resort because it does not have any /// information about event IDs. If multiple events are observed, we will /// not be able to know which event record belongs to which attr. pub fn parse_attr_section( mut cursor: C, attr_section: &PerfFileSection, attr_size: u64, ) -> Result, Error> { cursor.seek(SeekFrom::Start(attr_section.offset))?; let attr_count = attr_section.size / attr_size; let mut attributes = Vec::with_capacity(attr_count as usize); for _ in 0..attr_count { let attr = PerfEventAttr::parse::<_, T>(&mut cursor, Some(attr_size as u32)) .map_err(|_| ReadError::PerfEventAttr)?; attributes.push(AttributeDescription { attr, name: None, event_ids: vec![], }); } Ok(attributes) } /// The event attributes. pub fn attributes(&self) -> &PerfEventAttr { &self.attr } /// The event name. pub fn name(&self) -> Option<&str> { self.name.as_deref() } /// The IDs for this event. pub fn ids(&self) -> &[u64] { &self.event_ids } } /// The names of the dynamic PMU types used in [`PerfEventType::DynamicPmu`](linux_perf_event_reader::PerfEventType::DynamicPmu). /// /// For example, this allows you to find out whether a `DynamicPmu` /// perf event is a kprobe or a uprobe, which then lets you interpret /// the meaning of the config fields. pub struct PmuMappings; impl PmuMappings { pub fn parse( mut reader: R, ) -> Result, std::io::Error> { // struct { // uint32_t nr; // struct pmu { // uint32_t pmu_type; // struct perf_header_string pmu_name; // } [nr]; /* Variable length records */ // }; let nr = reader.read_u32::()?; let mut vec = Vec::with_capacity(nr as usize); for _ in 0..nr { let pmu_type = reader.read_u32::()?; if let Some(pmu_name) = HeaderString::parse::<_, T>(&mut reader)? { vec.push((pmu_type, pmu_name)); } } vec.sort_by_key(|item| item.0); Ok(vec.into_iter().collect()) } } linux-perf-data-0.6.0/src/features.rs000064400000000000000000000205450072674642500156400ustar 00000000000000use std::fmt; pub const HEADER_TRACING_DATA: u32 = 1; pub const HEADER_BUILD_ID: u32 = 2; pub const HEADER_HOSTNAME: u32 = 3; pub const HEADER_OSRELEASE: u32 = 4; pub const HEADER_VERSION: u32 = 5; pub const HEADER_ARCH: u32 = 6; pub const HEADER_NRCPUS: u32 = 7; pub const HEADER_CPUDESC: u32 = 8; pub const HEADER_CPUID: u32 = 9; pub const HEADER_TOTAL_MEM: u32 = 10; pub const HEADER_CMDLINE: u32 = 11; pub const HEADER_EVENT_DESC: u32 = 12; pub const HEADER_CPU_TOPOLOGY: u32 = 13; pub const HEADER_NUMA_TOPOLOGY: u32 = 14; pub const HEADER_BRANCH_STACK: u32 = 15; pub const HEADER_PMU_MAPPINGS: u32 = 16; pub const HEADER_GROUP_DESC: u32 = 17; pub const HEADER_AUXTRACE: u32 = 18; pub const HEADER_STAT: u32 = 19; pub const HEADER_CACHE: u32 = 20; pub const HEADER_SAMPLE_TIME: u32 = 21; pub const HEADER_SAMPLE_TOPOLOGY: u32 = 22; pub const HEADER_CLOCKID: u32 = 23; pub const HEADER_DIR_FORMAT: u32 = 24; pub const HEADER_BPF_PROG_INFO: u32 = 25; pub const HEADER_BPF_BTF: u32 = 26; pub const HEADER_COMPRESSED: u32 = 27; pub const HEADER_CPU_PMU_CAPS: u32 = 28; pub const HEADER_CLOCK_DATA: u32 = 29; pub const HEADER_HYBRID_TOPOLOGY: u32 = 30; pub const HEADER_HYBRID_CPU_PMU_CAPS: u32 = 31; /// simpleperf `FEAT_META_INFO` pub const HEADER_SIMPLEPERF_META_INFO: u32 = 128; /// simpleperf `FEAT_DEBUG_UNWIND` pub const HEADER_SIMPLEPERF_DEBUG_UNWIND: u32 = 129; /// simpleperf `FEAT_DEBUG_UNWIND_FILE` pub const HEADER_SIMPLEPERF_DEBUG_UNWIND_FILE: u32 = 130; /// simpleperf `FEAT_FILE2` pub const HEADER_SIMPLEPERF_FILE2: u32 = 131; /// A piece of optional data stored in a perf.data file. Its data is contained in a /// "feature section" at the end of the file. /// /// For each used feature, a bit is set in the feature flags in the file header. /// The feature sections are stored just after the file's data section; there's /// one section for each enabled feature, ordered from low feature bit to high /// feature bit. #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Feature(pub u32); impl Feature { pub const TRACING_DATA: Self = Self(HEADER_TRACING_DATA); pub const BUILD_ID: Self = Self(HEADER_BUILD_ID); pub const HOSTNAME: Self = Self(HEADER_HOSTNAME); pub const OSRELEASE: Self = Self(HEADER_OSRELEASE); pub const VERSION: Self = Self(HEADER_VERSION); pub const ARCH: Self = Self(HEADER_ARCH); pub const NRCPUS: Self = Self(HEADER_NRCPUS); pub const CPUDESC: Self = Self(HEADER_CPUDESC); pub const CPUID: Self = Self(HEADER_CPUID); pub const TOTAL_MEM: Self = Self(HEADER_TOTAL_MEM); pub const CMDLINE: Self = Self(HEADER_CMDLINE); pub const EVENT_DESC: Self = Self(HEADER_EVENT_DESC); pub const CPU_TOPOLOGY: Self = Self(HEADER_CPU_TOPOLOGY); pub const NUMA_TOPOLOGY: Self = Self(HEADER_NUMA_TOPOLOGY); pub const BRANCH_STACK: Self = Self(HEADER_BRANCH_STACK); pub const PMU_MAPPINGS: Self = Self(HEADER_PMU_MAPPINGS); pub const GROUP_DESC: Self = Self(HEADER_GROUP_DESC); pub const AUXTRACE: Self = Self(HEADER_AUXTRACE); pub const STAT: Self = Self(HEADER_STAT); pub const CACHE: Self = Self(HEADER_CACHE); pub const SAMPLE_TIME: Self = Self(HEADER_SAMPLE_TIME); pub const SAMPLE_TOPOLOGY: Self = Self(HEADER_SAMPLE_TOPOLOGY); pub const CLOCKID: Self = Self(HEADER_CLOCKID); pub const DIR_FORMAT: Self = Self(HEADER_DIR_FORMAT); pub const BPF_PROG_INFO: Self = Self(HEADER_BPF_PROG_INFO); pub const BPF_BTF: Self = Self(HEADER_BPF_BTF); pub const COMPRESSED: Self = Self(HEADER_COMPRESSED); pub const CPU_PMU_CAPS: Self = Self(HEADER_CPU_PMU_CAPS); pub const CLOCK_DATA: Self = Self(HEADER_CLOCK_DATA); pub const HYBRID_TOPOLOGY: Self = Self(HEADER_HYBRID_TOPOLOGY); pub const HYBRID_CPU_PMU_CAPS: Self = Self(HEADER_HYBRID_CPU_PMU_CAPS); pub const SIMPLEPERF_META_INFO: Self = Self(HEADER_SIMPLEPERF_META_INFO); pub const SIMPLEPERF_DEBUG_UNWIND: Self = Self(HEADER_SIMPLEPERF_DEBUG_UNWIND); pub const SIMPLEPERF_DEBUG_UNWIND_FILE: Self = Self(HEADER_SIMPLEPERF_DEBUG_UNWIND_FILE); pub const SIMPLEPERF_FILE2: Self = Self(HEADER_SIMPLEPERF_FILE2); } impl fmt::Debug for Feature { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::TRACING_DATA => "TRACING_DATA".fmt(f), Self::BUILD_ID => "BUILD_ID".fmt(f), Self::HOSTNAME => "HOSTNAME".fmt(f), Self::OSRELEASE => "OSRELEASE".fmt(f), Self::VERSION => "VERSION".fmt(f), Self::ARCH => "ARCH".fmt(f), Self::NRCPUS => "NRCPUS".fmt(f), Self::CPUDESC => "CPUDESC".fmt(f), Self::CPUID => "CPUID".fmt(f), Self::TOTAL_MEM => "TOTAL_MEM".fmt(f), Self::CMDLINE => "CMDLINE".fmt(f), Self::EVENT_DESC => "EVENT_DESC".fmt(f), Self::CPU_TOPOLOGY => "CPU_TOPOLOGY".fmt(f), Self::NUMA_TOPOLOGY => "NUMA_TOPOLOGY".fmt(f), Self::BRANCH_STACK => "BRANCH_STACK".fmt(f), Self::PMU_MAPPINGS => "PMU_MAPPINGS".fmt(f), Self::GROUP_DESC => "GROUP_DESC".fmt(f), Self::AUXTRACE => "AUXTRACE".fmt(f), Self::STAT => "STAT".fmt(f), Self::CACHE => "CACHE".fmt(f), Self::SAMPLE_TIME => "SAMPLE_TIME".fmt(f), Self::SAMPLE_TOPOLOGY => "SAMPLE_TOPOLOGY".fmt(f), Self::CLOCKID => "CLOCKID".fmt(f), Self::DIR_FORMAT => "DIR_FORMAT".fmt(f), Self::BPF_PROG_INFO => "BPF_PROG_INFO".fmt(f), Self::BPF_BTF => "BPF_BTF".fmt(f), Self::COMPRESSED => "COMPRESSED".fmt(f), Self::CPU_PMU_CAPS => "CPU_PMU_CAPS".fmt(f), Self::CLOCK_DATA => "CLOCK_DATA".fmt(f), Self::HYBRID_TOPOLOGY => "HYBRID_TOPOLOGY".fmt(f), Self::HYBRID_CPU_PMU_CAPS => "HYBRID_CPU_PMU_CAPS".fmt(f), Self::SIMPLEPERF_META_INFO => "SIMPLEPERF_META_INFO".fmt(f), Self::SIMPLEPERF_DEBUG_UNWIND => "SIMPLEPERF_DEBUG_UNWIND".fmt(f), Self::SIMPLEPERF_DEBUG_UNWIND_FILE => "SIMPLEPERF_DEBUG_UNWIND_FILE".fmt(f), Self::SIMPLEPERF_FILE2 => "SIMPLEPERF_FILE2".fmt(f), _ => f.write_fmt(format_args!("Unknown Feature {}", &self.0)), } } } /// The set of features used in the perf file. The perf file contains one /// feature section for each feature. /// /// This set is provided in the perf file header. /// It has room for 4 * 64 = 256 feature bits. #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct FeatureSet(pub [u64; 4]); impl FeatureSet { pub const MAX_BITS: u32 = 64 * 4; /// The number of features in this set. pub fn len(&self) -> usize { let b = &self.0; let len = b[0].count_ones() + b[1].count_ones() + b[2].count_ones() + b[3].count_ones(); len as usize } /// Whether the set is empty. pub fn is_empty(&self) -> bool { self.0 == [0, 0, 0, 0] } /// Returns an iterator over all features in this set, from low to high. pub fn iter(&self) -> FeatureSetIter { FeatureSetIter { current_feature: Feature(0), set: *self, } } /// Checks if the feature is contained in this set. #[inline] pub fn has_feature(&self, feature: Feature) -> bool { if feature.0 >= 256 { return false; } let features_chunk_index = (feature.0 / 64) as usize; let feature_bit = feature.0 % 64; let features_chunk = self.0[features_chunk_index]; (features_chunk & (1 << feature_bit)) != 0 } } impl fmt::Debug for FeatureSet { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut set = f.debug_set(); for feature in self.iter() { set.entry(&feature); } set.finish() } } /// An iterator over all the features that are included in a [`FeatureSet`], /// ordered from low to high feature bit. /// /// The iteration order is the order in which the feature sections are stored /// in a perf.data file. pub struct FeatureSetIter { current_feature: Feature, set: FeatureSet, } impl Iterator for FeatureSetIter { type Item = Feature; fn next(&mut self) -> Option { while self.current_feature.0 < FeatureSet::MAX_BITS { let feature = self.current_feature; self.current_feature.0 += 1; if self.set.has_feature(feature) { return Some(feature); } } None } } linux-perf-data-0.6.0/src/lib.rs000064400000000000000000000667030072674642500145760ustar 00000000000000//! A parser for the perf.data file format. //! //! Files of this format consist of a header, a data section, and a few other //! supplemental sections. The data section contains the main content of the //! file: a sequence of records. //! //! There are two types of records: event records from the kernel, and "user //! records" from perf / simpleperf. //! //! # Example //! //! ``` //! use linux_perf_data::{AttributeDescription, PerfFileReader, PerfFileRecord}; //! //! # fn wrapper() -> Result<(), linux_perf_data::Error> { //! let file = std::fs::File::open("perf.data")?; //! let reader = std::io::BufReader::new(file); //! let PerfFileReader { mut perf_file, mut record_iter } = PerfFileReader::parse_file(reader)?; //! let event_names: Vec<_> = //! perf_file.event_attributes().iter().filter_map(AttributeDescription::name).collect(); //! println!("perf events: {}", event_names.join(", ")); //! //! while let Some(record) = record_iter.next_record(&mut perf_file)? { //! match record { //! PerfFileRecord::EventRecord { attr_index, record } => { //! let record_type = record.record_type; //! let parsed_record = record.parse()?; //! println!("{:?} for event {}: {:?}", record_type, attr_index, parsed_record); //! } //! PerfFileRecord::UserRecord(record) => { //! let record_type = record.record_type; //! let parsed_record = record.parse()?; //! println!("{:?}: {:?}", record_type, parsed_record); //! } //! } //! } //! # Ok(()) //! # } //! ``` mod build_id_event; mod constants; mod dso_key; mod error; mod feature_sections; mod features; mod perf_file; mod record; mod sorter; mod thread_map; pub use dso_key::DsoKey; pub use error::{Error, ReadError}; use feature_sections::PmuMappings; pub use feature_sections::{AttributeDescription, NrCpus, SampleTimeRange}; pub use features::{Feature, FeatureSet, FeatureSetIter}; pub use record::{PerfFileRecord, RawUserRecord, UserRecord, UserRecordType}; pub use thread_map::ThreadMap; /// This is a re-export of the linux-perf-event-reader crate. We use its types /// in our public API. pub use linux_perf_event_reader; pub use linux_perf_event_reader::Endianness; use std::collections::{HashMap, VecDeque}; use std::io::{Read, Seek, SeekFrom}; use std::ops::Deref; use build_id_event::BuildIdEvent; use byteorder::{BigEndian, ByteOrder, LittleEndian}; use linear_map::LinearMap; use linux_perf_event_reader::{ get_record_id, get_record_identifier, get_record_timestamp, RawEventRecord, RecordIdParseInfo, RecordParseInfo, }; use linux_perf_event_reader::{ AttrFlags, CpuMode, PerfEventHeader, RawData, RecordType, SampleFormat, }; use perf_file::{PerfFileSection, PerfHeader}; use sorter::Sorter; /// A parser for the perf.data file format. /// /// # Example /// /// ``` /// use linux_perf_data::{AttributeDescription, PerfFileReader, PerfFileRecord}; /// /// # fn wrapper() -> Result<(), linux_perf_data::Error> { /// let file = std::fs::File::open("perf.data")?; /// let reader = std::io::BufReader::new(file); /// let PerfFileReader { mut perf_file, mut record_iter } = PerfFileReader::parse_file(reader)?; /// let event_names: Vec<_> = /// perf_file.event_attributes().iter().filter_map(AttributeDescription::name).collect(); /// println!("perf events: {}", event_names.join(", ")); /// /// while let Some(record) = record_iter.next_record(&mut perf_file)? { /// match record { /// PerfFileRecord::EventRecord { attr_index, record } => { /// let record_type = record.record_type; /// let parsed_record = record.parse()?; /// println!("{:?} for event {}: {:?}", record_type, attr_index, parsed_record); /// } /// PerfFileRecord::UserRecord(record) => { /// let record_type = record.record_type; /// let parsed_record = record.parse()?; /// println!("{:?}: {:?}", record_type, parsed_record); /// } /// } /// } /// # Ok(()) /// # } /// ``` pub struct PerfFileReader { pub perf_file: PerfFile, pub record_iter: PerfRecordIter, } impl PerfFileReader { pub fn parse_file(mut cursor: C) -> Result { let header = PerfHeader::parse(&mut cursor)?; match &header.magic { b"PERFILE2" => { Self::parse_file_impl::(cursor, header, Endianness::LittleEndian) } b"2ELIFREP" => { Self::parse_file_impl::(cursor, header, Endianness::BigEndian) } _ => Err(Error::UnrecognizedMagicValue(header.magic)), } } fn parse_file_impl( mut cursor: C, header: PerfHeader, endian: Endianness, ) -> Result where T: ByteOrder, { // Read the section information for each feature, starting just after the data section. let feature_pos = header.data_section.offset + header.data_section.size; cursor.seek(SeekFrom::Start(feature_pos))?; let mut feature_sections_info = Vec::new(); for feature in header.features.iter() { let section = PerfFileSection::parse::<_, T>(&mut cursor)?; feature_sections_info.push((feature, section)); } let mut feature_sections = LinearMap::new(); for (feature, section) in feature_sections_info { let offset = section.offset; let size = usize::try_from(section.size).map_err(|_| Error::SectionSizeTooBig)?; let mut data = vec![0; size]; cursor.seek(SeekFrom::Start(offset))?; cursor.read_exact(&mut data)?; feature_sections.insert(feature, data); } let attributes = if let Some(event_desc_section) = feature_sections.get(&Feature::EVENT_DESC) { AttributeDescription::parse_event_desc_section::<_, T>(&event_desc_section[..])? } else if header.event_types_section.size != 0 { AttributeDescription::parse_event_types_section::<_, T>( &mut cursor, &header.event_types_section, header.attr_size, )? } else { AttributeDescription::parse_attr_section::<_, T>( &mut cursor, &header.attr_section, header.attr_size, )? }; let mut event_id_to_attr_index = HashMap::new(); for (attr_index, AttributeDescription { event_ids, .. }) in attributes.iter().enumerate() { for event_id in event_ids { event_id_to_attr_index.insert(*event_id, attr_index); } } let parse_infos: Vec<_> = attributes .iter() .map(|attr| RecordParseInfo::new(&attr.attr, endian)) .collect(); let first_attr = attributes.first().ok_or(Error::NoAttributes)?; let first_has_sample_id_all = first_attr.attr.flags.contains(AttrFlags::SAMPLE_ID_ALL); let (first_parse_info, remaining_parse_infos) = parse_infos.split_first().unwrap(); let id_parse_infos = if remaining_parse_infos.is_empty() { IdParseInfos::OnlyOneEvent } else if remaining_parse_infos .iter() .all(|parse_info| parse_info.id_parse_info == first_parse_info.id_parse_info) { IdParseInfos::Same(first_parse_info.id_parse_info) } else { // Make sure that all attributes have IDENTIFIER and the same SAMPLE_ID_ALL setting. // Otherwise we won't be able to know which attr a record belongs to; we need to know // the record's ID for that, and we can only read the ID if it's in the same location // regardless of attr. // In theory we could make the requirements weaker, and take the record type into // account for disambiguation. For example, if there are two events, but one of them // only creates SAMPLE records and the other only non-SAMPLE records, we don't // necessarily need IDENTIFIER in order to be able to read the record ID. for (attr_index, AttributeDescription { attr, .. }) in attributes.iter().enumerate() { if !attr.sample_format.contains(SampleFormat::IDENTIFIER) { return Err(Error::NoIdentifierDespiteMultiEvent(attr_index)); } if attr.flags.contains(AttrFlags::SAMPLE_ID_ALL) != first_has_sample_id_all { return Err(Error::InconsistentSampleIdAllWithMultiEvent(attr_index)); } } IdParseInfos::PerAttribute(first_has_sample_id_all) }; // Move the cursor to the start of the data section so that we can start // reading records from it. cursor.seek(SeekFrom::Start(header.data_section.offset))?; let perf_file = PerfFile { endian, features: header.features, feature_sections, attributes, }; let record_iter = PerfRecordIter { reader: cursor, endian, id_parse_infos, parse_infos, event_id_to_attr_index, read_offset: 0, record_data_len: header.data_section.size, sorter: Sorter::new(), buffers_for_recycling: VecDeque::new(), current_event_body: Vec::new(), }; Ok(Self { perf_file, record_iter, }) } } /// Contains the information from the perf.data file header and feature sections. pub struct PerfFile { endian: Endianness, features: FeatureSet, feature_sections: LinearMap>, /// Guaranteed to have at least one element attributes: Vec, } impl PerfFile { /// The attributes which were requested for each perf event, along with the IDs. pub fn event_attributes(&self) -> &[AttributeDescription] { &self.attributes } /// Returns a map of build ID entries. `perf record` creates these records for any DSOs /// which it thinks have been "hit" in the profile. They supplement Mmap records, which /// usually don't come with build IDs. /// /// This method returns a HashMap so that you can easily look up the right build ID from /// the DsoKey in an Mmap event. For some DSOs, the path in the raw Mmap event can be /// different from the path in the build ID record; for example, the Mmap event for the /// kernel ("vmlinux") image could have the path "[kernel.kallsyms]_text", whereas the /// corresponding build ID record might have the path "[kernel.kallsyms]" (without the /// trailing "_text"), or it could even have the full absolute path to a vmlinux file. /// The DsoKey canonicalizes those differences away. /// /// Having the build ID for a DSO allows you to do the following: /// /// - If the DSO file has changed in the time since the perf.data file was captured, /// you can detect this change because the new file will have a different build ID. /// - If debug symbols are installed for the DSO, you can sometimes find the debug symbol /// file using the build ID. For example, you might find it at /// /usr/lib/debug/.build-id/b8/037b6260865346802321dd2256b8ad1d857e63.debug /// - If the original DSO file is gone, or you're trying to read the perf.data file on /// an entirely different machine, you can sometimes retrieve the original DSO file just /// from its build ID, for example from a debuginfod server. /// - This also works for DSOs which are not present on the file system at all; /// specifically, the vDSO file is a bit of a pain to obtain. With the build ID you can /// instead obtain it from, say, /// /// /// This method is a bit lossy. We discard the pid, because it seems to be always -1 in /// the files I've tested. We also discard any entries for which we fail to create a `DsoKey`. pub fn build_ids(&self) -> Result, Error> { let section_data = match self.feature_section_data(Feature::BUILD_ID) { Some(section) => section, None => return Ok(HashMap::new()), }; let mut cursor = section_data; let mut build_ids = HashMap::new(); loop { let event = match self.endian { Endianness::LittleEndian => BuildIdEvent::parse::<_, LittleEndian>(&mut cursor), Endianness::BigEndian => BuildIdEvent::parse::<_, BigEndian>(&mut cursor), }; let event = match event { Ok(e) => e, Err(_) => break, }; let misc = event.header.misc; let path = event.file_path; let build_id = event.build_id; let dso_key = match DsoKey::detect(&path, CpuMode::from_misc(misc)) { Some(dso_key) => dso_key, None => continue, }; build_ids.insert(dso_key, DsoInfo { path, build_id }); } Ok(build_ids) } /// The timestamp of the first and the last sample in this file. pub fn sample_time_range(&self) -> Result, Error> { let section_data = match self.feature_section_data(Feature::SAMPLE_TIME) { Some(section) => section, None => return Ok(None), }; let time_range = match self.endian { Endianness::LittleEndian => SampleTimeRange::parse::<_, LittleEndian>(section_data)?, Endianness::BigEndian => SampleTimeRange::parse::<_, BigEndian>(section_data)?, }; Ok(Some(time_range)) } /// Only call this for features whose section is just a perf_header_string. fn feature_string(&self, feature: Feature) -> Result, Error> { match self.feature_section_data(feature) { Some(section) => Ok(Some(self.read_string(section)?.0)), None => Ok(None), } } /// The hostname where the data was collected (`uname -n`). pub fn hostname(&self) -> Result, Error> { self.feature_string(Feature::HOSTNAME) } /// The OS release where the data was collected (`uname -r`). pub fn os_release(&self) -> Result, Error> { self.feature_string(Feature::OSRELEASE) } /// The perf user tool version where the data was collected. This is the same /// as the version of the Linux source tree the perf tool was built from. pub fn perf_version(&self) -> Result, Error> { self.feature_string(Feature::VERSION) } /// The CPU architecture (`uname -m`). pub fn arch(&self) -> Result, Error> { self.feature_string(Feature::ARCH) } /// A structure defining the number of CPUs. pub fn nr_cpus(&self) -> Result, Error> { self.feature_section_data(Feature::NRCPUS) .map(|section| { Ok(match self.endian { Endianness::LittleEndian => NrCpus::parse::<_, LittleEndian>(section), Endianness::BigEndian => NrCpus::parse::<_, BigEndian>(section), }?) }) .transpose() } /// The description of the CPU. On x86 this is the model name /// from `/proc/cpuinfo`. pub fn cpu_desc(&self) -> Result, Error> { self.feature_string(Feature::CPUDESC) } /// The exact CPU type. On x86 this is `vendor,family,model,stepping`. /// For example: `GenuineIntel,6,69,1` pub fn cpu_id(&self) -> Result, Error> { self.feature_string(Feature::CPUID) } /// If true, the data section contains data recorded from `perf stat record`. pub fn is_stats(&self) -> bool { self.features.has_feature(Feature::STAT) } /// The perf arg-vector used to collect the data. pub fn cmdline(&self) -> Result>, Error> { match self.feature_section_data(Feature::CMDLINE) { Some(section) => Ok(Some(self.read_string_list(section)?.0)), None => Ok(None), } } /// The total memory in kilobytes. (MemTotal from /proc/meminfo) pub fn total_mem(&self) -> Result, Error> { let data = match self.feature_section_data(Feature::TOTAL_MEM) { Some(data) => data, None => return Ok(None), }; if data.len() < 8 { return Err(Error::FeatureSectionTooSmall); } let b = data; let data = [b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]; let mem = match self.endian { Endianness::LittleEndian => u64::from_le_bytes(data), Endianness::BigEndian => u64::from_be_bytes(data), }; Ok(Some(mem)) } /// The names of the dynamic PMU types used in [`PerfEventType::DynamicPmu`](linux_perf_event_reader::PerfEventType::DynamicPmu). /// /// This mapping allows you to interpret the perf event type field of the perf event /// attributes returned by [`PerfFile::event_attributes`]. /// /// For example, let's say you observed a kprobe or a uprobe. The perf event will be /// of type `DynamicPmu`, and its dynamic PMU type ID might be 6 or 7. /// /// Just by seeing this 6 or 7 you don't know for sure what type of event it is. /// But the `pmu_mappings()` map will have a 6 => "kprobe" and a 7 => "uprobe" entry. /// Once you see those entries, you can be sure what you're dealing with. /// /// This map also contains the values "software", "tracepoint", and "breakpoint"; those /// always have the IDs 1, 2 and 5, respectively. /// /// Additionally, the map contains the CPU-specific dynamic entries. For example, an Intel /// CPU might have IDs for the names "cpu", "intel_bts", "intel_pt", "msr", "uncore_imc", /// "uncore_cbox_0", ..., "uncore_cbox_7", "uncore_arb", "cstate_core", "cstate_pkg", "power", /// "i915". pub fn pmu_mappings(&self) -> Result>, Error> { self.feature_section_data(Feature::PMU_MAPPINGS) .map(|section| { Ok(match self.endian { Endianness::LittleEndian => PmuMappings::parse::<_, LittleEndian>(section), Endianness::BigEndian => PmuMappings::parse::<_, BigEndian>(section), }?) }) .transpose() } /// The set of features used in this perf file. pub fn features(&self) -> FeatureSet { self.features } /// The raw data of a feature section. pub fn feature_section_data(&self, feature: Feature) -> Option<&[u8]> { self.feature_sections.get(&feature).map(Deref::deref) } /// The file endian. pub fn endian(&self) -> Endianness { self.endian } fn read_string<'s>(&self, s: &'s [u8]) -> Result<(&'s str, &'s [u8]), Error> { if s.len() < 4 { return Err(Error::NotEnoughSpaceForStringLen); } let (len_bytes, rest) = s.split_at(4); let len_bytes = [len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]; let len = match self.endian { Endianness::LittleEndian => u32::from_le_bytes(len_bytes), Endianness::BigEndian => u32::from_be_bytes(len_bytes), }; let len = usize::try_from(len).map_err(|_| Error::StringLengthBiggerThanUsize)?; if rest.len() < len { return Err(Error::StringLengthTooLong); } let (s, rest) = rest.split_at(len); let actual_len = memchr::memchr(0, s).unwrap_or(s.len()); let s = std::str::from_utf8(&s[..actual_len]).map_err(|_| Error::StringUtf8)?; Ok((s, rest)) } fn read_string_list<'s>(&self, s: &'s [u8]) -> Result<(Vec<&'s str>, &'s [u8]), Error> { if s.len() < 4 { return Err(Error::NotEnoughSpaceForStringListLen); } let (len_bytes, mut rest) = s.split_at(4); let len_bytes = [len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]; let len = match self.endian { Endianness::LittleEndian => u32::from_le_bytes(len_bytes), Endianness::BigEndian => u32::from_be_bytes(len_bytes), }; let len = usize::try_from(len).map_err(|_| Error::StringListLengthBiggerThanUsize)?; let mut vec = Vec::with_capacity(len); for _ in 0..len { let s; (s, rest) = self.read_string(rest)?; vec.push(s); } Ok((vec, rest)) } } /// An iterator which incrementally reads and sorts the records from a perf.data file. pub struct PerfRecordIter { reader: R, endian: Endianness, read_offset: u64, record_data_len: u64, current_event_body: Vec, id_parse_infos: IdParseInfos, /// Guaranteed to have at least one element parse_infos: Vec, event_id_to_attr_index: HashMap, sorter: Sorter, buffers_for_recycling: VecDeque>, } impl PerfRecordIter { /// Iterates the records in this file. The records are emitted in the /// correct order, i.e. sorted by time. /// /// `next_record` does some internal buffering so that the sort order can /// be guaranteed. This buffering takes advantage of `FINISHED_ROUND` /// records so that we don't buffer more records than necessary. pub fn next_record( &mut self, _perf_file: &mut PerfFile, ) -> Result, Error> { if !self.sorter.has_more() { self.read_next_round()?; } if let Some(pending_record) = self.sorter.get_next() { let record = self.convert_pending_record(pending_record); return Ok(Some(record)); } Ok(None) } /// Reads events into self.sorter until a FINISHED_ROUND record is found /// and self.sorter is non-empty, or until we've run out of records to read. fn read_next_round(&mut self) -> Result<(), Error> { if self.endian == Endianness::LittleEndian { self.read_next_round_impl::() } else { self.read_next_round_impl::() } } /// Reads events into self.sorter until a FINISHED_ROUND record is found /// and self.sorter is non-empty, or until we've run out of records to read. fn read_next_round_impl(&mut self) -> Result<(), Error> { while self.read_offset < self.record_data_len { let offset = self.read_offset; let header = PerfEventHeader::parse::<_, T>(&mut self.reader)?; let size = header.size as usize; if size < PerfEventHeader::STRUCT_SIZE { return Err(Error::InvalidPerfEventSize); } self.read_offset += u64::from(header.size); if UserRecordType::try_from(RecordType(header.type_)) == Some(UserRecordType::PERF_FINISHED_ROUND) { self.sorter.finish_round(); if self.sorter.has_more() { // The sorter is non-empty. We're done. return Ok(()); } // Keep going so that we never exit the loop with sorter // being empty, unless we've truly run out of data to read. continue; } let event_body_len = size - PerfEventHeader::STRUCT_SIZE; let mut buffer = self.buffers_for_recycling.pop_front().unwrap_or_default(); buffer.resize(event_body_len, 0); self.reader .read_exact(&mut buffer) .map_err(|_| ReadError::PerfEventData)?; let data = RawData::from(&buffer[..]); let record_type = RecordType(header.type_); let (attr_index, timestamp) = if record_type.is_builtin_type() { let attr_index = match &self.id_parse_infos { IdParseInfos::OnlyOneEvent => 0, IdParseInfos::Same(id_parse_info) => { get_record_id::(record_type, data, id_parse_info) .and_then(|id| self.event_id_to_attr_index.get(&id).cloned()) .unwrap_or(0) } IdParseInfos::PerAttribute(sample_id_all) => { // We have IDENTIFIER (guaranteed by PerAttribute). get_record_identifier::(record_type, data, *sample_id_all) .and_then(|id| self.event_id_to_attr_index.get(&id).cloned()) .unwrap_or(0) } }; let parse_info = self.parse_infos[attr_index]; let timestamp = get_record_timestamp::(record_type, data, &parse_info); (Some(attr_index), timestamp) } else { // user type (None, None) }; let sort_key = RecordSortKey { timestamp, offset }; let misc = header.misc; let pending_record = PendingRecord { record_type, misc, buffer, attr_index, }; self.sorter.insert_unordered(sort_key, pending_record); } // Everything has been read. self.sorter.finish(); Ok(()) } /// Converts pending_record into an RawRecord which references the data in self.current_event_body. fn convert_pending_record(&mut self, pending_record: PendingRecord) -> PerfFileRecord { let PendingRecord { record_type, misc, buffer, attr_index, .. } = pending_record; let prev_buffer = std::mem::replace(&mut self.current_event_body, buffer); self.buffers_for_recycling.push_back(prev_buffer); let data = RawData::from(&self.current_event_body[..]); if let Some(record_type) = UserRecordType::try_from(record_type) { let endian = self.endian; PerfFileRecord::UserRecord(RawUserRecord { record_type, misc, data, endian, }) } else { let attr_index = attr_index.unwrap(); let parse_info = self.parse_infos[attr_index]; let record = RawEventRecord { record_type, misc, data, parse_info, }; PerfFileRecord::EventRecord { attr_index, record } } } } /// The file path and the build ID of a DSO. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct DsoInfo { /// The file path. Can be an absolute path or a special string /// of various forms, e.g. `[vdso]`. pub path: Vec, /// The build ID. pub build_id: Vec, } #[derive(Debug, Clone)] enum IdParseInfos { /// There is only one event. OnlyOneEvent, /// There are multiple events, but all events are parsed the same way. Same(RecordIdParseInfo), /// All elements are guaranteed to have [`SampleFormat::IDENTIFIER`] set in `attr.sample_format`. /// The inner element indicates sample_id_all. PerAttribute(bool), } #[derive(Clone, Debug, PartialEq, Eq)] struct PendingRecord { record_type: RecordType, misc: u16, buffer: Vec, attr_index: Option, } #[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] struct RecordSortKey { timestamp: Option, offset: u64, } linux-perf-data-0.6.0/src/perf_file.rs000064400000000000000000000052210072674642500157470ustar 00000000000000use std::io::Read; use byteorder::{ByteOrder, ReadBytesExt}; use crate::features::FeatureSet; /// `perf_header` /// /// The magic number identifies the perf file and the version. Current perf versions /// use PERFILE2. Old perf versions generated a version 1 format (PERFFILE). Version 1 /// is not described here. The magic number also identifies the endian. When the /// magic value is 64bit byte swapped compared the file is in non-native /// endian. #[derive(Debug, Clone, Copy)] pub struct PerfHeader { pub magic: [u8; 8], /// size of the header pub header_size: u64, /// size of an attribute in attrs pub attr_size: u64, pub attr_section: PerfFileSection, pub data_section: PerfFileSection, pub event_types_section: PerfFileSection, /// Feature flags pub features: FeatureSet, } impl PerfHeader { pub fn parse(mut reader: R) -> Result { let mut magic = [0; 8]; reader.read_exact(&mut magic)?; if magic[0] == b'P' { Self::parse_impl::(reader, magic) } else { Self::parse_impl::(reader, magic) } } fn parse_impl( mut reader: R, magic: [u8; 8], ) -> Result { let header_size = reader.read_u64::()?; let attr_size = reader.read_u64::()?; let attr_section = PerfFileSection::parse::<_, T>(&mut reader)?; let data_section = PerfFileSection::parse::<_, T>(&mut reader)?; let event_types_section = PerfFileSection::parse::<_, T>(&mut reader)?; let features = FeatureSet([ reader.read_u64::()?, reader.read_u64::()?, reader.read_u64::()?, reader.read_u64::()?, ]); Ok(Self { magic, header_size, attr_size, attr_section, data_section, event_types_section, features, }) } } /// `perf_file_section` /// /// A PerfFileSection contains a pointer to another section of the perf file. /// The header contains three such pointers: for attributes, data and event types. #[derive(Debug, Clone, Copy)] pub struct PerfFileSection { /// offset from start of file pub offset: u64, /// size of the section pub size: u64, } impl PerfFileSection { pub const STRUCT_SIZE: u64 = 8 + 8; pub fn parse(mut reader: R) -> Result { let offset = reader.read_u64::()?; let size = reader.read_u64::()?; Ok(Self { offset, size }) } } linux-perf-data-0.6.0/src/record.rs000064400000000000000000000205000072674642500152670ustar 00000000000000use byteorder::{BigEndian, ByteOrder, LittleEndian}; use linux_perf_event_reader::RawEventRecord; use linux_perf_event_reader::{Endianness, RawData, RecordType}; use crate::constants::*; use crate::thread_map::ThreadMap; /// A record from a perf.data file's data stream. /// /// This can be either a record emitted by the kernel for a perf event, or a /// synthesized record that was added by a user-space tool like `perf`. pub enum PerfFileRecord<'a> { /// Emitted by the kernel for a perf event. EventRecord { /// And index into the array returned by [`PerfFile::event_attributes`](crate::PerfFile::event_attributes). attr_index: usize, /// The record. record: RawEventRecord<'a>, }, /// Synthesized by a user space tool, for example by `perf` or by `simpleperf`. UserRecord(RawUserRecord<'a>), } /// A record emitted by a user space tool, for example by `perf` or by `simpleperf`. #[derive(Debug, Clone)] #[non_exhaustive] pub enum UserRecord<'a> { ThreadMap(ThreadMap<'a>), Raw(RawUserRecord<'a>), } /// A newtype wrapping `RecordType` values for which `RecordType::is_user_type()` returns true. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UserRecordType(RecordType); impl UserRecordType { pub const PERF_HEADER_ATTR: Self = Self(RecordType(PERF_RECORD_HEADER_ATTR)); pub const PERF_HEADER_EVENT_TYPE: Self = Self(RecordType(PERF_RECORD_HEADER_EVENT_TYPE)); pub const PERF_HEADER_TRACING_DATA: Self = Self(RecordType(PERF_RECORD_HEADER_TRACING_DATA)); pub const PERF_HEADER_BUILD_ID: Self = Self(RecordType(PERF_RECORD_HEADER_BUILD_ID)); pub const PERF_FINISHED_ROUND: Self = Self(RecordType(PERF_RECORD_FINISHED_ROUND)); pub const PERF_ID_INDEX: Self = Self(RecordType(PERF_RECORD_ID_INDEX)); pub const PERF_AUXTRACE_INFO: Self = Self(RecordType(PERF_RECORD_AUXTRACE_INFO)); pub const PERF_AUXTRACE: Self = Self(RecordType(PERF_RECORD_AUXTRACE)); pub const PERF_AUXTRACE_ERROR: Self = Self(RecordType(PERF_RECORD_AUXTRACE_ERROR)); pub const PERF_THREAD_MAP: Self = Self(RecordType(PERF_RECORD_THREAD_MAP)); pub const PERF_CPU_MAP: Self = Self(RecordType(PERF_RECORD_CPU_MAP)); pub const PERF_STAT_CONFIG: Self = Self(RecordType(PERF_RECORD_STAT_CONFIG)); pub const PERF_STAT: Self = Self(RecordType(PERF_RECORD_STAT)); pub const PERF_STAT_ROUND: Self = Self(RecordType(PERF_RECORD_STAT_ROUND)); pub const PERF_EVENT_UPDATE: Self = Self(RecordType(PERF_RECORD_EVENT_UPDATE)); pub const PERF_TIME_CONV: Self = Self(RecordType(PERF_RECORD_TIME_CONV)); pub const PERF_HEADER_FEATURE: Self = Self(RecordType(PERF_RECORD_HEADER_FEATURE)); pub const PERF_COMPRESSED: Self = Self(RecordType(PERF_RECORD_COMPRESSED)); pub const SIMPLEPERF_KERNEL_SYMBOL: Self = Self(RecordType(SIMPLE_PERF_RECORD_KERNEL_SYMBOL)); pub const SIMPLEPERF_DSO: Self = Self(RecordType(SIMPLE_PERF_RECORD_DSO)); pub const SIMPLEPERF_SYMBOL: Self = Self(RecordType(SIMPLE_PERF_RECORD_SYMBOL)); pub const SIMPLEPERF_SPLIT: Self = Self(RecordType(SIMPLE_PERF_RECORD_SPLIT)); pub const SIMPLEPERF_SPLIT_END: Self = Self(RecordType(SIMPLE_PERF_RECORD_SPLIT_END)); pub const SIMPLEPERF_EVENT_ID: Self = Self(RecordType(SIMPLE_PERF_RECORD_EVENT_ID)); pub const SIMPLEPERF_CALLCHAIN: Self = Self(RecordType(SIMPLE_PERF_RECORD_CALLCHAIN)); pub const SIMPLEPERF_UNWINDING_RESULT: Self = Self(RecordType(SIMPLE_PERF_RECORD_UNWINDING_RESULT)); pub const SIMPLEPERF_TRACING_DATA: Self = Self(RecordType(SIMPLE_PERF_RECORD_TRACING_DATA)); pub fn try_from(record_type: RecordType) -> Option { if record_type.is_user_type() { Some(Self(record_type)) } else { None } } pub fn record_type(&self) -> RecordType { self.0 } } impl From for RecordType { fn from(record_type: UserRecordType) -> Self { record_type.0 } } impl std::fmt::Debug for UserRecordType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { Self::PERF_HEADER_ATTR => "PERF_HEADER_ATTR".fmt(f), Self::PERF_HEADER_EVENT_TYPE => "PERF_HEADER_EVENT_TYPE".fmt(f), Self::PERF_HEADER_TRACING_DATA => "PERF_HEADER_TRACING_DATA".fmt(f), Self::PERF_HEADER_BUILD_ID => "PERF_HEADER_BUILD_ID".fmt(f), Self::PERF_FINISHED_ROUND => "PERF_FINISHED_ROUND".fmt(f), Self::PERF_ID_INDEX => "PERF_ID_INDEX".fmt(f), Self::PERF_AUXTRACE_INFO => "PERF_AUXTRACE_INFO".fmt(f), Self::PERF_AUXTRACE => "PERF_AUXTRACE".fmt(f), Self::PERF_AUXTRACE_ERROR => "PERF_AUXTRACE_ERROR".fmt(f), Self::PERF_THREAD_MAP => "PERF_THREAD_MAP".fmt(f), Self::PERF_CPU_MAP => "PERF_CPU_MAP".fmt(f), Self::PERF_STAT_CONFIG => "PERF_STAT_CONFIG".fmt(f), Self::PERF_STAT => "PERF_STAT".fmt(f), Self::PERF_STAT_ROUND => "PERF_STAT_ROUND".fmt(f), Self::PERF_EVENT_UPDATE => "PERF_EVENT_UPDATE".fmt(f), Self::PERF_TIME_CONV => "PERF_TIME_CONV".fmt(f), Self::PERF_HEADER_FEATURE => "PERF_HEADER_FEATURE".fmt(f), Self::PERF_COMPRESSED => "PERF_COMPRESSED".fmt(f), Self::SIMPLEPERF_KERNEL_SYMBOL => "SIMPLEPERF_KERNEL_SYMBOL".fmt(f), Self::SIMPLEPERF_DSO => "SIMPLEPERF_DSO".fmt(f), Self::SIMPLEPERF_SYMBOL => "SIMPLEPERF_SYMBOL".fmt(f), Self::SIMPLEPERF_SPLIT => "SIMPLEPERF_SPLIT".fmt(f), Self::SIMPLEPERF_SPLIT_END => "SIMPLEPERF_SPLIT_END".fmt(f), Self::SIMPLEPERF_EVENT_ID => "SIMPLEPERF_EVENT_ID".fmt(f), Self::SIMPLEPERF_CALLCHAIN => "SIMPLEPERF_CALLCHAIN".fmt(f), Self::SIMPLEPERF_UNWINDING_RESULT => "SIMPLEPERF_UNWINDING_RESULT".fmt(f), Self::SIMPLEPERF_TRACING_DATA => "SIMPLEPERF_TRACING_DATA".fmt(f), other => f.write_fmt(format_args!("Unknown UserRecordType {}", other.0 .0)), } } } /// A raw user record. /// /// Can be turned into a parsed [`UserRecord`] using [`RawUserRecord::parse`]. #[derive(Debug, Clone)] pub struct RawUserRecord<'a> { pub record_type: UserRecordType, pub endian: Endianness, pub misc: u16, pub data: RawData<'a>, } impl<'a> RawUserRecord<'a> { pub fn parse(&self) -> Result, std::io::Error> { match self.endian { Endianness::LittleEndian => self.parse_impl::(), Endianness::BigEndian => self.parse_impl::(), } } pub fn parse_impl(&self) -> Result, std::io::Error> { let record_type = self.record_type; let record = match record_type { // UserRecordType::PERF_HEADER_ATTR => {}, // UserRecordType::PERF_HEADER_EVENT_TYPE => {}, // UserRecordType::PERF_HEADER_TRACING_DATA => {}, // UserRecordType::PERF_HEADER_BUILD_ID => {}, // UserRecordType::PERF_FINISHED_ROUND => {}, // UserRecordType::PERF_ID_INDEX => {}, // UserRecordType::PERF_AUXTRACE_INFO => {}, // UserRecordType::PERF_AUXTRACE => {}, // UserRecordType::PERF_AUXTRACE_ERROR => {}, UserRecordType::PERF_THREAD_MAP => { UserRecord::ThreadMap(ThreadMap::parse::(self.data)?) } // UserRecordType::PERF_CPU_MAP => {}, // UserRecordType::PERF_STAT_CONFIG => {}, // UserRecordType::PERF_STAT => {}, // UserRecordType::PERF_STAT_ROUND => {}, // UserRecordType::PERF_EVENT_UPDATE => {}, // UserRecordType::PERF_TIME_CONV => {}, // UserRecordType::PERF_HEADER_FEATURE => {}, // UserRecordType::PERF_COMPRESSED => {}, // UserRecordType::SIMPLEPERF_KERNEL_SYMBOL => {}, // UserRecordType::SIMPLEPERF_DSO => {}, // UserRecordType::SIMPLEPERF_SYMBOL => {}, // UserRecordType::SIMPLEPERF_SPLIT => {}, // UserRecordType::SIMPLEPERF_SPLIT_END => {}, // UserRecordType::SIMPLEPERF_EVENT_ID => {}, // UserRecordType::SIMPLEPERF_CALLCHAIN => {}, // UserRecordType::SIMPLEPERF_UNWINDING_RESULT => {}, // UserRecordType::SIMPLEPERF_TRACING_DATA => {}, _ => UserRecord::Raw(self.clone()), }; Ok(record) } } linux-perf-data-0.6.0/src/sorter.rs000064400000000000000000000204060072674642500153340ustar 00000000000000use std::collections::VecDeque; /// Accumulates unordered key-value pairs and emits them in order, sorted by the key. /// /// The caller can indicate "rounds" with the property that round N cannot /// overlap with round N + 2. In other words, the lowest key in round N + 2 /// must be greater than or equal to the highest key in round N. /// /// Every time a round is finished, some values become available for ordered /// iteration, specifically those values whose order cannot be affected by /// upcoming values due to the overlap guarantee. // // Implementation notes: // // i: incoming values (unordered) // o: outgoing values (ordered) // // Round 1: |<============>| // insert_unordered is called with unordered keys in this range // ^ ^--- cur_max // `------------------ prev_max // finish_round() iiiiiiiiiiiiiiii // nothing is available in outgoing yet, everything is still incoming // ^--- cur_max // ^--- prev_max // Round 2: |<======================>| // more insert_unordered calls // ^ ^--- cur_max // `-------------------- prev_max // finish_round() ooooooooooooooooiiiiiiiiiiiiiiiii // everything <= prev_max is moved to outgoing // ^--- cur_max // ^--- prev_max // Round 3: |<================>| // more insert_unordered calls, no overlap with round 1 // ^ ^--- cur_max // `-------- prev_max // finish_round() oooooooooooooooooiiiii // everything <= prev_max is moved to outgoing #[derive(Debug, Clone)] pub struct Sorter { /// This list is ordered and all values are <= prev_max. outgoing: VecDeque, /// Unsorted values. incoming: VecDeque<(K, V)>, /// The maximum key of incoming in previous round. prev_max: K, /// The maximum key of incoming in the current round. cur_max: K, /// The number of values in incoming which are <= prev_max. incoming_lte_prev_max_count: usize, } impl Default for Sorter { fn default() -> Self { Self { outgoing: VecDeque::new(), incoming: VecDeque::new(), prev_max: Default::default(), cur_max: Default::default(), incoming_lte_prev_max_count: 0, } } } impl Sorter { /// Create a new sorter. pub fn new() -> Self { Default::default() } /// Whether there are more ordered values available. If this returns false, /// the next round must be read. pub fn has_more(&self) -> bool { !self.outgoing.is_empty() } /// Returns values in order. /// /// The order is only guaranteed if the caller respected the contract for /// `insert_unordered`. pub fn get_next(&mut self) -> Option { self.outgoing.pop_front() } /// Insert an element. The caller guarantees that `key` is at least as large /// as the largest key seen two `finish_round` calls ago. In other words, round /// N must not overlap with round N - 2. pub fn insert_unordered(&mut self, key: K, value: V) { if key <= self.prev_max { self.incoming_lte_prev_max_count += 1; } else if key > self.cur_max { self.cur_max = key.clone(); } self.incoming.push_back((key, value)); } /// Finish the current round. This makes some of the inserted values available /// from `get_next`, specifically any values which cannot have their order affected /// by values from the next round. pub fn finish_round(&mut self) { if let Some(n) = self.incoming_lte_prev_max_count.checked_sub(1) { let (new_outgoing, _middle, _remaining) = self .incoming .make_contiguous() .select_nth_unstable_by_key(n, |(key, _value)| key.clone()); new_outgoing.sort_unstable_by_key(|(key, _value)| key.clone()); // Move everything <= prev_max from incoming into outgoing. for _ in 0..self.incoming_lte_prev_max_count { let (_key, value) = self.incoming.pop_front().unwrap(); self.outgoing.push_back(value); } } self.prev_max = self.cur_max.clone(); self.incoming_lte_prev_max_count = self.incoming.len(); } /// Finish all rounds and declare that no more values will be inserted after this call. /// This makes all inserted values available from `get_next()`. pub fn finish(&mut self) { self.incoming .make_contiguous() .sort_unstable_by_key(|(key, _value)| key.clone()); while let Some((_key, value)) = self.incoming.pop_front() { self.outgoing.push_back(value); } self.prev_max = self.cur_max.clone(); } } #[cfg(test)] mod test { use super::Sorter; // Example from the perf FINISHED_ROUND docs: // // ============ PASS n ================= // CPU 0 | CPU 1 // | // cnt1 timestamps | cnt2 timestamps // 1 | 2 // 2 | 3 // - | 4 <--- max recorded // // ============ PASS n + 1 ============== // CPU 0 | CPU 1 // | // cnt1 timestamps | cnt2 timestamps // 3 | 5 // 4 | 6 // 5 | 7 <---- max recorded // // Flush every events below timestamp 4 // // ============ PASS n + 2 ============== // CPU 0 | CPU 1 // | // cnt1 timestamps | cnt2 timestamps // 6 | 8 // 7 | 9 // - | 10 // // Flush every events below timestamp 7 // etc... #[test] fn it_works() { let mut sorter = Sorter::new(); sorter.insert_unordered(1, "1"); // cpu 0 sorter.insert_unordered(2, "2"); // cpu 1 sorter.insert_unordered(3, "3"); // cpu 1 sorter.insert_unordered(2, "2"); // cpu 0 sorter.insert_unordered(4, "4"); // cpu 1 assert_eq!(sorter.get_next(), None); sorter.finish_round(); assert_eq!(sorter.get_next(), None); sorter.insert_unordered(3, "3"); // cpu 0 sorter.insert_unordered(5, "5"); // cpu 1 sorter.insert_unordered(6, "6"); // cpu 1 sorter.insert_unordered(7, "7"); // cpu 1 sorter.insert_unordered(4, "4"); // cpu 0 sorter.insert_unordered(5, "5"); // cpu 0 assert_eq!(sorter.get_next(), None); sorter.finish_round(); assert_eq!(sorter.get_next(), Some("1")); assert_eq!(sorter.get_next(), Some("2")); assert_eq!(sorter.get_next(), Some("2")); assert_eq!(sorter.get_next(), Some("3")); assert_eq!(sorter.get_next(), Some("3")); assert_eq!(sorter.get_next(), Some("4")); assert_eq!(sorter.get_next(), Some("4")); assert_eq!(sorter.get_next(), None); sorter.insert_unordered(6, "6"); // cpu 0 sorter.insert_unordered(8, "8"); // cpu 1 sorter.insert_unordered(9, "9"); // cpu 1 sorter.insert_unordered(7, "7"); // cpu 0 sorter.insert_unordered(10, "10"); // cpu 1 assert_eq!(sorter.get_next(), None); sorter.finish_round(); assert_eq!(sorter.get_next(), Some("5")); assert_eq!(sorter.get_next(), Some("5")); assert_eq!(sorter.get_next(), Some("6")); assert_eq!(sorter.get_next(), Some("6")); assert_eq!(sorter.get_next(), Some("7")); assert_eq!(sorter.get_next(), Some("7")); assert_eq!(sorter.get_next(), None); sorter.finish(); assert_eq!(sorter.get_next(), Some("8")); assert_eq!(sorter.get_next(), Some("9")); assert_eq!(sorter.get_next(), Some("10")); assert_eq!(sorter.get_next(), None); } } linux-perf-data-0.6.0/src/thread_map.rs000064400000000000000000000121160072674642500161210ustar 00000000000000use std::fmt; use byteorder::{ByteOrder, NativeEndian}; use linux_perf_event_reader::{is_swapped_endian, RawData}; /// A list of threads, usually without names. /// /// It's not clear to me what the point of this list is. It doesn't even give you the /// pid of the process that each thread belongs to. And unless you use `perf stat`, /// it doesn't seem to have thread names either. /// /// So it seems like all the useful information is instead in the PERF_RECORD_COMM /// records which get synthesized at the start of a file for `perf record -p `. /// It seems you're better of just reading those, instead of looking at the thread map. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ThreadMap<'a> { swap_endian: bool, data: RawData<'a>, } const THREAD_ENTRY_SIZE: usize = 8 + 16; impl<'a> ThreadMap<'a> { pub fn parse(mut data: RawData<'a>) -> Result { let len = data.read_u64::()?; let len = usize::try_from(len).map_err(|_| std::io::ErrorKind::InvalidData)?; let datalen = len .checked_mul(THREAD_ENTRY_SIZE) .ok_or(std::io::ErrorKind::InvalidData)?; let data = data.split_off_prefix(datalen)?; Ok(Self { swap_endian: is_swapped_endian::(), data, }) } pub fn len(&self) -> usize { self.data.len() / THREAD_ENTRY_SIZE } pub fn is_empty(&self) -> bool { self.len() == 0 } pub fn iter(&self) -> ThreadMapIter<'a> { ThreadMapIter { swap_endian: self.swap_endian, index: 0, len: self.len(), data: self.data, } } } #[derive(Clone, Copy, PartialEq, Eq)] pub struct ThreadMapEntry<'a> { /// The tid of this thread. pub tid: u64, /// The name is usually empty, unfortunately. It looks like `thread_map__read_comms` /// only gets called by `perf stat`, not by `perf record`. pub name: RawData<'a>, } impl<'a> fmt::Debug for ThreadMapEntry<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { use std::str; let mut map = fmt.debug_map(); map.entry(&"tid", &self.tid); if let Ok(string) = str::from_utf8(&self.name.as_slice()) { map.entry(&"name", &string); } else { map.entry(&"name", &self.name); } map.finish() } } pub struct ThreadMapIter<'a> { swap_endian: bool, data: RawData<'a>, index: usize, len: usize, } impl<'a> Iterator for ThreadMapIter<'a> { type Item = ThreadMapEntry<'a>; fn next(&mut self) -> Option { if self.index >= self.len { return None; } let mut tid = self.data.read_u64::().unwrap(); if self.swap_endian { tid = tid.swap_bytes(); } let mut name = self.data.split_off_prefix(16).unwrap(); let name = name.read_string().unwrap_or(name); self.index += 1; Some(ThreadMapEntry { tid, name }) } fn size_hint(&self) -> (usize, Option) { (0, Some(self.len)) } } #[cfg(test)] mod test { use byteorder::LittleEndian; use crate::RawData; use super::ThreadMap; #[test] fn parse_one() { let data = RawData::Single(&[ 1, 0, 0, 0, 0, 0, 0, 0, 108, 71, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); let map = ThreadMap::parse::(data).unwrap(); assert_eq!(map.len(), 1); let vec: Vec<_> = map.iter().collect(); assert_eq!(vec.len(), 1); assert_eq!(vec[0].tid, 542572); assert_eq!(&vec[0].name.as_slice()[..], b""); } #[test] fn parse_big() { let data = RawData::Single(&[ 12, 0, 0, 0, 0, 0, 0, 0, 165, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 169, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 171, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 187, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 188, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 189, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 190, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 197, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 199, 115, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); let map = ThreadMap::parse::(data).unwrap(); assert_eq!(map.len(), 12); let vec: Vec<_> = map.iter().collect(); assert_eq!(vec.len(), 12); assert_eq!(vec[8].tid, 95167); assert_eq!(&vec[8].name.as_slice()[..], b""); } }