Merge pull request #1518 from influxdata/pprofrs

feat: Add /debug/pprof/profile
pull/24376/head
kodiakhq[bot] 2021-05-19 11:15:54 +00:00 committed by GitHub
commit 08eb4ad775
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 270 additions and 7 deletions

157
Cargo.lock generated
View File

@ -31,6 +31,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "796540673305a66d127804eef19ad696f1f204b8c1025aaca4958c17eab32877"
dependencies = [
"getrandom 0.2.2",
"once_cell",
"version_check",
]
[[package]]
name = "ahash"
version = "0.7.2"
@ -96,6 +107,15 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
dependencies = [
"nodrop",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
@ -159,7 +179,7 @@ dependencies = [
name = "arrow_util"
version = "0.1.0"
dependencies = [
"ahash",
"ahash 0.7.2",
"arrow 0.1.0",
"futures",
"hashbrown 0.11.2",
@ -372,7 +392,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
dependencies = [
"arrayref",
"arrayvec",
"arrayvec 0.5.2",
"constant_time_eq",
]
@ -424,6 +444,12 @@ version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]]
name = "bytemuck"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58"
[[package]]
name = "byteorder"
version = "1.4.3"
@ -804,7 +830,7 @@ name = "datafusion"
version = "4.0.0-SNAPSHOT"
source = "git+https://github.com/apache/arrow-datafusion.git?rev=9cf32cf2cda8472b87130142c4eee1126d4d9cbe#9cf32cf2cda8472b87130142c4eee1126d4d9cbe"
dependencies = [
"ahash",
"ahash 0.7.2",
"arrow 4.0.0-SNAPSHOT",
"async-trait",
"chrono",
@ -830,6 +856,15 @@ dependencies = [
"futures",
]
[[package]]
name = "debugid"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91cf5a8c2f2097e2a32627123508635d47ce10563d999ec1a95addf08b502ba"
dependencies = [
"uuid",
]
[[package]]
name = "difference"
version = "2.0.0"
@ -1319,7 +1354,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
"ahash 0.7.2",
]
[[package]]
@ -1477,6 +1512,24 @@ dependencies = [
"hashbrown 0.9.1",
]
[[package]]
name = "inferno"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37fb405dbcc505ed20838c4f8dad7b901094de90add237755df54bd5dcda2fdd"
dependencies = [
"ahash 0.6.3",
"atty",
"indexmap",
"itoa",
"lazy_static",
"log",
"num-format",
"quick-xml",
"rgb",
"str_stack",
]
[[package]]
name = "influxdb2_client"
version = "0.1.0"
@ -1541,6 +1594,7 @@ dependencies = [
"panic_logging",
"parking_lot",
"parquet 0.1.0",
"pprof",
"predicates",
"prettytable-rs",
"prost",
@ -1738,7 +1792,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"bitflags",
"cfg-if",
"ryu",
@ -1859,6 +1913,16 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "memmap"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "memoffset"
version = "0.6.3"
@ -2024,6 +2088,12 @@ dependencies = [
"libc",
]
[[package]]
name = "nodrop"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "nom"
version = "5.1.2"
@ -2095,6 +2165,16 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-format"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465"
dependencies = [
"arrayvec 0.4.12",
"itoa",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@ -2625,6 +2705,27 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "pprof"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cb1ca0d59aa771d9bc7e268739d7aef6ca7e9e8d3b78d92f266cd663fd0c1"
dependencies = [
"backtrace",
"inferno",
"lazy_static",
"libc",
"log",
"nix",
"parking_lot",
"prost",
"prost-build",
"prost-derive",
"symbolic-demangle",
"tempfile",
"thiserror",
]
[[package]]
name = "ppv-lite86"
version = "0.2.10"
@ -2824,6 +2925,15 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26aab6b48e2590e4a64d1ed808749ba06257882b461d01ca71baeb747074a6dd"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.9"
@ -3095,6 +3205,15 @@ dependencies = [
"winreg",
]
[[package]]
name = "rgb"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fddb3b23626145d1776addfc307e1a1851f60ef6ca64f376bcb889697144cf0"
dependencies = [
"bytemuck",
]
[[package]]
name = "ring"
version = "0.16.20"
@ -3702,6 +3821,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "str_stack"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb"
[[package]]
name = "strsim"
version = "0.8.0"
@ -3738,6 +3863,28 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "symbolic-common"
version = "8.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be7dfa630954f18297ceae1ff2890cb7f5008a0b2d2106b0468dafc45b0b6b12"
dependencies = [
"debugid",
"memmap",
"stable_deref_trait",
"uuid",
]
[[package]]
name = "symbolic-demangle"
version = "8.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b4ba42bd1221803e965054767b1899f2db9a12c89969965c6cb3a02af7014eb"
dependencies = [
"rustc-demangle",
"symbolic-common",
]
[[package]]
name = "syn"
version = "1.0.67"

View File

@ -92,6 +92,7 @@ parking_lot = "0.11.1"
itertools = "0.9.0"
# used by arrow/datafusion anyway
prettytable-rs = "0.8"
pprof = { version = "^0.4", default-features = false, features = ["flamegraph", "protobuf"] }
prost = "0.7"
# Forked to upgrade hyper and tokio
routerify = { git = "https://github.com/influxdata/routerify", rev = "274e250" }

View File

@ -26,21 +26,24 @@ use server::{ConnectionManager, Server as AppServer};
use bytes::{Bytes, BytesMut};
use futures::{self, StreamExt};
use http::header::{CONTENT_ENCODING, CONTENT_TYPE};
use hyper::{Body, Method, Request, Response, StatusCode};
use hyper::{http::HeaderValue, Body, Method, Request, Response, StatusCode};
use observability_deps::{
opentelemetry::KeyValue,
tracing::{self, debug, error},
tracing::{self, debug, error, info},
};
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.
@ -217,6 +220,15 @@ pub enum ApplicationError {
partition: String,
table_name: String,
},
#[snafu(display("PProf error: {}", source))]
PProf { source: pprof::Error },
#[snafu(display("Protobuf error: {}", source))]
Prost { source: prost::EncodeError },
#[snafu(display("Empty flamegraph"))]
EmptyFlamegraph,
}
impl ApplicationError {
@ -251,6 +263,9 @@ impl ApplicationError {
Self::ParsingFormat { .. } => self.bad_request(),
Self::Planning { .. } => self.bad_request(),
Self::NoSnapshot { .. } => self.not_modified(),
Self::PProf { .. } => self.internal_error(),
Self::Prost { .. } => self.internal_error(),
Self::EmptyFlamegraph => self.no_content(),
}
}
@ -282,6 +297,13 @@ impl ApplicationError {
.unwrap()
}
fn no_content(&self) -> Response<Body> {
Response::builder()
.status(StatusCode::NO_CONTENT)
.body(self.body())
.unwrap()
}
fn body(&self) -> Body {
let json =
serde_json::json!({"error": self.to_string(), "error_code": self.api_error_code()})
@ -340,6 +362,8 @@ where
.get("/iox/api/v1/databases/:name/query", query::<M>)
.get("/api/v1/partitions", list_partitions::<M>)
.post("/api/v1/snapshot", snapshot_partition::<M>)
.get("/debug/pprof", pprof_home::<M>)
.get("/debug/pprof/profile", pprof_profile::<M>)
// Specify the error handler to handle any errors caused by
// a route or any middleware.
.err_handler_with_info(error_handler)
@ -785,6 +809,97 @@ async fn snapshot_partition<M: ConnectionManager + Send + Sync + Debug + 'static
}
}
#[tracing::instrument(level = "debug")]
async fn pprof_home<M: ConnectionManager + Send + Sync + Debug + 'static>(
req: Request<Body>,
) -> Result<Response<Body>, ApplicationError> {
let default_host = HeaderValue::from_static("localhost");
let host = req
.headers()
.get("host")
.unwrap_or(&default_host)
.to_str()
.unwrap_or_default();
let cmd = format!(
"/debug/pprof/profile?seconds={}",
PProfArgs::default_seconds()
);
Ok(Response::new(Body::from(format!(
r#"<a href="{}">http://{}{}</a>"#,
cmd, host, cmd
))))
}
async fn dump_rsprof(seconds: u64, frequency: i32) -> pprof::Result<pprof::Report> {
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")]
seconds: u64,
#[serde(default = "PProfArgs::default_frequency")]
frequency: NonZeroI32,
}
impl PProfArgs {
fn default_seconds() -> u64 {
30
}
// 99Hz to avoid coinciding with special periods
fn default_frequency() -> NonZeroI32 {
NonZeroI32::new(99).unwrap()
}
}
#[tracing::instrument(level = "debug")]
async fn pprof_profile<M: ConnectionManager + Send + Sync + Debug + 'static>(
req: Request<Body>,
) -> Result<Response<Body>, ApplicationError> {
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())
.await
.context(PProf)?;
let mut body: Vec<u8> = Vec::new();
// render flamegraph when opening in the browser
// otherwise render as protobuf; works great with: go tool pprof http://..../debug/pprof/profile
if req
.headers()
.get_all("Accept")
.iter()
.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)?;
if body.is_empty() {
return EmptyFlamegraph.fail();
}
} else {
let profile = report.pprof().context(PProf)?;
profile.encode(&mut body).context(Prost)?;
}
Ok(Response::new(Body::from(body)))
}
pub async fn serve<M>(
addr: AddrIncoming,
server: Arc<AppServer<M>>,