use crate::{
ConstantEntry,
data_pointer::DataPointer,
entry_value::EntryValue,
errors::Error,
perf_data::{DataType, Unit, Variability},
safish_pointer::SafishPointer,
variable_data_reference::VariableDataReference,
};
#[repr(C)]
#[derive(Debug)]
pub(crate) struct PerfDataEntryHeader {
entry_length: i32,
name_offset: i32,
vector_length: i32,
data_type: DataType, flags: Flag,
data_units: Unit,
data_variability: Variability,
data_offset: u8, }
impl PerfDataEntryHeader {
pub(crate) fn is_variable_entry(&self) -> bool {
self.data_variability == Variability::Monotonic
|| self.data_variability == Variability::Variable
}
pub(crate) fn entry_length(&self) -> i32 {
self.entry_length
}
pub(crate) fn read_variable_entry(
&self,
header_ptr: SafishPointer<PerfDataEntryHeader>,
) -> Result<VariableDataReference, Error> {
let entry_ptr = header_ptr.convert()?;
let variability = self.data_variability;
let unit = self.data_units;
let name = self.read_name(entry_ptr.clone().add(self.name_offset as usize)?)?;
let data_ptr = entry_ptr.add(self.data_offset as usize)?;
let data_pointer = if self.vector_length == 0 {
DataPointer::new_scalar(&self.data_type, data_ptr)
} else {
DataPointer::new_vector(&self.data_type, self.vector_length as usize, data_ptr)
}?;
VariableDataReference::new(name, data_pointer, variability, unit)
}
pub(crate) fn read_constant_entry(
&self,
header_ptr: SafishPointer<PerfDataEntryHeader>,
) -> Result<ConstantEntry, Error> {
let entry_ptr = header_ptr.convert()?;
let name = self.read_name(entry_ptr.clone().add(self.name_offset as usize)?)?;
let unit = self.data_units;
let data_ptr = entry_ptr.add(self.data_offset as usize)?;
let data_pointer = if self.vector_length == 0 {
DataPointer::new_scalar(&self.data_type, data_ptr)?
} else {
DataPointer::new_vector(&self.data_type, self.vector_length as usize, data_ptr)?
};
let value = EntryValue::new(&data_pointer)?;
let reference = ConstantEntry::new(name, value, unit);
Ok(reference)
}
fn read_name(&self, ptr: SafishPointer<u8>) -> Result<String, Error> {
ptr.read_string(self.data_offset as usize - self.name_offset as usize)
}
}
#[repr(u8)]
#[derive(Debug, Clone)]
#[allow(dead_code)]
enum Flag {
None = 0,
Supported = 1,
}
#[cfg(test)]
mod tests {
use parameterized::parameterized;
use test_strategy::proptest;
use crate::{
EntryValue,
errors::Error,
perf_data::{
DataType, Endianness, Variability,
perf_data_entry_header::{Flag, PerfDataEntryHeader},
unit::Unit,
},
safish_pointer::SafishPointer,
};
fn given_a_header() -> PerfDataEntryHeader {
PerfDataEntryHeader {
entry_length: 0,
name_offset: 0,
vector_length: 0,
data_type: DataType::Boolean,
flags: Flag::None,
data_units: Unit::Hertz,
data_variability: Variability::Invalid,
data_offset: 0 as u8,
}
}
fn pack_binary_value(name: &str, value: u8) -> Vec<u8> {
let mut binary_data: Vec<u8> = vec![0; (name.len() + 1) as usize];
binary_data[..name.len()].copy_from_slice(name.as_bytes());
binary_data[name.len()] = value;
binary_data
}
fn pack_binary_array(name: &str, value: &str) -> Vec<u8> {
let mut binary_data: Vec<u8> = vec![0; (name.len() + value.len()) as usize];
binary_data[..name.len()].copy_from_slice(name.as_bytes());
binary_data[name.len()..].copy_from_slice(value.as_bytes());
binary_data
}
#[parameterized(variability = {
Variability::Variable, Variability::Monotonic
})]
fn is_variable_entry(variability: Variability) {
let mut tested_header = given_a_header();
tested_header.data_variability = variability;
assert!(tested_header.is_variable_entry());
}
#[parameterized(variability = {
Variability::Invalid, Variability::Constant
})]
fn is_not_variable_entry(variability: Variability) {
let mut tested_header = given_a_header();
tested_header.data_variability = variability;
assert!(!tested_header.is_variable_entry());
}
#[proptest]
fn entry_length_returns_the_entry_length(entry_lemgth: i32) {
let mut tested_header = given_a_header();
tested_header.entry_length = entry_lemgth;
assert_eq!(entry_lemgth, tested_header.entry_length())
}
#[proptest]
fn name_is_read_from_the_pointer(
#[strategy("[a-zA-Z.]{1, 10}")] name: String,
#[strategy(0..100)] name_offset: i32,
) {
let mut tested_header = given_a_header();
tested_header.name_offset = name_offset;
tested_header.data_offset = name_offset as u8 + name.len() as u8;
let safish_pointer =
SafishPointer::new(name.as_ptr(), name.len(), Endianness::BigEndian).unwrap();
assert_eq!(name, tested_header.read_name(safish_pointer).unwrap());
}
#[test]
fn read_constant_entry_populates_a_constant_scalar_entry() {
let name = "test.vm";
let value = 123;
let unit = Unit::Ticks;
let mut tested_header = given_a_header();
tested_header.name_offset = 0;
tested_header.data_offset = name.len() as u8;
tested_header.data_type = DataType::Byte;
tested_header.vector_length = 0;
tested_header.data_units = unit;
let binary_data = pack_binary_value(&name, value);
let safish_pointer = SafishPointer::new(
binary_data.as_ptr(),
size_of::<PerfDataEntryHeader>() + name.len() + 1,
Endianness::BigEndian,
)
.unwrap();
let constant_entry = tested_header
.read_constant_entry(safish_pointer.convert().unwrap())
.unwrap();
assert_eq!(name, constant_entry.name());
match constant_entry.value() {
EntryValue::Byte(content) => assert_eq!(*content, value),
_ => assert!(false),
}
assert!(matches!(constant_entry.unit(), Unit::Ticks));
}
#[test]
fn read_constant_entry_populates_a_constant_vector_entry() {
let name = "test.vm";
let value = "12";
let unit = Unit::Ticks;
let mut tested_header = given_a_header();
tested_header.name_offset = 0;
tested_header.data_offset = name.len() as u8;
tested_header.data_type = DataType::Byte;
tested_header.vector_length = 2;
tested_header.data_units = unit;
let binary_data = pack_binary_array(&name, &value);
let safish_pointer = SafishPointer::new(
binary_data.as_ptr(),
size_of::<PerfDataEntryHeader>() + name.len() + 1,
Endianness::BigEndian,
)
.unwrap();
let constant_entry = tested_header
.read_constant_entry(safish_pointer.convert().unwrap())
.unwrap();
assert_eq!(name, constant_entry.name());
match constant_entry.value() {
EntryValue::String(content) => assert_eq!(*content, value.to_string()),
_ => assert!(false),
}
assert!(matches!(constant_entry.unit(), Unit::Ticks));
}
#[test]
fn read_constant_entry_returns_err_when_pointer_too_short_for_name() {
let name = "test.vm";
let value = 123;
let unit = Unit::Ticks;
let mut tested_header = given_a_header();
tested_header.name_offset = size_of::<PerfDataEntryHeader>() as i32;
tested_header.data_offset = (size_of::<PerfDataEntryHeader>() + name.len()) as u8;
tested_header.data_type = DataType::Byte;
tested_header.vector_length = 0;
tested_header.data_units = unit;
let binary_data = pack_binary_value(&name, value);
let safish_pointer = SafishPointer::new(
binary_data.as_ptr(),
size_of::<PerfDataEntryHeader>(),
Endianness::BigEndian,
)
.unwrap();
let error = tested_header
.read_constant_entry(safish_pointer.convert().unwrap())
.err()
.unwrap();
assert!(matches!(error, Error::WontBeAbleToRead));
}
#[test]
fn read_variable_entry_populates_a_variable_scalar_data_reference() {
let name = "test.vm";
let value = 123;
let unit = Unit::Ticks;
let variability = Variability::Monotonic;
let mut tested_header = given_a_header();
tested_header.name_offset = 0;
tested_header.data_offset = name.len() as u8;
tested_header.data_type = DataType::Byte;
tested_header.vector_length = 0;
tested_header.data_units = unit;
tested_header.data_variability = variability;
let binary_data = pack_binary_value(&name, value);
let safish_pointer = SafishPointer::new(
binary_data.as_ptr(),
size_of::<PerfDataEntryHeader>() + name.len() + 1,
Endianness::BigEndian,
)
.unwrap();
let mut variable_data_reference = tested_header
.read_variable_entry(safish_pointer.convert().unwrap())
.unwrap();
let entry = variable_data_reference.refresh_entry().unwrap();
assert_eq!(name, entry.name());
assert!(matches!(entry.unit(), Unit::Ticks));
assert!(matches!(entry.variability(), Variability::Monotonic));
match entry.value() {
EntryValue::Byte(content) => assert_eq!(*content, value),
_ => assert!(false),
}
}
#[test]
fn read_variable_entry_populates_a_variable_vector_data_reference() {
let name = "test.vm";
let value = "12";
let unit = Unit::Ticks;
let variability = Variability::Monotonic;
let mut tested_header = given_a_header();
tested_header.name_offset = 0;
tested_header.data_offset = name.len() as u8;
tested_header.data_type = DataType::Byte;
tested_header.vector_length = 2;
tested_header.data_units = unit;
tested_header.data_variability = variability;
let binary_data = pack_binary_array(&name, &value);
let safish_pointer = SafishPointer::new(
binary_data.as_ptr(),
size_of::<PerfDataEntryHeader>() + name.len() + 1,
Endianness::BigEndian,
)
.unwrap();
let mut variable_data_reference = tested_header
.read_variable_entry(safish_pointer.convert().unwrap())
.unwrap();
let entry = variable_data_reference.refresh_entry().unwrap();
assert_eq!(name, entry.name());
match entry.value() {
EntryValue::String(content) => assert_eq!(*content, value.to_string()),
_ => assert!(false),
}
assert!(matches!(entry.unit(), Unit::Ticks));
assert!(matches!(entry.variability(), Variability::Monotonic));
}
}