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

Add wrapper types to move from raw integers to types. #10

Merged
merged 1 commit into from
Feb 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ keywords = ["rdtsc", "timing", "nanosecond"]
[features]
asm = ["tsc"]
tsc = []
metrics = ["metrics-core"]

[dependencies]
libc = "0.2.50"
libc = "^0.2"
metrics-core = { version = "^0.5", optional = true }

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.6", features = ["profileapi"] }
Expand Down
175 changes: 175 additions & 0 deletions src/instant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#[cfg(feature = "metrics")]
use metrics_core::AsNanoseconds;

use std::fmt;
use std::ops::{Add, AddAssign, Sub, SubAssign};
use std::time::Duration;

/// A point-in-time wall-clock measurement.
///
/// Represents a time measurement that has been taken by [`Clock`](crate::Clock) and scaled to wall-clock time.
///
/// Unlike the stdlib `Instant`, this type has two meaningful differences:
/// - It provides no guarantees around monotonicity whatsoever, beyond any guarantees provided by
/// `Clock` itself.
/// - It is intended to be opaque, but the internal value can be accessed. There are no guarantees
/// on the internal value timebase, or other factors, remaining stable over time and this
/// convenience is only intended for comparisons of `Instant`s provided by the same exact `Clock`
/// instance.
///
/// An `Instant` is 8 bytes.
#[derive(Clone, Copy)]
pub struct Instant(pub(crate) u64);

impl Instant {
pub(crate) fn new(inner: u64) -> Self {
Instant(inner)
}

/// Returns the amount of time elapsed from another instant to this one.
///
/// # Panics
///
/// This function will panic if `earlier` is later than `self`.
///
/// # Examples
///
/// ```no_run
/// use quanta::Clock;
/// use std::time::Duration;
/// use std::thread::sleep;
///
/// let clock = Clock::new();
/// let now = clock.now();
/// sleep(Duration::new(1, 0));
/// let new_now = clock.now();
/// println!("{:?}", new_now.duration_since(now));
/// ```
pub fn duration_since(&self, earlier: Instant) -> Duration {
self.0.checked_sub(earlier.0)
.map(Duration::from_nanos)
.expect("supplied instant is later than self")
}

/// Returns the amount of time elapsed from another instant to this one,
/// or `None` if that instant is earlier than this one.
///
/// # Examples
///
/// ```no_run
/// use quanta::Clock;
/// use std::time::Duration;
/// use std::thread::sleep;
///
/// let clock = Clock::new();
/// let now = clock.now();
/// sleep(Duration::new(1, 0));
/// let new_now = clock.now();
/// println!("{:?}", new_now.checked_duration_since(now));
/// println!("{:?}", now.checked_duration_since(new_now)); // None
/// ```
pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
self.0.checked_sub(earlier.0)
.map(Duration::from_nanos)
}

/// Returns the amount of time elapsed from another instant to this one,
/// or zero duration if that instant is earlier than this one.
///
/// # Examples
///
/// ```no_run
/// use quanta::Clock;
/// use std::time::Duration;
/// use std::thread::sleep;
///
/// let clock = Clock::new();
/// let now = clock.now();
/// sleep(Duration::new(1, 0));
/// let new_now = clock.now();
/// println!("{:?}", new_now.saturating_duration_since(now));
/// println!("{:?}", now.saturating_duration_since(new_now)); // 0ns
/// ```
pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
self.checked_duration_since(earlier)
.unwrap_or(Duration::new(0, 0))
}

/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
/// otherwise.
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
self.0.checked_add(duration.as_nanos() as u64)
.map(Instant)
}

/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
/// otherwise.
pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
self.0.checked_sub(duration.as_nanos() as u64)
.map(Instant)
}

/// Gets the inner value of this `Instant`.
pub fn as_u64(&self) -> u64 {
self.0
}
}

impl Add<Duration> for Instant {
type Output = Instant;

/// # Panics
///
/// This function may panic if the resulting point in time cannot be represented by the
/// underlying data structure. See [`Instant::checked_add`] for a version without panic.
fn add(self, other: Duration) -> Instant {
self.checked_add(other)
.expect("overflow when adding duration to instant")
}
}

impl AddAssign<Duration> for Instant {
fn add_assign(&mut self, other: Duration) {
// This is not millenium-safe, but, I think that's OK. :)
self.0 = self.0 + other.as_nanos() as u64;
}
}

impl Sub<Duration> for Instant {
type Output = Instant;

fn sub(self, other: Duration) -> Instant {
self.checked_sub(other)
.expect("overflow when subtracting duration from instant")
}
}

impl SubAssign<Duration> for Instant {
fn sub_assign(&mut self, other: Duration) {
// This is not millenium-safe, but, I think that's OK. :)
self.0 = self.0 - other.as_nanos() as u64;
}
}

impl Sub<Instant> for Instant {
type Output = Duration;

fn sub(self, other: Instant) -> Duration {
self.duration_since(other)
}
}

impl fmt::Debug for Instant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

#[cfg(feature = "metrics")]
impl AsNanoseconds for Instant {
fn as_nanos(&self) -> u64 {
self.0
}
}
46 changes: 26 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use std::sync::{
atomic::{AtomicU64, Ordering},
Arc,
};
use std::time::Duration;

mod monotonic;
use self::monotonic::Monotonic;
Expand All @@ -59,6 +60,8 @@ mod counter;
use self::counter::Counter;
mod mock;
pub use self::mock::{IntoNanoseconds, Mock};
mod instant;
pub use self::instant::Instant;
mod upkeep;
pub use self::upkeep::{Builder, Handle};

Expand Down Expand Up @@ -222,11 +225,11 @@ impl Clock {

/// Gets the current time, scaled to reference time.
///
/// Value is in nanoseconds.
pub fn now(&self) -> u64 {
/// Returns an [`Instant`]
pub fn now(&self) -> Instant {
match &self.inner {
ClockType::Optimized(_, source, _) => self.scaled(source.now()),
ClockType::Mock(mock) => mock.now(),
ClockType::Mock(mock) => Instant(mock.now()),
}
}

Expand Down Expand Up @@ -291,17 +294,18 @@ impl Clock {
/// measurement is not guaranteed to be in nanoseconds and may vary. It is only OK to avoid
/// scaling raw measurements if you don't need actual nanoseconds.
///
/// Value is in nanoseconds.
pub fn scaled(&self, value: u64) -> u64 {
/// Returns an [`Instant`].
pub fn scaled(&self, value: u64) -> Instant {
match &self.inner {
ClockType::Optimized(_, _, calibration) => {
if calibration.identical {
let scaled = if calibration.identical {
value
} else {
(((value as f64 - calibration.src_time) * calibration.hz_ratio) + calibration.ref_time) as u64
}
};
Instant(scaled)
},
ClockType::Mock(_) => value,
ClockType::Mock(_) => Instant(value),
}
}

Expand All @@ -313,12 +317,13 @@ impl Clock {
/// measurements, or a start/end measurement, than using [`scaled`] for both conversions.
///
/// [`scaled`]: Clock::scaled
pub fn delta(&self, start: u64, end: u64) -> u64 {
pub fn delta(&self, start: u64, end: u64) -> Duration {
let raw_delta = end.wrapping_sub(start);
match &self.inner {
let scaled = match &self.inner {
ClockType::Optimized(_, _, calibration) => (raw_delta as f64 * calibration.hz_ratio) as u64,
ClockType::Mock(_) => raw_delta,
}
};
Duration::from_nanos(scaled)
}

/// Gets the most recent current time, scaled to reference time.
Expand All @@ -340,16 +345,16 @@ impl Clock {
///
/// If the upkeep thread has not been started, the return value will be `0`.
///
/// Value is in nanoseconds.
pub fn recent(&self) -> u64 {
/// Returns an [`Instant`].
pub fn recent(&self) -> Instant {
match &self.inner {
ClockType::Optimized(_, _, _) => GLOBAL_RECENT.load(Ordering::Relaxed),
ClockType::Mock(mock) => mock.now(),
ClockType::Optimized(_, _, _) => Instant::new(GLOBAL_RECENT.load(Ordering::Relaxed)),
ClockType::Mock(mock) => Instant::new(mock.now()),
}
}

/// Updates the recent current time.
pub(crate) fn upkeep(value: u64) { GLOBAL_RECENT.store(value, Ordering::Release); }
pub(crate) fn upkeep(value: Instant) { GLOBAL_RECENT.store(value.0, Ordering::Release); }
}

impl Default for Clock {
Expand All @@ -363,15 +368,15 @@ mod tests {
#[test]
fn test_mock() {
let (clock, mock) = Clock::mock();
assert_eq!(clock.now(), 0);
assert_eq!(clock.now().as_u64(), 0);
mock.increment(42);
assert_eq!(clock.now(), 42);
assert_eq!(clock.now().as_u64(), 42);
}

#[test]
fn test_now() {
let clock = Clock::new();
assert!(clock.now() > 0);
assert!(clock.now().as_u64() > 0);
}

#[test]
Expand All @@ -396,6 +401,7 @@ mod tests {
fn test_scaled() {
let clock = Clock::new();
let raw = clock.raw();
assert!(clock.scaled(raw) > 0);
let scaled = clock.scaled(raw);
assert!(scaled.as_u64() > 0);
}
}