Skip to content

Commit 963aa81

Browse files
authored
Merge pull request #203 from http-rs/server-timings
Add trace::ServerTiming
2 parents 3a4d187 + 5780f55 commit 963aa81

File tree

6 files changed

+842
-263
lines changed

6 files changed

+842
-263
lines changed

src/headers/constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ pub const RETRY_AFTER: HeaderName = HeaderName::from_lowercase_str("retry-after"
137137
/// The `Server` Header
138138
pub const SERVER: HeaderName = HeaderName::from_lowercase_str("server");
139139

140+
/// The `Server` Header
141+
pub const SERVER_TIMING: HeaderName = HeaderName::from_lowercase_str("server-timing");
142+
140143
/// The `Te` Header
141144
pub const TE: HeaderName = HeaderName::from_lowercase_str("te");
142145

src/trace/mod.rs

Lines changed: 12 additions & 263 deletions
Original file line numberDiff line numberDiff line change
@@ -1,268 +1,17 @@
1-
//! Extract and inject [trace context](https://w3c.github.io/trace-context/) headers.
1+
//! HTTP timings and traces.
22
//!
3-
//! ## Examples
3+
//! This module implements parsers and serializers for timing-related headers.
4+
//! These headers enable tracing and timing requests, and help answer the
5+
//! question of: _"Where is my program spending its time?"_
46
//!
5-
//! ```
6-
//! use http_types::trace::TraceContext;
7+
//! # Specifications
78
//!
8-
//! let mut res = http_types::Response::new(200);
9-
//!
10-
//! res.insert_header(
11-
//! "traceparent",
12-
//! "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"
13-
//! );
14-
//!
15-
//! let context = TraceContext::extract(&res).unwrap();
16-
//!
17-
//! let trace_id = u128::from_str_radix("0af7651916cd43dd8448eb211c80319c", 16);
18-
//! let parent_id = u64::from_str_radix("00f067aa0ba902b7", 16);
19-
//!
20-
//! assert_eq!(context.trace_id(), trace_id.unwrap());
21-
//! assert_eq!(context.parent_id(), parent_id.ok());
22-
//! assert_eq!(context.sampled(), true);
23-
//! ```
24-
25-
use rand::Rng;
26-
use std::fmt;
27-
28-
use crate::Headers;
29-
30-
/// A TraceContext object
31-
#[derive(Debug)]
32-
pub struct TraceContext {
33-
id: u64,
34-
version: u8,
35-
trace_id: u128,
36-
parent_id: Option<u64>,
37-
flags: u8,
38-
}
39-
40-
impl TraceContext {
41-
/// Create and return TraceContext object based on `traceparent` HTTP header.
42-
///
43-
/// ## Examples
44-
/// ```
45-
/// use http_types::trace::TraceContext;
46-
///
47-
/// let mut res = http_types::Response::new(200);
48-
/// res.insert_header(
49-
/// "traceparent",
50-
/// "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"
51-
/// );
52-
///
53-
/// let context = TraceContext::extract(&res).unwrap();
54-
///
55-
/// let trace_id = u128::from_str_radix("0af7651916cd43dd8448eb211c80319c", 16);
56-
/// let parent_id = u64::from_str_radix("00f067aa0ba902b7", 16);
57-
///
58-
/// assert_eq!(context.trace_id(), trace_id.unwrap());
59-
/// assert_eq!(context.parent_id(), parent_id.ok());
60-
/// assert_eq!(context.sampled(), true);
61-
/// ```
62-
pub fn extract(headers: impl AsRef<Headers>) -> crate::Result<Self> {
63-
let headers = headers.as_ref();
64-
let mut rng = rand::thread_rng();
65-
66-
let traceparent = match headers.get("traceparent") {
67-
Some(header) => header.as_str(),
68-
None => return Ok(Self::new_root()),
69-
};
70-
71-
let parts: Vec<&str> = traceparent.split('-').collect();
72-
73-
Ok(Self {
74-
id: rng.gen(),
75-
version: u8::from_str_radix(parts[0], 16)?,
76-
trace_id: u128::from_str_radix(parts[1], 16)?,
77-
parent_id: Some(u64::from_str_radix(parts[2], 16)?),
78-
flags: u8::from_str_radix(parts[3], 16)?,
79-
})
80-
}
81-
82-
/// Generate a new TraceContect object without a parent.
83-
///
84-
/// By default root TraceContext objects are sampled.
85-
/// To mark it unsampled, call `context.set_sampled(false)`.
86-
///
87-
/// ## Examples
88-
/// ```
89-
/// use http_types::trace::TraceContext;
90-
///
91-
/// let context = TraceContext::new_root();
92-
///
93-
/// assert_eq!(context.parent_id(), None);
94-
/// assert_eq!(context.sampled(), true);
95-
/// ```
96-
pub fn new_root() -> Self {
97-
let mut rng = rand::thread_rng();
98-
99-
Self {
100-
id: rng.gen(),
101-
version: 0,
102-
trace_id: rng.gen(),
103-
parent_id: None,
104-
flags: 1,
105-
}
106-
}
107-
108-
/// Add the traceparent header to the http headers
109-
///
110-
/// ## Examples
111-
/// ```
112-
/// use http_types::trace::TraceContext;
113-
/// use http_types::{Request, Response, Url, Method};
114-
///
115-
/// let mut req = Request::new(Method::Get, Url::parse("https://example.com").unwrap());
116-
/// req.insert_header(
117-
/// "traceparent",
118-
/// "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"
119-
/// );
120-
///
121-
/// let parent = TraceContext::extract(&req).unwrap();
122-
///
123-
/// let mut res = Response::new(200);
124-
/// parent.inject(&mut res);
125-
///
126-
/// let child = TraceContext::extract(&res).unwrap();
127-
///
128-
/// assert_eq!(child.version(), parent.version());
129-
/// assert_eq!(child.trace_id(), parent.trace_id());
130-
/// assert_eq!(child.parent_id(), Some(parent.id()));
131-
/// ```
132-
pub fn inject(&self, mut headers: impl AsMut<Headers>) {
133-
let headers = headers.as_mut();
134-
headers.insert("traceparent", format!("{}", self));
135-
}
136-
137-
/// Generate a child of the current TraceContext and return it.
138-
///
139-
/// The child will have a new randomly genrated `id` and its `parent_id` will be set to the
140-
/// `id` of this TraceContext.
141-
pub fn child(&self) -> Self {
142-
let mut rng = rand::thread_rng();
143-
144-
Self {
145-
id: rng.gen(),
146-
version: self.version,
147-
trace_id: self.trace_id,
148-
parent_id: Some(self.id),
149-
flags: self.flags,
150-
}
151-
}
152-
153-
/// Return the id of the TraceContext.
154-
pub fn id(&self) -> u64 {
155-
self.id
156-
}
157-
158-
/// Return the version of the TraceContext spec used.
159-
///
160-
/// You probably don't need this.
161-
pub fn version(&self) -> u8 {
162-
self.version
163-
}
164-
165-
/// Return the trace id of the TraceContext.
166-
///
167-
/// All children will have the same `trace_id`.
168-
pub fn trace_id(&self) -> u128 {
169-
self.trace_id
170-
}
171-
172-
/// Return the id of the parent TraceContext.
173-
#[inline]
174-
pub fn parent_id(&self) -> Option<u64> {
175-
self.parent_id
176-
}
177-
178-
/// Returns true if the trace is sampled
179-
///
180-
/// ## Examples
181-
///
182-
/// ```
183-
/// use http_types::trace::TraceContext;
184-
/// use http_types::Response;
185-
///
186-
/// let mut res = Response::new(200);
187-
/// res.insert_header("traceparent", "00-00000000000000000000000000000001-0000000000000002-01");
188-
/// let context = TraceContext::extract(&res).unwrap();
189-
/// assert_eq!(context.sampled(), true);
190-
/// ```
191-
pub fn sampled(&self) -> bool {
192-
(self.flags & 0b00000001) == 1
193-
}
194-
195-
/// Change sampled flag
196-
///
197-
/// ## Examples
198-
///
199-
/// ```
200-
/// use http_types::trace::TraceContext;
201-
///
202-
/// let mut context = TraceContext::new_root();
203-
/// assert_eq!(context.sampled(), true);
204-
/// context.set_sampled(false);
205-
/// assert_eq!(context.sampled(), false);
206-
/// ```
207-
pub fn set_sampled(&mut self, sampled: bool) {
208-
let x = sampled as u8;
209-
self.flags ^= (x ^ self.flags) & (1 << 0);
210-
}
211-
}
212-
213-
impl fmt::Display for TraceContext {
214-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215-
write!(
216-
f,
217-
"{:02x}-{:032x}-{:016x}-{:02x}",
218-
self.version, self.trace_id, self.id, self.flags
219-
)
220-
}
221-
}
222-
223-
#[cfg(test)]
224-
mod test {
225-
use super::*;
226-
227-
#[test]
228-
fn default() -> crate::Result<()> {
229-
let mut headers = crate::Headers::new();
230-
headers.insert("traceparent", "00-01-deadbeef-00");
231-
let context = TraceContext::extract(&mut headers)?;
232-
assert_eq!(context.version(), 0);
233-
assert_eq!(context.trace_id(), 1);
234-
assert_eq!(context.parent_id().unwrap(), 3735928559);
235-
assert_eq!(context.flags, 0);
236-
assert_eq!(context.sampled(), false);
237-
Ok(())
238-
}
239-
240-
#[test]
241-
fn no_header() -> crate::Result<()> {
242-
let mut headers = crate::Headers::new();
243-
let context = TraceContext::extract(&mut headers)?;
244-
assert_eq!(context.version(), 0);
245-
assert_eq!(context.parent_id(), None);
246-
assert_eq!(context.flags, 1);
247-
assert_eq!(context.sampled(), true);
248-
Ok(())
249-
}
9+
//! - [W3C Trace-Context headers](https://w3c.github.io/trace-context/)
10+
//! - [W3C Server-Timing headers](https://w3c.github.io/server-timing/#the-server-timing-header-field)
25011
251-
#[test]
252-
fn not_sampled() -> crate::Result<()> {
253-
let mut headers = crate::Headers::new();
254-
headers.insert("traceparent", "00-01-02-00");
255-
let context = TraceContext::extract(&mut headers)?;
256-
assert_eq!(context.sampled(), false);
257-
Ok(())
258-
}
12+
pub mod server_timing;
13+
mod trace_context;
25914

260-
#[test]
261-
fn sampled() -> crate::Result<()> {
262-
let mut headers = crate::Headers::new();
263-
headers.insert("traceparent", "00-01-02-01");
264-
let context = TraceContext::extract(&mut headers)?;
265-
assert_eq!(context.sampled(), true);
266-
Ok(())
267-
}
268-
}
15+
#[doc(inline)]
16+
pub use server_timing::{Metric, ServerTiming};
17+
pub use trace_context::TraceContext;

src/trace/server_timing/metric.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use std::time::Duration;
2+
3+
use crate::headers::HeaderValue;
4+
5+
/// An individual entry into `ServerTiming`.
6+
//
7+
// # Implementation notes
8+
//
9+
// Four different cases are valid:
10+
//
11+
// 1. metric name only cache
12+
// 2. metric + value cache;dur=2.4
13+
// 3. metric + desc cache;desc="Cache Read"
14+
// 4. metric + value + desc cache;desc="Cache Read";dur=23.2
15+
//
16+
// Multiple different entries per line are supported; separated with a `,`.
17+
#[derive(Debug, Clone, Eq, PartialEq)]
18+
pub struct Metric {
19+
pub(crate) name: String,
20+
pub(crate) dur: Option<Duration>,
21+
pub(crate) desc: Option<String>,
22+
}
23+
24+
impl Metric {
25+
/// Create a new instance of `Metric`.
26+
///
27+
/// # Errors
28+
///
29+
/// An error will be returned if the string values are invalid ASCII.
30+
pub fn new(name: String, dur: Option<Duration>, desc: Option<String>) -> crate::Result<Self> {
31+
crate::ensure!(name.is_ascii(), "Name should be valid ASCII");
32+
if let Some(desc) = desc.as_ref() {
33+
crate::ensure!(desc.is_ascii(), "Description should be valid ASCII");
34+
};
35+
36+
Ok(Self { name, dur, desc })
37+
}
38+
39+
/// The timing name.
40+
pub fn name(&self) -> &String {
41+
&self.name
42+
}
43+
44+
/// The timing duration.
45+
pub fn duration(&self) -> Option<Duration> {
46+
self.dur
47+
}
48+
49+
/// The timing description.
50+
pub fn description(&self) -> Option<&str> {
51+
self.desc.as_ref().map(|s| s.as_str())
52+
}
53+
}
54+
55+
impl From<Metric> for HeaderValue {
56+
fn from(entry: Metric) -> HeaderValue {
57+
let mut string = entry.name;
58+
59+
// Format a `Duration` into the format that the spec expects.
60+
let f = |d: Duration| d.as_secs_f64() * 1000.0;
61+
62+
match (entry.dur, entry.desc) {
63+
(Some(dur), Some(desc)) => {
64+
string.push_str(&format!("; dur={}; desc=\"{}\"", f(dur), desc))
65+
}
66+
(Some(dur), None) => string.push_str(&format!("; dur={}", f(dur))),
67+
(None, Some(desc)) => string.push_str(&format!("; desc=\"{}\"", desc)),
68+
(None, None) => {}
69+
};
70+
71+
// SAFETY: we validate that the values are valid ASCII on creation.
72+
unsafe { HeaderValue::from_bytes_unchecked(string.into_bytes()) }
73+
}
74+
}
75+
76+
#[cfg(test)]
77+
mod test {
78+
use super::*;
79+
use crate::headers::HeaderValue;
80+
use std::time::Duration;
81+
82+
#[test]
83+
fn encode() -> crate::Result<()> {
84+
let name = String::from("Server");
85+
let dur = Duration::from_secs(1);
86+
let desc = String::from("A server timing");
87+
88+
let val: HeaderValue = Metric::new(name.clone(), None, None)?.into();
89+
assert_eq!(val, "Server");
90+
91+
let val: HeaderValue = Metric::new(name.clone(), Some(dur), None)?.into();
92+
assert_eq!(val, "Server; dur=1000");
93+
94+
let val: HeaderValue = Metric::new(name.clone(), None, Some(desc.clone()))?.into();
95+
assert_eq!(val, r#"Server; desc="A server timing""#);
96+
97+
let val: HeaderValue = Metric::new(name.clone(), Some(dur), Some(desc.clone()))?.into();
98+
assert_eq!(val, r#"Server; dur=1000; desc="A server timing""#);
99+
Ok(())
100+
}
101+
}

0 commit comments

Comments
 (0)