hsperf 0.2.0

A library to monitor java virtual machines performance data
Documentation
use std::{os::raw::c_void, ptr::NonNull};

use crate::{
    ConstantEntry, errors::Error, perf_data::PerfDataEntryHeader, safish_pointer::SafishPointer,
    variable_data_reference::VariableDataReference,
};

/// /**
///  ** PerfData Version Constants
///  **   - Major Version - change whenever the structure of PerfDataEntry changes
///  **   - Minor Version - change whenever the data within the PerfDataEntry
///  **                     structure changes. for example, new unit or variability
///  **                     values are added or new PerfData subtypes are added.
///  **/
/// #define PERFDATA_MAJOR_VERSION 2
/// #define PERFDATA_MINOR_VERSION 0
///
const MAJOR_VERSION: u8 = 2;
const MINOR_VERSION: u8 = 0;

const BIG_ENDIAN_MAGIC_NUMBER: u32 = 0xcafec0c0u32;
const LITTLE_ENDIAN_MAGIC_NUMBER: u32 = 0xc0c0fecau32;

#[repr(u32)]
#[derive(Debug, PartialEq)]
enum MagicNumber {
    LittleEndian = LITTLE_ENDIAN_MAGIC_NUMBER,
    BigEndian = BIG_ENDIAN_MAGIC_NUMBER,
}

/// /* Byte order of the PerfData memory region. The byte order is exposed in
///  * the PerfData memory region as the data in the memory region may have
///  * been generated by a little endian JVM implementation. Tracking the byte
///  * order in the PerfData memory region allows Java applications to adapt
///  * to the native byte order for monitoring purposes. This indicator is
///  * also useful when a snapshot of the PerfData memory region is shipped
///  * to a machine with a native byte order different from that of the
///  * originating machine.
///  */
/// #define PERFDATA_BIG_ENDIAN     0
/// #define PERFDATA_LITTLE_ENDIAN  1
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum Endianness {
    BigEndian = 0,
    LittleEndian = 1,
}

/// /*
///  * The PerfDataPrologue structure is known by the PerfDataBuffer Java class
///  * libraries that read the PerfData memory region. The size and the position
///  * of the fields must be changed along with their counterparts in the
///  * PerfDataBuffer Java class. The first four bytes of this structure
///  * should never change, or compatibility problems between the monitoring
///  * applications and HotSpot VMs will result. The reserved fields are
///  * available for future enhancements.
///  */
/// typedef struct {
///     jint   magic;              // magic number - 0xcafec0c0
///     jbyte  byte_order;         // byte order of the buffer
///     jbyte  major_version;      // major and minor version numbers
///     jbyte  minor_version;
///     jbyte  accessible;         // ready to access
///     jint   used;               // number of PerfData memory bytes used
///     jint   overflow;           // number of bytes of overflow
///     jlong  mod_time_stamp;     // time stamp of last structural modification
///     jint   entry_offset;       // offset of the first PerfDataEntry
///     jint   num_entries;        // number of allocated PerfData entries
///   } PerfDataPrologue;
#[repr(C)]
#[derive(Debug)]
pub(crate) struct PerfDataProlog {
    magic: MagicNumber,
    byte_order: Endianness,
    major_version: u8,
    minor_version: u8,
    accessible: u8,
    used: i32,
    overflow: i32,
    mod_time_stamp: i64,
    entry_offset: i32,
    num_entries: i32,
}

impl PerfDataProlog {
    pub(crate) fn read_entries(
        prolog_addr: &NonNull<c_void>,
        length: usize,
    ) -> Result<(Vec<ConstantEntry>, Vec<VariableDataReference>), Error> {
        let prolog = Self::new(prolog_addr).validate()?.validate_length(length)?;
        prolog.map_entries(prolog_addr)
    }

    fn new(prolog_addr: &NonNull<c_void>) -> Self {
        unsafe { (prolog_addr.as_ptr() as *const PerfDataProlog).read_volatile() }
    }

    fn validate_length(self, length: usize) -> Result<Self, crate::errors::Error> {
        // Ensure there are enough bytes to read the entries.
        if length >= self.used as usize {
            Ok(self)
        } else {
            Err(Error::WontBeAbleToRead)
        }
    }

    /// Verifies the struct matches the specs used to create the crate
    fn validate(self) -> Result<Self, crate::errors::Error> {
        if self.major_version != MAJOR_VERSION {
            Err(crate::errors::Error::UnsupportedMajorVersion)
        } else if self.minor_version != MINOR_VERSION {
            Err(crate::errors::Error::UnsupportedMinorVersion)
        } else if self.byte_order == Endianness::BigEndian && self.magic != MagicNumber::BigEndian {
            Err(crate::errors::Error::InvalidMagicNumber)
        } else if self.byte_order == Endianness::LittleEndian
            && self.magic != MagicNumber::LittleEndian
        {
            Err(crate::errors::Error::InvalidMagicNumber)
        } else {
            Ok(self)
        }
    }

    fn map_entries(
        self,
        prolog_addr: &NonNull<c_void>,
    ) -> Result<(Vec<ConstantEntry>, Vec<VariableDataReference>), Error> {
        let entries_ptr = self.entries_ptr(&prolog_addr)?;
        let entries_count = self.num_entries as usize;
        let mut variables: Vec<VariableDataReference> = Vec::with_capacity(entries_count);
        let mut constants: Vec<ConstantEntry> = Vec::with_capacity(entries_count);
        let mut offset = 0 as usize;
        for _ in 0..entries_count {
            let entry_ptr: SafishPointer<PerfDataEntryHeader> =
                entries_ptr.clone().add(offset)?.convert()?;
            let header = entry_ptr.read();
            if header.is_variable_entry() {
                variables.push(header.read_variable_entry(entry_ptr)?);
            } else {
                constants.push(header.read_constant_entry(entry_ptr)?);
            };
            offset += header.entry_length() as usize;
        }
        constants.shrink_to_fit();
        variables.shrink_to_fit();
        Ok((constants, variables))
    }

    fn entries_ptr(&self, prolog_addr: &NonNull<c_void>) -> Result<SafishPointer<u8>, Error> {
        SafishPointer::new(
            prolog_addr.as_ptr() as *const u8,
            self.used as usize,
            self.byte_order,
        )
        .and_then(|ptr| ptr.add(self.entry_offset as usize))
    }
}

#[cfg(test)]
mod tests {
    use parameterized::parameterized;

    use crate::perf_data::{
        Endianness,
        perf_data_prolog::{MAJOR_VERSION, MINOR_VERSION, MagicNumber, PerfDataProlog},
    };

    #[test]
    fn validate_is_success() {
        let tested_prolog = PerfDataProlog {
            magic: super::MagicNumber::BigEndian,
            byte_order: super::Endianness::BigEndian,
            major_version: MAJOR_VERSION,
            minor_version: MINOR_VERSION,
            accessible: 0 as u8,
            used: size_of::<PerfDataProlog>() as i32,
            overflow: 0,
            mod_time_stamp: 12345,
            entry_offset: 0,
            num_entries: 0,
        };
        let validation_result = tested_prolog.validate();

        assert!(validation_result.is_ok());
    }

    #[test]
    fn validate_is_an_error_when_major_version_value_is_incorrect() {
        let tested_prolog = PerfDataProlog {
            magic: super::MagicNumber::BigEndian,
            byte_order: super::Endianness::BigEndian,
            major_version: 123 as u8,
            minor_version: MINOR_VERSION,
            accessible: 0 as u8,
            used: size_of::<PerfDataProlog>() as i32,
            overflow: 0,
            mod_time_stamp: 12345,
            entry_offset: 0,
            num_entries: 0,
        };
        let validation_result = tested_prolog.validate();

        assert!(validation_result.is_err());
        assert!(matches!(
            validation_result.err().unwrap(),
            crate::errors::Error::UnsupportedMajorVersion
        ));
    }

    #[test]
    fn validate_is_an_error_when_minor_version_value_is_incorrect() {
        let tested_prolog = PerfDataProlog {
            magic: super::MagicNumber::BigEndian,
            byte_order: super::Endianness::BigEndian,
            major_version: MAJOR_VERSION,
            minor_version: 123 as u8,
            accessible: 0 as u8,
            used: size_of::<PerfDataProlog>() as i32,
            overflow: 0,
            mod_time_stamp: 12345,
            entry_offset: 0,
            num_entries: 0,
        };
        let validation_result = tested_prolog.validate();

        assert!(validation_result.is_err());
        assert!(matches!(
            validation_result.err().unwrap(),
            crate::errors::Error::UnsupportedMinorVersion
        ));
    }

    #[parameterized(magic_number = {
        MagicNumber::BigEndian, MagicNumber::LittleEndian
    }, byte_order = {
        Endianness::LittleEndian, Endianness::BigEndian
    })]
    fn validate_is_an_error_when_magic_number_does_not_match_byte_order(
        magic_number: MagicNumber,
        byte_order: Endianness,
    ) {
        let tested_prolog = PerfDataProlog {
            magic: magic_number,
            byte_order: byte_order,
            major_version: MAJOR_VERSION,
            minor_version: MINOR_VERSION,
            accessible: 0 as u8,
            used: size_of::<PerfDataProlog>() as i32,
            overflow: 0,
            mod_time_stamp: 12345,
            entry_offset: 0,
            num_entries: 0,
        };
        let validation_result = tested_prolog.validate();

        assert!(validation_result.is_err());
        assert!(matches!(
            validation_result.err().unwrap(),
            crate::errors::Error::InvalidMagicNumber
        ));
    }
}