From 80a2d652628ab3229de2ac899af099d58fe523d3 Mon Sep 17 00:00:00 2001 From: Avril Date: Wed, 13 Apr 2022 07:37:42 +0100 Subject: [PATCH] Made spantrace capture optional at the compilation level. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added `logging` (default) feature for enabling/disabling spantrace captures entirely on build. Fortune for collect's current commit: Half curse − 半凶 --- Cargo.lock | 41 ++++++++++++ Cargo.toml | 26 +++++--- src/buffers.rs | 162 ++++++++++++++++++++++++++++++++++++++++------ src/main.rs | 172 +++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 340 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e8e978..524f8e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -70,9 +81,13 @@ name = "collect" version = "0.1.0" dependencies = [ "bytes", + "cfg-if", "color-eyre", "jemallocator", + "lazy_format", "libc", + "memchr", + "recolored", "tracing", "tracing-error", "tracing-subscriber", @@ -127,6 +142,15 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "indenter" version = "0.3.3" @@ -154,6 +178,12 @@ dependencies = [ "libc", ] +[[package]] +name = "lazy_format" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05662be9cd63006934464f935195ae936460edb75de7b9a07e0509795afbdc3" + [[package]] name = "lazy_static" version = "1.4.0" @@ -245,6 +275,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "recolored" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1584c92dd8a87686229f766bb3a62d263a90c47c81e45a49f1a6d684a1b7968d" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "regex" version = "1.5.5" diff --git a/Cargo.toml b/Cargo.toml index a7a6442..fcfff04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["jemalloc", "tracing/release_max_level_warn"] +default = ["jemalloc", "logging", "tracing/release_max_level_warn"] # TODO: mmap, memfd_create() ver -# XXX: without bytes, it completely fails?? # bytes: use `bytes` crate for collecting instead of `std::vec` # Use jemalloc instead of system malloc. @@ -18,8 +17,15 @@ default = ["jemalloc", "tracing/release_max_level_warn"] # Decreases memory-handling function calls, resulting in less "used" memory and faster allocation speeds at the cost of mapping a huge amount of virtual memory. jemalloc = ["jemallocator"] -# Remove all tracing points -no-logging = ["tracing/max_level_off"] +# Remove all runtime logging code. +# +# The capturing of spantraces will still happen if `logging` is enabled. +disable-logging = [] #["tracing/max_level_off"] <-- no longer needed, would enable the `tracing` feature which we don't want. + +# Capture spantraces +# +# Will cause a slowdown, but provide more information in the event of an error or when debugging. +logging = ["tracing", "tracing-subscriber", "tracing-error", "color-eyre/capture-spantrace"] #, "recolored" <- XXX doesn't work in tracing output for some reason...] [profile.release] opt-level = 3 @@ -34,9 +40,13 @@ strip=false [dependencies] bytes = { version = "1.1.0", optional = true } -color-eyre = { version = "0.6.1", features = ["capture-spantrace"] } +cfg-if = { version = "1.0.0" } jemallocator = { version = "0.3.2", optional = true } libc = "0.2.122" -tracing = { version = "0.1.33", features = ["attributes"] } -tracing-error = "0.2.0" -tracing-subscriber = { version = "0.3.11", features = ["tracing", "env-filter"] } +tracing = { version = "0.1.33", features = ["attributes"], optional = true } +tracing-error = {version = "0.2.0", optional = true } +tracing-subscriber = { version = "0.3.11", features = ["tracing", "env-filter"], optional = true } +color-eyre = { version = "0.6.1", default-features=false }#, features = ["capture-spantrace"] } +recolored = { version = "1.9.3", optional = true } +memchr = "2.4.1" +lazy_format = "1.10.0" diff --git a/src/buffers.rs b/src/buffers.rs index 0c8328e..554306e 100644 --- a/src/buffers.rs +++ b/src/buffers.rs @@ -85,10 +85,11 @@ const _: () = { impl<'a, B: ?Sized + Buffer> io::Read for BufferReader<'a, B> { #[inline] - #[instrument(level="trace", skip_all, fields(buf = ?buf.len()))] + #[cfg_attr(feature="logging", instrument(level="trace", skip_all, fields(buf = ?buf.len())))] fn read(&mut self, buf: &mut [u8]) -> io::Result { let adv = self.0.copy_to_slice(self.1, buf); self.1 += adv; + if_trace!(? trace!(" -> reading one buffer +{adv}")); Ok(adv) } } @@ -96,11 +97,13 @@ impl<'a, B: ?Sized + Buffer> io::Read for BufferReader<'a, B> impl<'a, B: ?Sized + MutBuffer> io::Write for BufferWriter<'a, B> { #[inline] - #[instrument(level="trace", skip_all, fields(buf = ?buf.len()))] + #[cfg_attr(feature="logging", instrument(level="trace", skip_all, fields(buf = ?buf.len())))] fn write(&mut self, buf: &[u8]) -> io::Result { let adv = self.0.copy_from_slice(self.1, buf); self.1 += adv; + + if_trace!(? trace!(" <- writing one buffer {adv}")); Ok(adv) } @@ -114,7 +117,7 @@ impl<'a, B: ?Sized + MutBuffer> io::Write for BufferWriter<'a, B> pub trait Buffer: AsRef<[u8]> { #[inline] - #[instrument(level="trace", skip_all, fields(buf = ?slice.len()))] + #[cfg_attr(feature="logging", instrument(level="trace", skip_all, fields(buf = ?slice.len())))] fn copy_to_slice(&self, st: usize, slice: &mut [u8]) -> usize { let by = self.as_ref(); @@ -162,7 +165,7 @@ pub trait MutBuffer: AsMut<[u8]> fn freeze(self) -> Self::Frozen; #[inline] - #[instrument(level="debug", skip_all, fields(st, buflen = ?slice.len()))] + #[cfg_attr(feature="logging", instrument(level="debug", skip_all, fields(st, buflen = ?slice.len())))] fn copy_from_slice(&mut self, st: usize, slice: &[u8]) -> usize { let by = self.as_mut(); @@ -189,10 +192,10 @@ pub trait MutBuffer: AsMut<[u8]> pub trait MutBufferExt: MutBuffer { #[inline(always)] - #[instrument(level="info", skip(self))] + #[cfg_attr(feature="logging", instrument(level="info", skip(self)))] fn writer_from(&mut self, st: usize) -> BufferWriter<'_, Self> { - debug!("creating writer at start {st}"); + if_trace!(debug!("creating writer at start {st}")); BufferWriter(self, st) } #[inline] @@ -210,7 +213,7 @@ impl MutBuffer for bytes::BytesMut type Frozen = bytes::Bytes; #[inline(always)] - #[instrument(level="trace")] + #[cfg_attr(feature="logging", instrument(level="trace"))] fn freeze(self) -> Self::Frozen { bytes::BytesMut::freeze(self) } @@ -222,7 +225,7 @@ impl MutBuffer for bytes::BytesMut if (st + buf.len()) <= self.len() { // We can put `buf` in st..buf.len() self[st..].copy_from_slice(buf); -} else if st <= self.len() { +} else if st < self.len() { // The start is lower but the end is not let rem = self.len() - st; self[st..].copy_from_slice(&buf[..rem]); @@ -235,29 +238,150 @@ impl MutBuffer for bytes::BytesMut }*/ } +#[cfg(feature="recolored")] +mod perc { + #[deprecated = "this is absolutely retardedly unsafe and unsound... fuck this shit man lole"] + pub(super) unsafe fn gen_perc_boring(low: f64, high: f64) -> std::pin::Pin<&'static (impl std::fmt::Display + ?Sized + 'static)> + { + use std::{ + cell::RefCell, + mem::MaybeUninit, + pin::Pin, + + }; + thread_local! { + static STRING_BUFFER: RefCell> = RefCell::new(MaybeUninit::uninit()); + } + STRING_BUFFER.try_with(|buffer| -> Result, Box>{ + let mut buffer = buffer.try_borrow_mut()?; + use std::io::Write; + write!(unsafe {&mut buffer.assume_init_mut()[..]}, "{:0.2}", (low / high) * 100f64)?; + let s_ref = unsafe { + #[derive(Debug)] + struct FindFailed; + impl std::error::Error for FindFailed{} + impl std::fmt::Display for FindFailed { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + f.write_str("boring perc: failed to write whole string into buffer of size 16") + } + } + let buf = buffer.assume_init_mut(); + let spl = memchr::memchr(b'%', &buf[..]).ok_or(FindFailed)?; + std::str::from_utf8_mut(&mut buf[..=spl])? + }; + unsafe { + Ok(Pin::new(std::mem::transmute::<_, &'static _>(s_ref))) + } + }).expect("bad static memory access").expect("failed to calc") + } + + #[inline] + //XXX::: WHY::: TRACING IGNORES MY COLOURS!!! + #[deprecated(note="my colouring is ignored. we'll have to either: figure out why. or, use a different method to highlight abnormal (above 100) percentages")] + pub(super) fn gen_perc(low: f64, high: f64) -> impl std::fmt::Display + { + use std::fmt; + let f = low / match high { + 0f64 => if low != 0f64 { + return Perc::Invalid + } else { + 0f64 + } + x => x, + }; + enum Perc { + Normal(f64), + Goal(String), + High(String), + Zero(String), + Low(String), + + Invalid, + } + + macro_rules! fmt_str { + (%) => ("{:0.2}%"); + () => ("{:0.2}") + } + impl fmt::Display for Perc + { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + use recolored::Colorize; + + write!(f, "{}", match self { + Self::Normal(p) => return write!(f, fmt_str!(%), p), + Self::Goal(p) => p.green(), + Self::High(p) => p.red(), + Self::Zero(p) => p.purple().bold(), + Self::Low(p) => p.on_red().white().underline(), + Self::Invalid => return write!(f, fmt_str!(%), ("0.00%".on_bright_red().white().strikethrough())), + })?; + { + use fmt::Write; + f.write_char('%') + } + } + } + + //TODO: StackStr instead of String + (match f { + 0f64 => Perc::Zero, + 1f64 => Perc::Goal, + 0f64..=1f64 => return Perc::Normal(f * 100f64), + 1f64.. => Perc::High, + _ => Perc::Low, + })(format!(fmt_str!(), f * 100f64)) + } +} + impl MutBuffer for Vec { type Frozen = Box<[u8]>; #[inline] - #[instrument(level="trace")] + #[cfg_attr(feature="logging", instrument(level="trace"))] fn freeze(self) -> Self::Frozen { self.into_boxed_slice() } - #[instrument(level="trace", skip_all, fields(st, buflen = ?buf.len()))] + #[cfg_attr(feature="logging", instrument(level="trace", skip(buf, self), fields(st = ?st, self = ?self.len(), alloc= ?self.capacity())))] fn copy_from_slice(&mut self, st: usize, buf: &[u8]) -> usize { if (st + buf.len()) <= self.len() { // We can put `buf` in st..buf.len() self[st..].copy_from_slice(buf); - } else if st <= self.len() { + } else if st < self.len() { // The start is lower but the end is not let rem = self.len() - st; self[st..].copy_from_slice(&buf[..rem]); + if_trace!(trace!("extending buffer (partial, +{})", buf[rem..].len())); self.extend_from_slice(&buf[rem..]); } else { // it is past the end, extend. + if_trace!(trace!("extending buffer (whole, self + buf = {} / {}: {})" + ,self.len() + buf.len() + , self.capacity() + , { + cfg_if! { + if #[cfg(feature="recolored")] { + use perc::*; + (if cfg!(feature="recolored") { + |x,y| -> Box { Box::new(gen_perc(x,y)) } + } else { + |x,y| -> Box { Box::new(unsafe {gen_perc_boring(x,y)}.get_ref()) } + })((self.len() + buf.len()) as f64, self.capacity() as f64) + } else { + let t= self.len(); + let c= self.capacity(); + let b = buf.len(); + lazy_format::lazy_format!("{:0.2}", ((t + b) as f64 / c as f64) * 100f64) + } + } + })); self.extend_from_slice(buf); } buf.len() @@ -274,15 +398,15 @@ pub trait WithCapacity: Sized impl WithCapacity for Box<[u8]> { #[inline(always)] - #[instrument(level="info", fields(cap = "(unbound)"))] + #[cfg_attr(feature="logging", instrument(level="info", fields(cap = "(unbound)")))] fn wc_new() -> Self { - info!("creating new boxed slice with size 0"); + if_trace!(debug!("creating new boxed slice with size 0")); Vec::wc_new().into_boxed_slice() } #[inline(always)] - #[instrument(level="info")] + #[cfg_attr(feature="logging", instrument(level="info"))] fn wc_with_capacity(cap: usize) -> Self { - info!("creating new boxed slice with size {cap}"); + if_trace!(debug!("creating new boxed slice with size {cap}")); Vec::wc_with_capacity(cap).into_boxed_slice() } } @@ -337,17 +461,17 @@ macro_rules! cap_buffer { impl $crate::buffers::WithCapacity for $name { #[inline(always)] - #[instrument(level="info", fields(cap = "(unbound)"))] + #[cfg_attr(feature="logging", instrument(level="info", fields(cap = "(unbound)")))] fn wc_new() -> Self { - info!("creating {} with no cap", std::any::type_name::()); + if_trace! (debug!("creating {} with no cap", std::any::type_name::())); Self::new() } #[inline(always)] - #[instrument(level="info")] + #[cfg_attr(feature="logging", instrument(level="info"))] fn wc_with_capacity(cap: usize) -> Self { - info!("creating {} with {cap}", std::any::type_name::()); + if_trace!(debug!("creating {} with {cap}", std::any::type_name::())); Self::with_capacity(cap) } } diff --git a/src/main.rs b/src/main.rs index 94b6954..3c7f97d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,26 @@ +#[macro_use] extern crate cfg_if; +#[cfg(feature="logging")] #[macro_use] extern crate tracing; +/// Run this statement only if `tracing` is enabled +macro_rules! if_trace { + (? $expr:expr) => { + cfg_if! { + if #[cfg(all(feature="logging", debug_assertions))] { + $expr; + } + } + }; + ($expr:expr) => { + cfg_if! { + if #[cfg(feature="logging")] { + $expr; + } + } + }; +} + #[cfg(feature="jemalloc")] extern crate jemallocator; @@ -37,7 +57,88 @@ use bytes::{ BufMut, }; -#[instrument(level="debug", skip(reader), fields(reader = ?std::any::type_name::()))] +/* TODO: XXX: For colouring buffer::Perc +#[derive(Debug)] +struct StackStr(usize, std::mem::MaybeUninit<[u8; MAXLEN]>); + +impl StackStr +{ + #[inline] + pub const fn new() -> Self + { + Self(0, std::mem::MaybeUninit::uninit()) + } + + #[inline(always)] + pub const unsafe fn slice_mut(&mut self) -> &mut [u8] + { + &mut self.1[self.0..] + } + #[inline] + pub const fn slice(&self) -> &[u8] + { + &self.1[self.0..] + } + + #[inline] + pub const unsafe fn as_str_unchecked(&self) -> &str + { + std::str::from_utf8_unchecked(&self.1[self.0..]) + } + + #[inline] + pub const unsafe fn as_mut_str_unchecked(&mut self) -> &mut str + { + std::str::from_utf8_unchecked_mut(&mut self.1[..self.0]) + } + + #[inline] + #[cfg_attr(feature="logging", instrument(level="debug"))] + pub fn as_str(&self) -> &str + { + std::str::from_utf8(self.slice()).expect("Invalid string") + } + + #[inline(always)] + const fn left(&self) -> usize { + SZ - self.0 + } + + #[inline(always)] + pub fn write_bytes(&mut self, s: &[u8]) -> usize { + let b = &s[..std::cmp::min(match self.left() { + 0 => return 0, + x => x, + }, s.len())]; + unsafe { &mut self.slice_mut() [self.0..] }.copy_from_slice(b); + let v = b.len(); + self.0 += v; + v + } +} + +impl std::fmt::Write for StackStr +{ + #[inline] + fn write_str(&mut self, s: &str) -> std::fmt::Result { + self.write_bytes(s.as_bytes()); + Ok(()) + } + #[inline] + fn write_char(&mut self, c: char) -> std::fmt::Result { + let l = c.len_utf8(); + if l > self.left() { + return Ok(()) + } + self.write_bytes(c.encode_utf8(unsafe { &mut self.slice_mut() [self.0..] })); + self.0 += l; + + Ok(()) + } +} +*/ + +#[cfg_attr(feature="logging", instrument(level="info", skip(reader), fields(reader = ?std::any::type_name::())))] fn try_get_size(reader: &R) -> Option where R: AsRawFd { @@ -63,45 +164,47 @@ where R: AsRawFd fn init() -> eyre::Result<()> { - fn install_tracing() - { - //! Install spantrace handling - - use tracing_error::ErrorLayer; - use tracing_subscriber::prelude::*; - use tracing_subscriber::{fmt, EnvFilter}; - - let fmt_layer = fmt::layer() - .with_target(false) - .with_writer(io::stderr); - - let filter_layer = EnvFilter::try_from_default_env() - .or_else(|_| EnvFilter::try_new(if cfg!(debug_assertions) { - "info" - } else if cfg!(feature="no-logging") { - "off" - } else { - "warn" - })) - .unwrap(); - - tracing_subscriber::registry() - .with(fmt_layer) - .with(filter_layer) - .with(ErrorLayer::default()) - .init(); - } + cfg_if!{ if #[cfg(feature="logging")] { + fn install_tracing() + { + //! Install spantrace handling + + use tracing_error::ErrorLayer; + use tracing_subscriber::prelude::*; + use tracing_subscriber::{fmt, EnvFilter}; + + let fmt_layer = fmt::layer() + .with_target(false) + .with_writer(io::stderr); + + let filter_layer = EnvFilter::try_from_default_env() + .or_else(|_| EnvFilter::try_new(if cfg!(debug_assertions) { + "debug" + } else { + "info" + })) + .unwrap(); + + tracing_subscriber::registry() + .with(fmt_layer) + .with(filter_layer) + .with(ErrorLayer::default()) + .init(); + } - //if !cfg!(feature="no-logging") { - install_tracing(); - //} + if !cfg!(feature="disable-logging") { + install_tracing(); + if_trace!(trace!("installed tracing")); + } + } } color_eyre::install() } -#[instrument(err)] +#[cfg_attr(tracing, instrument(err))] fn main() -> eyre::Result<()> { init()?; + if_trace!(debug!("initialised")); let (bytes, read) = { let stdin = io::stdin(); @@ -112,9 +215,9 @@ fn main() -> eyre::Result<()> { .with_section(|| bytes.capacity().header("Buffer cap is")) .with_section(|| format!("{:?}", bytes).header("Buffer is")) .wrap_err("Failed to read into buffer")?; - (bytes.freeze(), read as usize) }; + if_trace!(info!("collected {read} from stdin. starting write.")); let written = io::copy(&mut (&bytes[..read]).reader() , &mut io::stdout().lock()) @@ -123,6 +226,7 @@ fn main() -> eyre::Result<()> { .with_section(|| format!("{:?}", &bytes[..read]).header("Read Buffer")) .with_section(|| format!("{:?}", bytes).header("Full Buffer")) .wrap_err("Failed to write from buffer")?; + if_trace!(info!("written {written} to stdout.")); if read != written as usize { return Err(io::Error::new(io::ErrorKind::BrokenPipe, format!("read {read} bytes, but only wrote {written}")))