blob: ebbaab18fa13595f67091c556f4270ac2d2026c8 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! File implements IoPacket and Target for POSIX File like objects. Current
//! implementation limits itself to blocking calls.
use {
crate::io_packet::{IoPacket, IoPacketType, TimeInterval},
crate::operations::TargetType,
crate::operations::{OperationType, PipelineStages, Target, TargetOps},
libc::{c_void, pwrite},
log::debug,
log::error,
std::{
fs::{File, OpenOptions},
io::{Error, ErrorKind, Result},
ops::Range,
os::unix::io::AsRawFd,
process,
sync::Arc,
time::Instant,
},
};
#[derive(Clone)]
pub struct FileIoPacket {
// io_sequence_number is monotonically increasing number which doesn't
// repeat for this run and for this generator. This is used to
// order-replay load.
io_sequence_number: u64,
// This value represents the seed used to generate the contents of the
// IoPacket.
seed: u64,
// Start and finish timestamps for different stages of IO.
stage_timestamps: [TimeInterval; PipelineStages::stage_count()],
// Type of IO operation
operation_type: OperationType,
// Range within the file on which this IO will be performed on. May not
// applicable to all operations ex. create
offset_range: Range<u64>,
// Result of the completed IO operation
io_result: Option<ErrorKind>,
// The target(file) on which IO will be performed
target: TargetType,
// Payload of the IO
buffer: Vec<u8>,
}
impl FileIoPacket {
pub fn new(
operation_type: OperationType,
seq: u64,
seed: u64,
offset_range: &Range<u64>,
target: &TargetType,
) -> FileIoPacket {
let mut p = FileIoPacket {
operation_type: operation_type,
io_sequence_number: seq,
seed: seed,
stage_timestamps: [TimeInterval::new(); PipelineStages::stage_count()],
offset_range: offset_range.clone(),
io_result: None,
target: target.clone(),
buffer: Vec::with_capacity(offset_range.end as usize - offset_range.start as usize),
};
p.buffer.resize(p.buffer.capacity(), 0);
return p;
}
}
impl IoPacket for FileIoPacket {
fn operation_type(&self) -> OperationType {
self.operation_type.clone()
}
fn timestamp_stage_start(&mut self, stage: &PipelineStages) {
self.stage_timestamps[stage.stage_number()].start();
}
fn timestamp_stage_end(&mut self, stage: &PipelineStages) {
self.stage_timestamps[stage.stage_number()].end();
}
fn sequence_number(&self) -> &u64 {
&self.io_sequence_number
}
fn stage_duration(&self, stage: &PipelineStages) -> u128 {
self.stage_timestamps[stage.stage_number()].duration()
}
fn interval_to_u64(&self, stage: &PipelineStages) -> (u64, u64) {
self.stage_timestamps[stage.stage_number()].interval_to_u64(&self.target.start_instant())
}
fn io_offset_range(&self) -> &Range<u64> {
&self.offset_range
}
fn do_io(&mut self) {
let target = self.target.clone();
target.do_io(self);
}
fn is_complete(&self) -> bool {
let target = self.target.clone();
return target.is_complete(self);
}
fn verify_needs_io(&self) -> bool {
let target = self.target.clone();
return target.verify_needs_io(self);
}
fn generate_verify_io(&mut self) {
let target = self.target.clone();
return target.generate_verify_io(self);
}
fn verify(&mut self, verify_packet: &IoPacket) -> bool {
let target = self.target.clone();
return target.verify(self, verify_packet);
}
fn get_error(&self) -> Result<()> {
match self.io_result {
Some(error) => Err(Error::new(error.clone(), "something went wrong")),
None => Ok(()),
}
}
fn set_error(&mut self, io_error: Error) {
self.io_result = Some(io_error.kind());
}
fn buffer_mut(&mut self) -> &mut Vec<u8> {
return &mut self.buffer;
}
fn buffer(&mut self) -> &Vec<u8> {
return &self.buffer;
}
}
pub struct FileBlockingTarget {
/// File name
_name: String,
/// Set of [supported] operations
ops: TargetOps,
/// Open file descriptor
file: File,
/// Unique file id for this run and for this generator
target_unique_id: u64,
/// Range within which this Targets operates on the file
offset_range: Range<u64>,
start_instant: Instant,
}
impl FileBlockingTarget {
// Create a new Target instance. Fails when opening an existing file fails.
// TODO(auradkar): Open should be moved to setup phase when all operations
// file are supported.
pub fn new(
target_name: String,
target_id: u64,
offset_range: &Range<u64>,
start_instant: &Instant,
) -> Result<TargetType> {
let ops = TargetOps { write: Some(OperationType::Write),
truncate: None,
open: Some(OperationType::Open),
create: None,
};
//let file = OpenOptions::new().append(false).open(&target_name).unwrap();
let file = OpenOptions::new().write(true).append(false).open(&target_name).unwrap();
Ok(Arc::new(Box::new(FileBlockingTarget {
_name: target_name,
file: file,
ops: ops,
target_unique_id: target_id,
offset_range: offset_range.clone(),
start_instant: start_instant.clone(),
})))
}
// pwrite the buffer in IoPacket at io_offset_range.
fn write(&self, io_packet: &mut IoPacket) {
let offset_range = io_packet.io_offset_range().clone();
if offset_range.start < self.offset_range.start || offset_range.end > self.offset_range.end
{
io_packet.set_error(Error::new(ErrorKind::AddrInUse, "Offset out of range!"));
return;
}
let raw_fd = self.file.as_raw_fd().clone();
let b = io_packet.buffer_mut();
let ret = unsafe {
pwrite(raw_fd, b.as_ptr() as *const c_void, b.len(), offset_range.start as i64)
};
debug!(
"do_write: {}..{} {}",
io_packet.io_offset_range().start,
io_packet.io_offset_range().end,
ret
);
if ret < 0 {
io_packet.set_error(Error::last_os_error());
} else if ret < 8 {
io_packet.set_error(Error::new(ErrorKind::Other, "oh no!"));
}
}
fn open(&self, io_packet: &mut IoPacket) {
error!("open not yet supported {}", io_packet.sequence_number());
process::abort();
}
fn exit(&self, io_packet: &mut IoPacket) {
debug!("Nothing to do for exit path {}", io_packet.sequence_number());
}
}
impl Target for FileBlockingTarget {
fn setup(&mut self, _file_name: &String, _range: Range<u64>) -> Result<()> {
Ok(())
}
fn create_io_packet(
&self,
operation_type: OperationType,
seq: u64,
seed: u64,
io_offset_range: Range<u64>,
target: &TargetType,
) -> IoPacketType {
return Box::new(FileIoPacket::new(operation_type, seq, seed, &io_offset_range, target));
}
fn id(&self) -> u64 {
self.target_unique_id
}
fn supported_ops(&self) -> &TargetOps {
return &(self.ops);
}
fn do_io(&self, io_packet: &mut IoPacket) {
match io_packet.operation_type() {
OperationType::Write => self.write(io_packet),
OperationType::Open => self.open(io_packet),
OperationType::Exit => self.exit(io_packet),
_ => {
error!("Unsupported operation");
process::abort();
}
};
}
fn is_complete(&self, io_packet: &IoPacket) -> bool {
match io_packet.operation_type() {
OperationType::Write | OperationType::Open | OperationType::Exit => true,
_ => {
error!("Complete for unsupported operation");
process::abort();
}
}
}
fn verify_needs_io(&self, io_packet: &IoPacket) -> bool {
match io_packet.operation_type() {
OperationType::Write | OperationType::Open | OperationType::Exit => false,
_ => {
error!("verify_needs_io for unsupported operation");
process::abort();
}
}
}
fn generate_verify_io(&self, io_packet: &mut IoPacket) {
match io_packet.operation_type() {
_ => {
error!("generate_verify_io for unsupported operation");
process::abort();
}
};
}
fn verify(&self, io_packet: &mut IoPacket, _verify_packet: &IoPacket) -> bool {
match io_packet.operation_type() {
OperationType::Write | OperationType::Exit => true,
_ => {
error!("verify for unsupported operation");
process::abort();
}
}
}
fn start_instant(&self) -> Instant {
self.start_instant.clone()
}
}
#[cfg(test)]
mod tests {
use {
crate::file_target::FileBlockingTarget,
crate::operations::OperationType,
crate::operations::TargetType,
std::{fs, fs::File, time::Instant},
};
static FILE_LENGTH: u64 = 1 * 1024 * 1024; // 1 MiB
fn setup(file_name: &String) -> TargetType {
let f = File::create(&file_name).unwrap();
f.set_len(FILE_LENGTH).unwrap();
let start_instant: Instant = Instant::now();
FileBlockingTarget::new(file_name.to_string(), 0, &(0..FILE_LENGTH), &start_instant)
.unwrap()
}
fn teardown(file_name: &String) {
if false {
fs::remove_file(file_name).unwrap();
}
}
#[test]
fn simple_write() {
let file_name = "/tmp/FileBlockingTargetTestFile".to_string();
let target = setup(&file_name);
let mut io_packet = target.create_io_packet(OperationType::Write, 0, 0, 0..4096, &target);
let mut _buffer = io_packet.buffer_mut();
io_packet.do_io();
assert_eq!(io_packet.is_complete(), true);
io_packet.get_error().unwrap();
teardown(&file_name);
}
#[test]
fn write_failure() {
let file_name = "/tmp/FileBlockingTargetTestFile2".to_string();
let target = setup(&file_name);
// Try to write beyond allowed offset range
let mut io_packet = target.create_io_packet(
OperationType::Write,
0,
0,
(2 * FILE_LENGTH)..(3 * FILE_LENGTH),
&target,
);
let mut _buffer = io_packet.buffer_mut();
io_packet.do_io();
assert_eq!(io_packet.is_complete(), true);
assert_eq!(io_packet.get_error().is_err(), true);
teardown(&file_name);
}
}