Skip to content

Commit eca8416

Browse files
authored
Pre-define buffers of lines (#30)
This commit introduces a startup-defined cache of lines that is then rotated through to reduce incidental CPU costs while emitting. This raises the program throughput. Bump to 0.2. Signed-off-by: Brian L. Troutwine <brian@troutwine.us>
1 parent 87c5064 commit eca8416

File tree

4 files changed

+70
-38
lines changed

4 files changed

+70
-38
lines changed

Cargo.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "file_gen"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
authors = ["Brian L. Troutwine <brian@troutwine.us>"]
55
edition = "2018"
66
license = "MIT"

src/config.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ pub struct LogTargetTemplate {
5757
/// possible as the internal governor accumulates, up to
5858
/// `maximum_bytes_burst`.
5959
bytes_per_second: String,
60+
/// Defines the maximum number of bytes that a single line can be, meaning
61+
/// lines range from 2 bytes (one character, newline) to this value plus
62+
/// space for a newline.
6063
maximum_line_size_bytes: String,
64+
/// Defines the maximum internal cache of this log target. file_gen will
65+
/// pre-build its outputs up to the byte capacity specified here.
66+
maximum_prebuild_cache_size_bytes: String,
6167
}
6268

6369
/// The [`LogTarget`] is generated by [`LogTargetTemplate`]
@@ -83,6 +89,8 @@ pub struct LogTarget {
8389
/// The maximum size in bytes that a line may be. This program may generate
8490
/// lines of less size.
8591
pub maximum_line_size_bytes: NonZeroU32,
92+
/// The maximum size in bytes that the prebuild cache may be.
93+
pub maximum_prebuild_cache_size_bytes: NonZeroU32,
8694
}
8795

8896
/// Configuration errors
@@ -127,6 +135,7 @@ impl Iterator for LogTargetTemplateIter {
127135
maximum_bytes_per_file: self.maximum_bytes_per_file,
128136
bytes_per_second: self.bytes_per_second,
129137
maximum_line_size_bytes: self.maximum_line_size_bytes,
138+
maximum_prebuild_cache_size_bytes: self.maximum_prebuild_cache_size_bytes,
130139
})
131140
}
132141
}
@@ -141,6 +150,7 @@ pub struct LogTargetTemplateIter {
141150
maximum_bytes_per_file: NonZeroU32,
142151
bytes_per_second: NonZeroU32,
143152
maximum_line_size_bytes: NonZeroU32,
153+
maximum_prebuild_cache_size_bytes: NonZeroU32,
144154
}
145155

146156
impl LogTargetTemplate {
@@ -159,6 +169,7 @@ impl LogTargetTemplate {
159169
maximum_bytes_per_file: self.maximum_bytes_per_file()?,
160170
bytes_per_second: self.bytes_per_second()?,
161171
maximum_line_size_bytes: self.maximum_line_size_bytes()?,
172+
maximum_prebuild_cache_size_bytes: self.maximum_prebuild_cache_size_bytes()?,
162173
})
163174
}
164175

@@ -178,6 +189,23 @@ impl LogTargetTemplate {
178189
.expect("maximum_line_size_bytes must not be 0"))
179190
}
180191

192+
/// Determine the `maximum_prebuild_cache_size_bytes` for this
193+
/// [`LogTargetTemplate`]
194+
///
195+
/// Parses the user's supplied stringy number into a non-zero u32 of bytes.
196+
///
197+
/// # Errors
198+
///
199+
/// If the users supplies anything other than a stringy number plus some
200+
/// recognizable unit this function will return an error. Likewise if the
201+
/// user supplies a number that is larger than `u32::MAX` bytes this
202+
/// function will return an error.
203+
pub fn maximum_prebuild_cache_size_bytes(&self) -> Result<NonZeroU32, Error> {
204+
let bytes = Byte::from_str(&self.maximum_prebuild_cache_size_bytes)?;
205+
Ok(NonZeroU32::new(u32::try_from(bytes.get_bytes())?)
206+
.expect("maximum_prebuild_cache_size_bytes must not be 0"))
207+
}
208+
181209
/// Determine the `bytes_per_second` for this [`LogTargetTemplate`]
182210
///
183211
/// Parses the user's supplied stringy number into a non-zero u32 of bytes.

src/file.rs

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use governor::{clock, state};
55
use governor::{Quota, RateLimiter};
66
use metrics::{counter, gauge};
77
use rand::Rng;
8+
use std::convert::TryInto;
89
use std::mem;
910
use std::num::NonZeroU32;
1011
use std::path::PathBuf;
@@ -33,24 +34,17 @@ impl From<::std::io::Error> for Error {
3334
/// The [`Log`] defines a task that emits variant lines to a file, managing
3435
/// rotation and controlling rate limits.
3536
#[derive(Debug)]
36-
pub struct Log<R>
37-
where
38-
R: Rng + Sized,
39-
{
37+
pub struct Log {
4038
path: PathBuf,
4139
name: String, // this is the stringy version of `path`
4240
fp: BufWriter<fs::File>,
43-
variant: Variant,
4441
maximum_bytes_per_file: NonZeroU32,
4542
maximum_line_size_bytes: NonZeroU32,
4643
rate_limiter: RateLimiter<direct::NotKeyed, state::InMemoryState, clock::QuantaClock>,
47-
rng: R,
44+
line_cache: Vec<(NonZeroU32, Vec<u8>)>,
4845
}
4946

50-
impl<R> Log<R>
51-
where
52-
R: Rng + Sized,
53-
{
47+
impl Log {
5448
/// Create a new [`Log`]
5549
///
5650
/// A new instance of this type requires a random generator, its name and
@@ -60,7 +54,15 @@ where
6054
/// # Errors
6155
///
6256
/// Creation will fail if the target file cannot be opened for writing.
63-
pub async fn new(rng: R, name: String, target: LogTarget) -> Result<Self, Error> {
57+
///
58+
/// # Panics
59+
///
60+
/// This function will panic if the value of `maximum_line_size_bytes`
61+
/// cannot be coerced into a machine word size.
62+
pub async fn new<R>(mut rng: R, name: String, target: LogTarget) -> Result<Self, Error>
63+
where
64+
R: Rng + Sized,
65+
{
6466
let rate_limiter: RateLimiter<direct::NotKeyed, state::InMemoryState, clock::QuantaClock> =
6567
RateLimiter::direct(
6668
Quota::per_second(target.bytes_per_second)
@@ -79,15 +81,30 @@ where
7981
.await?,
8082
);
8183

84+
let mut line_cache = Vec::with_capacity(1024);
85+
let mut bytes_remaining = target.maximum_prebuild_cache_size_bytes.get() as usize;
86+
while bytes_remaining > 0 {
87+
let bytes = rng.gen_range(1..maximum_line_size_bytes.get()) as usize;
88+
let mut buffer: Vec<u8> = vec![0; bytes];
89+
let res = match target.variant {
90+
Variant::Ascii => buffer::fill_ascii(&mut rng, &mut buffer),
91+
Variant::Constant => buffer::fill_constant(&mut rng, &mut buffer),
92+
Variant::Json => buffer::fill_json(&mut rng, &mut buffer),
93+
};
94+
res.expect("could not pre-fill line");
95+
let nz_bytes = NonZeroU32::new(bytes.try_into().unwrap()).unwrap();
96+
line_cache.push((nz_bytes, buffer));
97+
bytes_remaining = bytes_remaining.saturating_sub(bytes);
98+
}
99+
82100
Ok(Self {
83101
fp,
84102
maximum_bytes_per_file,
85103
name,
86104
path: target.path,
87-
variant: target.variant,
88105
maximum_line_size_bytes,
89106
rate_limiter,
90-
rng,
107+
line_cache,
91108
})
92109
}
93110

@@ -120,32 +137,16 @@ where
120137
&labels
121138
);
122139

123-
let mut buffer: Vec<u8> = vec![0; self.maximum_line_size_bytes.get() as usize];
140+
for (nz_bytes, line) in self.line_cache.iter().cycle() {
141+
self.rate_limiter.until_n_ready(*nz_bytes).await?;
124142

125-
loop {
126143
{
127-
let bytes = self.rng.gen_range(1..maximum_line_size_bytes);
128-
let nz_bytes =
129-
NonZeroU32::new(bytes).expect("invalid condition, should never trigger");
130-
self.rate_limiter.until_n_ready(nz_bytes).await?;
131-
132-
let slice = &mut buffer[0..bytes as usize];
133-
let res = match self.variant {
134-
Variant::Ascii => buffer::fill_ascii(&mut self.rng, slice),
135-
Variant::Constant => buffer::fill_constant(&mut self.rng, slice),
136-
Variant::Json => buffer::fill_json(&mut self.rng, slice),
137-
};
138-
if let Ok(filled_bytes) = res {
139-
self.fp.write(&slice[0..filled_bytes]).await?;
140-
counter!("bytes_written", filled_bytes as u64, &labels);
141-
counter!("lines_written", 1, &labels);
144+
self.fp.write(&line).await?;
145+
counter!("bytes_written", line.len() as u64, &labels);
146+
counter!("lines_written", 1, &labels);
142147

143-
bytes_written += filled_bytes as u64;
144-
gauge!("current_target_size_bytes", bytes_written as f64, &labels);
145-
} else {
146-
counter!("unable_to_write_to_target", 1, &labels);
147-
continue;
148-
}
148+
bytes_written += line.len() as u64;
149+
gauge!("current_target_size_bytes", bytes_written as f64, &labels);
149150
}
150151

151152
if bytes_written > maximum_bytes_per_file {
@@ -173,5 +174,6 @@ where
173174
counter!("file_rotated", 1, &labels);
174175
}
175176
}
177+
Ok(())
176178
}
177179
}

0 commit comments

Comments
 (0)