diff --git a/Cargo.lock b/Cargo.lock index f08ed2d744..b0c97d64e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +dependencies = [ + "getrandom 0.2.9", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.5" @@ -132,6 +143,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "asn1-rs" version = "0.5.2" @@ -246,6 +263,17 @@ dependencies = [ "critical-section", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -334,7 +362,7 @@ dependencies = [ "http", "percent-encoding", "tracing", - "uuid", + "uuid 1.3.3", ] [[package]] @@ -708,7 +736,7 @@ dependencies = [ "log", "paste", "pin-project", - "quick-xml", + "quick-xml 0.30.0", "rand 0.8.5", "reqwest", "rustc_version", @@ -716,7 +744,7 @@ dependencies = [ "serde_json", "time", "url", - "uuid", + "uuid 1.3.3", ] [[package]] @@ -737,7 +765,7 @@ dependencies = [ "time", "tz-rs", "url", - "uuid", + "uuid 1.3.3", ] [[package]] @@ -759,7 +787,7 @@ dependencies = [ "sha2", "time", "url", - "uuid", + "uuid 1.3.3", ] [[package]] @@ -779,7 +807,7 @@ dependencies = [ "serde_json", "time", "url", - "uuid", + "uuid 1.3.3", ] [[package]] @@ -896,6 +924,12 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + [[package]] name = "byteorder" version = "1.4.3" @@ -1255,6 +1289,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "cpp_demangle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.2.9" @@ -1456,6 +1499,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +[[package]] +name = "debugid" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ee87af31d84ef885378aebca32be3d682b0e0dc119d5b4860a2c5bb5046730" +dependencies = [ + "uuid 0.8.2", +] + [[package]] name = "debugid" version = "0.8.0" @@ -1463,7 +1515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "serde", - "uuid", + "uuid 1.3.3", ] [[package]] @@ -1648,6 +1700,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1937,7 +2001,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash", + "ahash 0.8.5", ] [[package]] @@ -1973,6 +2037,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.3" @@ -2227,6 +2300,24 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" +[[package]] +name = "inferno" +version = "0.10.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3886428c6400486522cf44b8626e7b94ad794c14390290f2a274dcf728a58f" +dependencies = [ + "ahash 0.7.7", + "atty", + "indexmap", + "itoa", + "lazy_static", + "log", + "num-format", + "quick-xml 0.22.0", + "rgb", + "str_stack", +] + [[package]] name = "inotify" version = "0.9.6" @@ -2278,7 +2369,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "libc", "windows-sys 0.48.0", ] @@ -2295,7 +2386,7 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "io-lifetimes", "rustix 0.37.25", "windows-sys 0.48.0", @@ -2466,6 +2557,24 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.7.1" @@ -2571,6 +2680,19 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + [[package]] name = "nix" version = "0.25.1" @@ -2645,6 +2767,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -2670,7 +2802,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "libc", ] @@ -2957,6 +3089,7 @@ dependencies = [ "postgres_backend", "postgres_connection", "postgres_ffi", + "pprof", "pq_proto", "rand 0.8.5", "regex", @@ -3328,6 +3461,25 @@ dependencies = [ "workspace_hack", ] +[[package]] +name = "pprof" +version = "0.6.1" +source = "git+https://github.com/neondatabase/pprof-rs.git?branch=wallclock-profiling#4e011a87d22fb4d21d15cc38bce81ff1c75e4bc9" +dependencies = [ + "backtrace", + "cfg-if", + "findshlibs", + "inferno", + "lazy_static", + "libc", + "log", + "nix 0.23.2", + "parking_lot 0.11.2", + "symbolic-demangle", + "tempfile", + "thiserror", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3535,12 +3687,21 @@ dependencies = [ "tracing-utils", "url", "utils", - "uuid", + "uuid 1.3.3", "webpki-roots 0.25.2", "workspace_hack", "x509-parser", ] +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.30.0" @@ -3872,6 +4033,15 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.16.20" @@ -4310,7 +4480,7 @@ version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99dc599bd6646884fc403d593cdcb9816dd67c50cff3271c01ff123617908dcd" dependencies = [ - "debugid", + "debugid 0.8.0", "getrandom 0.2.9", "hex", "serde", @@ -4318,7 +4488,7 @@ dependencies = [ "thiserror", "time", "url", - "uuid", + "uuid 1.3.3", ] [[package]] @@ -4607,6 +4777,12 @@ dependencies = [ "workspace_hack", ] +[[package]] +name = "str_stack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" + [[package]] name = "stringprep" version = "0.1.2" @@ -4654,6 +4830,29 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2" +[[package]] +name = "symbolic-common" +version = "8.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f551f902d5642e58039aee6a9021a61037926af96e071816361644983966f540" +dependencies = [ + "debugid 0.7.3", + "memmap2", + "stable_deref_trait", + "uuid 0.8.2", +] + +[[package]] +name = "symbolic-demangle" +version = "8.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564ca7b4e6eb14105aa8bbbce26e080f6b5d9c4373e67167ab31f7b86443750" +dependencies = [ + "cpp_demangle", + "rustc-demangle", + "symbolic-common", +] + [[package]] name = "syn" version = "1.0.109" @@ -5507,10 +5706,16 @@ dependencies = [ "tracing-error", "tracing-subscriber", "url", - "uuid", + "uuid 1.3.3", "workspace_hack", ] +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + [[package]] name = "uuid" version = "1.3.3" @@ -6030,7 +6235,7 @@ dependencies = [ "tracing-core", "tungstenite", "url", - "uuid", + "uuid 1.3.3", ] [[package]] diff --git a/pageserver/Cargo.toml b/pageserver/Cargo.toml index 3eb01003df..ab6783f918 100644 --- a/pageserver/Cargo.toml +++ b/pageserver/Cargo.toml @@ -9,6 +9,7 @@ default = [] # Enables test-only APIs, incuding failpoints. In particular, enables the `fail_point!` macro, # which adds some runtime cost to run tests on outage conditions testing = ["fail/failpoints"] +profiling = ["pprof"] [dependencies] anyhow.workspace = true @@ -82,6 +83,7 @@ enum-map.workspace = true enumset.workspace = true strum.workspace = true strum_macros.workspace = true +pprof = { git = "https://github.com/neondatabase/pprof-rs.git", branch = "wallclock-profiling", features = ["flamegraph"], optional = true } [dev-dependencies] criterion.workspace = true diff --git a/pageserver/src/bin/pageserver.rs b/pageserver/src/bin/pageserver.rs index 5b0c140d00..68865b1684 100644 --- a/pageserver/src/bin/pageserver.rs +++ b/pageserver/src/bin/pageserver.rs @@ -49,6 +49,8 @@ const PID_FILE_NAME: &str = "pageserver.pid"; const FEATURES: &[&str] = &[ #[cfg(feature = "testing")] "testing", + #[cfg(feature = "profiling")] + "profiling", ]; fn version() -> String { diff --git a/pageserver/src/lib.rs b/pageserver/src/lib.rs index 3f74694ef2..11d732ccbd 100644 --- a/pageserver/src/lib.rs +++ b/pageserver/src/lib.rs @@ -15,6 +15,7 @@ pub mod metrics; pub mod page_cache; pub mod page_service; pub mod pgdatadir_mapping; +pub mod profiling; pub mod repository; pub(crate) mod statvfs; pub mod task_mgr; diff --git a/pageserver/src/profiling.rs b/pageserver/src/profiling.rs new file mode 100644 index 0000000000..b6b3658546 --- /dev/null +++ b/pageserver/src/profiling.rs @@ -0,0 +1,87 @@ +//! +//! Support for profiling +//! +//! This relies on a modified version of the 'pprof-rs' crate. That's not very +//! nice, so to avoid a hard dependency on that, this is an optional feature. +//! + +/// The actual implementation is in the `profiling_impl` submodule. If the profiling +/// feature is not enabled, it's just a dummy implementation that panics if you +/// try to enabled profiling in the configuration. +pub use profiling_impl::*; + +#[cfg(feature = "profiling")] +mod profiling_impl { + use super::*; + use pprof; + use std::marker::PhantomData; + + /// Start profiling the current thread. Returns a guard object; + /// the profiling continues until the guard is dropped. + /// + /// Note: profiling is not re-entrant. If you call 'profpoint_start' while + /// profiling is already started, nothing happens, and the profiling will be + /// stopped when either guard object is dropped. + #[inline] + pub fn profpoint_start() -> Option { + pprof::start_profiling(); + Some(ProfilingGuard(PhantomData)) + } + + /// A hack to remove Send and Sync from the ProfilingGuard. Because the + /// profiling is attached to current thread. + //// + /// See comments in https://github.com/rust-lang/rust/issues/68318 + type PhantomUnsend = std::marker::PhantomData<*mut u8>; + + pub struct ProfilingGuard(PhantomUnsend); + + unsafe impl Send for ProfilingGuard {} + + impl Drop for ProfilingGuard { + fn drop(&mut self) { + pprof::stop_profiling(); + } + } + + /// Initialize the profiler. This must be called before any 'profpoint_start' calls. + pub fn init_profiler<'a>() -> Option> { + Some(pprof::ProfilerGuardBuilder::default().build().unwrap()) + } + + /// Exit the profiler. Writes the flamegraph to current workdir. + pub fn exit_profiler(profiler_guard: &Option) { + // Write out the flamegraph + if let Some(profiler_guard) = profiler_guard { + if let Ok(report) = profiler_guard.report().build() { + // this gets written under the workdir + let file = std::fs::File::create("flamegraph.svg").unwrap(); + let mut options = pprof::flamegraph::Options::default(); + options.image_width = Some(2500); + report.flamegraph_with_options(file, &mut options).unwrap(); + } + } + } +} + +/// Dummy implementation when compiling without profiling feature or for non-linux OSes. +#[cfg(not(feature = "profiling"))] +mod profiling_impl { + pub struct DummyProfilerGuard; + + impl Drop for DummyProfilerGuard { + fn drop(&mut self) { + // do nothing, this exists to calm Clippy down + } + } + + pub fn profpoint_start() -> Option { + None + } + + pub fn init_profiler() -> Option { + None + } + + pub fn exit_profiler(profiler_guard: &Option) {} +} diff --git a/pageserver/src/walingest.rs b/pageserver/src/walingest.rs index 6ab8fc4e3f..1f802af66d 100644 --- a/pageserver/src/walingest.rs +++ b/pageserver/src/walingest.rs @@ -2127,7 +2127,9 @@ mod tests { buffer }; - // TODO start a profiler too + // Start profiling + let profiler_guard = crate::profiling::init_profiler(); + let prof_guard = crate::profiling::profpoint_start(); let started_at = std::time::Instant::now(); // Initialize walingest @@ -2152,7 +2154,11 @@ mod tests { } } + drop(prof_guard); + let duration = started_at.elapsed(); println!("done in {:?}", duration); + + crate::profiling::exit_profiler(&profiler_guard); } }