blob: eb4504c6c5121e0db3d9f0f7bc0ec3542b820ccc [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,
io_sequence_number: u64,
seed: u64,
offset_range: Range<u64>,
target: TargetType,
) -> FileIoPacket {
let buffer = vec![0; offset_range.end as usize - offset_range.start as usize];
FileIoPacket {
operation_type,
io_sequence_number,
seed,
stage_timestamps: [TimeInterval::new(); PipelineStages::stage_count()],
offset_range: offset_range.clone(),
io_result: None,
target,
buffer,
}
}
}
impl IoPacket for FileIoPacket {
fn operation_type(&self) -> OperationType {
self.operation_type
}
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.clone()
}
fn do_io(&mut self) {
self.target.clone().do_io(self)
}
fn is_complete(&self) -> bool {
self.target.clone().is_complete(self)
}
fn verify_needs_io(&self) -> bool {
self.target.clone().verify_needs_io(self)
}
fn generate_verify_io(&mut self) {
self.target.clone().generate_verify_io(self)
}
fn verify(&mut self, verify_packet: &IoPacket) -> bool {
self.target.clone().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> {
&mut self.buffer
}
fn buffer(&mut self) -> &Vec<u8> {
&self.buffer
}
}
pub struct FileBlockingTarget {
/// File name
#[allow(unused)]
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(
name: String,
target_unique_id: u64,
offset_range: Range<u64>,
start_instant: Instant,
) -> TargetType {
let ops = TargetOps { write: true, open: true };
let file = OpenOptions::new().write(true).append(false).open(&name).unwrap();
Arc::new(Box::new(FileBlockingTarget {
name,
file,
ops,
target_unique_id,
offset_range,
start_instant,
}))
}
// 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: {:?} {}", offset_range, ret);
if ret < 0 {
io_packet.set_error(Error::last_os_error());
} else if ret < b.len() as isize {
// TODO(auradkar): Define a set of error codes to be used throughout the app.
io_packet
.set_error(Error::new(ErrorKind::Other, "pwrite wrote less bytes than requested!"));
}
}
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 {
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 {
&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
}
}
#[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::now();
FileBlockingTarget::new(file_name.to_string(), 0, 0..FILE_LENGTH, start_instant)
}
fn teardown(file_name: &String) {
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.clone());
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.clone(),
);
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);
}
}