diff --git a/lading/src/generator/file_gen/logrotate_fs.rs b/lading/src/generator/file_gen/logrotate_fs.rs index 89186d2740..e72cf41434 100644 --- a/lading/src/generator/file_gen/logrotate_fs.rs +++ b/lading/src/generator/file_gen/logrotate_fs.rs @@ -188,12 +188,13 @@ fn getattr_helper( let access_duration = Duration::from_secs(attr.access_tick); let modified_duration = Duration::from_secs(attr.modified_tick); let status_duration = Duration::from_secs(attr.status_tick); + let created_duration = Duration::from_secs(attr.created_tick); // Calculate SystemTime instances let atime = start_time_system + access_duration; let mtime = start_time_system + modified_duration; let ctime = start_time_system + status_duration; - let crtime = start_time_system; // Assume creation time is when the filesystem started + let crtime = start_time_system + created_duration; FileAttr { ino: attr.inode as u64, diff --git a/lading/src/generator/file_gen/logrotate_fs/model.rs b/lading/src/generator/file_gen/logrotate_fs/model.rs index de4f707e43..2d5862db7f 100644 --- a/lading/src/generator/file_gen/logrotate_fs/model.rs +++ b/lading/src/generator/file_gen/logrotate_fs/model.rs @@ -30,6 +30,8 @@ pub(crate) struct File { /// Property: `bytes_written` >= `bytes_read`. bytes_read: u64, + /// The `Tick` on which the `File` was created. + created_tick: Tick, /// The `Tick` on which the `File` was last accessed. Updated on reads, /// opens for reading. access_tick: Tick, @@ -49,6 +51,9 @@ pub(crate) struct File { /// happen -- or not. read_only: bool, + /// When the file became read-only. Will only be Some if read_only is false. + read_only_since: Option, + /// The peer of this file, the next in line in rotation. So, if this file is /// foo.log the peer will be foo.log.1 and its peer foo.log.2 etc. peer: Option, @@ -176,8 +181,9 @@ impl File { /// /// This function flips the internal bool on this `File` stopping any future /// byte accumulations. - pub(crate) fn set_read_only(&mut self) { + pub(crate) fn set_read_only(&mut self, now: Tick) { self.read_only = true; + self.read_only_since = Some(now); } /// Return whether the file is read-only or not @@ -204,6 +210,15 @@ impl File { pub(crate) fn size(&self) -> u64 { self.bytes_written } + + /// Calculate the expected bytes written based on writable duration. + #[cfg(test)] + pub(crate) fn expected_bytes_written(&self, now: Tick) -> u64 { + let start_tick = self.created_tick; + let end_tick = self.read_only_since.unwrap_or(now); + let writable_duration = end_tick.saturating_sub(start_tick); + self.bytes_per_tick.saturating_mul(writable_duration) + } } /// Model representation of a `Directory`. Contains children are `Directory` @@ -279,6 +294,8 @@ pub(crate) struct NodeAttributes { pub(crate) modified_tick: Tick, /// The last status change time in ticks. pub(crate) status_tick: Tick, + /// The tick on which the file was created. + pub(crate) created_tick: Tick, } /// Describe whether the Node is a File or Directory. @@ -430,6 +447,7 @@ impl State { access_tick: state.now, modified_tick: state.now, status_tick: state.now, + created_tick: state.now, bytes_per_tick, read_only: false, ordinal: 0, @@ -438,6 +456,7 @@ impl State { open_handles: 0, unlinked: false, max_offset_observed: 0, + read_only_since: None, }; state.nodes.insert(file_inode, Node::File { file }); @@ -514,7 +533,7 @@ impl State { "Expected rotated file to be 0th ordinal, was {ordinal}", ordinal = file.ordinal() ); - file.set_read_only(); + file.set_read_only(now); Some(( inode, file.parent, @@ -555,6 +574,7 @@ impl State { access_tick: self.now, modified_tick: self.now, status_tick: self.now, + created_tick: self.now, bytes_per_tick, read_only: false, ordinal: 0, @@ -563,6 +583,7 @@ impl State { open_handles: 0, unlinked: false, max_offset_observed: 0, + read_only_since: None, }; new_file.advance_time(now); @@ -720,6 +741,7 @@ impl State { access_tick: file.access_tick, modified_tick: file.modified_tick, status_tick: file.status_tick, + created_tick: file.created_tick, }, Node::Directory { .. } => NodeAttributes { inode, @@ -728,6 +750,7 @@ impl State { access_tick: self.now, modified_tick: self.now, status_tick: self.now, + created_tick: self.now, }, }) } @@ -1112,6 +1135,21 @@ mod test { } } } + + // Property 7: bytes_written are tick accurate + for node in state.nodes.values() { + if let Node::File { file } = node { + let expected_bytes = file.expected_bytes_written(state.now); + assert_eq!( + file.bytes_written, + expected_bytes, + "bytes_written ({}) does not match expected_bytes_written ({}) for file with inode {}", + file.bytes_written, + expected_bytes, + file.parent + ); + } + } } proptest! {