diff --git a/.circleci/config.yml b/.circleci/config.yml index 316d4b7f83..decf27aada 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -214,7 +214,7 @@ jobs: command: cargo test --workspace --benches --no-run - run: name: Build with object store + exporter support + HEAP profiling - command: cargo build --features="aws,gcp,azure,jaeger,otlp,heappy" + command: cargo build --features="aws,gcp,azure,jaeger,otlp,heappy,pprof" - cache_save # Lint protobufs. diff --git a/Cargo.toml b/Cargo.toml index 210e036cb4..1e9b34bfc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,7 @@ itertools = "0.10.1" parquet = "5.0" # used by arrow/datafusion anyway prettytable-rs = "0.8" -pprof = { version = "^0.5", default-features = false, features = ["flamegraph", "protobuf"] } +pprof = { version = "^0.5", default-features = false, features = ["flamegraph", "protobuf"], optional = true} prost = "0.8" # Forked to upgrade hyper and tokio routerify = { git = "https://github.com/influxdata/routerify", rev = "274e250" } @@ -153,4 +153,5 @@ aws = ["object_store/aws"] # Optional AWS / S3 object store support jaeger = ["trogging/jaeger"] # Enable optional jaeger tracing support otlp = ["trogging/otlp"] # Enable optional open telemetry collector # heappy is also an optional feature; Not on by default as it -# runtime overhead on all allocations (calls to malloc) \ No newline at end of file +# runtime overhead on all allocations (calls to malloc) +# pprof is also an optional feature for pprof support \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 18641b77c0..0a3022534d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN \ --mount=type=cache,id=influxdb_iox_git,sharing=locked,target=/usr/local/cargo/git \ --mount=type=cache,id=influxdb_iox_target,sharing=locked,target=/influxdb_iox/target \ du -cshx /usr/local/cargo/registry /usr/local/cargo/git /influxdb_iox/target && \ - cargo build --target-dir /influxdb_iox/target --release --features azure,gcp,aws,jaeger,otlp && \ + cargo build --target-dir /influxdb_iox/target --release --features azure,gcp,aws,jaeger,otlp,pprof && \ cp /influxdb_iox/target/release/influxdb_iox /root/influxdb_iox && \ du -cshx /usr/local/cargo/registry /usr/local/cargo/git /influxdb_iox/target diff --git a/src/influxdb_ioxd/http.rs b/src/influxdb_ioxd/http.rs index 5fd4cc2459..322b1824ab 100644 --- a/src/influxdb_ioxd/http.rs +++ b/src/influxdb_ioxd/http.rs @@ -13,6 +13,9 @@ #[cfg(feature = "heappy")] mod heappy; +#[cfg(feature = "pprof")] +mod pprof; + // Influx crates use super::planner::Planner; use data_types::{ @@ -32,21 +35,19 @@ use http::header::{CONTENT_ENCODING, CONTENT_TYPE}; use hyper::{http::HeaderValue, Body, Method, Request, Response, StatusCode}; use observability_deps::{ opentelemetry::KeyValue, - tracing::{self, debug, error, info}, + tracing::{self, debug, error}, }; use routerify::{prelude::*, Middleware, RequestInfo, Router, RouterError, RouterService}; use serde::Deserialize; use snafu::{OptionExt, ResultExt, Snafu}; use hyper::server::conn::AddrIncoming; -use pprof::protos::Message; use std::num::NonZeroI32; use std::{ fmt::Debug, str::{self, FromStr}, sync::Arc, }; -use tokio::time::Duration; use tokio_util::sync::CancellationToken; /// Constants used in API error codes. @@ -200,7 +201,9 @@ pub enum ApplicationError { Planning { source: super::planner::Error }, #[snafu(display("PProf error: {}", source))] - PProf { source: pprof::Error }, + PProf { + source: Box, + }, #[snafu(display("Protobuf error: {}", source))] Prost { source: prost::EncodeError }, @@ -223,8 +226,11 @@ pub enum ApplicationError { #[snafu(display("Internal server error"))] InternalServerError, - #[snafu(display("Heappy is not compiled"))] + #[snafu(display("heappy support is not compiled"))] HeappyIsNotCompiled, + + #[snafu(display("pprof support is not compiled"))] + PProfIsNotCompiled, } type Result = std::result::Result; @@ -266,6 +272,7 @@ impl ApplicationError { Self::DatabaseNotInitialized { .. } => self.bad_request(), Self::InternalServerError => self.internal_error(), Self::HeappyIsNotCompiled => self.internal_error(), + Self::PProfIsNotCompiled => self.internal_error(), } } @@ -786,22 +793,6 @@ async fn pprof_home( )))) } -async fn dump_rsprof(seconds: u64, frequency: i32) -> pprof::Result { - let guard = pprof::ProfilerGuard::new(frequency)?; - info!( - "start profiling {} seconds with frequency {} /s", - seconds, frequency - ); - - tokio::time::sleep(Duration::from_secs(seconds)).await; - - info!( - "done profiling {} seconds with frequency {} /s", - seconds, frequency - ); - guard.report().build() -} - #[derive(Debug, Deserialize)] struct PProfArgs { #[serde(default = "PProfArgs::default_seconds")] @@ -845,16 +836,19 @@ impl PProfAllocsArgs { } } +#[cfg(feature = "pprof")] #[tracing::instrument(level = "debug")] async fn pprof_profile( req: Request, ) -> Result, ApplicationError> { + use ::pprof::protos::Message; let query_string = req.uri().query().unwrap_or_default(); let query: PProfArgs = serde_urlencoded::from_str(query_string).context(InvalidQueryString { query_string })?; - let report = dump_rsprof(query.seconds, query.frequency.get()) + let report = self::pprof::dump_rsprof(query.seconds, query.frequency.get()) .await + .map_err(|e| Box::new(e) as _) .context(PProf)?; let mut body: Vec = Vec::new(); @@ -868,18 +862,32 @@ async fn pprof_profile( .flat_map(|i| i.to_str().unwrap_or_default().split(',')) .any(|i| i == "text/html" || i == "image/svg+xml") { - report.flamegraph(&mut body).context(PProf)?; + report + .flamegraph(&mut body) + .map_err(|e| Box::new(e) as _) + .context(PProf)?; if body.is_empty() { return EmptyFlamegraph.fail(); } } else { - let profile = report.pprof().context(PProf)?; + let profile = report + .pprof() + .map_err(|e| Box::new(e) as _) + .context(PProf)?; profile.encode(&mut body).context(Prost)?; } Ok(Response::new(Body::from(body))) } +#[cfg(not(feature = "pprof"))] +#[tracing::instrument(level = "debug")] +async fn pprof_profile( + req: Request, +) -> Result, ApplicationError> { + PProfIsNotCompiled {}.fail() +} + // If heappy support is enabled, call it #[cfg(feature = "heappy")] #[tracing::instrument(level = "debug")] diff --git a/src/influxdb_ioxd/http/pprof.rs b/src/influxdb_ioxd/http/pprof.rs new file mode 100644 index 0000000000..c49992f596 --- /dev/null +++ b/src/influxdb_ioxd/http/pprof.rs @@ -0,0 +1,18 @@ +use observability_deps::tracing::info; +use tokio::time::Duration; + +pub async fn dump_rsprof(seconds: u64, frequency: i32) -> pprof::Result { + let guard = pprof::ProfilerGuard::new(frequency)?; + info!( + "start profiling {} seconds with frequency {} /s", + seconds, frequency + ); + + tokio::time::sleep(Duration::from_secs(seconds)).await; + + info!( + "done profiling {} seconds with frequency {} /s", + seconds, frequency + ); + guard.report().build() +}