#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct ProcessStatusStatistics
{
pub process_name: Option<Box<[u8]>>,
pub file_mode_creation_mask: Option<mode_t>,
pub state: Option<ProcessState>,
pub thread_group_identifier: Option<pid_t>,
pub numa_group_identifier: Option<NumaNode>,
pub process_identifier: Option<pid_t>,
pub parent_process_identifier: Option<pid_t>,
pub tracer_process_identifier: Option<pid_t>,
pub user_identifiers: Option<ProcessUserIdentifiers>,
pub group_identifiers: Option<ProcessGroupIdentifiers>,
pub number_of_file_descriptor_slots_currently_allocated: Option<u64>,
pub groups: Option<BTreeSet<gid_t>>,
pub descendant_namespace_thread_group_identifier: Option<BTreeSet<pid_t>>,
pub descendant_namespace_process_identifier: Option<BTreeSet<pid_t>>,
pub descendant_namespace_process_group_identifier: Option<BTreeSet<pid_t>>,
pub descendant_namespace_session_identifier: Option<BTreeSet<pid_t>>,
pub peak_virtual_memory_size: Option<Kilobyte>,
pub total_program_size: Option<Kilobyte>,
pub locked_memory_size: Option<Kilobyte>,
pub pinned_memory_size: Option<Kilobyte>,
pub peak_resident_set_size: Option<Kilobyte>,
pub resident_set_memory_size: Option<Kilobyte>,
pub anonymous_resident_set_memory_size: Option<Kilobyte>,
pub resident_set_file_mappings_memory_size: Option<Kilobyte>,
pub resident_set_shared_memory_size: Option<Kilobyte>,
pub private_data_segments_size: Option<Kilobyte>,
pub stack_segments_size: Option<Kilobyte>,
pub text_segment_size: Option<Kilobyte>,
pub dynamically_loaded_shared_library_size: Option<Kilobyte>,
pub page_table_entries_size: Option<Kilobyte>,
pub vm_pmd: Option<Kilobyte>,
pub swap_memory_size: Option<Kilobyte>,
pub huge_tlb_pages_memory_size: Option<Kilobyte>,
pub threads: Option<u64>,
pub signal_queue: Option<SignalQueueStatus>,
pub thread_pending_signals: Option<Bitmask>,
pub process_shared_pending_signals: Option<Bitmask>,
pub blocked_signals: Option<Bitmask>,
pub ignored_signals: Option<Bitmask>,
pub caught_signals: Option<Bitmask>,
pub inheritable_capabilities: Option<Bitmask>,
pub permitted_capabilities: Option<Bitmask>,
pub effective_capabilities: Option<Bitmask>,
pub capabilities_bounding_set: Option<Bitmask>,
pub ambient_capabilities: Option<Bitmask>,
pub thread_no_new_privileges_bit: Option<bool>,
pub seccomp_mode: Option<SeccompMode>,
pub speculation_store_bypass: Option<SpeculationStoreBypassStatus>,
pub cpus_allowed_bitmask: Option<HyperThreadBitmask>,
pub cpus_allowed_list: Option<BTreeSet<HyperThread>>,
pub numa_nodes_allowed_bitmask: Option<NumaNodeBitmask>,
pub numa_nodes_allowed_list: Option<BTreeSet<NumaNode>>,
pub voluntary_context_switches: Option<u64>,
pub involuntary_context_switches: Option<u64>,
unrecognised: HashMap<Box<[u8]>, Box<[u8]>>,
}
impl ProcessStatusStatistics
{
#[inline(always)]
pub fn unrecognised_statistic(&self, statistic_name: &[u8]) -> Option<&Box<[u8]>>
{
self.unrecognised.get(statistic_name)
}
pub fn parse(reader: BufReader<File>) -> Result<Self, ProcessStatusFileParseError>
{
use self::ProcessStatusFileParseError::*;
let mut this = Self::default();
let mut zero_based_line_number = 0;
for line in reader.split(b'\n')
{
let mut line = match line
{
Err(cause) => return Err(CouldNotReadLine { zero_based_line_number, cause }),
Ok(line) => line,
};
{
let mut split = splitn(&line, 2, b':');
let statistic_name = split.next().unwrap();
match split.next()
{
None => return Err(CouldNotParseLine { zero_based_line_number, cause: ProcessStatusStatisticParseError::NoValue }),
Some(tab_then_statistic_value) =>
{
if unlikely!(!tab_then_statistic_value.starts_with(b"\t"))
{
return Err(CouldNotParseLine { zero_based_line_number, cause: ProcessStatusStatisticParseError::ValueNotPreceededByHorizontalTab })
}
let statistic_value = &tab_then_statistic_value[1 ..];
match this.parse_line(statistic_name, statistic_value)
{
Err(cause) => return Err(CouldNotParseLine { zero_based_line_number, cause }),
Ok(()) => (),
}
}
};
}
zero_based_line_number += 1;
}
this.unrecognised.shrink_to_fit();
Ok(this)
}
#[inline]
fn parse_line(&mut self, statistic_name: &[u8], statistic_value: &[u8]) -> Result<(), ProcessStatusStatisticParseError>
{
#[inline(always)]
fn to_box(value: &[u8]) -> Box<[u8]>
{
value.to_vec().into_boxed_slice()
}
#[inline(always)]
fn parse_token(value: &[u8]) -> Result<Box<[u8]>, ProcessStatusStatisticParseError>
{
Ok(to_box(value))
}
#[inline(always)]
fn parse_mode(value: &[u8]) -> Result<mode_t, ProcessStatusStatisticParseError>
{
if likely!(value.len() == 4)
{
Ok(mode_t::from_str_radix(from_utf8(value)?, 8)?)
}
else
{
Err(ProcessStatusStatisticParseError::InvalidLength)
}
}
#[inline(always)]
fn parse_process_state(value: &[u8]) -> Result<ProcessState, ProcessStatusStatisticParseError>
{
if unlikely!(value.len() == 0)
{
return Err(ProcessStatusStatisticParseError::InvalidLength)
}
use self::ProcessState::*;
let value = match value[0]
{
b'R' => Running,
b'S' => Sleeping,
b'D' => SleepingInAnUninterruptibleWait,
b'T' => TracedOrStopped,
b't' => TracingStop,
b'X' => Dead,
b'Z' => Zombie,
b'P' => Parked,
b'I' => Idle,
_ => return Err(ProcessStatusStatisticParseError::OutOfRange)
};
Ok(value)
}
#[inline(always)]
fn parse_pid(value: &[u8]) -> Result<pid_t, ProcessStatusStatisticParseError>
{
Ok(pid_t::from_str_radix(from_utf8(value)?, 10)?)
}
#[inline(always)]
fn parse_numa_node(value: &[u8]) -> Result<NumaNode, ProcessStatusStatisticParseError>
{
Ok(NumaNode(u16::from_str_radix(from_utf8(value)?, 10)?))
}
#[inline(always)]
fn parse_uid(value: &[u8]) -> Result<uid_t, ProcessStatusStatisticParseError>
{
Ok(uid_t::from_str_radix(from_utf8(value)?, 10)?)
}
#[inline(always)]
fn parse_gid(value: &[u8]) -> Result<gid_t, ProcessStatusStatisticParseError>
{
Ok(gid_t::from_str_radix(from_utf8(value)?, 10)?)
}
#[inline(always)]
fn parse_user_identifiers(value: &[u8]) -> Result<ProcessUserIdentifiers, ProcessStatusStatisticParseError>
{
#[inline(always)]
fn parse_subsequent<'a>(iterator: &mut impl Iterator<Item=&'a [u8]>) -> Result<uid_t, ProcessStatusStatisticParseError>
{
if let Some(effective) = iterator.next()
{
parse_uid(effective)
}
else
{
Err(ProcessStatusStatisticParseError::InvalidSeparator)
}
}
let mut iterator = splitn(value, 4, b'\t');
Ok
(
ProcessUserIdentifiers
{
real: parse_uid(iterator.next().unwrap())?,
effective: parse_subsequent(&mut iterator)?,
saved_set: parse_subsequent(&mut iterator)?,
file_system: parse_subsequent(&mut iterator)?,
}
)
}
#[inline(always)]
fn parse_group_identifiers(value: &[u8]) -> Result<ProcessGroupIdentifiers, ProcessStatusStatisticParseError>
{
#[inline(always)]
fn parse_subsequent<'a>(iterator: &mut impl Iterator<Item=&'a [u8]>) -> Result<gid_t, ProcessStatusStatisticParseError>
{
if let Some(effective) = iterator.next()
{
parse_gid(effective)
}
else
{
Err(ProcessStatusStatisticParseError::InvalidSeparator)
}
}
let mut iterator = splitn(value, 4, b'\t');
Ok
(
ProcessGroupIdentifiers
{
real: parse_gid(iterator.next().unwrap())?,
effective: parse_subsequent(&mut iterator)?,
saved_set: parse_subsequent(&mut iterator)?,
file_system: parse_subsequent(&mut iterator)?,
}
)
}
#[inline(always)]
fn parse_groups(value: &[u8]) -> Result<BTreeSet<gid_t>, ProcessStatusStatisticParseError>
{
let mut groups = BTreeSet::new();
for value in split(value, b' ')
{
let was_added_for_the_first_time = groups.insert(parse_gid(value)?);
if unlikely!(!was_added_for_the_first_time)
{
return Err(ProcessStatusStatisticParseError::DuplicatedStatisticValue)
}
}
Ok(groups)
}
#[inline(always)]
fn parse_pids(value: &[u8]) -> Result<BTreeSet<pid_t>, ProcessStatusStatisticParseError>
{
let mut pids = BTreeSet::new();
for value in split(value, b'\t')
{
let was_added_for_the_first_time = pids.insert(parse_pid(value)?);
if unlikely!(!was_added_for_the_first_time)
{
return Err(ProcessStatusStatisticParseError::DuplicatedStatisticValue)
}
}
Ok(pids)
}
#[inline(always)]
fn parse_u64(value: &[u8]) -> Result<u64, ProcessStatusStatisticParseError>
{
Ok(u64::from_str_radix(from_utf8(value)?, 10)?)
}
#[inline(always)]
fn parse_kb(value: &[u8]) -> Result<Kilobyte, ProcessStatusStatisticParseError>
{
const Ending: &'static [u8] = b" kB";
if likely!(value.ends_with(b" kB"))
{
parse_u64(&value[0 .. value.len() - Ending.len()])
}
else
{
Err(ProcessStatusStatisticParseError::InvalidEnding)
}
}
#[inline(always)]
fn parse_signal_queue(value: &[u8]) -> Result<SignalQueueStatus, ProcessStatusStatisticParseError>
{
let mut iterator = splitn(value, 2, b'/');
let number_of_signals_queued = parse_u64(iterator.next().unwrap())?;
let maximum_number_of_signals_that_can_be_queued = match iterator.next()
{
None => return Err(ProcessStatusStatisticParseError::InvalidSeparator),
Some(maximum_number_of_signals_that_can_be_queued) => parse_u64(maximum_number_of_signals_that_can_be_queued)?,
};
Ok
(
SignalQueueStatus
{
number_of_signals_queued,
maximum_number_of_signals_that_can_be_queued
}
)
}
#[inline(always)]
fn parse_bitmask(value: &[u8]) -> Result<Bitmask, ProcessStatusStatisticParseError>
{
if likely!(value.len() == 16)
{
Ok(u64::from_str_radix(from_utf8(value)?, 16)?)
}
else
{
Err(ProcessStatusStatisticParseError::InvalidLength)
}
}
#[inline(always)]
fn parse_bool(value: &[u8]) -> Result<bool, ProcessStatusStatisticParseError>
{
if likely!(value.len() == 1)
{
match value[0]
{
b'0' => Ok(false),
b'1' => Ok(true),
_ => Err(ProcessStatusStatisticParseError::OutOfRange)
}
}
else
{
Err(ProcessStatusStatisticParseError::InvalidLength)
}
}
#[inline(always)]
fn parse_seccomp_mode(value: &[u8]) -> Result<SeccompMode, ProcessStatusStatisticParseError>
{
if likely!(value.len() == 1)
{
use self::SeccompMode::*;
match value[0]
{
b'0' => Ok(Off),
b'1' => Ok(Strict),
b'2' => Ok(Filter),
_ => Err(ProcessStatusStatisticParseError::OutOfRange)
}
}
else
{
Err(ProcessStatusStatisticParseError::InvalidLength)
}
}
#[inline(always)]
fn parse_speculation_store_bypass(value: &[u8]) -> Result<SpeculationStoreBypassStatus, ProcessStatusStatisticParseError>
{
use self::SpeculationStoreBypassStatus::*;
let value = match value
{
b"unknown" => SpeculationStoreBypassStatus::Unknown,
b"not vulnerable" => NotVulnerable,
b"thread force mitigated" => ThreadForceMitigated,
b"thread mitigated" => ThreadMitigated,
b"thread vulnerable" => ThreadVulnerable,
b"globally mitigated" => GloballyMitigated,
b"vulnerable" => Vulnerable,
_ => return Err(ProcessStatusStatisticParseError::OutOfRange),
};
Ok(value)
}
#[inline(always)]
fn parse_cpus_or_numa_nodes_allowed_bitmask(value: &[u8]) -> Result<u32, ProcessStatusStatisticParseError>
{
if likely!(value.len() <= 8 && value.len() != 0)
{
Ok(u32::from_str_radix(from_utf8(value)?, 16)?)
}
else
{
Err(ProcessStatusStatisticParseError::InvalidLength)
}
}
#[inline(always)]
fn parse_cpus_allowed_list(value: &[u8]) -> Result<BTreeSet<HyperThread>, ProcessStatusStatisticParseError>
{
Ok(ListParseError::parse_linux_list_string(value, HyperThread)?)
}
#[inline(always)]
fn parse_numa_nodes_allowed_list(value: &[u8]) -> Result<BTreeSet<NumaNode>, ProcessStatusStatisticParseError>
{
Ok(ListParseError::parse_linux_list_string(value, NumaNode)?)
}
macro_rules! parse
{
($statistic_name: ident, $statistic_value: ident, $($proc_status_name: literal => $struct_field: ident @ $parse_expr: ident,)*) =>
{
match $statistic_name
{
$(
$proc_status_name => if unlikely!(self.$struct_field.is_some())
{
Err(ProcessStatusStatisticParseError::DuplicatedStatistic)
}
else
{
let result = $parse_expr($statistic_value);
let parsed_value = result?;
let some = Some(parsed_value);
self.$struct_field = some;
Ok(())
},
)*
_ =>
{
let previous = self.unrecognised.insert(to_box($statistic_name), to_box($statistic_value));
return if unlikely!(previous.is_some())
{
Err(ProcessStatusStatisticParseError::DuplicatedStatistic)
}
else
{
Ok(())
}
}
}
}
}
parse!
(
statistic_name, statistic_value,
b"Name" => process_name @ parse_token,
b"Umask" => file_mode_creation_mask @ parse_mode,
b"State" => state @ parse_process_state,
b"Tgid" => thread_group_identifier @ parse_pid,
b"Ngid" => numa_group_identifier @ parse_numa_node,
b"Pid" => process_identifier @ parse_pid,
b"PPid" => parent_process_identifier @ parse_pid,
b"TracerPid" => tracer_process_identifier @ parse_pid,
b"Uid" => user_identifiers @ parse_user_identifiers,
b"Gid" => group_identifiers @ parse_group_identifiers,
b"FDSize" => number_of_file_descriptor_slots_currently_allocated @ parse_u64,
b"Groups" => groups @ parse_groups,
b"NStgid" => descendant_namespace_thread_group_identifier @ parse_pids,
b"NSpid" => descendant_namespace_process_identifier @ parse_pids,
b"NSpgid" => descendant_namespace_process_group_identifier @ parse_pids,
b"NSsid" => descendant_namespace_session_identifier @ parse_pids,
b"VmPeak" => peak_virtual_memory_size @ parse_kb,
b"VmSize" => total_program_size @ parse_kb,
b"VmLck" => locked_memory_size @ parse_kb,
b"VmPin" => pinned_memory_size @ parse_kb,
b"VmHWM" => peak_resident_set_size @ parse_kb,
b"VmRSS" => resident_set_memory_size @ parse_kb,
b"RssAnon" => anonymous_resident_set_memory_size @ parse_kb,
b"RssFile" => resident_set_file_mappings_memory_size @ parse_kb,
b"RssShmem" => resident_set_shared_memory_size @ parse_kb,
b"VmData" => private_data_segments_size @ parse_kb,
b"VmStk" => stack_segments_size @ parse_kb,
b"VmExe" => text_segment_size @ parse_kb,
b"VmLi" => dynamically_loaded_shared_library_size @ parse_kb,
b"VmPTE" => page_table_entries_size @ parse_kb,
b"VmPMD" => vm_pmd @ parse_kb,
b"VmSwap" => swap_memory_size @ parse_kb,
b"HugetlbPages" => huge_tlb_pages_memory_size @ parse_kb,
b"Threads" => threads @ parse_u64,
b"SigQ" => signal_queue @ parse_signal_queue,
b"SigPnd" => thread_pending_signals @ parse_bitmask,
b"ShdPnd" => process_shared_pending_signals @ parse_bitmask,
b"SigBlk" => blocked_signals @ parse_bitmask,
b"SigIgn" => ignored_signals @ parse_bitmask,
b"SigCgt" => caught_signals @ parse_bitmask,
b"CapInh" => inheritable_capabilities @ parse_bitmask,
b"CapPrm" => permitted_capabilities @ parse_bitmask,
b"CapEff" => effective_capabilities @ parse_bitmask,
b"CapBnd" => capabilities_bounding_set @ parse_bitmask,
b"CapAm" => ambient_capabilities @ parse_bitmask,
b"NoNewPrivs" => thread_no_new_privileges_bit @ parse_bool,
b"Seccomp" => seccomp_mode @ parse_seccomp_mode,
b"Speculation_Store_Bypass" => speculation_store_bypass @ parse_speculation_store_bypass,
b"Cpus_allowed" => cpus_allowed_bitmask @ parse_cpus_or_numa_nodes_allowed_bitmask,
b"Cpus_allowed_list" => cpus_allowed_list @ parse_cpus_allowed_list,
b"Mems_allowed" => numa_nodes_allowed_bitmask @ parse_cpus_or_numa_nodes_allowed_bitmask,
b"Mems_allowed_list" => numa_nodes_allowed_list @ parse_numa_nodes_allowed_list,
b"voluntary_ctxt_switches" => voluntary_context_switches @ parse_u64,
b"nonvoluntary_ctxt_switches" => involuntary_context_switches @ parse_u64,
)
}
}