Skip to content

Commit cf9ec24

Browse files
Parse fractional seconds into a Duration directly.
Parsing into an f64 then separately truncating to an int and multiplying the fractional part by 1_000_000_000 introduced errors in the fractional part since frequently abc.xyz cannot produce 0.xyz as an f64, but has to have 0.xyz0000001 or something like that.
1 parent bba8970 commit cf9ec24

File tree

4 files changed

+149
-44
lines changed

4 files changed

+149
-44
lines changed

benches/record.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,9 @@ fn do_add_benchmark<F: Fn() -> Histogram<u64>>(
175175
addends.push(h);
176176
}
177177

178-
b.iter(|| for h in addends.iter() {
179-
accum.add(h).unwrap();
178+
b.iter(|| {
179+
for h in addends.iter() {
180+
accum.add(h).unwrap();
181+
}
180182
})
181183
}

src/serialization/benchmarks.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,10 @@ fn do_varint_write_rand(b: &mut Bencher, range: Range<u64>) {
6363
}
6464

6565
let mut buf = [0; 9];
66-
b.iter(|| for i in vec.iter() {
67-
let _ = varint_write(*i, &mut buf);
66+
b.iter(|| {
67+
for i in vec.iter() {
68+
let _ = varint_write(*i, &mut buf);
69+
}
6870
});
6971
}
7072

src/serialization/interval_log/mod.rs

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ extern crate base64;
166166
use std::{fmt, io, ops, str, time};
167167
use std::fmt::Write;
168168

169-
use nom::{double, IResult};
169+
use nom::{double, is_digit, ErrorKind, IResult};
170170

171171
use super::super::{Counter, Histogram};
172172
use super::Serializer;
@@ -450,9 +450,8 @@ impl<'a> ops::Deref for Tag<'a> {
450450
#[derive(PartialEq, Debug)]
451451
pub struct IntervalLogHistogram<'a> {
452452
tag: Option<Tag<'a>>,
453-
start_timestamp: f64,
454-
// lazily map to Duration to save parsing time and a few bytes of space on ILH
455-
duration: f64,
453+
start_timestamp: time::Duration,
454+
duration: time::Duration,
456455
max: f64,
457456
encoded_histogram: &'a str,
458457
}
@@ -470,12 +469,12 @@ impl<'a> IntervalLogHistogram<'a> {
470469
/// the log, in which case you may wish to consider this number as a delta vs those timestamps.
471470
/// See the module-level documentation about timestamps.
472471
pub fn start_timestamp(&self) -> time::Duration {
473-
fp_seconds_as_duration(self.start_timestamp)
472+
self.start_timestamp
474473
}
475474

476475
/// Duration of the interval in seconds.
477476
pub fn duration(&self) -> time::Duration {
478-
fp_seconds_as_duration(self.duration)
477+
self.duration
479478
}
480479

481480
/// Max value in the encoded histogram
@@ -506,9 +505,9 @@ impl<'a> IntervalLogHistogram<'a> {
506505
#[allow(variant_size_differences)]
507506
pub enum LogEntry<'a> {
508507
/// Logs may include a StartTime. If present, it represents seconds since the epoch.
509-
StartTime(f64),
508+
StartTime(time::Duration),
510509
/// Logs may include a BaseTime. If present, it represents seconds since the epoch.
511-
BaseTime(f64),
510+
BaseTime(time::Duration),
512511
/// An individual interval histogram.
513512
Interval(IntervalLogHistogram<'a>),
514513
}
@@ -606,13 +605,6 @@ impl<'a> Iterator for IntervalLogIterator<'a> {
606605
}
607606
}
608607

609-
fn fp_seconds_as_duration(fp_secs: f64) -> time::Duration {
610-
let secs = fp_secs as u64;
611-
// can't overflow because this can be at most 1 billion which is approx 2^30
612-
let nsecs = (fp_secs.fract() * 1_000_000_000_f64) as u32;
613-
time::Duration::new(secs, nsecs)
614-
}
615-
616608
fn duration_as_fp_seconds(d: time::Duration) -> f64 {
617609
d.as_secs() as f64 + d.subsec_nanos() as f64 / 1_000_000_000_f64
618610
}
@@ -629,19 +621,19 @@ fn system_time_as_fp_seconds(time: time::SystemTime) -> f64 {
629621
named!(start_time<&[u8], LogEntry>,
630622
do_parse!(
631623
tag!("#[StartTime: ") >>
632-
n: double >>
624+
dur: fract_sec_duration >>
633625
char!(' ') >>
634626
take_until_and_consume!("\n") >>
635-
(LogEntry::StartTime(n))
627+
(LogEntry::StartTime(dur))
636628
));
637629

638630
named!(base_time<&[u8], LogEntry>,
639631
do_parse!(
640632
tag!("#[BaseTime: ") >>
641-
n: double >>
633+
dur: fract_sec_duration >>
642634
char!(' ') >>
643635
take_until_and_consume!("\n") >>
644-
(LogEntry::BaseTime(n))
636+
(LogEntry::BaseTime(dur))
645637
));
646638

647639
named!(interval_hist<&[u8], LogEntry>,
@@ -652,9 +644,9 @@ named!(interval_hist<&[u8], LogEntry>,
652644
map!(pair!(tag!("Tag="), take_until_and_consume!(",")), |p| p.1),
653645
str::from_utf8),
654646
|s| Tag(s))) >>
655-
start_timestamp: double >>
647+
start_timestamp: fract_sec_duration >>
656648
char!(',') >>
657-
duration: double >>
649+
duration: fract_sec_duration >>
658650
char!(',') >>
659651
max: double >>
660652
char!(',') >>
@@ -669,7 +661,8 @@ named!(interval_hist<&[u8], LogEntry>,
669661
)
670662
);
671663

672-
named!(log_entry<&[u8], LogEntry>, alt_complete!(start_time | base_time | interval_hist));
664+
named!(log_entry<&[u8], LogEntry>,
665+
alt_complete!(start_time | base_time | interval_hist));
673666

674667
named!(comment_line<&[u8], ()>,
675668
do_parse!(tag!("#") >> take_until_and_consume!("\n") >> (()))
@@ -681,5 +674,44 @@ named!(legend<&[u8], ()>,
681674

682675
named!(ignored_line<&[u8], ()>, alt!(comment_line | legend));
683676

677+
fn fract_sec_duration(input: &[u8]) -> IResult<&[u8], time::Duration> {
678+
match fract_sec_tuple(input) {
679+
IResult::Done(rest, data) => {
680+
let (secs, nanos_str) = data;
681+
682+
// only read up to 9 digits since we can only support nanos, not smaller precision
683+
let nanos_parse_res = if nanos_str.len() > 9 {
684+
nanos_str[0..9].parse::<u32>()
685+
} else if nanos_str.len() == 9 {
686+
nanos_str.parse::<u32>()
687+
} else {
688+
nanos_str
689+
.parse::<u32>()
690+
// subtraction will not overflow because len is < 9
691+
.map(|n| n * 10_u32.pow(9 - nanos_str.len() as u32))
692+
};
693+
694+
if let Ok(nanos) = nanos_parse_res {
695+
return IResult::Done(rest, time::Duration::new(secs, nanos));
696+
}
697+
698+
// nanos were invalid utf8. We don't expose these errors, so don't bother defining a
699+
// custom error type.
700+
return IResult::Error(ErrorKind::Custom(0));
701+
}
702+
IResult::Error(e) => return IResult::Error(e),
703+
IResult::Incomplete(n) => return IResult::Incomplete(n),
704+
}
705+
}
706+
707+
named!(fract_sec_tuple<&[u8], (u64, &str)>,
708+
do_parse!(
709+
secs: flat_map!(recognize!(take_until!(".")), parse_to!(u64)) >>
710+
tag!(".") >>
711+
nanos_str: map_res!(take_while1!(is_digit), str::from_utf8) >>
712+
(secs, nanos_str)
713+
)
714+
);
715+
684716
#[cfg(test)]
685717
mod tests;

src/serialization/interval_log/tests.rs

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
extern crate rand;
2+
3+
use self::rand::Rng;
4+
15
use std::{iter, time};
26
use std::ops::Add;
37

@@ -72,7 +76,12 @@ fn write_comment_control_characters_still_parseable() {
7276
assert_eq!(&expected, str::from_utf8(&buf[..]).unwrap());
7377

7478
let mut i = IntervalLogIterator::new(&buf);
75-
assert_eq!(Some(Ok(LogEntry::StartTime(123.456))), i.next());
79+
assert_eq!(
80+
Some(Ok(
81+
LogEntry::StartTime(time::Duration::new(123, 456_000_000))
82+
)),
83+
i.next()
84+
);
7685
assert_eq!(None, i.next());
7786
}
7887

@@ -214,13 +223,73 @@ fn write_base_time() {
214223
);
215224
}
216225

226+
#[test]
227+
fn parse_duration_full_ns() {
228+
let (rest, dur) = fract_sec_duration(b"123456.789012345foo").unwrap();
229+
230+
assert_eq!(time::Duration::new(123456, 789_012_345), dur);
231+
assert_eq!(b"foo", rest);
232+
}
233+
234+
#[test]
235+
fn parse_duration_scale_ns() {
236+
let (rest, dur) = fract_sec_duration(b"123456.789012foo").unwrap();
237+
238+
assert_eq!(time::Duration::new(123456, 789_012_000), dur);
239+
assert_eq!(b"foo", rest);
240+
}
241+
242+
#[test]
243+
fn parse_duration_too_many_ns() {
244+
let (rest, dur) = fract_sec_duration(b"123456.7890123456foo").unwrap();
245+
246+
// consumes all the numbers, but only parses the first 9
247+
assert_eq!(time::Duration::new(123456, 789_012_345), dur);
248+
assert_eq!(b"foo", rest);
249+
}
250+
251+
#[test]
252+
fn duration_fp_roundtrip_accuracy() {
253+
let mut rng = rand::thread_rng();
254+
255+
let mut buf = String::new();
256+
let mut errors = Vec::new();
257+
for _ in 0..100_000 {
258+
buf.clear();
259+
260+
// pick seconds
261+
let secs = rng.gen_range(0, 2_000_000_000);
262+
// pick nsecs that only has ms accuracy
263+
let nsecs = rng.gen_range(0, 1000) * 1000_000;
264+
265+
let dur = time::Duration::new(secs, nsecs);
266+
let fp_secs = duration_as_fp_seconds(dur);
267+
268+
write!(&mut buf, "{:.3}", fp_secs).unwrap();
269+
270+
let (_, dur2) = fract_sec_duration(buf.as_bytes()).unwrap();
271+
272+
if dur != dur2 {
273+
errors.push((dur, dur2));
274+
}
275+
}
276+
277+
if !errors.is_empty() {
278+
for &(dur, dur2) in &errors {
279+
println!("{:?} -> {:?}", dur, dur2);
280+
}
281+
}
282+
283+
assert_eq!(0, errors.len());
284+
}
285+
217286
#[test]
218287
fn parse_start_time_with_human_date() {
219288
let (rest, e) = start_time(
220289
b"#[StartTime: 1441812279.474 (seconds since epoch), Wed Sep 09 08:24:39 PDT 2015]\nfoo",
221290
).unwrap();
222291

223-
let expected = LogEntry::StartTime(1441812279.474);
292+
let expected = LogEntry::StartTime(time::Duration::new(1441812279, 474_000_000));
224293

225294
assert_eq!(expected, e);
226295
assert_eq!(b"foo", rest);
@@ -233,7 +302,7 @@ fn parse_start_time_without_human_date() {
233302
// Also, BaseTime doesn't have a human-formatted time.
234303
let (rest, e) = start_time(b"#[StartTime: 1441812279.474 (seconds since epoch)]\nfoo").unwrap();
235304

236-
let expected = LogEntry::StartTime(1441812279.474);
305+
let expected = LogEntry::StartTime(time::Duration::new(1441812279, 474_000_000));
237306

238307
assert_eq!(expected, e);
239308
assert_eq!(b"foo", rest);
@@ -243,7 +312,7 @@ fn parse_start_time_without_human_date() {
243312
fn parse_base_time() {
244313
let (rest, e) = base_time(b"#[BaseTime: 1441812279.474 (seconds since epoch)]\nfoo").unwrap();
245314

246-
let expected = LogEntry::BaseTime(1441812279.474);
315+
let expected = LogEntry::BaseTime(time::Duration::new(1441812279, 474_000_000));
247316

248317
assert_eq!(expected, e);
249318
assert_eq!(b"foo", rest);
@@ -271,8 +340,8 @@ fn parse_interval_hist_no_tag() {
271340

272341
let expected = LogEntry::Interval(IntervalLogHistogram {
273342
tag: None,
274-
start_timestamp: 0.127,
275-
duration: 1.007,
343+
start_timestamp: time::Duration::new(0, 127_000_000),
344+
duration: time::Duration::new(1, 7_000_000),
276345
max: 2.769,
277346
encoded_histogram: "couldBeBase64",
278347
});
@@ -287,8 +356,8 @@ fn parse_interval_hist_with_tag() {
287356

288357
let expected = LogEntry::Interval(IntervalLogHistogram {
289358
tag: Some(Tag("t")),
290-
start_timestamp: 0.127,
291-
duration: 1.007,
359+
start_timestamp: time::Duration::new(0, 127_000_000),
360+
duration: time::Duration::new(1, 7_000_000),
292361
max: 2.769,
293362
encoded_histogram: "couldBeBase64",
294363
});
@@ -311,13 +380,13 @@ fn iter_with_ignored_prefix() {
311380

312381
let expected0 = LogEntry::Interval(IntervalLogHistogram {
313382
tag: Some(Tag("t")),
314-
start_timestamp: 0.127,
315-
duration: 1.007,
383+
start_timestamp: time::Duration::new(0, 127_000_000),
384+
duration: time::Duration::new(1, 7_000_000),
316385
max: 2.769,
317386
encoded_histogram: "couldBeBase64",
318387
});
319388

320-
let expected1 = LogEntry::StartTime(1441812279.474);
389+
let expected1 = LogEntry::StartTime(time::Duration::new(1441812279, 474_000_000));
321390

322391
assert_eq!(vec![expected0, expected1], entries)
323392
}
@@ -334,13 +403,13 @@ fn iter_without_ignored_prefix() {
334403

335404
let expected0 = LogEntry::Interval(IntervalLogHistogram {
336405
tag: Some(Tag("t")),
337-
start_timestamp: 0.127,
338-
duration: 1.007,
406+
start_timestamp: time::Duration::new(0, 127_000_000),
407+
duration: time::Duration::new(1, 7_000_000),
339408
max: 2.769,
340409
encoded_histogram: "couldBeBase64",
341410
});
342411

343-
let expected1 = LogEntry::StartTime(1441812279.474);
412+
let expected1 = LogEntry::StartTime(time::Duration::new(1441812279, 474_000_000));
344413

345414
assert_eq!(vec![expected0, expected1], entries)
346415
}
@@ -363,14 +432,14 @@ fn iter_multiple_entrties_with_interleaved_ignored() {
363432

364433
let expected0 = LogEntry::Interval(IntervalLogHistogram {
365434
tag: Some(Tag("t")),
366-
start_timestamp: 0.127,
367-
duration: 1.007,
435+
start_timestamp: time::Duration::new(0, 127_000_000),
436+
duration: time::Duration::new(1, 7_000_000),
368437
max: 2.769,
369438
encoded_histogram: "couldBeBase64",
370439
});
371440

372-
let expected1 = LogEntry::StartTime(1441812279.474);
373-
let expected2 = LogEntry::BaseTime(1441812279.474);
441+
let expected1 = LogEntry::StartTime(time::Duration::new(1441812279, 474_000_000));
442+
let expected2 = LogEntry::BaseTime(time::Duration::new(1441812279, 474_000_000));
374443

375444
assert_eq!(vec![expected0, expected1, expected2], entries)
376445
}

0 commit comments

Comments
 (0)