diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3ab6872..5e3e783 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,12 +30,7 @@ jobs: - name: Install Protoc uses: arduino/setup-protoc@v3 with: - version: "28.x" - - - name: Build - run: | - if [ ! -d target ]; then mkdir target; fi - protoc --rust_opt='experimental-codegen=enabled,kernel=cpp' --rust_out=./target psh.proto + version: "32.x" - name: Test run: | diff --git a/Cargo.lock b/Cargo.lock index 1228939..fc7c9fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,6 +121,29 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "2.9.0" @@ -133,12 +156,32 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "either" version = "1.15.0" @@ -218,6 +261,17 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.3.2" @@ -236,6 +290,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "h2" version = "0.4.8" @@ -267,6 +327,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "1.3.1" @@ -391,12 +460,40 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.9.3" @@ -421,12 +518,27 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.5" @@ -453,6 +565,16 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "object" version = "0.36.7" @@ -468,12 +590,41 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "perf-event-rs" +version = "0.0.0" +source = "git+https://github.com/saying121/perf-event-rs.git?rev=d6881f34b8a9cde1d70dab5fb1415271e6b0bb25#d6881f34b8a9cde1d70dab5fb1415271e6b0bb25" +dependencies = [ + "bindgen", + "libc", + "memmap2", + "page_size", + "rand", + "thiserror", +] + [[package]] name = "petgraph" version = "0.7.1" @@ -516,6 +667,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.31" @@ -591,7 +751,9 @@ dependencies = [ name = "psh-proto" version = "0.1.0" dependencies = [ + "perf-event-rs", "prost", + "serde", "tonic", "tonic-build", ] @@ -611,6 +773,36 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "regex" version = "1.11.1" @@ -646,6 +838,25 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.52.0", +] + [[package]] name = "rustix" version = "1.0.3" @@ -655,7 +866,7 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.9.3", "windows-sys 0.59.0", ] @@ -685,6 +896,12 @@ dependencies = [ "syn", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "slab" version = "0.4.9" @@ -734,12 +951,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.3.2", "once_cell", - "rustix", + "rustix 1.0.3", "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" version = "1.44.1" @@ -932,6 +1169,40 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" @@ -1022,3 +1293,23 @@ checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 9a022ed..dfff6c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,14 @@ -[workspace.package] -edition = "2024" - [workspace] -members = ["./rust"] resolver = "3" +members = ["./rust"] exclude = [".github"] +[workspace.package] +edition = "2024" + [workspace.dependencies] -tonic = "0.13" +perf-event-rs = { git = "https://github.com/saying121/perf-event-rs.git", rev = "d6881f34b8a9cde1d70dab5fb1415271e6b0bb25" } prost = "0.13" +serde = "1" +tonic = "0.13" tonic-build = "0.13" diff --git a/proto/perf/perf_data.proto b/proto/perf/perf_data.proto new file mode 100644 index 0000000..4020570 --- /dev/null +++ b/proto/perf/perf_data.proto @@ -0,0 +1,1132 @@ +// This code is adapted from: perf_data_converter - https://github.com/google/perf_data_converter/blob/master/src/quipper/perf_data.proto +// Copyright: Google LLC +// License: BSD-style +// +// Modifications include: Add more fields + +syntax = "proto3"; + +package psh.proto.instance; + +// Stores information from a perf session generated via running: +// "perf record" +// +// See $kernel/tools/perf/design.txt for more details. + +// Next tag: 18 +message PerfDataProto { + // Perf event attribute. Stores the event description. + // This data structure is defined in the linux kernel: + // $kernel/include/uapi/linux/perf_event.h. + // Next tag: 48 + message PerfEventAttr { + // Type of the event. Type is an enumeration and can be one of the values + // described at: $kernel/include/linux/perf_event.h. + // Example types are: + // PERF_TYPE_HARDWARE + // PERF_TYPE_SOFTWARE, etc. + optional uint32 type = 1; + + // Size of the event data in bytes. + optional uint32 size = 2; + + // The config stores the CPU-specific counter information. + optional uint64 config = 3; + + // Sample period of the event. Indicates how often the event is + // triggered in terms of # of events. After |sample_period| events, an event + // will be recorded and stored. + optional uint64 sample_period = 4; + + // Sample frequency of the event. Indicates how often the event is + // triggered in terms of # per second. The kernel will try to record + // |sample_freq| events per second. + optional uint64 sample_freq = 5; + + // Sample type is a bitfield that records attributes of the sample. Example, + // whether an entire callchain was recorded, etc. + optional uint64 sample_type = 6; + + // Bitfield that indicates whether reads on the counter will return the + // total time enabled and total time running. + optional uint64 read_format = 7; + + // Indicates whether the counter starts off disabled. + optional bool disabled = 8; + + // Indicates whether child processes inherit the counter. + optional bool inherit = 9; + + // Indicates whether the counter is pinned to a particular CPU. + optional bool pinned = 10; + + // Indicates whether this counter's group has exclusive access to the CPU's + // counters. + optional bool exclusive = 11; + + // The following bits restrict events to be counted when the CPU is in user, + // kernel, hypervisor or idle modes. + optional bool exclude_user = 12; + optional bool exclude_kernel = 13; + optional bool exclude_hv = 14; + optional bool exclude_idle = 15; + + // Indicates whether mmap events should be recorded. + optional bool mmap = 16; + + // Indicates whether process comm information should be recorded upon + // process creation. + optional bool comm = 17; + + // Indicates that we are in frequency mode, not period mode. + optional bool freq = 18; + + // Indicates whether we have per-task counts. + optional bool inherit_stat = 19; + + // Indicates whether we enable perf events after an exec() function call. + optional bool enable_on_exec = 20; + + // Indicates whether we trace fork/exit. + optional bool task = 21; + + // Indicates whether we are using a watermark to wake up. + optional bool watermark = 22; + + // CPUs often "skid" when recording events. That means the instruction + // pointer may not be the same as the one that caused the counter overflow. + // Indicates the capabilities of the CPU in terms of recording precise + // instruction pointer. + optional uint32 precise_ip = 23; + + // Indicates whether we have non-exec mmap data. + optional bool mmap_data = 24; + + // If set, all the event types will have the same sample_type. + optional bool sample_id_all = 25; + + // Indicates whether we are counting events from the host (when running a + // VM). + optional bool exclude_host = 26; + + // Exclude events that happen on a guest OS. + optional bool exclude_guest = 27; + + // Exclude kernel callchains. + optional bool exclude_callchain_kernel = 36; + + // Exclude user callchains. + optional bool exclude_callchain_user = 37; + + // Include mmap2 events that have inode data. + optional bool mmap2 = 38; + + // Flag comm events that are due to an exec. + optional bool comm_exec = 39; + + // Indicates whether or not to use @clockid for time fields. + optional bool use_clockid = 42; + + // Include context switch data. + optional bool context_switch = 43; + + // Indicates whether ring buffer is written from end to beginning. + optional bool write_backward = 44; + + // Include namespace data. + optional bool namespaces = 45; + + // Include cgroup data. + optional bool cgroup = 46; + + // Include ksymbol data. + optional bool ksymbol = 47; + + // Contains the number of events after which we wake up. + optional uint32 wakeup_events = 28; + + // Contains the number of bytes after which we wake up. + optional uint32 wakeup_watermark = 29; + + // Information about the type of the breakpoint. + optional uint32 bp_type = 30; + + // Contains the breakpoint address. + optional uint64 bp_addr = 31; + + // This is an extension of config (see above). + optional uint64 config1 = 32; + + // The length of the breakpoint data in bytes. + optional uint64 bp_len = 33; + + // This is an extension of config (see above). + optional uint64 config2 = 34; + + // Contains the type of branch, example: user, kernel, call, return, etc. + optional uint64 branch_sample_type = 35; + + // Defines set of user regs to dump on samples. + optional uint64 sample_regs_user = 40; + + // Defines size of the user stack to dump on samples. + optional uint32 sample_stack_user = 41; + } + + // Describes a perf.data file attribute. + // Next tag: 3 + message PerfFileAttr { + optional PerfEventAttr attr = 1; + + // List of perf file attribute ids. Each id describes an event. + repeated uint64 ids = 2; + } + + // Protobuf version of the perf_event_type struct found in perf/util/event.h. + // Contains the name of the event (such as "cycles" or "branch-misses") and + // the event id (which is not unique). + // Next tag: 4 + message PerfEventType { + // Event id. This is not unique across event types. + // The combination of the event id and the type field in PerfEventAttr is + // unique across event types. + optional uint64 id = 1; + + // Event name. + optional string name = 2; + + // Event name's md5 prefix. + optional uint64 name_md5_prefix = 3; + } + + // This message contains information about a perf sample itself, as opposed to + // a perf event captured by a sample. + // Next tag: 7 + message SampleInfo { + // Process ID / thread ID from which this sample was taken. + optional uint32 pid = 1; + optional uint32 tid = 2; + + // Time this sample was taken (NOT the same as an event time). + // It is the number of nanoseconds since bootup. + optional uint64 sample_time_ns = 3; + + // The ID of the sample's event type (cycles, instructions, etc). + // The event type IDs are defined in PerfFileAttr. + optional uint64 id = 4; + + // The CPU on which this sample was taken. + optional uint32 cpu = 5; + + // The stream id of the sample. + optional uint64 stream_id = 6; + } + + // Next tag: 7 + message CommEvent { + // Process id. + optional uint32 pid = 1; + + // Thread id. + optional uint32 tid = 2; + + // Comm string. + optional string comm = 3; + + // Comm string's md5 prefix. + optional uint64 comm_md5_prefix = 4; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 6; + } + + // Represents both mmap_event and mmap2_event. + // Next tag: 18 + message MMapEvent { + // Process id. + optional uint32 pid = 1; + + // Thread id. + optional uint32 tid = 2; + + // Start address. + optional uint64 start = 3; + + // Length. + optional uint64 len = 4; + + // PG Offset. + optional uint64 pgoff = 5; + + // Only in MMAP2 events, information about the mapped inode: + // Major/minor numbers + // Note that those the fields maj, min, ino, ino_generation should be + // unspecified if build_id is set. + optional uint32 maj = 9; + optional uint32 min = 10; + // Inode number and generation. + optional uint64 ino = 11; + optional uint64 ino_generation = 12; + + // Only in MMAP2 events, the build ID of this mmap. + // The build_id is a HexString representation of the bytes in original + // perf.data (example value: "af2272facafc97e344e12aff495cbddd00000000"). + // Note that this field should be unspecified if any of the inode-related + // fields (maj, min, ino, ino_generation) are set. + optional string build_id = 17; + + // Protection bits and flags. + optional uint32 prot = 13; + optional uint32 flags = 14; + + // In both MMAP and MMAP2 events: + + // Filename. + optional string filename = 6; + + // Filename's md5 prefix. + optional uint64 filename_md5_prefix = 7; + + // The root path is defined as the first two levels of directories starting + // from the root, e.g. /data/app, /system/lib. + optional string root_path = 15; + + // Root path's md5 prefix. It helps to categorize filenames we could not + // recover by the filename_md5_prefix. + optional uint64 root_path_md5_prefix = 16; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 8; + } + + // Next tag: 7 + message KsymbolEvent { + // Address of kernel symbol. + optional uint64 addr = 1; + // Address length of kernel symbol. + optional uint32 len = 2; + // Type of the kernel symbol. Currently the following types are available: + // PERF_RECORD_KSYMBOL_TYPE_BPF. More info in documentation: + // https://screenshot.googleplex.com/VZsTDGmoGqgHRDJ + optional uint32 ksym_type = 3; + // Ksymbol flags. PERF_RECORD_KSYMBOL_FLAGS_UNREGISTER is the only supported + // flag. More info in documentation: + // https://screenshot.googleplex.com/VZsTDGmoGqgHRDJ + optional uint32 flags = 4; + + // Ksymbol name. + optional string name = 5; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 6; + } + + // Next tag: 4 + message ReadInfo { + optional uint64 time_enabled = 1; + + optional uint64 time_running = 2; + + message ReadValue { + optional uint64 value = 1; + optional uint64 id = 2; + optional uint64 lost = 3; + } + + // Based on the value of |PerfEventAttr::read_format & PERF_FORMAT_GROUP|, + // the read info could contain one or multiple read values and IDs. If the + // format is non-grouped, the repeated field will have only one entry. + repeated ReadValue read_value = 3; + } + + // Next tag: 10 + message BranchStackEntry { + // Branch source address. + optional uint64 from_ip = 1; + + // Branch destination address. + optional uint64 to_ip = 2; + + // Indicates a mispredicted branch. + optional bool mispredicted = 3; + + // Indicates a predicted branch. + optional bool predicted = 4; + + // Indicates running in a hardware transaction + optional bool in_transaction = 5; + + // Indicates aborting a hardware transaction + optional bool abort = 6; + + // Cycle count to last branch + optional uint32 cycles = 7; + + // Branch type. + optional uint32 type = 8; + + // Branch speculation info. + optional uint32 spec = 9; + } + + // WeightStruct is microarchitecture-dependent field, and represents sample + // weights for some special events. + // On Intel Sapphire Rapids and newer, with load latency events, the first + // field is the cache latency, and the second is the "total" instruction + // issue-to-retirement latency. + // On AMD, for IBS collections and only on load instructions, the first + // field is the cache latency, and the second is the "total" instruction + // issue-to-retirement latency. + // Next tag: 4 + message WeightStruct { + // First weight factor. + optional uint32 var1_dw = 1; + + // Second weight factor. + optional uint32 var2_w = 2; + + // Third weight factor. + optional uint32 var3_w = 3; + } + + // Next tag: 27 + message SampleEvent { + // Instruction pointer. + optional uint64 ip = 1; + + // Process id. + optional uint32 pid = 2; + + // Thread id. + optional uint32 tid = 3; + + // The time after boot when the sample was recorded, in nanoseconds. + optional uint64 sample_time_ns = 4; + + // The address of the sample. + optional uint64 addr = 5; + + // The id of the sample. + optional uint64 id = 6; + + // The stream id of the sample. + optional uint64 stream_id = 7; + + // The period of the sample. + optional uint64 period = 8; + + // The CPU where the event was recorded. + optional uint32 cpu = 9; + + // The raw data for the event. Historically, the raw data itself was not + // stored in the perf_data.proto and so it's possible that only raw_size is + // set in which case the reader should assume all-zero raw data of the + // specified size. If raw field is present, then len(raw) == raw_size. + optional bytes raw = 21; + optional uint32 raw_size = 10; + + // The read field. + optional ReadInfo read_info = 18; + + // Sample callchain info. + repeated uint64 callchain = 11; + + // Branch stack info. + repeated BranchStackEntry branch_stack = 12; + + // These are not yet implemented, but are listed as placeholders. + // + // optional RegsUser regs_user = 13; + // optional StackUser stack_user = 14; + + // Sample weight for special events. Microarchitecture-dependent. + // On Intel Icelake and older, for load latency events, this is the "total" + // instruction issue-to-retire latency. + // On AMD, for IBS collections and load instructions, this is the cache + // latency. + optional uint64 weight = 15; + + // Sample data source flags. + optional uint64 data_src = 16; + + // Sample transaction flags for special events. + optional uint64 transaction = 17; + + // Physical address of the sample. + optional uint64 physical_addr = 19; + + // Cgroup id. + optional uint64 cgroup = 20; + + // Data page size + optional uint64 data_page_size = 22; + + // Code page size + optional uint64 code_page_size = 23; + + // No hw_idx collected in branch_stack. + optional bool no_hw_idx = 24; + + // Branch stack hw index, + // optional field in PERF_SAMPLE_BRANCH_STACK + // used when PERF_SAMPLE_BRANCH_HW_INDEX is set. + optional uint64 branch_stack_hw_idx = 25; + + // Weight struct. + optional WeightStruct weight_struct = 26; + } + + // ForkEvent is used for both FORK and EXIT events, which have the same data + // format. We don't want to call this "ForkOrExitEvent", in case a separate + // exit event is introduced in the future. + // Next tag: 12 + message ForkEvent { + // Forked process ID. + optional uint32 pid = 1; + + // Parent process ID. + optional uint32 ppid = 2; + + // Forked process thread ID. + optional uint32 tid = 3; + + // Parent process thread ID. + optional uint32 ptid = 4; + + // Time of fork event in nanoseconds since bootup. + optional uint64 fork_time_ns = 5; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 11; + } + + // The kernel collects the number of events it couldn't send in a stretch and + // when possible sends this number in a PERF_RECORD_LOST event, which is + // stored in LostEvent. + // Next tag: 4 + message LostEvent { + // ID of the event which has been lost. This should be an id found in a + // PerfFileAttr. + optional uint64 id = 1; + + // Number of events that were lost. + optional uint64 lost = 2; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 3; + } + + // Next tag: 5 + message ThrottleEvent { + // Time of throttle event, in nanoseconds since system startup. + optional uint64 time_ns = 1; + + // Event ID. + optional uint64 id = 2; + + // Stream ID. + optional uint64 stream_id = 3; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 4; + } + + // Next tag: 8 + message ReadEvent { + option deprecated = true; + + // Process ID. + optional uint32 pid = 1; + + // Thread ID. + optional uint32 tid = 2; + + // Value of the event counter when it was queried. + optional uint64 value = 3; + + // Time enabled. + optional uint64 time_enabled = 4; + + // Time running. + optional uint64 time_running = 5; + + // ID. + optional uint64 id = 6; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 7; + } + + // Next tag: 7 + message AuxEvent { + // Aux offset. + optional uint64 aux_offset = 1; + + // Aux size. + optional uint64 aux_size = 2; + + // Is the record was truncated to fit. + optional bool is_truncated = 3; + + // Does the record contain snapshot from overwrite mode. + optional bool is_overwrite = 4; + + // Does the record contain gaps. + optional bool is_partial = 5; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 6; + } + + // Next tag: 3 + message AuxtraceInfoEvent { + // Auxtrace type from the auxtrace_type enum in tools/perf/util/auxtrace.h. + optional uint32 type = 1; + + // Private data. + // WARNING: unparsed_binary_blob_priv_data contains unparsed private data + // specific to the type stored in the above field. This data is included to + // support serialization of a perf.data to perf_data.proto and + // deserialization of a perf_data.proto to perf.data. If this data is used + // for something other than the aforementioned usecase, this data has to be + // parsed based on the type. + // For example: + // If type == PERF_AUXTRACE_INTEL_PT, unparsed_binary_blob_priv_data + // contains fields filled by intel_pt_info_fill() function in the file + // tools/perf/arch/x86/util/intel-pt.c. + // If type == PERF_AUXTRACE_INTEL_BTS, unparsed_binary_blob_priv_data + // contains fields filled by intel_bts_info_fill() function in the file + // tools/perf/arch/x86/util/intel-bts.c. + // + // NOTE: Do not read this unparsed data directly. Quipper should be + // modified to parse the data into a new field before reading. Please + // contact developers of quipper to add support for parsing this data. + // + repeated uint64 unparsed_binary_blob_priv_data = 2; + } + + // Next tag: 8 + message AuxtraceEvent { + // Size of AUX area tracing buffer. + optional uint64 size = 1; + + // Offset as determined by aux_head / aux_tail members of struct + // perf_event_mmap_page. + optional uint64 offset = 2; + + // Implementation specific reference determined when the data is recorded. + optional uint64 reference = 3; + + // Index of AUX area tracing data buffer. + optional uint32 idx = 4; + + // In per-thread mode, the tid this buffer is associated with. + optional uint32 tid = 5; + + // In per-cpu mode, the cpu this buffer is associated with. + optional uint32 cpu = 6; + + // The trace data. + optional bytes trace_data = 7; + } + + // Next tag: 9 + message AuxtraceErrorEvent { + // Error type as defined by the enum auxtrace_error_type_name. + optional uint32 type = 1; + + // Intel PT or Intel BTS specific error code. + optional uint32 code = 2; + + // In per-cpu mode, the cpu this auxtrace error is associated with. + optional uint32 cpu = 3; + + // Process ID. + optional uint32 pid = 4; + + // In per-thread mode, the tid this auxtrace error is associated with. + optional uint32 tid = 5; + + // From instruction pointer. + optional uint64 ip = 6; + + // Auxtrace error message. + optional string msg = 7; + + // Message's md5 prefix. + optional uint64 msg_md5_prefix = 8; + } + + // Next tag: 5 + message IdIndexEventEntry { + // Perf sample identifier. + optional uint64 id = 1; + + // Index associated with the sample identifier. + optional uint64 idx = 2; + + // CPU associated with the sample identifier. + optional uint64 cpu = 3; + + // Thread ID associated with the sample identifier. + optional uint64 tid = 4; + } + + message IdIndexEvent { + repeated IdIndexEventEntry entries = 1; + } + + // Next tag: 4 + message ItraceStartEvent { + optional uint32 pid = 1; + + optional uint32 tid = 2; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 3; + } + + // The kernel discards mixed up samples and sends the number in a + // PERF_RECORD_LOST_SAMPLES event. This event is stored in LostSamplesEvent. + // Next tag: 3 + message LostSamplesEvent { + // Number of lost samples. + optional uint64 num_lost = 1; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 2; + } + + // Next tag: 5 + message ContextSwitchEvent { + // Indicates whether the context switch is in or out. + optional bool is_out = 1; + + // Next (switching out) or previous (switching in) pid. + optional uint32 next_prev_pid = 2; + + // Next (switching out) or previous (switching in) tid. + optional uint32 next_prev_tid = 3; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 4; + } + + // Next tag: 3 + message PerfNamespacesLinkInfo { + // ID of device containing file. + optional uint64 dev = 1; + + // inode number. + optional uint64 ino = 2; + } + + // Next tag: 5 + message NamespacesEvent { + // PID of the process mapped to these namespaces. + optional uint32 pid = 1; + + // TID of the process mapped to these namespaces. + optional uint32 tid = 2; + + // Device/inode information per namespace up to NR_NAMESPACES. + repeated PerfNamespacesLinkInfo link_info = 3; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 4; + } + + // Next Tag: 4 + message ThreadMapEventEntry { + optional uint64 pid = 1; + + // Thread name. + optional string comm = 2; + + // Thread name's md5 prefix. + optional uint64 comm_md5_prefix = 3; + } + + // Next Tag: 2 + message ThreadMapEvent { + repeated ThreadMapEventEntry entries = 1; + } + + // Next Tag: 3 + message StatConfigEventEntry { + // An enum value representing a field in the struct perf_stat_config. + optional uint64 tag = 1; + + // Value of the field represented by the enum value in tag. + optional uint64 val = 2; + } + + // Next Tag: 2 + message StatConfigEvent { + // Key-value entries representing some fields of the struct + // perf_stat_config. + repeated StatConfigEventEntry data = 1; + } + + // Next Tag: 7 + message StatEvent { + // Perf sample identifier. + optional uint64 id = 1; + + // CPU for which the performance statistics was collected. + optional uint32 cpu = 2; + + // Thread for which the performance statistics was collected. + optional uint32 thread = 3; + + // Counter value. + optional uint64 value = 4; + + // Number of times the counter was enabled. + optional uint64 enabled = 5; + + // Number of times the counter was running. + optional uint64 running = 6; + } + + // Next Tag: 3 + message StatRoundEvent { + // Type is an enumeration defining the type of stat round described at: + // $kernel/tools/perf/util/event.h. + // Example types are: + // PERF_STAT_ROUND_TYPE__INTERVAL + // PERF_STAT_ROUND_TYPE__FINAL + optional uint64 type = 1; + + // Time taken in nanoseconds by the type of stat round. + optional uint64 time = 2; + } + + // Next tag: 4 + message CgroupEvent { + // Cgroup ID (file handle). + optional uint64 id = 1; + + // Cgroup path. + optional string path = 2; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 3; + } + + // Time members to convert between TSC and perf time. + // Next tag: 8 + message TimeConvEvent { + // Time shift value. + optional uint64 time_shift = 1; + + // Time multiplier. + optional uint64 time_mult = 2; + + // Base time. + optional uint64 time_zero = 3; + + // Fields added in perf 5.10. + optional uint64 time_cycles = 4; + optional uint64 time_mask = 5; + optional bool cap_user_time_zero = 6; + optional bool cap_user_time_short = 7; + } + + message BpfEvent { + uint32 type = 1; + uint32 flags = 2; + uint32 id = 3; + bytes tag = 4; + SampleInfo sample_info = 6; + } + + message TextPokeEvent { + uint64 addr = 1; + uint32 old_len = 2; + uint32 new_len = 3; + bytes bytes = 4; + SampleInfo sample_info = 6; + } + + // Next tag: 4 + message EventHeader { + // Type of event. + optional uint32 type = 1; + optional uint32 misc = 2; + // Size of event. + optional uint32 size = 3; + } + + // Next tag: 27 + message PerfEvent { + optional EventHeader header = 1; + oneof event_type { + MMapEvent mmap_event = 2; + SampleEvent sample_event = 3; + CommEvent comm_event = 4; + // FORK and EXIT events are structurally identical. They only differ by + // the event type. But using two distinct fields allows us to + // differentiate between them without having to check the event type under + // |header|. + ForkEvent fork_event = 5; + ForkEvent exit_event = 9; + LostEvent lost_event = 6; + ThrottleEvent throttle_event = 7; + ReadEvent read_event = 8 [deprecated = true]; + AuxEvent aux_event = 11; + ItraceStartEvent itrace_start_event = 13; + LostSamplesEvent lost_samples_event = 14; + ContextSwitchEvent context_switch_event = 15; + NamespacesEvent namespaces_event = 16; + AuxtraceInfoEvent auxtrace_info_event = 18; + AuxtraceEvent auxtrace_event = 12; + AuxtraceErrorEvent auxtrace_error_event = 19; + IdIndexEvent id_index_event = 26; + ThreadMapEvent thread_map_event = 20; + TimeConvEvent time_conv_event = 17; + StatConfigEvent stat_config_event = 21; + StatEvent stat_event = 22; + StatRoundEvent stat_round_event = 23; + CgroupEvent cgroup_event = 24; + KsymbolEvent ksymbol_event = 25; + BpfEvent bpf_event = 27; + TextPokeEvent text_poke_event = 28; + } + // Time after boot in nanoseconds corresponding to the event. + optional uint64 timestamp = 10; + } + + // Next tag: 8 + message PerfEventStats { + // Total number of events read from perf data. + optional uint32 num_events_read = 1; + + // Total number of various types of events. + optional uint32 num_sample_events = 2; + optional uint32 num_mmap_events = 3; + optional uint32 num_fork_events = 4; + optional uint32 num_exit_events = 5; + + // Number of sample events that were successfully mapped by the address + // mapper, a quipper module that is used to obscure addresses and convert + // them to DSO name + offset. Sometimes it fails to process sample events. + // This field allows us to track the success rate of the address mapper. + optional uint32 num_sample_events_mapped = 6; + + // Whether address remapping was enabled. + optional bool did_remap = 7; + } + + // Next tag: 3 + message PerfUint32Metadata { + // Type of metadata, such as nrcpus. + optional uint32 type = 1; + + // uint32 data. + repeated uint32 data = 2; + } + + // Next tag: 3 + message PerfUint64Metadata { + // Type of metadata, such as totalmem. + optional uint32 type = 1; + + // uint64 data. + repeated uint64 data = 2; + } + + // Next tag: 3 + message PerfTracingMetadata { + // The trace event metadata. + optional bytes tracing_data = 1; + + // Trace event metedata Md5sum prefix. + // Deprecated as md5prefix of blob data is not useful. + optional uint64 tracing_data_md5_prefix = 2 [deprecated = true]; + } + + // Next tag: 8 + message PerfBuildID { + // Misc field in perf_event_header. + optional uint32 misc = 1; + + // Process ID. + optional uint32 pid = 2; + + // Build id. Should always contain kBuildIDArraySize bytes of data. + // perf_reader.h defines kBuildIDArraySize = 20. + optional bytes build_id_hash = 3; + + // Filename. + optional string filename = 4; + + // Filename Md5sum prefix. + optional uint64 filename_md5_prefix = 5; + + // Build id size. + optional uint32 size = 6; + + // If the build ID event is injected through a filename -> build ID map + // of quipper's PerfReader::InjectBuildIDs(), i.e., the Build ID event is + // not in the original perf.data. + optional bool is_injected = 7; + } + + // Next tag: 6 + message PerfCPUTopologyMetadata { + // Core siblings. + repeated string core_siblings = 1; + + // Core siblings' md5 prefixes. + repeated uint64 core_siblings_md5_prefix = 2; + + // Thread siblings. + repeated string thread_siblings = 3; + + // Thread siblings' md5 prefixes. + repeated uint64 thread_siblings_md5_prefix = 4; + // Core ID and Socket ID per CPU. + message CPU { + // Core ID + optional uint32 core_id = 1; + // Socket ID + optional uint32 socket_id = 2; + } + repeated CPU available_cpus = 5; + } + + // Next tag: 6 + message PerfNodeTopologyMetadata { + // Node id. + optional uint32 id = 1; + + // Total memory of the node. + optional uint64 total_memory = 2; + + // Free memory of the node. + optional uint64 free_memory = 3; + + // List of CPUs in the node. + optional string cpu_list = 4; + + // CPU list's md5 prefix. + optional uint64 cpu_list_md5_prefix = 5; + } + + // Next tag: 4 + message PerfPMUMappingsMetadata { + // Mapping type. + optional uint32 type = 1; + + // Mapping name. + optional string name = 2; + + // Mapping name's md5 prefix. + optional uint64 name_md5_prefix = 3; + } + + // Next tag: 5 + message PerfGroupDescMetadata { + // Group name. + optional string name = 1; + + // Group name's md5 prefix. + optional uint64 name_md5_prefix = 2; + + // Group's leader index. + optional uint32 leader_idx = 3; + + // Number of members in the group. + optional uint32 num_members = 4; + } + + // Next tag: 5 + message PerfHybridTopologyMetadata { + // Name of the PMU device, e.g. software, cpu, cs_etm, etc. + optional string pmu_name = 1; + optional uint64 pmu_name_md5_prefix = 2; + + // A range of cpu numbers returned in various /sys/devices locations. + // e.g. "0-3", "0,3". + optional string cpus = 3; + // Same content as above, but as a list of numbers for ease of use. + repeated uint32 cpu_list = 4 [packed = true]; + } + + repeated PerfFileAttr file_attrs = 1; + repeated PerfEvent events = 2; + + repeated PerfEventType event_types = 10; + + // Time when quipper generated this perf data / protobuf, given as seconds + // since the epoch. + optional uint64 timestamp_sec = 3; + + // Records some stats about the serialized perf events. + optional PerfEventStats stats = 4; + + // Bit mask used to determine what metadata has been included. + // At the moment, only the first number is actually used. + // See adds_features in perf_reader.cc + repeated uint64 metadata_mask = 5; + + optional PerfTracingMetadata tracing_data = 14; + + repeated PerfBuildID build_ids = 7; + + repeated PerfUint32Metadata uint32_metadata = 8; + + repeated PerfUint64Metadata uint64_metadata = 9; + + optional PerfCPUTopologyMetadata cpu_topology = 11; + + repeated PerfNodeTopologyMetadata numa_topology = 12; + + repeated PerfPMUMappingsMetadata pmu_mappings = 15; + + repeated PerfGroupDescMetadata group_desc = 16; + + repeated PerfHybridTopologyMetadata hybrid_topology = 17; + + // Next tag: 9 + message StringMetadata { + // Next tag: 3 + message StringAndMd5sumPrefix { + // The string value. + optional string value = 1; + + // The string value's md5sum prefix. + optional uint64 value_md5_prefix = 2; + } + + // Name of the machine, e.g. "localhost". + optional StringAndMd5sumPrefix hostname = 1; + + // Kernel version, e.g. "3.4.0". + optional StringAndMd5sumPrefix kernel_version = 2; + + // Perf version, e.g. "3.4.2642.g0aa604". + optional StringAndMd5sumPrefix perf_version = 3; + + // CPU architecture family, e.g. "x86_64". + optional StringAndMd5sumPrefix architecture = 4; + + // CPU description, e.g. "Intel(R) Celeron(R) CPU 867 @ 1.30GHz". + optional StringAndMd5sumPrefix cpu_description = 5; + + // CPU ID string, with the format: "$VENDOR,$FAMILY,$MODEL,$STEP" + optional StringAndMd5sumPrefix cpu_id = 6; + + // Command line used to run perf to collect this profile. + // This is split into string tokens to reflect the way it is stored in the + // raw perf data. e.g. "perf record -a -- sleep 2" become stored as: + // { "perf", "record", "-a", "--", "sleep", "2" } + repeated StringAndMd5sumPrefix perf_command_line_token = 7; + + // The command line stored as a single string. + optional StringAndMd5sumPrefix perf_command_line_whole = 8; + } + + optional StringMetadata string_metadata = 13; +} diff --git a/psh.proto b/proto/psh.proto similarity index 85% rename from psh.proto rename to proto/psh.proto index dbf776f..4e3a591 100644 --- a/psh.proto +++ b/proto/psh.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package psh.proto.instance; +import "perf/perf_data.proto"; + service PshService { rpc Heartbeat(HeartbeatReq) returns (Unit); rpc SendHostInfo(SendHostInfoReq) returns (Unit); @@ -53,15 +55,22 @@ message Task { message ExportDataReq { string task_id = 1; + + message Data { + oneof data_type { + FileData file = 1; + LineProtocolData line_protocol = 2; + PerfDataProto perf_data = 3; + } + } + repeated Data data = 2; } -message Data { - DataType ty = 1; - bytes bytes = 2; +message FileData { + bytes bytes = 1; } -enum DataType { - FILE = 0; - LINE_PROTOCOL = 1; +message LineProtocolData { + bytes bytes = 1; } diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 182d3ef..4c816ff 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -4,8 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -tonic.workspace = true +perf-event-rs.workspace = true prost.workspace = true +serde = { workspace = true, features = ["derive"] } +tonic.workspace = true [build-dependencies] tonic-build.workspace = true diff --git a/rust/build.rs b/rust/build.rs index 8c1c927..a900d3e 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -1,3 +1,6 @@ fn main() { - tonic_build::compile_protos("psh.proto").unwrap(); + tonic_build::configure() + .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") + .compile_protos(&["../proto/psh.proto"], &["../proto"]) + .unwrap(); } diff --git a/rust/psh.proto b/rust/psh.proto deleted file mode 120000 index 565cc05..0000000 --- a/rust/psh.proto +++ /dev/null @@ -1 +0,0 @@ -../psh.proto \ No newline at end of file diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 3db7097..a7fdc9d 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,3 +1,12 @@ +use perf_event_rs::sampling::record::{RecordBody, SampleId}; + +use self::perf_data_proto::{ + perf_event::EventType, AuxEvent, BpfEvent, CgroupEvent, CommEvent, ContextSwitchEvent, + ForkEvent, ItraceStartEvent, KsymbolEvent, LostEvent, LostSamplesEvent, MMapEvent, + NamespacesEvent, PerfEventAttr, ReadEvent, SampleEvent, SampleInfo, TextPokeEvent, + ThrottleEvent, WeightStruct, +}; + tonic::include_proto!("psh.proto.instance"); impl From for Ipv6Addr { @@ -19,6 +28,410 @@ impl From for std::net::Ipv6Addr { } } +impl From for SampleInfo { + fn from(value: SampleId) -> Self { + Self { + pid: value.pid, + tid: value.tid, + sample_time_ns: value.time, + id: value.id_1, + cpu: value.cpu, + stream_id: value.stream_id, + } + } +} + +impl From for MMapEvent { + fn from(body: perf_event_rs::sampling::record::mmap::Body) -> Self { + MMapEvent { + pid: body.pid.into(), + tid: body.tid.into(), + start: body.addr.into(), + len: body.len.into(), + pgoff: body.pgoff.into(), + maj: None, + min: None, + ino: None, + ino_generation: None, + build_id: None, + prot: None, + flags: None, + filename: body.filename.into_string().ok(), + filename_md5_prefix: None, + root_path: None, + root_path_md5_prefix: None, + sample_info: None, + } + } +} + +impl From for MMapEvent { + fn from(body: perf_event_rs::sampling::record::mmap2::Body) -> Self { + let (maj, min, ino, ino_generation, build_id) = match body.anon_enum { + perf_event_rs::sampling::record::mmap2::AnonEnum::Normal { + maj, + min, + ino, + ino_generation, + } => (Some(maj), Some(min), Some(ino), Some(ino_generation), None), + perf_event_rs::sampling::record::mmap2::AnonEnum::BuildId(items) => { + let build_id = String::from_utf8(items).ok(); + (None, None, None, None, build_id) + } + }; + MMapEvent { + pid: body.pid.into(), + tid: body.tid.into(), + start: body.addr.into(), + len: body.len.into(), + pgoff: body.pgoff.into(), + maj, + min, + ino, + ino_generation, + build_id, + prot: body.prot.into(), + flags: body.flags.into(), + filename: body.filename.into_string().ok(), + filename_md5_prefix: None, + root_path: None, + root_path_md5_prefix: None, + sample_info: body.sample_id.map(Into::into), + } + } +} + +impl From for LostEvent { + fn from(body: perf_event_rs::sampling::record::lost::Body) -> Self { + LostEvent { + id: body.id.into(), + lost: body.lost.into(), + sample_info: body.sample_id.map(Into::into), + } + } +} + +impl From for CommEvent { + fn from(body: perf_event_rs::sampling::record::comm::Body) -> Self { + CommEvent { + pid: body.pid.into(), + tid: body.tid.into(), + comm: body.comm.into_string().ok(), + comm_md5_prefix: None, + sample_info: body.sample_id.map(Into::into), + } + } +} + +impl From for ForkEvent { + fn from(body: perf_event_rs::sampling::record::exit::Body) -> Self { + ForkEvent { + pid: body.pid.into(), + ppid: body.ppid.into(), + tid: body.tid.into(), + ptid: body.ptid.into(), + fork_time_ns: None, + sample_info: body.sample_id.map(Into::into), + } + } +} + +impl From for ThrottleEvent { + fn from(body: perf_event_rs::sampling::record::throttle::Body) -> Self { + ThrottleEvent { + time_ns: body.time.into(), + id: body.id.into(), + stream_id: body.stream_id.into(), + sample_info: body.sample_id.map(Into::into), + } + } +} + +impl From for ForkEvent { + fn from(body: perf_event_rs::sampling::record::fork::Body) -> Self { + Self { + pid: body.pid.into(), + ppid: body.ppid.into(), + tid: body.tid.into(), + ptid: body.ptid.into(), + fork_time_ns: body.time.into(), + sample_info: body.sample_id.map(Into::into), + } + } +} + +impl From for ReadEvent { + fn from(body: perf_event_rs::sampling::record::read::Body) -> Self { + Self { + pid: body.pid.into(), + tid: body.tid.into(), + value: None, + time_enabled: body.values.time_enabled.into(), + time_running: body.values.time_running.into(), + id: None, + sample_info: body.sample_id.map(Into::into), + } + } +} +impl From for SampleEvent { + fn from(body: perf_event_rs::sampling::record::sample::Body) -> Self { + let (weight, weight_struct) = match body.weight { + Some(weight) => match weight { + perf_event_rs::sampling::record::sample::Weight::Full(f) => (Some(f), None), + perf_event_rs::sampling::record::sample::Weight::Vars { + var1_dw, + var2_w, + var3_w, + } => ( + None, + Some(WeightStruct { + var1_dw: var1_dw.into(), + var2_w: (var2_w as u32).into(), + var3_w: (var3_w as u32).into(), + }), + ), + }, + None => (None, None), + }; + Self { + ip: body.ip, + pid: body.pid, + tid: body.tid, + sample_time_ns: body.time, + addr: body.addr, + id: body.id, + stream_id: body.stream_id, + period: body.period, + cpu: body.cpu, + raw_size: body.data_raw.as_ref().map(|v| v.len() as _), + raw: body.data_raw, + read_info: None, + callchain: body.ips.unwrap_or_default(), + branch_stack: vec![], + weight, + weight_struct, + data_src: None, + transaction: None, + physical_addr: body.phys_addr, + cgroup: body.cgroup, + data_page_size: body.data_page_size, + code_page_size: body.code_page_size, + no_hw_idx: None, + branch_stack_hw_idx: None, + } + } +} + +impl From for AuxEvent { + fn from(body: perf_event_rs::sampling::record::aux::Body) -> Self { + Self { + aux_offset: body.aux_offset.into(), + aux_size: body.aux_size.into(), + is_truncated: None, + is_overwrite: None, + is_partial: None, + sample_info: body.sample_id.map(Into::into), + } + } +} +impl From for ItraceStartEvent { + fn from(body: perf_event_rs::sampling::record::intrace_start::Body) -> Self { + Self { + pid: body.pid.into(), + tid: body.tid.into(), + sample_info: None, + } + } +} +impl From for LostSamplesEvent { + fn from(body: perf_event_rs::sampling::record::lost_samples::Body) -> Self { + Self { + num_lost: body.lost.into(), + sample_info: body.sample_id.map(Into::into), + } + } +} +impl From for ContextSwitchEvent { + fn from(body: perf_event_rs::sampling::record::switch::Body) -> Self { + Self { + is_out: None, + next_prev_pid: None, + next_prev_tid: None, + sample_info: body.sample_id.map(Into::into), + } + } +} +impl From for ContextSwitchEvent { + fn from(body: perf_event_rs::sampling::record::switch_cpu_wide::Body) -> Self { + Self { + is_out: None, + next_prev_pid: body.next_prev_pid.into(), + next_prev_tid: body.next_prev_tid.into(), + sample_info: body.sample_id.map(Into::into), + } + } +} +impl From for NamespacesEvent { + fn from(body: perf_event_rs::sampling::record::namespaces::Body) -> Self { + Self { + pid: body.pid.into(), + tid: body.tid.into(), + link_info: vec![], + sample_info: body.sample_id.map(Into::into), + } + } +} +impl From for KsymbolEvent { + fn from(body: perf_event_rs::sampling::record::ksymbol::Body) -> Self { + Self { + addr: body.addr.into(), + len: body.len.into(), + ksym_type: (body.ksym_type as u32).into(), + flags: (body.flags as u32).into(), + name: body.name.into_string().ok(), + sample_info: body.sample_id.map(Into::into), + } + } +} +impl From for BpfEvent { + fn from(body: perf_event_rs::sampling::record::bpf_event::Body) -> Self { + Self { + r#type: body.r#type as _, + flags: body.flags as _, + id: body.id, + tag: body.tag.to_vec(), + sample_info: body.sample_id.map(Into::into), + } + } +} +impl From for CgroupEvent { + fn from(value: perf_event_rs::sampling::record::cgroup::Body) -> Self { + Self { + id: value.id.into(), + path: value.path.into_string().ok(), + sample_info: value.sample_id.map(Into::into), + } + } +} +impl From for TextPokeEvent { + fn from(value: perf_event_rs::sampling::record::text_poke::Body) -> Self { + Self { + addr: value.addr, + old_len: value.old_len as _, + new_len: value.new_len as _, + bytes: value.bytes, + sample_info: value.sample_id.map(Into::into), + } + } +} + +impl From for EventType { + fn from(value: RecordBody) -> Self { + match value { + RecordBody::Mmap(body) => Self::MmapEvent((*body).into()), + RecordBody::Lost(body) => Self::LostEvent((*body).into()), + RecordBody::Comm(body) => Self::CommEvent((*body).into()), + RecordBody::Exit(body) => Self::ExitEvent((*body).into()), + RecordBody::Throttle(body) => Self::ThrottleEvent((*body).into()), + RecordBody::Unthrottle(body) => Self::ThrottleEvent((*body).into()), + RecordBody::Fork(body) => Self::ForkEvent((*body).into()), + RecordBody::Read(body) => Self::ReadEvent((*body).into()), + RecordBody::Sample(body) => Self::SampleEvent((*body).into()), + RecordBody::Mmap2(body) => Self::MmapEvent((*body).into()), + RecordBody::Aux(body) => Self::AuxEvent((*body).into()), + RecordBody::ItraceStart(body) => Self::ItraceStartEvent((*body).into()), + RecordBody::LostSamples(body) => Self::LostSamplesEvent((*body).into()), + RecordBody::Switch(body) => Self::ContextSwitchEvent((*body).into()), + RecordBody::SwitchCpuWide(body) => Self::ContextSwitchEvent((*body).into()), + RecordBody::Namespaces(body) => Self::NamespacesEvent((*body).into()), + RecordBody::Ksymbol(body) => Self::KsymbolEvent((*body).into()), + RecordBody::BpfEvent(body) => Self::BpfEvent((*body).into()), + RecordBody::Cgroup(body) => Self::CgroupEvent((*body).into()), + RecordBody::TextPoke(body) => Self::TextPokeEvent((*body).into()), + RecordBody::AuxOutputHwId(_body) => todo!(), + } + } +} + +impl From for PerfEventAttr { + fn from(attr: perf_event_rs::PerfEventAttr) -> Self { + attr.0.into() + } +} + +impl From for PerfEventAttr { + fn from(attr: perf_event_rs::RawPerfEventAttr) -> Self { + let watermark = attr.watermark() != 0; + + let (wakeup_watermark, wakeup_events) = if watermark { + ( + unsafe { attr.__bindgen_anon_2.wakeup_watermark.into() }, + None, + ) + } else { + (None, unsafe { attr.__bindgen_anon_2.wakeup_events.into() }) + }; + + let freq = attr.freq() != 0; + + let (sample_period, sample_freq) = if freq { + (unsafe { attr.__bindgen_anon_1.sample_freq.into() }, None) + } else { + (None, unsafe { attr.__bindgen_anon_1.sample_period.into() }) + }; + + PerfEventAttr { + r#type: attr.type_.into(), + size: attr.size.into(), + config: attr.config.into(), + sample_period, + sample_freq, + sample_type: attr.sample_type.into(), + read_format: attr.read_format.into(), + disabled: Some(attr.disabled() != 0), + inherit: Some(attr.inherit() != 0), + pinned: Some(attr.pinned() != 0), + exclusive: Some(attr.exclusive() != 0), + exclude_user: Some(attr.exclude_user() != 0), + exclude_kernel: Some(attr.exclude_kernel() != 0), + exclude_hv: Some(attr.exclude_hv() != 0), + exclude_idle: Some(attr.exclude_idle() != 0), + mmap: Some(attr.mmap() != 0), + comm: Some(attr.comm() != 0), + freq: Some(freq), + inherit_stat: Some(attr.inherit_stat() != 0), + enable_on_exec: Some(attr.enable_on_exec() != 0), + task: Some(attr.task() != 0), + watermark: Some(watermark), + precise_ip: Some(attr.precise_ip() as _), + mmap_data: Some(attr.mmap_data() != 0), + sample_id_all: Some(attr.sample_id_all() != 0), + exclude_host: Some(attr.exclude_host() != 0), + exclude_guest: Some(attr.exclude_guest() != 0), + exclude_callchain_kernel: Some(attr.exclude_callchain_kernel() != 0), + exclude_callchain_user: Some(attr.exclude_callchain_user() != 0), + mmap2: Some(attr.mmap2() != 0), + comm_exec: Some(attr.comm_exec() != 0), + use_clockid: Some(attr.use_clockid() != 0), + context_switch: Some(attr.context_switch() != 0), + write_backward: Some(attr.write_backward() != 0), + namespaces: Some(attr.namespaces() != 0), + cgroup: Some(attr.cgroup() != 0), + ksymbol: Some(attr.ksymbol() != 0), + wakeup_events, + wakeup_watermark, + bp_type: attr.bp_type.into(), + bp_addr: unsafe { attr.__bindgen_anon_3.bp_addr.into() }, + config1: attr.config.into(), + bp_len: unsafe { attr.__bindgen_anon_4.bp_len.into() }, + config2: attr.config3.into(), + branch_sample_type: attr.branch_sample_type.into(), + sample_regs_user: attr.sample_regs_user.into(), + sample_stack_user: attr.sample_stack_user.into(), + } + } +} + #[test] fn test_ipv6_into_pb_repr() { use std::net::Ipv6Addr as StdIpv6Addr;