mod chunk;
mod chunk_by_counter;
mod chunk_by_time;
mod display;
mod tensorboard;
pub use chunk::ChunkLogger;
pub use chunk_by_counter::ByCounter;
pub use chunk_by_time::ByTime;
pub use display::{DisplayBackend, DisplayLogger};
pub use tensorboard::{TensorBoardBackend, TensorBoardLogger};
use smallvec::SmallVec;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::ops::Drop;
use std::time::{Duration, Instant};
use std::{fmt, iter, slice};
use thiserror::Error;
pub trait StatsLogger: Send {
#[inline]
fn log(&mut self, id: Id, value: LogValue) -> Result<(), LogError> {
self.group_start();
let result = self.group_log(id, value);
self.group_end();
result
}
fn group_start(&mut self);
fn group_log(&mut self, id: Id, value: LogValue) -> Result<(), LogError>;
fn group_end(&mut self);
fn flush(&mut self);
#[inline]
fn group(self) -> LogGroup<Self>
where
Self: Sized,
{
LogGroup::new(self)
}
#[inline]
fn with_scope(self, scope: &'static str) -> ScopedLogger<Self>
where
Self: Sized,
{
ScopedLogger::new(scope, self)
}
#[inline]
fn log_counter_increment(&mut self, name: &'static str, increment: u64) {
self.log(name.into(), LogValue::CounterIncrement(increment))
.unwrap()
}
#[inline]
fn log_duration(&mut self, name: &'static str, duration: Duration) {
self.log(name.into(), LogValue::Duration(duration)).unwrap()
}
#[inline]
fn log_elapsed<F, T>(&mut self, name: &'static str, f: F) -> T
where
F: FnOnce(&mut Self) -> T,
Self: Sized,
{
let start = Instant::now();
let result = f(self);
self.log_duration(name, start.elapsed());
result
}
#[inline]
fn log_scalar(&mut self, name: &'static str, value: f64) {
self.log(name.into(), LogValue::Scalar(value)).unwrap()
}
#[inline]
fn log_index(&mut self, name: &'static str, value: usize, size: usize) {
self.log(name.into(), LogValue::Index { value, size })
.unwrap()
}
}
macro_rules! impl_wrapped_stats_logger {
($wrapper:ty) => {
impl<T> StatsLogger for $wrapper
where
T: StatsLogger + ?Sized,
{
#[inline]
fn group_start(&mut self) {
T::group_start(self)
}
#[inline]
fn group_log(&mut self, id: Id, value: LogValue) -> Result<(), LogError> {
T::group_log(self, id, value)
}
#[inline]
fn group_end(&mut self) {
T::group_end(self)
}
#[inline]
fn flush(&mut self) {
T::flush(self)
}
}
};
}
impl_wrapped_stats_logger!(&'_ mut T);
impl_wrapped_stats_logger!(Box<T>);
#[derive(Debug, Clone, PartialEq)]
pub enum LogValue {
Nothing,
CounterIncrement(u64),
Duration(Duration),
Scalar(f64),
Index { value: usize, size: usize },
}
impl From<f64> for LogValue {
fn from(scalar: f64) -> Self {
Self::Scalar(scalar)
}
}
impl From<Duration> for LogValue {
fn from(duration: Duration) -> Self {
Self::Duration(duration)
}
}
impl LogValue {
const fn variant_name(&self) -> &'static str {
use LogValue::*;
match self {
Nothing => "Nothing",
CounterIncrement(_) => "CounterIncrement",
Duration(_) => "Duration",
Scalar(_) => "Scalar",
Index { value: _, size: _ } => "Index",
}
}
}
pub trait Loggable {
fn log<L: StatsLogger + ?Sized>(
&self,
name: &'static str,
logger: &mut L,
) -> Result<(), LogError>;
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Id {
name: Cow<'static, str>,
namespace: SmallVec<[&'static str; 6]>,
}
impl PartialOrd for Id {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Id {
fn cmp(&self, other: &Self) -> Ordering {
self.components().cmp(other.components())
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let total_len = self.name.len() + self.namespace.iter().map(|s| s.len() + 1).sum::<usize>();
if let Some(width) = f.width() {
if width > total_len && matches!(f.align(), Some(fmt::Alignment::Right)) {
let c = f.fill();
for _ in 0..(width - total_len) {
write!(f, "{}", c)?;
}
}
}
for scope in self.namespace.iter().rev() {
write!(f, "{}/", scope)?;
}
write!(f, "{}", self.name)?;
if let Some(width) = f.width() {
if width > total_len && matches!(f.align(), Some(fmt::Alignment::Left)) {
let c = f.fill();
for _ in 0..(width - total_len) {
write!(f, "{}", c)?;
}
}
}
Ok(())
}
}
impl<T> From<T> for Id
where
T: Into<Cow<'static, str>>,
{
#[inline]
fn from(name: T) -> Self {
let name = name.into();
debug_assert!(
!name.contains('/'),
"path separators are not allowed in Id name; \
use [...].collect() or logger.with_scope(...) instead"
);
Self {
name,
namespace: SmallVec::new(),
}
}
}
impl FromIterator<&'static str> for Id {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = &'static str>,
{
let mut rev_namespace: SmallVec<[&'static str; 6]> = iter.into_iter().collect();
let name = rev_namespace
.pop()
.expect("must have at least one name")
.into();
Self {
name,
namespace: rev_namespace.into_iter().rev().collect(),
}
}
}
impl Id {
#[must_use]
pub fn with_prefix(mut self, scope: &'static str) -> Self {
self.namespace.push(scope);
self
}
pub fn components(
&self,
) -> iter::Chain<iter::Copied<iter::Rev<slice::Iter<&str>>>, iter::Once<&str>> {
self.namespace
.iter()
.rev()
.copied() .chain(iter::once(self.name.as_ref()))
}
}
#[derive(Error, Debug, Clone, PartialEq)]
pub enum LogError {
#[error("incompatible value type; previously {prev} now {now}")]
IncompatibleValue {
prev: &'static str,
now: &'static str,
},
#[error("incompatible index size; previously {prev} now {now}")]
IncompatibleIndexSize { prev: usize, now: usize },
}
impl StatsLogger for () {
#[inline]
fn group_start(&mut self) {}
#[inline]
fn group_log(&mut self, _: Id, _: LogValue) -> Result<(), LogError> {
Ok(())
}
#[inline]
fn group_end(&mut self) {}
#[inline]
fn flush(&mut self) {}
}
impl<A, B> StatsLogger for (A, B)
where
A: StatsLogger,
B: StatsLogger,
{
fn group_start(&mut self) {
self.0.group_start();
self.1.group_start();
}
fn group_log(&mut self, id: Id, value: LogValue) -> Result<(), LogError> {
let r1 = self.0.group_log(id.clone(), value.clone());
let r2 = self.1.group_log(id, value);
r1.and(r2)
}
fn group_end(&mut self) {
self.0.group_end();
self.1.group_end();
}
fn flush(&mut self) {
self.0.flush();
self.1.flush();
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ScopedLogger<L> {
scope: &'static str,
logger: L,
}
impl<L> ScopedLogger<L> {
#[inline]
pub const fn new(scope: &'static str, logger: L) -> Self {
Self { scope, logger }
}
}
impl<L: StatsLogger> StatsLogger for ScopedLogger<L> {
#[inline]
fn group_start(&mut self) {
self.logger.group_start()
}
#[inline]
fn group_log(&mut self, id: Id, value: LogValue) -> Result<(), LogError> {
self.logger.group_log(id.with_prefix(self.scope), value)
}
#[inline]
fn group_end(&mut self) {
self.logger.group_end()
}
#[inline]
fn flush(&mut self) {
self.logger.flush()
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct LogGroup<L: StatsLogger>(L);
impl<L: StatsLogger> LogGroup<L> {
#[inline]
pub fn new(mut logger: L) -> Self {
logger.group_start();
Self(logger)
}
}
impl<L: StatsLogger> StatsLogger for LogGroup<L> {
#[inline]
fn group_start(&mut self) {}
#[inline]
fn group_log(&mut self, id: Id, value: LogValue) -> Result<(), LogError> {
self.0.group_log(id, value)
}
#[inline]
fn group_end(&mut self) {}
#[inline]
fn flush(&mut self) {}
}
impl<L: StatsLogger> Drop for LogGroup<L> {
#[inline]
fn drop(&mut self) {
self.0.group_end()
}
}