Skip to content

Commit 0bae930

Browse files
Provide raw download links
1 parent f3c48db commit 0bae930

File tree

4 files changed

+178
-15
lines changed

4 files changed

+178
-15
lines changed

Cargo.lock

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ arc-swap = "0.4"
3838
rusqlite = { version = "0.23", features = ["bundled"] }
3939
async-trait = "0.1"
4040
database = { path = "../database" }
41+
bytes = "0.5.6"
42+
url = "2"
4143

4244
[dependencies.collector]
4345
path = "../collector"

site/src/server.rs

Lines changed: 157 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
// option. This file may not be copied, modified, or distributed
88
// except according to those terms.
99

10+
use bytes::buf::BufExt;
1011
use parking_lot::Mutex;
1112
use std::cell::RefCell;
1213
use std::collections::HashMap;
1314
use std::convert::TryInto;
1415
use std::fmt;
1516
use std::fs;
17+
use std::io::Read;
1618
use std::net::SocketAddr;
1719
use std::path::Path;
1820
use std::str;
@@ -783,9 +785,96 @@ fn get_self_profile_data(
783785
Ok(profile)
784786
}
785787

788+
pub async fn handle_self_profile_raw_download(
789+
body: self_profile_raw::Request,
790+
data: &InputData,
791+
) -> Response {
792+
let res = handle_self_profile_raw(body, data, false).await;
793+
let url = match res {
794+
Ok(v) => v.url,
795+
Err(e) => {
796+
let mut resp = Response::new(e.into());
797+
*resp.status_mut() = StatusCode::BAD_REQUEST;
798+
return resp;
799+
}
800+
};
801+
log::trace!("downloading {}", url);
802+
803+
let resp = match reqwest::get(&url).await {
804+
Ok(r) => r,
805+
Err(e) => {
806+
let mut resp = Response::new(format!("{:?}", e).into());
807+
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
808+
return resp;
809+
}
810+
};
811+
812+
if !resp.status().is_success() {
813+
let mut resp =
814+
Response::new(format!("upstream status {:?} is not successful", resp.status()).into());
815+
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
816+
return resp;
817+
}
818+
819+
let (sender, body) = hyper::Body::channel();
820+
let mut server_resp = Response::new(body);
821+
let mut header = vec![];
822+
ContentType::octet_stream().encode(&mut header);
823+
server_resp
824+
.headers_mut()
825+
.insert(hyper::header::CONTENT_TYPE, header.pop().unwrap());
826+
server_resp.headers_mut().insert(
827+
hyper::header::CONTENT_DISPOSITION,
828+
hyper::header::HeaderValue::from_maybe_shared(format!(
829+
"attachment; filename=\"self-profile.tar\""
830+
))
831+
.expect("valid header"),
832+
);
833+
*server_resp.status_mut() = StatusCode::OK;
834+
tokio::spawn(tarball(resp, sender));
835+
server_resp
836+
}
837+
838+
async fn tarball(resp: reqwest::Response, mut sender: hyper::body::Sender) {
839+
// Ideally, we would stream the response though the snappy decoding, but
840+
// snappy doesn't support that AFAICT -- we'd need it to implement AsyncRead
841+
// or correctly handle WouldBlock, and neither is true.
842+
let input = match resp.bytes().await {
843+
Ok(b) => b,
844+
Err(e) => {
845+
log::error!("failed to receive data: {:?}", e);
846+
sender.abort();
847+
return;
848+
}
849+
};
850+
let mut decoder = snap::read::FrameDecoder::new(input.reader());
851+
let mut buffer = vec![0; 32 * 1024];
852+
loop {
853+
match decoder.read(&mut buffer[..]) {
854+
Ok(0) => return,
855+
Ok(length) => {
856+
if let Err(e) = sender
857+
.send_data(bytes::Bytes::copy_from_slice(&buffer[..length]))
858+
.await
859+
{
860+
log::error!("failed to send data: {:?}", e);
861+
sender.abort();
862+
return;
863+
}
864+
}
865+
Err(e) => {
866+
log::error!("failed to fill buffer: {:?}", e);
867+
sender.abort();
868+
return;
869+
}
870+
}
871+
}
872+
}
873+
786874
pub async fn handle_self_profile_raw(
787875
body: self_profile_raw::Request,
788876
data: &InputData,
877+
validate: bool,
789878
) -> ServerResult<self_profile_raw::Response> {
790879
log::info!("handle_self_profile_raw({:?})", body);
791880
let mut it = body.benchmark.rsplitn(2, '-');
@@ -835,16 +924,18 @@ pub async fn handle_self_profile_raw(
835924
cid
836925
);
837926

838-
let resp = reqwest::Client::new()
839-
.head(&url)
840-
.send()
841-
.await
842-
.map_err(|e| format!("fetching artifact: {:?}", e))?;
843-
if !resp.status().is_success() {
844-
return Err(format!(
845-
"Artifact did not resolve successfully: {:?} received",
846-
resp.status()
847-
));
927+
if validate {
928+
let resp = reqwest::Client::new()
929+
.head(&url)
930+
.send()
931+
.await
932+
.map_err(|e| format!("fetching artifact: {:?}", e))?;
933+
if !resp.status().is_success() {
934+
return Err(format!(
935+
"Artifact did not resolve successfully: {:?} received",
936+
resp.status()
937+
));
938+
}
848939
}
849940

850941
Ok(self_profile_raw::Response {
@@ -1195,6 +1286,61 @@ async fn serve_req(ctx: Arc<Server>, req: Request) -> Result<Response, ServerErr
11951286
if req.uri().path() == "/perf/onpush" {
11961287
return Ok(ctx.handle_push(req).await);
11971288
}
1289+
if req.uri().path() == "/perf/download-raw-self-profile" {
1290+
// FIXME: how should this look?
1291+
let url = match url::Url::parse(&format!("http://example.com{}", req.uri())) {
1292+
Ok(v) => v,
1293+
Err(e) => {
1294+
error!("failed to parse url {}: {:?}", req.uri(), e);
1295+
return Ok(http::Response::builder()
1296+
.header_typed(ContentType::text_utf8())
1297+
.status(StatusCode::BAD_REQUEST)
1298+
.body(hyper::Body::from(format!(
1299+
"failed to parse url {}: {:?}",
1300+
req.uri(),
1301+
e
1302+
)))
1303+
.unwrap());
1304+
}
1305+
};
1306+
let mut parts = url
1307+
.query_pairs()
1308+
.into_owned()
1309+
.collect::<HashMap<String, String>>();
1310+
macro_rules! key_or_error {
1311+
($ident:ident) => {
1312+
if let Some(v) = parts.remove(stringify!($ident)) {
1313+
v
1314+
} else {
1315+
error!(
1316+
"failed to deserialize request {}: missing {} in query string",
1317+
req.uri(),
1318+
stringify!($ident)
1319+
);
1320+
return Ok(http::Response::builder()
1321+
.header_typed(ContentType::text_utf8())
1322+
.status(StatusCode::BAD_REQUEST)
1323+
.body(hyper::Body::from(format!(
1324+
"failed to deserialize request {}: missing {} in query string",
1325+
req.uri(),
1326+
stringify!($ident)
1327+
)))
1328+
.unwrap());
1329+
}
1330+
};
1331+
}
1332+
let data: Arc<InputData> = ctx.data.read().as_ref().unwrap().clone();
1333+
return Ok(handle_self_profile_raw_download(
1334+
self_profile_raw::Request {
1335+
commit: key_or_error!(commit),
1336+
benchmark: key_or_error!(benchmark),
1337+
run_name: key_or_error!(run_name),
1338+
cid: None,
1339+
},
1340+
&data,
1341+
)
1342+
.await);
1343+
}
11981344

11991345
let (req, mut body_stream) = req.into_parts();
12001346
let p = req.uri.path();
@@ -1277,7 +1423,7 @@ async fn serve_req(ctx: Arc<Server>, req: Request) -> Result<Response, ServerErr
12771423
))
12781424
} else if p == "/perf/self-profile-raw" {
12791425
Ok(to_response(
1280-
handle_self_profile_raw(body!(parse_body(&body)), &data).await,
1426+
handle_self_profile_raw(body!(parse_body(&body)), &data, true).await,
12811427
))
12821428
} else {
12831429
return Ok(http::Response::builder()

site/static/detailed-query.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
<a href="dashboard.html">dashboard</a>, <a href="status.html">status</a>.</div>
5555
<div id="content">
5656
<h3 id="title"></h3>
57+
<div id="raw-urls"></div>
5758
<p>'Time (%)' is the percentage of the cpu-clock time spent on this query (we do not use
5859
wall-time as we want to account for parallelism).</p>
5960
<p>Executions do not include cached executions.</p>
@@ -119,6 +120,18 @@ <h3 id="title"></h3>
119120
txt += `<br><a href="${self_href}">query info for just this commit</a>`;
120121
}
121122
document.querySelector("#title").innerHTML = txt;
123+
let dl_url = (commit, bench, run) => {
124+
return `/perf/download-raw-self-profile?commit=${commit}&benchmark=${bench}&run_name=${run}`
125+
};
126+
let dl_link = (commit, bench, run) => {
127+
let url = dl_url(commit, bench, run);
128+
return `<a href="${url}">Download raw results for ${commit.substring(0, 10)}</a>`;
129+
};
130+
txt = dl_link(state.commit, state.benchmark, state.run_name);
131+
if (state.base_commit) {
132+
txt += dl_link(state.base_commit, state.benchmark, state.run_name);
133+
}
134+
document.querySelector("#raw-urls").innerHTML = txt;
122135
let sort_idx = state.sort_idx;
123136
if (!data.base_profile) {
124137
document.body.classList.add("hide-delta");

0 commit comments

Comments
 (0)