Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define HTTP request latency histogram timeseries in TOML #6006

Merged
merged 3 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion nexus/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ impl ServerContext {
let authz = Arc::new(authz::Authz::new(&log));
let create_tracker = |name: &str| {
let target = HttpService {
name: name.to_string(),
name: name.to_string().into(),
id: config.deployment.id,
};
const START_LATENCY_DECADE: i16 = -6;
Expand Down
3 changes: 2 additions & 1 deletion openapi/nexus.json
Original file line number Diff line number Diff line change
Expand Up @@ -19798,7 +19798,8 @@
"type": "string",
"enum": [
"count",
"bytes"
"bytes",
"seconds"
bnaecker marked this conversation as resolved.
Show resolved Hide resolved
]
},
"User": {
Expand Down
1 change: 1 addition & 0 deletions oximeter/impl/src/schema/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ impl quote::ToTokens for Units {
let toks = match self {
Units::Count => quote! { ::oximeter::schema::Units::Count },
Units::Bytes => quote! { ::oximeter::schema::Units::Bytes },
Units::Seconds => quote! { ::oximeter::schema::Units::Seconds },
};
toks.to_tokens(tokens);
}
Expand Down
1 change: 1 addition & 0 deletions oximeter/impl/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ pub struct TimeseriesDescription {
pub enum Units {
Count,
Bytes,
Seconds,
}

/// The schema for a timeseries.
Expand Down
49 changes: 17 additions & 32 deletions oximeter/instruments/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,24 @@ use futures::Future;
use http::StatusCode;
use http::Uri;
use oximeter::{
histogram::Histogram, histogram::Record, Metric, MetricsError, Producer,
Sample, Target,
histogram::Histogram, histogram::Record, MetricsError, Producer, Sample,
};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use uuid::Uuid;

/// The [`HttpService`] is an [`oximeter::Target`] for monitoring HTTP servers.
#[derive(Debug, Clone, Target)]
pub struct HttpService {
pub name: String,
pub id: Uuid,
}

/// An [`oximeter::Metric`] that tracks a histogram of the latency of requests to a specified HTTP
/// endpoint.
#[derive(Debug, Clone, Metric)]
pub struct RequestLatencyHistogram {
pub route: String,
pub method: String,
pub status_code: i64,
#[datum]
pub latency: Histogram<f64>,
}
oximeter::use_timeseries!("http-service.toml");
pub use http_service::HttpService;
pub use http_service::RequestLatencyHistogram;

// Return the route portion of the request, normalized to include a single
// leading slash and no trailing slashes.
fn normalized_uri_path(uri: &Uri) -> String {
format!("/{}", uri.path().trim_end_matches('/').trim_start_matches('/'))
fn normalized_uri_path(uri: &Uri) -> Cow<'static, str> {
Cow::Owned(format!(
"/{}",
uri.path().trim_end_matches('/').trim_start_matches('/')
))
}

impl RequestLatencyHistogram {
Expand All @@ -56,9 +44,9 @@ impl RequestLatencyHistogram {
) -> Self {
Self {
route: normalized_uri_path(request.uri()),
method: request.method().to_string(),
status_code: status_code.as_u16().into(),
latency: histogram,
method: request.method().to_string().into(),
status_code: status_code.as_u16(),
datum: histogram,
}
}

Expand Down Expand Up @@ -154,7 +142,7 @@ impl LatencyTracker {
self.histogram.clone(),
)
});
entry.latency.sample(latency.as_secs_f64()).map_err(MetricsError::from)
entry.datum.sample(latency.as_secs_f64()).map_err(MetricsError::from)
}

/// Instrument the given Dropshot endpoint handler function.
Expand Down Expand Up @@ -228,10 +216,8 @@ mod tests {

#[test]
fn test_latency_tracker() {
let service = HttpService {
name: String::from("my-service"),
id: ID.parse().unwrap(),
};
let service =
HttpService { name: "my-service".into(), id: ID.parse().unwrap() };
let hist = Histogram::new(&[0.0, 1.0]).unwrap();
let tracker = LatencyTracker::new(service, hist);
let request = http::request::Builder::new()
Expand All @@ -249,8 +235,7 @@ mod tests {
.unwrap();

let key = "/some/uri:GET:200";
let actual_hist =
tracker.latencies.lock().unwrap()[key].latency.clone();
let actual_hist = tracker.latencies.lock().unwrap()[key].datum.clone();
assert_eq!(actual_hist.n_samples(), 1);
let bins = actual_hist.iter().collect::<Vec<_>>();
assert_eq!(bins[1].count, 1);
Expand Down
38 changes: 38 additions & 0 deletions oximeter/oximeter/schema/http-service.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
format_version = 1

[target]
name = "http_service"
description = "An Oxide HTTP server"
authz_scope = "fleet"
versions = [
{ version = 1, fields = [ "name", "id" ] },
]

[[metrics]]
name = "request_latency_histogram"
description = "Duration for the server to handle a request"
units = "seconds"
datum_type = "histogram_f64"
versions = [
{ added_in = 1, fields = [ "route", "method", "status_code" ] }
]

[fields.name]
type = "string"
description = "The name of the HTTP server, or program running it"

[fields.id]
type = "uuid"
description = "UUID of the HTTP server"

[fields.route]
type = "string"
description = "HTTP route in the request"

[fields.method]
type = "string"
description = "HTTP method in the request"

[fields.status_code]
type = "u16"
bnaecker marked this conversation as resolved.
Show resolved Hide resolved
description = "HTTP status code in the server's response"
Loading