Skip to content

Commit

Permalink
Avoid some allocations, hashes that aren't needed
Browse files Browse the repository at this point in the history
This commit removes allocations in the FUSE component that don't
need to exist and adjust the interior hashmap to use FxHashMap,
avoiding a secure hash in favor of a fast one. Also, directories
now keep their children in a BTreeSet so that readdir is always
in the same order when called.

Signed-off-by: Brian L. Troutwine <brian.troutwine@datadoghq.com>
  • Loading branch information
blt committed Oct 31, 2024
1 parent d0d870e commit 94a0a35
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 54 deletions.
2 changes: 1 addition & 1 deletion examples/lading-logrotatefs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ generator:
total_rotations: 4
max_depth: 0
variant: "ascii"
bytes_per_second: 10MB
bytes_per_second: 1.3MB
maximum_prebuild_cache_size_bytes: 1GB
mount_point: /tmp/logrotate

Expand Down
72 changes: 38 additions & 34 deletions lading/src/generator/file_gen/logrotate_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,52 +342,56 @@ impl Filesystem for LogrotateFS {
state.advance_time(tick);

let root_inode = state.root_inode();
let mut entry_offset = 0;

// TODO building up a vec of entries here to handle offset really does
// suggest that the model needs to be exposed in such a way that this
// needn't be done.
//
// Ah, actually, the right buffer to push into is reply.add. There's no
// need for `entries` at all.
let mut entries = Vec::new();
// Entry 0: "."
if entry_offset >= offset
&& reply.add(ino, entry_offset + 1, fuser::FileType::Directory, ".")
{
reply.ok();
return;
}
entry_offset += 1;

// '.' and '..'
entries.push((ino, fuser::FileType::Directory, ".".to_string()));
// Entry 1: ".." when applicable
if ino != root_inode as u64 {
let parent_ino = state
.get_parent_inode(ino as usize)
.expect("inode must have parent");
entries.push((
parent_ino as u64,
fuser::FileType::Directory,
"..".to_string(),
));
if entry_offset >= offset {
let parent_ino = state
.get_parent_inode(ino as usize)
.expect("inode must have parent");
if reply.add(
parent_ino as u64,
entry_offset + 1,
fuser::FileType::Directory,
"..",
) {
reply.ok();
return;
}
}
entry_offset += 1;
}

// remaining children
// Child entries, returned in inode order by `State::readdir`
if let Some(child_inodes) = state.readdir(ino as usize) {
for child_ino in child_inodes {
let file_type = state
.get_file_type(*child_ino)
.expect("inode must have file type");
let child_name = state.get_name(*child_ino).expect("inode must have a name");
entries.push((*child_ino as u64, file_type, child_name.to_string()));
for &child_ino in child_inodes {
if entry_offset >= offset {
let file_type = state
.get_file_type(child_ino)
.expect("inode must have file type");
let child_name = state.get_name(child_ino).expect("inode must have a name");
if reply.add(child_ino as u64, entry_offset + 1, file_type, child_name) {
reply.ok();
return;
}
}
entry_offset += 1;
}
} else {
reply.error(ENOENT);
return;
}

let mut idx = offset as usize;
while idx < entries.len() {
let (inode, file_type, name) = &entries[idx];
let next_offset = (idx + 1) as i64;
if reply.add(*inode, next_offset, *file_type, name) {
// Buffer is full, exit the loop
break;
}
idx += 1;
}
reply.ok();
}

Expand Down
48 changes: 29 additions & 19 deletions lading/src/generator/file_gen/logrotate_fs/model.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! Model the internal logic of a logrotate filesystem.

use std::collections::{HashMap, HashSet};

use bytes::Bytes;
use lading_payload::block;
use metrics::counter;
use rand::Rng;
use rustc_hash::FxHashMap;
use std::collections::BTreeSet;

/// Time representation of the model
pub(crate) type Tick = u64;
Expand Down Expand Up @@ -249,12 +249,21 @@ impl File {

/// Model representation of a `Directory`. Contains children are `Directory`
/// instances or `File` instances. Root directory will not have a `parent`.
#[derive(Debug)]
#[derive(Debug, Default)]
pub(crate) struct Directory {
children: HashSet<Inode>,
children: BTreeSet<Inode>,
parent: Option<Inode>,
}

impl Directory {
fn new(parent: Option<Inode>) -> Self {
Self {
children: BTreeSet::default(),
parent,
}
}
}

/// A filesystem object, either a `File` or a `Directory`.
#[derive(Debug)]
pub(crate) enum Node {
Expand All @@ -278,7 +287,7 @@ pub(crate) enum Node {
/// filesystem. It does not contain any bytes, the caller must maintain this
/// themselves.
pub(crate) struct State {
nodes: HashMap<Inode, Node>,
nodes: FxHashMap<Inode, Node>,
root_inode: Inode,
now: Tick,
block_cache: block::Cache,
Expand All @@ -288,6 +297,7 @@ pub(crate) struct State {
group_names: Vec<Vec<String>>,
next_inode: Inode,
next_file_handle: u64,
inode_scratch: Vec<Inode>,
}

impl std::fmt::Debug for State {
Expand All @@ -297,6 +307,7 @@ impl std::fmt::Debug for State {
.field("root_inode", &self.root_inode)
.field("now", &self.now)
// intentionally leaving out block_cache
// intentionally leaving out inode_scratch
.field("max_rotations", &self.max_rotations)
.field("max_bytes_per_file", &self.max_bytes_per_file)
.field("group_names", &self.group_names)
Expand Down Expand Up @@ -349,12 +360,9 @@ impl State {
R: Rng,
{
let root_inode: Inode = 1; // `/`
let mut nodes = HashMap::new();
let mut nodes = FxHashMap::default();

let root_dir = Directory {
children: HashSet::new(),
parent: None,
};
let root_dir = Directory::default();
nodes.insert(
root_inode,
Node::Directory {
Expand All @@ -373,6 +381,7 @@ impl State {
group_names: Vec::new(),
next_inode: 2,
next_file_handle: 0,
inode_scratch: Vec::with_capacity(concurrent_logs as usize),
};

if concurrent_logs == 0 {
Expand Down Expand Up @@ -435,10 +444,7 @@ impl State {
let new_inode = state.next_inode;
state.next_inode += 1;

let new_dir = Directory {
children: HashSet::new(),
parent: Some(current_inode),
};
let new_dir = Directory::new(Some(current_inode));
state.nodes.insert(
new_inode,
Node::Directory {
Expand Down Expand Up @@ -536,8 +542,11 @@ impl State {
fn advance_time_inner(&mut self, now: Tick) {
assert!(now >= self.now);

let mut inodes: Vec<Inode> = self.nodes.keys().copied().collect();
for inode in inodes.drain(..) {
for inode in self.nodes.keys() {
self.inode_scratch.push(*inode);
}

for inode in self.inode_scratch.drain(..) {
let (rotated_inode, parent_inode, bytes_per_tick, group_id, ordinal) = {
// If the node pointed to by inode doesn't exist, that's a
// catastrophic programming error. We just copied all inode to node
Expand Down Expand Up @@ -806,14 +815,15 @@ impl State {
}
}

/// Read inodes from a directory
/// Read inodes from a directory.
///
/// Returns None if the inode is a `File`, else returns the hashset of
/// children inodes.
/// children inodes. Guaranteed to be in the same order so long as time does
/// not advance.
///
/// Function does not advance time in the model.
#[tracing::instrument(skip(self))]
pub(crate) fn readdir(&self, inode: Inode) -> Option<&HashSet<Inode>> {
pub(crate) fn readdir(&self, inode: Inode) -> Option<&BTreeSet<Inode>> {
if let Some(Node::Directory { dir, .. }) = self.nodes.get(&inode) {
Some(&dir.children)
} else {
Expand Down

0 comments on commit 94a0a35

Please sign in to comment.