Skip to content

Error enums #22

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

Merged
merged 2 commits into from
Feb 20, 2017
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
113 changes: 80 additions & 33 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,52 @@ pub struct Histogram<T: Counter> {
counts: Vec<T>,
}

/// Errors that can occur when creating a histogram.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum CreationError {
/// Lowest discernible value must be >= 1.
LowIsZero,
/// Lowest discernible value must be <= `u64::max_value() / 2` because the highest value is
/// a `u64` and the lowest value must be no bigger than half the highest.
LowExceedsMax,
/// Highest trackable value must be >= 2 * lowest discernible value for some internal
/// calculations to work out. In practice, high is typically much higher than 2 * low.
HighLessThanTwiceLow,
/// Number of significant digits must be in the range `[0, 5]`. It is capped at 5 because 5
/// significant digits is already more than almost anyone needs, and memory usage scales
/// exponentially as this increases.
SigFigExceedsMax,
/// Cannot represent sigfig worth of values beyond the lowest discernible value. Decrease the
/// significant figures, lowest discernible value, or both.
///
/// This could happen if low is very large (like 2^60) and sigfigs is 5, which requires 18
/// additional bits, which would then require more bits than will fit in a u64. Specifically,
/// the exponent of the largest power of two that is smaller than the lowest value and the bits
/// needed to represent the requested significant figures must sum to 63 or less.
CannotRepresentSigFigBeyondLow
}

/// Errors that can occur when adding another histogram.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum AdditionError {
/// The other histogram includes values that do not fit in this histogram's range.
/// Only possible when auto resize is disabled.
OtherAddendValuesExceedRange
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annoyingly, "addend" refers to both terms in an addition. The first one is called the "augend" but that's not what we care about. Oddly this value thing means that addition of non-resizing histograms is not commutative.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, interesting. I think that's probably fine though. If I remember correctly, this is the reason I implement only AddAssign, and not Add.

}

/// Errors that can occur when subtracting another histogram.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum SubtractionError {
/// The other histogram includes values that do not fit in this histogram's range.
/// Only possible when auto resize is disabled.
SubtrahendValuesExceedMinuendRange,
/// The other histogram includes counts that are higher than the current count for a value, and
/// counts cannot go negative. The subtraction may have been partially applied to some counts as
/// this error is returned when the first impossible subtraction is detected.
SubtrahendCountExceedsMinuendCount
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For subtraction we can have maximally precise (or pedantic) names, though!

}


/// Module containing the implementations of all `Histogram` iterators.
pub mod iterators;

Expand Down Expand Up @@ -356,7 +402,7 @@ impl<T: Counter> Histogram<T> {

/// Overwrite this histogram with the given histogram. All data and statistics in this
/// histogram will be overwritten.
pub fn set_to<B: Borrow<Histogram<T>>>(&mut self, source: B) -> Result<(), &'static str> {
pub fn set_to<B: Borrow<Histogram<T>>>(&mut self, source: B) -> Result<(), AdditionError> {
self.reset();
self.add(source.borrow())
}
Expand All @@ -380,15 +426,14 @@ impl<T: Counter> Histogram<T> {
///
/// May fail if values in the other histogram are higher than `.high()`, and auto-resize is
/// disabled.
pub fn add<B: Borrow<Histogram<T>>>(&mut self, source: B) -> Result<(), &'static str> {
pub fn add<B: Borrow<Histogram<T>>>(&mut self, source: B) -> Result<(), AdditionError> {
let source = source.borrow();

// make sure we can take the values in source
let top = self.highest_equivalent(self.value_for(self.last()));
if top < source.max() {
if !self.auto_resize {
return Err("The other histogram includes values that do not fit in this \
histogram's range.");
return Err(AdditionError::OtherAddendValuesExceedRange);
}
self.resize(source.max());
}
Expand Down Expand Up @@ -480,15 +525,14 @@ impl<T: Counter> Histogram<T> {
/// disabled. Or, if the count for a given value in the other histogram is higher than that of
/// this histogram. In the latter case, some of the counts may still have been updated, which
/// may cause data corruption.
pub fn subtract<B: Borrow<Histogram<T>>>(&mut self, other: B) -> Result<(), &'static str> {
pub fn subtract<B: Borrow<Histogram<T>>>(&mut self, other: B) -> Result<(), SubtractionError> {
let other = other.borrow();

// make sure we can take the values in source
let top = self.highest_equivalent(self.value_for(self.last()));
if top < other.max() {
if !self.auto_resize {
return Err("The other histogram includes values that do not fit in this \
histogram's range.");
return Err(SubtractionError::SubtrahendValuesExceedMinuendRange);
}
self.resize(other.max());
}
Expand All @@ -498,8 +542,7 @@ impl<T: Counter> Histogram<T> {
if other_count != T::zero() {
let other_value = other.value_for(i);
if self.count_at(other_value).unwrap() < other_count {
return Err("The other histogram includes counts that are higher than the \
current count for that value.");
return Err(SubtractionError::SubtrahendCountExceedsMinuendCount);
}
self.alter_n(other_value, other_count, false).expect("value should fit by now");
}
Expand Down Expand Up @@ -553,10 +596,10 @@ impl<T: Counter> Histogram<T> {
/// auto-adjusting highest trackable value. Can auto-resize up to track values up to
/// `(i64::max_value() / 2)`.
///
/// `sigfig` specifies the number of significant value digits to preserve in the recorded data.
/// This is the number of significant decimal digits to which the histogram will maintain value
/// resolution and separation. Must be a non-negative integer between 0 and 5.
pub fn new(sigfig: u8) -> Result<Histogram<T>, &'static str> {
/// See [`new_with_bounds`] for info on `sigfig`.
///
/// [`new_with_bounds`]: #method.new_with_bounds
pub fn new(sigfig: u8) -> Result<Histogram<T>, CreationError> {
let mut h = Self::new_with_bounds(1, 2, sigfig);
if let Ok(ref mut h) = h {
h.auto_resize = true;
Expand All @@ -568,41 +611,45 @@ impl<T: Counter> Histogram<T> {
/// significant decimal digits. The histogram will be constructed to implicitly track
/// (distinguish from 0) values as low as 1. Auto-resizing will be disabled.
///
/// `high` is the highest value to be tracked by the histogram, and must be a positive integer
/// that is >= 2. `sigfig` specifies the number of significant figures to maintain. This is the
/// number of significant decimal digits to which the histogram will maintain value resolution
/// and separation. Must be a non-negative integer between 0 and 5.
pub fn new_with_max(high: u64, sigfig: u8) -> Result<Histogram<T>, &'static str> {
/// See [`new_with_bounds`] for info on `high` and `sigfig`.
///
/// [`new_with_bounds`]: #method.new_with_bounds
pub fn new_with_max(high: u64, sigfig: u8) -> Result<Histogram<T>, CreationError> {
Self::new_with_bounds(1, high, sigfig)
}

/// Construct a `Histogram` with known upper and lower bounds for recorded sample values.
/// Providing a lowest discernible value (`low`) is useful is situations where the units used
/// for the histogram's values are much smaller that the minimal accuracy required. E.g. when
/// tracking time values stated in nanosecond units, where the minimal accuracy required is a
/// microsecond, the proper value for `low` would be 1000.
///
/// `low` is the lowest value that can be discerned (distinguished from 0) by the histogram,
/// and must be a positive integer that is >= 1. It may be internally rounded down to nearest
/// power of 2. `high` is the highest value to be tracked by the histogram, and must be a
/// positive integer that is `>= (2 * low)`. `sigfig` Specifies the number of significant
/// figures to maintain. This is the number of significant decimal digits to which the
/// histogram will maintain value resolution and separation. Must be a non-negative integer
/// between 0 and 5.
pub fn new_with_bounds(low: u64, high: u64, sigfig: u8) -> Result<Histogram<T>, &'static str> {
/// power of 2. Providing a lowest discernible value (`low`) is useful is situations where the
/// units used for the histogram's values are much smaller that the minimal accuracy required.
/// E.g. when tracking time values stated in nanosecond units, where the minimal accuracy
/// required is a microsecond, the proper value for `low` would be 1000. If you're not sure,
/// use 1.
///
/// `high` is the highest value to be tracked by the histogram, and must be a
/// positive integer that is `>= (2 * low)`. If you're not sure, use `u64::max_value()`.
///
/// `sigfig` Specifies the number of significant figures to maintain. This is the number of
/// significant decimal digits to which the histogram will maintain value resolution and
/// separation. Must be in the range [0, 5]. If you're not sure, use 3. As `sigfig` increases,
/// memory usage grows exponentially, so choose carefully if there will be many histograms in
/// memory at once or if storage is otherwise a concern.
pub fn new_with_bounds(low: u64, high: u64, sigfig: u8) -> Result<Histogram<T>, CreationError> {
// Verify argument validity
if low < 1 {
return Err("lowest discernible value must be >= 1");
return Err(CreationError::LowIsZero);
}
if low > u64::max_value() / 2 {
// avoid overflow in 2 * low
return Err("lowest discernible value must be <= u64::max_value() / 2")
return Err(CreationError::LowExceedsMax)
}
if high < 2 * low {
return Err("highest trackable value must be >= 2 * lowest discernible value");
return Err(CreationError::HighLessThanTwiceLow);
}
if sigfig > 5 {
return Err("number of significant digits must be between 0 and 5");
return Err(CreationError::SigFigExceedsMax);
}

// Given a 3 decimal point accuracy, the expectation is obviously for "+/- 1 unit at 1000".
Expand Down Expand Up @@ -634,7 +681,7 @@ impl<T: Counter> Histogram<T> {
// histogram vs ones whose magnitude here fits in 63 bits is debatable, and it makes
// it harder to work through the logic. Sums larger than 64 are totally broken as
// leading_zero_count_base would go negative.
return Err("Cannot represent sigfig worth of values beyond low");
return Err(CreationError::CannotRepresentSigFigBeyondLow);
};

let sub_bucket_half_count = sub_bucket_count / 2;
Expand Down
4 changes: 2 additions & 2 deletions src/tests/index_calculation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::Histogram;
use super::super::{CreationError, Histogram};
use tests::helpers::histo64;

#[test]
Expand Down Expand Up @@ -114,7 +114,7 @@ fn unit_magnitude_52_sub_bucket_magnitude_11_index_calculations() {

#[test]
fn unit_magnitude_53_sub_bucket_magnitude_11_throws() {
assert_eq!("Cannot represent sigfig worth of values beyond low",
assert_eq!(CreationError::CannotRepresentSigFigBeyondLow,
Histogram::<u64>::new_with_bounds(1_u64 << 53, 1_u64 << 63, 3).unwrap_err());
}

Expand Down
4 changes: 2 additions & 2 deletions src/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::Histogram;
use super::{CreationError, Histogram};

#[path = "helpers.rs"]
mod helpers;
Expand All @@ -10,5 +10,5 @@ mod index_calculation;
#[test]
fn new_err_high_not_double_low() {
let res = Histogram::<u64>::new_with_bounds(10, 15, 0);
assert_eq!("highest trackable value must be >= 2 * lowest discernible value", res.unwrap_err());
assert_eq!(CreationError::HighLessThanTwiceLow, res.unwrap_err());
}