1use std::{
37 cmp::Ordering,
38 fmt::{self, Debug, Display},
39 hash::{Hash, Hasher},
40 marker::PhantomData,
41 ops::{Deref, DerefMut},
42};
43
44use crate::{MetricValue, Observation, ValidationError, Value, ValueWriter, value::MetricFlags};
45
46#[non_exhaustive]
51#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum Unit {
53 #[default]
55 None,
56 Count,
58 Percent,
60 Second(NegativeScale),
62 Byte(PositiveScale),
64 BytePerSecond(PositiveScale),
66 Bit(PositiveScale),
68 BitPerSecond(PositiveScale),
70 Custom(&'static str),
79}
80
81#[cfg(feature = "serde")]
82impl serde::Serialize for Unit {
83 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84 where
85 S: serde::Serializer,
86 {
87 serde::Serialize::serialize(self.name(), serializer)
88 }
89}
90
91impl Unit {
92 pub const fn name(self) -> &'static str {
94 macro_rules! positive_scale {
95 ($scale:expr, $base:literal, $scaled:literal) => {
96 match $scale {
97 PositiveScale::One => $base,
98 PositiveScale::Kilo => concat!("Kilo", $scaled),
99 PositiveScale::Mega => concat!("Mega", $scaled),
100 PositiveScale::Giga => concat!("Giga", $scaled),
101 PositiveScale::Tera => concat!("Tera", $scaled),
102 }
103 };
104 }
105
106 match self {
107 Self::None => "None",
108 Self::Count => "Count",
109 Self::Percent => "Percent",
110 Self::Second(scale) => match scale {
111 NegativeScale::Micro => "Microseconds",
112 NegativeScale::Milli => "Milliseconds",
113 NegativeScale::One => "Seconds",
114 },
115 Self::Byte(scale) => positive_scale!(scale, "Bytes", "bytes"),
116 Self::BytePerSecond(scale) => positive_scale!(scale, "Bytes/Second", "bytes/Second"),
117 Self::Bit(scale) => positive_scale!(scale, "Bits", "bits"),
118 Self::BitPerSecond(scale) => positive_scale!(scale, "Bits/Second", "bits/Second"),
119 Self::Custom(unit) => unit,
120 }
121 }
122}
123
124impl fmt::Debug for Unit {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 f.write_str(self.name())
127 }
128}
129
130impl fmt::Display for Unit {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 f.write_str(self.name())
133 }
134}
135
136#[non_exhaustive]
138#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
139pub enum NegativeScale {
140 Micro,
142 Milli,
144 #[default]
145 One,
147}
148
149#[non_exhaustive]
151#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
152pub enum PositiveScale {
153 #[default]
155 One,
156 Kilo,
158 Mega,
160 Giga,
162 Tera,
164}
165
166impl NegativeScale {
167 pub const fn reduction_factor(self) -> u64 {
176 match self {
177 Self::Micro => 1_000_000,
178 Self::Milli => 1_000,
179 Self::One => 1,
180 }
181 }
182}
183
184impl PositiveScale {
185 pub const fn expansion_factor(self) -> u64 {
194 match self {
195 Self::One => 1,
196 Self::Kilo => 1_000,
197 Self::Mega => 1_000_000,
198 Self::Giga => 1_000_000_000,
199 Self::Tera => 1_000_000_000_000,
200 }
201 }
202}
203
204pub trait UnitTag {
208 const UNIT: Unit;
210}
211
212pub trait Convert<U: UnitTag>: UnitTag {
245 const RATIO: f64;
247
248 fn convert(observation: Observation) -> Observation {
250 if Self::RATIO == 1.0 {
252 return observation;
253 }
254
255 match observation {
256 Observation::Unsigned(u) => Observation::Floating((u as f64) * Self::RATIO),
257 Observation::Floating(f) => Observation::Floating(f * Self::RATIO),
258 Observation::Repeated { total, occurrences } => Observation::Repeated {
259 total: total * Self::RATIO,
260 occurrences,
261 },
262 }
263 }
264}
265
266macro_rules! unit_tag {
267 ($struct:ident, $conversion:ident, $value:expr) => {
268 #[doc = concat!("[`UnitTag`] type that can be used to tag a value with `", stringify!($value), "`.")]
269 pub struct $struct;
270
271 impl UnitTag for $struct {
272 const UNIT: Unit = $value;
273 }
274
275 impl fmt::Debug for $struct {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 fmt::Debug::fmt(&Self::UNIT, f)
278 }
279 }
280
281 impl fmt::Display for $struct {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 fmt::Display::fmt(&Self::UNIT, f)
284 }
285 }
286
287 #[doc = concat!(
288 "Wrapper type that will cause the underlying unit be [`Convert::convert`]ed to `",
289 stringify!($value),
290 "` when written."
291 )]
292 pub type $conversion<V> = WithUnit<V, $struct>;
293 };
294}
295
296unit_tag!(None, AsNone, Unit::None);
299
300impl<U: UnitTag> Convert<U> for None {
301 const RATIO: f64 = 1.0;
302}
303
304unit_tag!(Count, AsCount, Unit::Count);
305unit_tag!(Percent, AsPercent, Unit::Percent);
306
307trait TimeTag: UnitTag {
310 const FROM_SECONDS: u64;
311}
312
313macro_rules! time_unit_tag {
314 ($($struct:ident, $conversion:ident, $scale:ident;)*) => {
315 $(
316 unit_tag!($struct, $conversion, Unit::Second(NegativeScale::$scale));
317
318 impl TimeTag for $struct {
319 const FROM_SECONDS: u64 = NegativeScale::$scale.reduction_factor();
320 }
321
322 impl<U: TimeTag> Convert<U> for $struct {
323 const RATIO: f64 = (U::FROM_SECONDS as f64)/(Self::FROM_SECONDS as f64);
324 }
325 )*
326 };
327}
328
329time_unit_tag! {
330 Second, AsSeconds, One;
331 Millisecond, AsMilliseconds, Milli;
332 Microsecond, AsMicroseconds, Micro;
333}
334
335trait BitTag: UnitTag {
338 const FROM_BITS: u64;
339}
340
341macro_rules! bit_unit_tag {
342 ($($struct:ident, $conversion:ident, $base:ident, $bits:expr, $scale:ident;)*) => {
343 $(
344 unit_tag!($struct, $conversion, Unit::$base(PositiveScale::$scale));
345
346 impl BitTag for $struct {
347 const FROM_BITS: u64 = $bits*PositiveScale::$scale.expansion_factor();
348 }
349
350 impl<U: BitTag> Convert<U> for $struct {
351 const RATIO: f64 = (Self::FROM_BITS as f64)/(U::FROM_BITS as f64);
352 }
353 )*
354 };
355}
356
357bit_unit_tag! {
358 Byte, AsBytes, Byte, 8, One;
359 Kilobyte, AsKilobytes, Byte, 8, Kilo;
360 Megabyte, AsMegabytes, Byte, 8, Mega;
361 Gigabyte, AsGigabytes, Byte, 8, Giga;
362 Terabyte, AsTerabytes, Byte, 8, Tera;
363
364 Bit, AsBits, Bit, 1, One;
365 Kilobit, AsKilobits, Bit, 1, Kilo;
366 Megabit, AsMegabits, Bit, 1, Mega;
367 Gigabit, AsGigabits, Bit, 1, Giga;
368 Terabit, AsTerabits, Bit, 1, Tera;
369
370 BytePerSecond, AsBytesPerSecond, BytePerSecond, 8, One;
371 KilobytePerSecond, AsKilobytesPerSecond, BytePerSecond, 8, Kilo;
372 MegabytePerSecond, AsMegabytesPerSecond, BytePerSecond, 8, Mega;
373 GigabytePerSecond, AsGigabytesPerSecond, BytePerSecond, 8, Giga;
374 TerabytePerSecond, AsTerabytesPerSecond, BytePerSecond, 8, Tera;
375
376 BitPerSecond, AsBitsPerSecond, BitPerSecond, 1, One;
377 KilobitPerSecond, AsKilobitsPerSecond, BitPerSecond, 1, Kilo;
378 MegabitPerSecond, AsMegabitsPerSecond, BitPerSecond, 1, Mega;
379 GigabitPerSecond, AsGigabitsPerSecond, BitPerSecond, 1, Giga;
380 TerabitPerSecond, AsTerabitsPerSecond, BitPerSecond, 1, Tera;
381}
382
383pub struct WithUnit<V, U> {
414 value: V,
415 _unit_tag: PhantomData<U>,
416}
417
418impl<V: MetricValue, U> From<V> for WithUnit<V, U> {
419 fn from(value: V) -> Self {
420 Self {
421 value,
422 _unit_tag: PhantomData,
423 }
424 }
425}
426
427impl<V, U> WithUnit<V, U> {
428 pub fn into_inner(self) -> V {
430 self.value
431 }
432}
433
434impl<V: Default + MetricValue, U> Default for WithUnit<V, U> {
437 fn default() -> Self {
438 Self {
439 value: V::default(),
440 _unit_tag: PhantomData,
441 }
442 }
443}
444
445impl<V, U> Deref for WithUnit<V, U> {
446 type Target = V;
447
448 fn deref(&self) -> &Self::Target {
449 &self.value
450 }
451}
452
453impl<V, U> DerefMut for WithUnit<V, U> {
454 fn deref_mut(&mut self) -> &mut Self::Target {
455 &mut self.value
456 }
457}
458
459impl<V: Clone, U> Clone for WithUnit<V, U> {
460 fn clone(&self) -> Self {
461 Self {
462 value: self.value.clone(),
463 _unit_tag: PhantomData,
464 }
465 }
466}
467
468impl<V: Copy, U> Copy for WithUnit<V, U> {}
469
470impl<V: PartialEq, U> PartialEq for WithUnit<V, U> {
471 fn eq(&self, other: &Self) -> bool {
472 self.value == other.value
473 }
474}
475
476impl<V: Eq, U> Eq for WithUnit<V, U> {}
477
478impl<V: PartialOrd, U> PartialOrd for WithUnit<V, U> {
479 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
480 self.value.partial_cmp(&other.value)
481 }
482}
483
484impl<V: Ord, U> Ord for WithUnit<V, U> {
485 fn cmp(&self, other: &Self) -> Ordering {
486 self.value.cmp(&other.value)
487 }
488}
489
490impl<V: Hash, U: UnitTag> Hash for WithUnit<V, U> {
491 fn hash<H: Hasher>(&self, state: &mut H) {
492 self.value.hash(state);
493 U::UNIT.hash(state);
494 }
495}
496
497impl<V: Debug, U: UnitTag> fmt::Debug for WithUnit<V, U> {
498 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
499 f.debug_struct("WithUnit")
500 .field("value", &self.value)
501 .field("unit", &U::UNIT)
502 .finish()
503 }
504}
505
506impl<V: Display, U: UnitTag> fmt::Display for WithUnit<V, U> {
507 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
508 write!(f, "{} {}", self.value, U::UNIT)
509 }
510}
511
512impl<V: MetricValue, U: UnitTag> Value for WithUnit<V, U>
513where
514 V::Unit: Convert<U>,
515{
516 fn write(&self, writer: impl ValueWriter) {
517 struct Wrapper<W, From, To> {
518 writer: W,
519 _convert: PhantomData<(From, To)>,
520 }
521
522 impl<W: ValueWriter, From: Convert<To>, To: UnitTag> ValueWriter for Wrapper<W, From, To> {
523 fn string(self, _value: &str) {
524 self.invalid("can't apply a unit to a string value");
525 }
526
527 fn metric<'a>(
528 self,
529 distribution: impl IntoIterator<Item = Observation>,
530 unit: Unit,
531 dimensions: impl IntoIterator<Item = (&'a str, &'a str)>,
532 flags: MetricFlags<'_>,
533 ) {
534 if unit != From::UNIT {
535 self.invalid(format!(
536 "value promised to write unit `{}` but wrote `{unit}` instead",
537 From::UNIT
538 ));
539 } else {
540 self.writer.metric(
541 distribution.into_iter().map(<From as Convert<To>>::convert),
542 To::UNIT,
543 dimensions,
544 flags,
545 )
546 }
547 }
548
549 fn error(self, error: ValidationError) {
550 self.writer.error(error)
551 }
552 }
553
554 self.value.write(Wrapper {
555 writer,
556 _convert: PhantomData::<(V::Unit, U)>,
557 })
558 }
559}
560
561impl<V: MetricValue, U: UnitTag> MetricValue for WithUnit<V, U>
562where
563 V::Unit: Convert<U>,
564{
565 type Unit = U;
566}
567
568#[cfg(test)]
569mod tests {
570 use std::time::Duration;
571
572 use crate::MetricValue;
573
574 use super::*;
575
576 #[test]
577 fn conversion_ratios() {
578 assert_eq!(<None as Convert<Millisecond>>::RATIO, 1.0);
580 assert_eq!(<None as Convert<Bit>>::RATIO, 1.0);
581 assert_eq!(<None as Convert<MegabytePerSecond>>::RATIO, 1.0);
582 assert_eq!(<None as Convert<Count>>::RATIO, 1.0);
583 assert_eq!(<None as Convert<None>>::RATIO, 1.0);
584
585 assert_eq!(<Second as Convert<Millisecond>>::RATIO, 1_000.0);
587 assert_eq!(<Millisecond as Convert<Second>>::RATIO, 1.0 / 1_000.0);
588
589 assert_eq!(<Byte as Convert<Bit>>::RATIO, 8.0);
591 assert_eq!(<Bit as Convert<Byte>>::RATIO, 1.0 / 8.0);
592 assert_eq!(<Megabyte as Convert<Gigabit>>::RATIO, 8.0 / 1_000.0);
593 assert_eq!(<Gigabit as Convert<Megabyte>>::RATIO, 1_000.0 / 8.0);
594
595 assert_eq!(<BytePerSecond as Convert<BitPerSecond>>::RATIO, 8.0);
597 assert_eq!(<BitPerSecond as Convert<BytePerSecond>>::RATIO, 1.0 / 8.0);
598 assert_eq!(
599 <MegabytePerSecond as Convert<GigabitPerSecond>>::RATIO,
600 8.0 / 1_000.0
601 );
602 assert_eq!(
603 <GigabitPerSecond as Convert<MegabytePerSecond>>::RATIO,
604 1_000.0 / 8.0
605 );
606 }
607
608 #[test]
609 fn fail_if_value_didnt_write_expected_unit() {
610 struct Writer;
611 impl ValueWriter for Writer {
612 fn string(self, value: &str) {
613 panic!("shouldn't have written {value}");
614 }
615
616 fn metric<'a>(
617 self,
618 _distribution: impl IntoIterator<Item = Observation>,
619 _unit: Unit,
620 _dimensions: impl IntoIterator<Item = (&'a str, &'a str)>,
621 _flags: MetricFlags<'_>,
622 ) {
623 panic!("shouldn't have emitted metric");
624 }
625
626 fn error(self, error: ValidationError) {
627 assert!(
628 error.to_string().contains(
629 "value promised to write unit `Seconds` but wrote `Bytes` instead"
630 )
631 );
632 }
633 }
634
635 struct BadValue;
636
637 impl MetricValue for BadValue {
638 type Unit = Second;
639 }
640
641 impl Value for BadValue {
642 fn write(&self, writer: impl ValueWriter) {
643 writer.metric([], Byte::UNIT, [], MetricFlags::empty());
644 }
645 }
646
647 AsMilliseconds::from(BadValue).write(Writer);
648 }
649
650 #[test]
651 fn converts_observations_and_passes_through_rest() {
652 struct Writer;
653 impl ValueWriter for Writer {
654 fn string(self, value: &str) {
655 panic!("shouldn't have written {value}");
656 }
657
658 fn metric<'a>(
659 self,
660 distribution: impl IntoIterator<Item = Observation>,
661 unit: Unit,
662 dimensions: impl IntoIterator<Item = (&'a str, &'a str)>,
663 _flags: MetricFlags<'_>,
664 ) {
665 let distribution = distribution.into_iter().collect::<Vec<_>>();
666 let dimensions = dimensions.into_iter().collect::<Vec<_>>();
667
668 assert_eq!(distribution, &[Observation::Floating(0.042)]);
669 assert_eq!(unit, Second::UNIT);
670 assert_eq!(dimensions, &[("foo", "bar")]);
671 }
672
673 fn error(self, error: ValidationError) {
674 panic!("unexpected error {error}");
675 }
676 }
677
678 AsSeconds::from(Duration::from_millis(42))
679 .with_dimension("foo", "bar")
680 .write(Writer);
681 }
682}