diff --git a/Cargo.lock b/Cargo.lock index 57775988..a8fcdca1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + [[package]] name = "cc" version = "1.1.30" @@ -333,6 +339,15 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.2" @@ -478,11 +493,17 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" -version = "1.0.34" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -828,13 +849,20 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "nix" version = "0.26.4" @@ -934,6 +962,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.6.0", +] + [[package]] name = "plain" version = "0.2.3" @@ -1012,6 +1050,67 @@ dependencies = [ "winapi", ] +[[package]] +name = "prost" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" +dependencies = [ + "heck 0.5.0", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.79", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "prost-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +dependencies = [ + "prost", +] + +[[package]] +name = "protobuf-src" +version = "2.1.1+27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6217c3504da19b85a3a4b2e9a5183d635822d83507ba0986624b5c05b83bfc40" +dependencies = [ + "cmake", +] + [[package]] name = "py-spy" version = "0.4.1" @@ -1024,6 +1123,7 @@ dependencies = [ "cpp_demangle", "ctrlc", "env_logger", + "flate2", "goblin", "indicatif", "inferno", @@ -1035,6 +1135,10 @@ dependencies = [ "num-traits", "page_size", "proc-maps", + "prost", + "prost-build", + "prost-types", + "protobuf-src", "py-spy-testdata", "rand", "rand_distr", @@ -1313,6 +1417,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "smallvec" version = "1.13.2" diff --git a/Cargo.toml b/Cargo.toml index ba931d29..6e8d63c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [features] unwind = ["remoteprocess/unwind"] +pprof = ["prost-build", "protobuf-src", "prost", "prost-types", "flate2"] [package] name = "py-spy" @@ -14,6 +15,10 @@ license = "MIT" build="build.rs" edition="2021" +[build-dependencies] +prost-build = { version = "0.14", optional = true } +protobuf-src = { version = "2.0", optional = true } + [dependencies] anyhow = "1" clap = {version="3.2", features=["wrap_help", "cargo", "derive"]} @@ -42,6 +47,10 @@ rand = "0.8" rand_distr = "0.4" remoteprocess = "0.5.1" chrono = "0.4.26" +prost = { version = "0.14", optional = true } +prost-build = { version = "0.14", optional = true } +prost-types = { version = "0.14", optional = true } +flate2 = { version = "1.1.5", features = ["rust_backend"], optional = true } [dev-dependencies] py-spy-testdata = "0.1.0" diff --git a/build.rs b/build.rs index f7eeadac..f46b2a62 100644 --- a/build.rs +++ b/build.rs @@ -9,4 +9,15 @@ fn main() { } _ => {} } + + // Only compile protobuf if pprof feature is enabled + #[cfg(feature = "pprof")] + { + // Use bundled protoc from protobuf-src + std::env::set_var("PROTOC", protobuf_src::protoc()); + + // Add prost-build configuration + prost_build::compile_protos(&["src/pprof/profile.proto"], &["src/pprof/"]) + .expect("Failed to compile protos"); + } } diff --git a/src/config.rs b/src/config.rs index f861d3b1..258d9537 100644 --- a/src/config.rs +++ b/src/config.rs @@ -68,6 +68,10 @@ pub enum FileFormat { raw, speedscope, chrometrace, + #[cfg(feature = "pprof")] + pprof, + #[cfg(feature = "pprof")] + pprof_gzip, } impl FileFormat { @@ -369,6 +373,18 @@ impl Config { std::process::exit(1); } + // Check if pprof format was requested but pprof feature is not enabled + if !cfg!(feature = "pprof") { + if let Some(format_str) = matches.value_of("format") { + if format_str == "pprof" || format_str == "pprof_gzip" { + eprintln!( + "pprof output format is not supported. Please rebuild with --features pprof" + ); + std::process::exit(1); + } + } + } + match subcommand { "record" => { config.sampling_rate = matches.value_of_t("rate")?; diff --git a/src/main.rs b/src/main.rs index 6351512d..40be8431 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,8 @@ mod dump; mod flamegraph; #[cfg(feature = "unwind")] mod native_stack_trace; +#[cfg(feature = "pprof")] +mod pprof; mod python_bindings; mod python_data_access; mod python_interpreters; @@ -118,6 +120,17 @@ impl Recorder for chrometrace::Chrometrace { } } +#[cfg(feature = "pprof")] +impl Recorder for pprof::Pprof { + fn increment(&mut self, trace: &StackTrace) -> Result<(), Error> { + Ok(self.increment(trace)?) + } + + fn write(&self, w: &mut dyn Write) -> Result<(), Error> { + self.write(w) + } +} + pub struct RawFlamegraph(flamegraph::Flamegraph); impl Recorder for RawFlamegraph { @@ -142,6 +155,10 @@ fn record_samples(pid: remoteprocess::Pid, config: &Config) -> Result<(), Error> Some(FileFormat::chrometrace) => { Box::new(chrometrace::Chrometrace::new(config.show_line_numbers)) } + #[cfg(feature = "pprof")] + Some(FileFormat::pprof) => Box::new(pprof::Pprof::new(false)), + #[cfg(feature = "pprof")] + Some(FileFormat::pprof_gzip) => Box::new(pprof::Pprof::new(true)), None => return Err(format_err!("A file format is required to record samples")), }; @@ -153,6 +170,10 @@ fn record_samples(pid: remoteprocess::Pid, config: &Config) -> Result<(), Error> Some(FileFormat::speedscope) => "json", Some(FileFormat::raw) => "txt", Some(FileFormat::chrometrace) => "json", + #[cfg(feature = "pprof")] + Some(FileFormat::pprof) => "pb", + #[cfg(feature = "pprof")] + Some(FileFormat::pprof_gzip) => "pb.gz", None => return Err(format_err!("A file format is required to record samples")), }; let local_time = Local::now().to_rfc3339_opts(SecondsFormat::Secs, true); @@ -361,6 +382,14 @@ fn record_samples(pid: remoteprocess::Pid, config: &Config) -> Result<(), Error> ); println!("{lede}Visit chrome://tracing or https://ui.perfetto.dev/ to view"); } + #[cfg(feature = "pprof")] + FileFormat::pprof | FileFormat::pprof_gzip => { + println!( + "{lede}Wrote pprof profile to '{filename}'. Samples: {samples} Errors: {errors}" + ); + println!("{lede}Use 'pprof -http=:8080 {filename}' to view"); + println!("{lede}Or visit https://pprof.me/"); + } }; Ok(()) diff --git a/src/pprof.rs b/src/pprof.rs new file mode 100644 index 00000000..73d45e9f --- /dev/null +++ b/src/pprof.rs @@ -0,0 +1,204 @@ +pub mod profile { + include!(concat!(env!("OUT_DIR"), "/perftools.profiles.rs")); +} + +use std::io::Write; +use std::time::Instant; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; + +use anyhow::Error; +use flate2::write::GzEncoder; +use flate2::Compression; +use prost::Message; + +use crate::pprof::profile::Function; +use crate::pprof::profile::Label; +use crate::pprof::profile::Line; +use crate::pprof::profile::Location; +use crate::pprof::profile::Profile; +use crate::pprof::profile::Sample; +use crate::pprof::profile::ValueType; +use crate::stack_trace::Frame; +use crate::stack_trace::StackTrace; + +pub struct Pprof { + profile: Profile, + start_instant: Instant, + start_system: SystemTime, + // previous_samples_to_sample_idx: HashMap, // TODO(torshepherd) don't make every stacktrace unique, batch them to save space. + gzip_profile: bool, + command_line: String, + + current_function_id: u64, + current_location_id: u64, +} + +enum LabelValue<'a> { + Str(String), + Num { value: i64, unit: &'a str }, +} + +impl<'a> LabelValue<'a> { + pub fn unitless(value: i64) -> Self { + LabelValue::Num { value, unit: "" } + } +} + +impl Pprof { + fn add_string(&mut self, s: &str) -> i64 { + if let Some(index) = self.profile.string_table.iter().position(|x| x == s) { + return index as i64; + } + self.profile.string_table.push(s.to_string()); + (self.profile.string_table.len() - 1) as i64 + } + + fn add_label(&mut self, sample: &mut Sample, key: &str, value: LabelValue) { + let mut label = Label::default(); + label.key = self.add_string(key); + match value { + LabelValue::Str(str) => label.str = self.add_string(&str), + LabelValue::Num { value, unit } => { + label.num = value; + label.num_unit = self.add_string(unit) + } + } + sample.label.push(label); + } + + fn add_location(&mut self, frame: &Frame) -> u64 { + // TODO(torshepherd) add Function caching as well + + // Determine the function name to use. We rename and raw addresses as the shortened + // filename to make pprof more intuitive and helpful at a glance. Consistent filenames also + // help with difference-mode pprofs. + let function_name: &str = if frame.name == "" || frame.name.starts_with("0x") { + frame.short_filename.as_deref().unwrap_or(&frame.filename) + } else { + &frame.name + }; + + let function = Function { + id: { + self.current_function_id += 1; + self.current_function_id + }, + name: self.add_string(function_name), + system_name: self.add_string(""), + filename: self.add_string(&frame.filename), + start_line: 0, + }; + self.profile.function.push(function); + + let location = Location { + id: { + self.current_location_id += 1; + self.current_location_id + }, + mapping_id: 0, + address: 0, + line: vec![Line { + function_id: self.current_function_id, + line: frame.line as i64, + column: 0, + }], + is_folded: false, + }; + self.profile.location.push(location); + + self.current_location_id + } + + pub fn new(gzip_profile: bool) -> Pprof { + let command_line = std::env::args().collect::>().join(" "); + let mut pprof = Pprof { + profile: Profile::default(), + start_instant: Instant::now(), + start_system: SystemTime::now(), + // previous_samples_to_sample_idx: HashMap::default(), + gzip_profile, + command_line, + current_function_id: 1, + current_location_id: 1, + }; + + // First index should always be empty string + pprof.add_string(""); + + // Set the sample type + let r#type = pprof.add_string("py-spy periodic oncpu"); // TODO(torshepherd) is this a good name? + let unit = pprof.add_string("count"); + pprof.profile.doc_url = pprof.add_string("https://github.com/benfred/py-spy"); + pprof.profile.sample_type.push(ValueType { r#type, unit }); + pprof.profile.default_sample_type = r#type; + + pprof + } + + pub fn increment(&mut self, trace: &StackTrace) -> std::io::Result<()> { + // // First, look up if we already have this stack in the profile. + // if let Some(sample_idx) = self.previous_samples_to_sample_idx.get(trace) { + // self.profile.sample[sample_idx]?.value + // } + + let mut sample = Sample::default(); + sample.value.push(1); + self.add_label(&mut sample, "pid", LabelValue::unitless(trace.pid as i64)); + // self.add_label(sample, "thread_id", trace.thread_id); + // self.add_label(sample, "thread_name", trace.thread_name); + // self.add_label(sample, "os_thread_id", trace.os_thread_id); + // self.add_label(sample, "active", trace.active); + // self.add_label(sample, "owns_gil", trace.owns_gil); + // self.add_label(sample, "command_line", trace.process_info.command_line); + // self.add_label(sample, "parent_pid", trace.process_info.parent_pid); + + sample.location_id = trace.frames.iter().map(|f| self.add_location(&f)).collect(); + self.profile.sample.push(sample); + + Ok(()) + } + + pub fn write(&self, w: &mut dyn Write) -> Result<(), Error> { + let mut profile = self.profile.clone(); // TODO(torshepherd) lol this is dumb + + // Set timing information + profile.time_nanos = self + .start_system + .duration_since(UNIX_EPOCH) + .ok() + .and_then(|d| i64::try_from(d.as_nanos()).ok()) + .unwrap_or(0); + let dur = Instant::now().duration_since(self.start_instant); + profile.duration_nanos = i64::try_from(dur.as_nanos()).unwrap_or(0); + + // Add command line invocation as a comment + // TODO(torshepherd) this is also dumb, duplicating impl of add_string + if !self.command_line.is_empty() { + // Find the string index or add it to the string table + let command_line_idx = if let Some(index) = profile + .string_table + .iter() + .position(|x| x == &self.command_line) + { + index as i64 + } else { + profile.string_table.push(self.command_line.clone()); + (profile.string_table.len() - 1) as i64 + }; + profile.comment.push(command_line_idx); + } + + // Serialize the protobuf + let bytes = profile.encode_to_vec(); + if self.gzip_profile { + let mut encoder = GzEncoder::new(w, Compression::default()); + encoder.write_all(&bytes)?; + encoder.finish()?; + } else { + w.write_all(&bytes)?; + } + + Ok(()) + } +} diff --git a/src/pprof/README.md b/src/pprof/README.md new file mode 100644 index 00000000..706e61f1 --- /dev/null +++ b/src/pprof/README.md @@ -0,0 +1,3 @@ +## Pprof format + +[profile.proto](profile.proto) is copied from [pprof source tree](https://github.com/google/pprof/blob/main/proto/profile.proto). \ No newline at end of file diff --git a/src/pprof/profile.proto b/src/pprof/profile.proto new file mode 100644 index 00000000..35993d83 --- /dev/null +++ b/src/pprof/profile.proto @@ -0,0 +1,233 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. + +// Profile is a common stacktrace profile format. +// +// Measurements represented with this format should follow the +// following conventions: +// +// - Consumers should treat unset optional fields as if they had been +// set with their default value. +// +// - When possible, measurements should be stored in "unsampled" form +// that is most useful to humans. There should be enough +// information present to determine the original sampled values. +// +// - On-disk, the serialized proto must be gzip-compressed. +// +// - The profile is represented as a set of samples, where each sample +// references a sequence of locations, and where each location belongs +// to a mapping. +// - There is a N->1 relationship from sample.location_id entries to +// locations. For every sample.location_id entry there must be a +// unique Location with that id. +// - There is an optional N->1 relationship from locations to +// mappings. For every nonzero Location.mapping_id there must be a +// unique Mapping with that id. + +syntax = "proto3"; + +package perftools.profiles; + +option java_package = "com.google.perftools.profiles"; +option java_outer_classname = "ProfileProto"; + +message Profile { + // A description of the samples associated with each Sample.value. + // For a cpu profile this might be: + // [["cpu","nanoseconds"]] or [["wall","seconds"]] or [["syscall","count"]] + // For a heap profile, this might be: + // [["allocations","count"], ["space","bytes"]], + // If one of the values represents the number of events represented + // by the sample, by convention it should be at index 0 and use + // sample_type.unit == "count". + repeated ValueType sample_type = 1; + // The set of samples recorded in this profile. + repeated Sample sample = 2; + // Mapping from address ranges to the image/binary/library mapped + // into that address range. mapping[0] will be the main binary. + repeated Mapping mapping = 3; + // Locations referenced by samples. + repeated Location location = 4; + // Functions referenced by locations. + repeated Function function = 5; + // A common table for strings referenced by various messages. + // string_table[0] must always be "". + repeated string string_table = 6; + // frames with Function.function_name fully matching the following + // regexp will be dropped from the samples, along with their successors. + int64 drop_frames = 7; // Index into string table. + // frames with Function.function_name fully matching the following + // regexp will be kept, even if it matches drop_frames. + int64 keep_frames = 8; // Index into string table. + + // The following fields are informational, do not affect + // interpretation of results. + + // Time of collection (UTC) represented as nanoseconds past the epoch. + int64 time_nanos = 9; + // Duration of the profile, if a duration makes sense. + int64 duration_nanos = 10; + // The kind of events between sampled occurrences. + // e.g [ "cpu","cycles" ] or [ "heap","bytes" ] + ValueType period_type = 11; + // The number of events between sampled occurrences. + int64 period = 12; + // Free-form text associated with the profile. The text is displayed as is + // to the user by the tools that read profiles (e.g. by pprof). This field + // should not be used to store any machine-readable information, it is only + // for human-friendly content. The profile must stay functional if this field + // is cleaned. + repeated int64 comment = 13; // Indices into string table. + // Index into the string table of the type of the preferred sample + // value. If unset, clients should default to the last sample value. + int64 default_sample_type = 14; + // Documentation link for this profile type. The URL must be absolute, + // e.g., http://pprof.example.com/cpu-profile.html + // + // The URL may be missing if the profile was generated by code that did not + // supply a link. + int64 doc_url = 15; // Index into string table. +} + +// ValueType describes the semantics and measurement units of a value. +message ValueType { + int64 type = 1; // Index into string table. + int64 unit = 2; // Index into string table. +} + +// Each Sample records values encountered in some program +// context. The program context is typically a stack trace, perhaps +// augmented with auxiliary information like the thread-id, some +// indicator of a higher level request being handled etc. +message Sample { + // The ids recorded here correspond to a Profile.location.id. + // The leaf is at location_id[0]. + repeated uint64 location_id = 1; + // The type and unit of each value is defined by the corresponding + // entry in Profile.sample_type. All samples must have the same + // number of values, the same as the length of Profile.sample_type. + // When aggregating multiple samples into a single sample, the + // result has a list of values that is the element-wise sum of the + // lists of the originals. + repeated int64 value = 2; + // label includes additional context for this sample. It can include + // things like a thread id, allocation size, etc. + // + // NOTE: While possible, having multiple values for the same label key is + // strongly discouraged and should never be used. Most tools (e.g. pprof) do + // not have good (or any) support for multi-value labels. And an even more + // discouraged case is having a string label and a numeric label of the same + // name on a sample. Again, possible to express, but should not be used. + repeated Label label = 3; +} + +message Label { + // Index into string table. An annotation for a sample (e.g. + // "allocation_size") with an associated value. + // Keys with "pprof::" prefix are reserved for internal use by pprof. + int64 key = 1; + + // At most one of the following must be present + int64 str = 2; // Index into string table + int64 num = 3; + + // Should only be present when num is present. + // Specifies the units of num. + // Use arbitrary string (for example, "requests") as a custom count unit. + // If no unit is specified, consumer may apply heuristic to deduce the unit. + // Consumers may also interpret units like "bytes" and "kilobytes" as memory + // units and units like "seconds" and "nanoseconds" as time units, + // and apply appropriate unit conversions to these. + int64 num_unit = 4; // Index into string table +} + +message Mapping { + // Unique nonzero id for the mapping. + uint64 id = 1; + // Address at which the binary (or DLL) is loaded into memory. + uint64 memory_start = 2; + // The limit of the address range occupied by this mapping. + uint64 memory_limit = 3; + // Offset in the binary that corresponds to the first mapped address. + uint64 file_offset = 4; + // The object this entry is loaded from. This can be a filename on + // disk for the main binary and shared libraries, or virtual + // abstractions like "[vdso]". + int64 filename = 5; // Index into string table + // A string that uniquely identifies a particular program version + // with high probability. E.g., for binaries generated by GNU tools, + // it could be the contents of the .note.gnu.build-id field. + int64 build_id = 6; // Index into string table + + // The following fields indicate the resolution of symbolic info. + bool has_functions = 7; + bool has_filenames = 8; + bool has_line_numbers = 9; + bool has_inline_frames = 10; +} + +// Describes function and line table debug information. +message Location { + // Unique nonzero id for the location. A profile could use + // instruction addresses or any integer sequence as ids. + uint64 id = 1; + // The id of the corresponding profile.Mapping for this location. + // It can be unset if the mapping is unknown or not applicable for + // this profile type. + uint64 mapping_id = 2; + // The instruction address for this location, if available. It + // should be within [Mapping.memory_start...Mapping.memory_limit] + // for the corresponding mapping. A non-leaf address may be in the + // middle of a call instruction. It is up to display tools to find + // the beginning of the instruction if necessary. + uint64 address = 3; + // Multiple line indicates this location has inlined functions, + // where the last entry represents the caller into which the + // preceding entries were inlined. + // + // E.g., if memcpy() is inlined into printf: + // line[0].function_name == "memcpy" + // line[1].function_name == "printf" + repeated Line line = 4; + // Provides an indication that multiple symbols map to this location's + // address, for example due to identical code folding by the linker. In that + // case the line information above represents one of the multiple + // symbols. This field must be recomputed when the symbolization state of the + // profile changes. + bool is_folded = 5; +} + +message Line { + // The id of the corresponding profile.Function for this line. + uint64 function_id = 1; + // Line number in source code. + int64 line = 2; + // Column number in source code. + int64 column = 3; +} + +message Function { + // Unique nonzero id for the function. + uint64 id = 1; + // Name of the function, in human-readable form if available. + int64 name = 2; // Index into string table + // Name of the function, as identified by the system. + // For instance, it can be a C++ mangled name. + int64 system_name = 3; // Index into string table + // Source file containing the function. + int64 filename = 4; // Index into string table + // Line number in source file. + int64 start_line = 5; +} \ No newline at end of file