Skip to content

Commit 267adca

Browse files
committed
Changed over to a handler for metrics recording
1 parent 9400536 commit 267adca

File tree

3 files changed

+62
-169
lines changed

3 files changed

+62
-169
lines changed

src/web/metrics.rs

Lines changed: 39 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -99,40 +99,35 @@ fn duration_to_seconds(d: Duration) -> f64 {
9999
d.as_secs() as f64 + nanos
100100
}
101101

102-
#[derive(Debug, Clone)]
103-
pub struct ResponseRecorder {
104-
start_time: Instant,
105-
route: Option<String>,
102+
pub struct RequestRecorder {
103+
handler: Box<dyn iron::Handler>,
104+
route_name: String,
106105
}
107106

108-
impl ResponseRecorder {
109-
#[inline]
110-
pub fn new() -> Self {
107+
impl RequestRecorder {
108+
pub fn new(handler: impl iron::Handler, route: impl Into<String>) -> Self {
111109
Self {
112-
start_time: Instant::now(),
113-
route: None,
110+
handler: Box::new(handler),
111+
route_name: route.into(),
114112
}
115113
}
116-
117-
#[inline]
118-
pub fn route(&mut self, route: impl Into<String>) {
119-
self.route = Some(route.into());
120-
}
121114
}
122115

123-
impl Drop for ResponseRecorder {
124-
fn drop(&mut self) {
125-
if let Some(route) = &self.route {
126-
ROUTES_VISITED.with_label_values(&[route]).inc();
116+
impl iron::Handler for RequestRecorder {
117+
fn handle(&self, request: &mut Request) -> IronResult<Response> {
118+
let start = Instant::now();
119+
let result = self.handler.handle(request);
120+
let resp_time = duration_to_seconds(start.elapsed());
127121

128-
let response_time = duration_to_seconds(self.start_time.elapsed());
129-
RESPONSE_TIMES
130-
.with_label_values(&[route])
131-
.observe(response_time);
122+
ROUTES_VISITED.with_label_values(&[&self.route_name]).inc();
123+
RESPONSE_TIMES
124+
.with_label_values(&[&self.route_name])
125+
.observe(resp_time);
132126

133-
#[cfg(test)]
134-
tests::record_tests(route);
135-
}
127+
#[cfg(test)]
128+
tests::record_tests(&self.route_name);
129+
130+
result
136131
}
137132
}
138133

@@ -178,14 +173,14 @@ mod tests {
178173
frontend.get("/").send()?;
179174
frontend.get("/").send()?;
180175
assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
181-
assert_eq!(RESPONSE_TIMES.lock().unwrap().get("home (found)"), Some(&2));
176+
assert_eq!(RESPONSE_TIMES.lock().unwrap().get("/"), Some(&2));
182177

183178
reset_records();
184179

185180
frontend.get("").send()?;
186181
frontend.get("").send()?;
187182
assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
188-
assert_eq!(RESPONSE_TIMES.lock().unwrap().get("home (found)"), Some(&2));
183+
assert_eq!(RESPONSE_TIMES.lock().unwrap().get("/"), Some(&2));
189184

190185
Ok(())
191186
})
@@ -202,6 +197,7 @@ mod tests {
202197
"/menu.js",
203198
"/sitemap.xml",
204199
"/opensearch.xml",
200+
"/robots.txt",
205201
];
206202

207203
for route in routes.iter() {
@@ -212,19 +208,11 @@ mod tests {
212208

213209
assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
214210
assert_eq!(
215-
RESPONSE_TIMES.lock().unwrap().get("resources (found)"),
211+
RESPONSE_TIMES.lock().unwrap().get("static resource"),
216212
Some(&2)
217213
);
218214
}
219215

220-
reset_records();
221-
222-
frontend.get("/robots.txt").send()?;
223-
frontend.get("/robots.txt").send()?;
224-
225-
assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
226-
assert_eq!(RESPONSE_TIMES.lock().unwrap().get("resources"), Some(&2));
227-
228216
Ok(())
229217
})
230218
}
@@ -248,19 +236,15 @@ mod tests {
248236
let frontend = env.frontend();
249237

250238
let routes = [
251-
("/releases", "recent releases (found)"),
252-
// ("/releases/recent", "recent releases (found)"),
253-
("/releases/recent/1", "recent releases (found)"),
254-
("/releases/feed", "release feed (found)"),
255-
("/releases/queue", "release queue (found)"),
256-
// ("/releases/search", "search releases (found)"),
257-
// ("/releases/stars", "release stars (found)"),
258-
// ("/releases/stars/1", "release stars (found)"),
259-
// ("/releases/activity", "release activity (found)"),
260-
// ("/releases/failures", "build failures (found)"),
261-
// ("/releases/failures/1", "build failures (found)"),
262-
("/releases/recent-failures", "recent failures (found)"),
263-
("/releases/recent-failures/1", "recent failures (found)"),
239+
("/releases", "/releases"),
240+
("/releases/recent/1", "/releases/recent/:page"),
241+
("/releases/feed", "static resource"),
242+
("/releases/queue", "/releases/queue"),
243+
("/releases/recent-failures", "/releases/recent-failures"),
244+
(
245+
"/releases/recent-failures/1",
246+
"/releases/recent-failures/:page",
247+
),
264248
];
265249

266250
for (route, correct) in routes.iter() {
@@ -293,29 +277,19 @@ mod tests {
293277

294278
let frontend = env.frontend();
295279

296-
let routes = [
297-
("/crate/rcc", "crate details (found)"),
298-
("/crate/rcc/", "crate details (found)"),
299-
("/crate/hexponent", "crate details (found)"),
300-
("/crate/hexponent/", "crate details (found)"),
301-
("/crate/rcc/0.0.0", "crate details (found)"),
302-
("/crate/rcc/0.0.0/", "crate details (found)"),
303-
("/crate/hexponent/0.2.0", "crate details (found)"),
304-
("/crate/hexponent/0.2.0/", "crate details (found)"),
305-
("/crate/i_dont_exist", "crate details (404)"),
306-
("/crate/i_dont_exist/", "crate details (404)"),
307-
("/crate/i_dont_exist/4.0.4", "crate details (404)"),
308-
("/crate/i_dont_exist/4.0.4/", "crate details (404)"),
309-
];
280+
let routes = ["/crate/rcc/0.0.0", "/crate/hexponent/0.2.0"];
310281

311-
for (route, correct) in routes.iter() {
282+
for route in routes.iter() {
312283
reset_records();
313284

314285
frontend.get(route).send()?;
315286
frontend.get(route).send()?;
316287

317288
assert_eq!(ROUTES_VISITED.load(Ordering::SeqCst), 2);
318-
assert_eq!(RESPONSE_TIMES.lock().unwrap().get(*correct), Some(&2));
289+
assert_eq!(
290+
RESPONSE_TIMES.lock().unwrap().get("/crate/:name/:version"),
291+
Some(&2)
292+
);
319293
}
320294

321295
Ok(())

src/web/mod.rs

Lines changed: 3 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,6 @@ macro_rules! extension {
4343
};
4444
}
4545

46-
/// Formats a route message with the status of the response
47-
macro_rules! msg {
48-
($resp:expr, $msg:expr) => {{
49-
use iron::status::Status;
50-
51-
match $resp.map_or_else(|err| err.response.status, |resp| resp.status) {
52-
Some(Status::Ok) | Some(Status::Found) => format!("{} (found)", $msg),
53-
Some(Status::NotFound) => format!("{} (404)", $msg),
54-
_ => $msg.to_string(),
55-
}
56-
}};
57-
}
58-
5946
mod builds;
6047
mod crate_details;
6148
mod error;
@@ -73,11 +60,7 @@ use handlebars_iron::{DirectorySource, HandlebarsEngine};
7360
use iron::headers::{CacheControl, CacheDirective, ContentType, Expires, HttpDate};
7461
use iron::modifiers::Redirect;
7562
use iron::prelude::*;
76-
use iron::{
77-
self,
78-
status::{self, Status},
79-
Handler, Listening, Url,
80-
};
63+
use iron::{self, status, Handler, Listening, Url};
8164
use postgres::Connection;
8265
use router::NoRoute;
8366
use rustc_serialize::json::{Json, ToJson};
@@ -158,12 +141,9 @@ impl CratesfyiHandler {
158141

159142
impl Handler for CratesfyiHandler {
160143
fn handle(&self, req: &mut Request) -> IronResult<Response> {
161-
let mut metrics = metrics::ResponseRecorder::new();
162-
163144
// try serving shared rustdoc resources first, then router, then db/static file handler
164145
// return 404 if none of them return Ok
165-
let response = self
166-
.shared_resource_handler
146+
self.shared_resource_handler
167147
.handle(req)
168148
.or_else(|e| self.router_handler.handle(req).or(Err(e)))
169149
.or_else(|e| {
@@ -208,80 +188,7 @@ impl Handler for CratesfyiHandler {
208188
}
209189

210190
Self::chain(&self.pool_factory, err).handle(req)
211-
});
212-
213-
let path: &[&str] = &req.url.path();
214-
match path {
215-
[""] => metrics.route(msg!(response.as_ref(), "home")),
216-
217-
["style.css"]
218-
| ["index.js"]
219-
| ["menu.js"]
220-
| ["robots.txt"]
221-
| ["sitemap.xml"]
222-
| ["opensearch.xml"] => metrics.route(msg!(response.as_ref(), "resources")),
223-
224-
["about"] => metrics.route(msg!(response.as_ref(), "about")),
225-
["about", "metrics"] => { /* Metrics route for prometheus */ }
226-
227-
["releases"] | ["releases", "recent", _] => {
228-
metrics.route(msg!(response.as_ref(), "recent releases"));
229-
}
230-
["releases", "feed"] => metrics.route(msg!(response.as_ref(), "release feed")),
231-
["releases", "queue"] => metrics.route(msg!(response.as_ref(), "release queue")),
232-
["releases", "search"] => metrics.route(msg!(response.as_ref(), "search releases")),
233-
["releases", "stars", ..] => metrics.route(msg!(response.as_ref(), "release stars")),
234-
["releases", "activity"] => metrics.route(msg!(response.as_ref(), "release activity")),
235-
["releases", "failures", ..] => {
236-
metrics.route(msg!(response.as_ref(), "build failures"));
237-
}
238-
["releases", "recent-failures", ..] => {
239-
metrics.route(msg!(response.as_ref(), "recent failures"));
240-
}
241-
242-
["crate", _] | ["crate", _, ""] | ["crate", _, _, ""] => {
243-
if let Some(Status::NotFound) = response
244-
.as_ref()
245-
.map_or_else(|err| err.response.status, |resp| resp.status)
246-
{
247-
// Crates that don't exist won't be redirected
248-
metrics.route(msg!(response.as_ref(), "crate details"));
249-
} else {
250-
// `/crate/:name` and `/crate/:name/` redirect to `/crate/:name/:version`
251-
// `/crate/:name/:version/` redirects to `/crate/:name/:version`
252-
}
253-
}
254-
["crate", _, _] => {
255-
metrics.route(msg!(response.as_ref(), "crate details"));
256-
}
257-
["crate", _, _, _, "builds"]
258-
| ["crate", _, _, _, "builds.json"]
259-
| ["crate", _, _, _, "builds", _] => {
260-
metrics.route(msg!(response.as_ref(), "crate builds"));
261-
}
262-
["crate", _, _, _, "source", ..] => {
263-
metrics.route(msg!(response.as_ref(), "crate source"));
264-
}
265-
["crate", _, _, _, "target-redirect", ..] => {
266-
metrics.route(msg!(response.as_ref(), "crate target redirect"));
267-
}
268-
269-
[_, "badge.svg"] => metrics.route(msg!(response.as_ref(), "crate badge")),
270-
[_, _, "settings.html"] => metrics.route(msg!(response.as_ref(), "crate settings")),
271-
272-
[_, _] | [_, _, _] => metrics.route(msg!(response.as_ref(), "crate docs")),
273-
[_, _, _, file] if file.ends_with(".html") => {
274-
metrics.route(msg!(response.as_ref(), "crate docs"));
275-
}
276-
277-
[route] if routes::DOC_RUST_LANG_ORG_REDIRECTS.contains(route) => {
278-
metrics.route(msg!(response.as_ref(), "rust-lang redirect"));
279-
}
280-
281-
_ => metrics.route(msg!(response.as_ref(), "crate docs")),
282-
}
283-
284-
response
191+
})
285192
}
286193
}
287194

src/web/routes.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use super::metrics::RequestRecorder;
12
use crate::web::{INDEX_JS, MENU_JS};
23
use iron::middleware::Handler;
34
use iron::Request;
@@ -184,7 +185,10 @@ impl Routes {
184185

185186
/// A static resource is a normal page without any special behavior on the router side.
186187
fn static_resource(&mut self, pattern: &str, handler: impl Handler) {
187-
self.get.push((pattern.to_string(), Box::new(handler)));
188+
self.get.push((
189+
pattern.to_string(),
190+
Box::new(RequestRecorder::new(handler, "static resource")),
191+
));
188192
}
189193

190194
/// Internal pages are docs.rs's own pages, instead of the documentation of a crate uploaded by
@@ -197,16 +201,22 @@ impl Routes {
197201
/// - If the page URL doesn't end with a slash, a redirect from the URL with the trailing slash
198202
/// to the one without is automatically added.
199203
fn internal_page(&mut self, pattern: &str, handler: impl Handler) {
200-
self.get.push((pattern.to_string(), Box::new(handler)));
204+
self.get.push((
205+
pattern.to_string(),
206+
Box::new(RequestRecorder::new(handler, pattern)),
207+
));
201208

202209
// Automatically add another route ending with / that redirects to the slash-less route.
203210
if !pattern.ends_with('/') {
204211
let pattern = format!("{}/", pattern);
205212
self.get.push((
206-
pattern,
207-
Box::new(SimpleRedirect::new(|url| {
208-
url.set_path(&url.path().trim_end_matches('/').to_string())
209-
})),
213+
pattern.to_string(),
214+
Box::new(RequestRecorder::new(
215+
SimpleRedirect::new(|url| {
216+
url.set_path(&url.path().trim_end_matches('/').to_string())
217+
}),
218+
pattern,
219+
)),
210220
));
211221
}
212222

@@ -225,8 +235,10 @@ impl Routes {
225235
/// resource, but path prefixes are automatically blacklisted (see internal pages to learn more
226236
/// about page prefixes).
227237
fn rustdoc_page(&mut self, pattern: &str, handler: impl Handler) {
228-
self.rustdoc_get
229-
.push((pattern.to_string(), Box::new(handler)));
238+
self.get.push((
239+
pattern.to_string(),
240+
Box::new(RequestRecorder::new(handler, "rustdoc page")),
241+
));
230242
}
231243
}
232244

0 commit comments

Comments
 (0)