Skip to content

Commit 495fe62

Browse files
committed
Add support for actix
1 parent 35a60c0 commit 495fe62

3 files changed

Lines changed: 158 additions & 45 deletions

File tree

microchassis/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ tikv-jemalloc-ctl = "0.5"
2020
tikv-jemalloc-sys = "0.5"
2121
tikv-jemallocator = { version = "0.5", features = ["profiling", "stats"] }
2222
tracing = { version = "0.1" }
23+
actix-web = { version = "4", optional = true }
24+
futures-util = { version = "0.3", optional = true }
2325

2426
[features]
2527
default = ["std", "jemalloc-profiling", "set-jemalloc-global"]
@@ -28,6 +30,7 @@ jemalloc-profiling = ["dep:backtrace"]
2830
oompanic-allocator = []
2931
set-jemalloc-global = []
3032
disable_aslr = ["dep:libc"]
33+
actix-handlers = ["dep:actix-web", "dep:futures-util"]
3134

3235
[[bin]]
3336
name = "disable_aslr"

microchassis/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
clippy::unwrap_in_result, // TODO: revisit
3636
clippy::multiple_crate_versions,
3737
clippy::needless_pass_by_value,
38+
clippy::implicit_hasher,
3839
)]
3940
#![cfg_attr(not(feature = "std"), no_std)]
4041

microchassis/src/profiling/jeprof.rs

Lines changed: 154 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@
1919
2020
use crate::profiling::mallctl;
2121
use http::{header, Method, Request, Response, StatusCode};
22-
use std::env;
22+
use std::{collections::HashMap, env, fmt};
2323

2424
#[inline]
2525
pub fn router(req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
2626
match (req.method(), req.uri().path()) {
27-
(&Method::GET, "/pprof/conf") => get_pprof_conf_handler(req),
28-
(&Method::POST, "/pprof/conf") => post_pprof_conf_handler(req),
29-
(&Method::GET, "/pprof/heap") => get_pprof_heap_handler(req),
30-
(&Method::GET, "/pprof/cmdline") => get_pprof_cmdline_handler(req),
31-
(&Method::GET, "/pprof/symbol") => get_pprof_symbol_handler(req),
32-
(&Method::POST, "/pprof/symbol") => post_pprof_symbol_handler(req),
33-
(&Method::GET, "/pprof/stats") => get_pprof_stats_handler(req),
27+
(&Method::GET, "/pprof/conf") => JeprofHandler(get_pprof_conf_handler).call(req),
28+
(&Method::POST, "/pprof/conf") => JeprofHandler(post_pprof_conf_handler).call(req),
29+
(&Method::GET, "/pprof/heap") => JeprofHandler(get_pprof_heap_handler).call(req),
30+
(&Method::GET, "/pprof/cmdline") => JeprofHandler(get_pprof_cmdline_handler).call(req),
31+
(&Method::GET, "/pprof/symbol") => JeprofHandler(get_pprof_symbol_handler).call(req),
32+
(&Method::POST, "/pprof/symbol") => JeprofHandler(post_pprof_symbol_handler).call(req),
33+
(&Method::GET, "/pprof/stats") => JeprofHandler(get_pprof_stats_handler).call(req),
3434
_ => {
3535
let body = b"Bad Request\r\n";
3636
Response::builder()
@@ -42,101 +42,207 @@ pub fn router(req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
4242
}
4343
}
4444

45+
#[cfg(feature = "actix-handlers")]
4546
#[inline]
46-
pub fn get_pprof_conf_handler(_req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
47+
pub fn actix_routes(cfg: &mut actix_web::web::ServiceConfig) {
48+
cfg.service(
49+
actix_web::web::scope("/pprof")
50+
.route("/conf", actix_web::web::get().to(JeprofHandler(get_pprof_conf_handler)))
51+
.route("/conf", actix_web::web::post().to(JeprofHandler(post_pprof_conf_handler)))
52+
.route("/heap", actix_web::web::get().to(JeprofHandler(get_pprof_heap_handler)))
53+
.route("/cmdline", actix_web::web::get().to(JeprofHandler(get_pprof_cmdline_handler)))
54+
.route("/symbol", actix_web::web::get().to(JeprofHandler(get_pprof_symbol_handler)))
55+
.route("/symbol", actix_web::web::post().to(JeprofHandler(post_pprof_symbol_handler)))
56+
.route("/stats", actix_web::web::get().to(JeprofHandler(get_pprof_stats_handler))),
57+
);
58+
}
59+
60+
#[derive(Debug)]
61+
pub struct ErrorResponse(String);
62+
63+
impl fmt::Display for ErrorResponse {
64+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65+
write!(f, "ERROR: {}", self.0)
66+
}
67+
}
68+
69+
#[cfg(feature = "actix-handlers")]
70+
impl actix_web::ResponseError for ErrorResponse {}
71+
72+
#[derive(Clone, Debug)]
73+
struct JeprofHandler<F>(F)
74+
where
75+
F: Fn(&[u8], &HashMap<String, String>) -> Result<(Vec<u8>, Option<String>), ErrorResponse>
76+
+ Clone
77+
+ 'static;
78+
79+
impl<F> JeprofHandler<F>
80+
where
81+
F: Fn(&[u8], &HashMap<String, String>) -> Result<(Vec<u8>, Option<String>), ErrorResponse>
82+
+ Clone
83+
+ 'static,
84+
{
85+
fn call(&self, req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
86+
let params: HashMap<String, String> = parse_malloc_conf_query(req.uri().query())
87+
.iter()
88+
.map(|(k, v)| ((*k).to_string(), v.unwrap_or_default().to_string()))
89+
.collect();
90+
match self.0(req.body(), &params) {
91+
Ok((body, Some(content_disposition))) => response_ok_binary(body, &content_disposition),
92+
Ok((body, None)) => response_ok(body),
93+
Err(err) => response_err(&err.0),
94+
}
95+
}
96+
}
97+
98+
#[cfg(feature = "actix-handlers")]
99+
impl<F>
100+
actix_web::Handler<(actix_web::web::Payload, actix_web::web::Query<HashMap<String, String>>)>
101+
for JeprofHandler<F>
102+
where
103+
F: Fn(&[u8], &HashMap<String, String>) -> Result<(Vec<u8>, Option<String>), ErrorResponse>
104+
+ Clone
105+
+ 'static,
106+
{
107+
type Output = Result<actix_web::HttpResponse, ErrorResponse>;
108+
type Future = std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output>>>;
109+
110+
fn call(
111+
&self,
112+
(mut body, query): (
113+
actix_web::web::Payload,
114+
actix_web::web::Query<HashMap<String, String>>,
115+
),
116+
) -> Self::Future {
117+
use futures_util::StreamExt as _;
118+
119+
let f = self.0.clone();
120+
Box::pin(async move {
121+
let mut data = Vec::<u8>::new();
122+
while let Some(item) = body.next().await {
123+
data.extend_from_slice(&item.map_err(|e| ErrorResponse(e.to_string()))?);
124+
}
125+
f(&data, &query.0).map(|(body, content_disposition)| {
126+
let mut resp = actix_web::HttpResponse::Ok();
127+
if let Some(filename) = content_disposition {
128+
resp.insert_header(actix_web::http::header::ContentDisposition::attachment(
129+
filename,
130+
));
131+
}
132+
resp.body(actix_web::web::Bytes::from(body))
133+
})
134+
})
135+
}
136+
}
137+
138+
#[inline]
139+
pub fn get_pprof_conf_handler(
140+
_body: &[u8],
141+
_params: &HashMap<String, String>,
142+
) -> Result<(Vec<u8>, Option<String>), ErrorResponse> {
47143
match mallctl::enabled() {
48144
Ok(true) => (),
49-
_ => return response_err("jemalloc profiling not enabled"),
145+
_ => return Err(ErrorResponse("jemalloc profiling not enabled".to_owned())),
50146
};
51147

52148
let Ok(state) = mallctl::active() else {
53-
return response_err("failed to read prof.active\r\n");
149+
return Err(ErrorResponse("failed to read prof.active\r\n".to_owned()));
54150
};
55151
let Ok(sample) = mallctl::sample_interval() else {
56-
return response_err("failed to read prof.lg_sample\r\n");
152+
return Err(ErrorResponse("failed to read prof.lg_sample\r\n".to_owned()));
57153
};
58154
let body = format!("prof.active:{state},prof.lg_sample:{sample}\r\n");
59-
response_ok(body.into_bytes())
155+
Ok((body.into_bytes(), None))
60156
}
61157

62158
#[inline]
63-
pub fn post_pprof_conf_handler(req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
159+
pub fn post_pprof_conf_handler(
160+
_body: &[u8],
161+
params: &HashMap<String, String>,
162+
) -> Result<(Vec<u8>, Option<String>), ErrorResponse> {
64163
match mallctl::enabled() {
65164
Ok(true) => (),
66-
_ => return response_err("jemalloc profiling not enabled\r\n"),
165+
_ => return Err(ErrorResponse("jemalloc profiling not enabled\r\n".to_owned())),
67166
};
68167

69-
let query = parse_malloc_conf_query(req.uri().query());
70-
71-
for (name, value) in query {
72-
if let Err(e) = match name {
168+
for (name, value) in params {
169+
if let Err(e) = match name.as_str() {
73170
"prof.reset" => {
74-
let Some(sample) = value.map(|v| v.parse().ok()) else {
75-
return response_err(format!("invalid prof.reset value: {value:?}\r\n").as_str());
76-
};
77-
mallctl::reset(sample)
171+
let sample = value.parse().map_err(|_| {
172+
ErrorResponse(format!("invalid prof.reset value: {value:?}\r\n"))
173+
})?;
174+
mallctl::reset(Some(sample))
78175
}
79176
"prof.active" => {
80-
let Some(value) = value else {
81-
return response_err("prof.active needs value\r\n");
82-
};
83177
let Some(state) = value.parse().ok() else {
84-
return response_err(format!("invalid prof.active value: {value:?}\r\n").as_str());
178+
return Err(ErrorResponse(format!("invalid prof.active value: {value:?}\r\n")));
85179
};
86180
mallctl::set_active(state)
87181
}
88182
_ => {
89-
return response_err(format!("{name}={value:?} unknown\r\n").as_str());
183+
return Err(ErrorResponse(format!("{name}={value:?} unknown\r\n")));
90184
}
91185
} {
92-
return response_err(format!("{name}={value:?} failed: {e}\r\n").as_str());
186+
return Err(ErrorResponse(format!("{name}={value:?} failed: {e}\r\n")));
93187
}
94188
}
95189

96-
response_ok(b"OK\r\n".to_vec())
190+
Ok((b"OK\r\n".to_vec(), None))
97191
}
98192

99193
#[inline]
100-
pub fn get_pprof_heap_handler(_req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
194+
pub fn get_pprof_heap_handler(
195+
_body: &[u8],
196+
_params: &HashMap<String, String>,
197+
) -> Result<(Vec<u8>, Option<String>), ErrorResponse> {
101198
match mallctl::enabled() {
102199
Ok(true) => (),
103-
_ => return response_err("jemalloc profiling not enabled\r\n"),
200+
_ => return Err(ErrorResponse("jemalloc profiling not enabled\r\n".to_owned())),
104201
};
105202

106203
let Ok(f) = tempfile::Builder::new().prefix("jemalloc.").suffix(".prof").tempfile() else {
107-
return response_err("cannot create temporary file for profile dump\r\n");
204+
return Err(ErrorResponse("cannot create temporary file for profile dump\r\n".to_owned()));
108205
};
109206

110207
let Ok(profile) = mallctl::dump(f.path().to_str()) else {
111-
return response_err("failed to dump profile\r\n");
208+
return Err(ErrorResponse("failed to dump profile\r\n".to_owned()));
112209
};
113210

114211
let filename = f.path().file_name().expect("proper filename from tempfile");
115-
response_ok_binary(profile.expect("profile not None"), filename.to_string_lossy().as_ref())
212+
Ok((profile.expect("profile not None"), Some(filename.to_string_lossy().to_string())))
116213
}
117214

118215
/// HTTP handler for GET /pprof/cmdline.
119216
#[inline]
120-
pub fn get_pprof_cmdline_handler(_req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
217+
pub fn get_pprof_cmdline_handler(
218+
_body: &[u8],
219+
_params: &HashMap<String, String>,
220+
) -> Result<(Vec<u8>, Option<String>), ErrorResponse> {
121221
let mut body = String::new();
122222
for arg in env::args() {
123223
body.push_str(arg.as_str());
124224
body.push_str("\r\n");
125225
}
126-
response_ok(body.into_bytes())
226+
Ok((body.into_bytes(), None))
127227
}
128228

129229
/// HTTP handler for GET /pprof/symbol.
130230
#[inline]
131-
pub fn get_pprof_symbol_handler(_req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
231+
pub fn get_pprof_symbol_handler(
232+
_body: &[u8],
233+
_params: &HashMap<String, String>,
234+
) -> Result<(Vec<u8>, Option<String>), ErrorResponse> {
132235
// TODO: any quick way to check if binary is stripped?
133236
let body = b"num_symbols: 1\r\n";
134-
response_ok(body.to_vec())
237+
Ok((body.to_vec(), None))
135238
}
136239

137240
/// HTTP handler for POST /pprof/symbol.
138241
#[inline]
139-
pub fn post_pprof_symbol_handler(req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
242+
pub fn post_pprof_symbol_handler(
243+
body: &[u8],
244+
_params: &HashMap<String, String>,
245+
) -> Result<(Vec<u8>, Option<String>), ErrorResponse> {
140246
fn lookup_symbol(addr: u64) -> Option<String> {
141247
let mut s: Option<String> = None;
142248
backtrace::resolve(addr as *mut _, |symbol| {
@@ -145,7 +251,7 @@ pub fn post_pprof_symbol_handler(req: Request<Vec<u8>>) -> http::Result<Response
145251
s
146252
}
147253

148-
let body = String::from_utf8_lossy(req.body());
254+
let body = String::from_utf8_lossy(body);
149255
let addrs = body
150256
.split('+')
151257
.filter_map(|addr| u64::from_str_radix(addr.trim_start_matches("0x"), 16).ok())
@@ -157,17 +263,20 @@ pub fn post_pprof_symbol_handler(req: Request<Vec<u8>>) -> http::Result<Response
157263
body.push_str(format!("{addr:#x}\t{sym}\r\n").as_str());
158264
}
159265

160-
response_ok(body.into_bytes())
266+
Ok((body.into_bytes(), None))
161267
}
162268

163269
/// HTTP handler for GET /pprof/stats.
164270
#[inline]
165-
pub fn get_pprof_stats_handler(_req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
271+
pub fn get_pprof_stats_handler(
272+
_body: &[u8],
273+
_params: &HashMap<String, String>,
274+
) -> Result<(Vec<u8>, Option<String>), ErrorResponse> {
166275
let body = match mallctl::stats() {
167276
Ok(body) => body,
168-
Err(e) => return response_err(format!("failed to print stats: {e}\r\n").as_str()),
277+
Err(e) => return Err(ErrorResponse(format!("failed to print stats: {e}\r\n"))),
169278
};
170-
response_ok(body)
279+
Ok((body, None))
171280
}
172281

173282
fn parse_malloc_conf_query(query: Option<&str>) -> Vec<(&str, Option<&str>)> {

0 commit comments

Comments
 (0)