blob: c0c4632d0fababe858052162975826e2adb62f8d [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Test code for memd.
include!(concat!(env!("OUT_DIR"), "/proto_include.rs"));
use libc;
use std;
use std::fs::{File, OpenOptions};
use std::io::prelude::*;
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};
use std::str;
use std::time::Duration;
// Imported from main program
use errno;
use get_runnables;
use get_vmstats;
use strerror;
use Dbus;
use FileWatcher;
use Paths;
use Result;
use Sample;
use SampleQueue;
use SampleType;
use Sampler;
use Timer;
use PAGE_SIZE;
use SAMPLE_QUEUE_LENGTH;
use VMSTAT_VALUES_COUNT;
// Different levels of emulated available RAM in MB.
const LOW_MEM_LOW_AVAILABLE: usize = 150;
const LOW_MEM_MEDIUM_AVAILABLE: usize = 300;
const LOW_MEM_HIGH_AVAILABLE: usize = 1000;
const LOW_MEM_MARGIN: usize = 200;
const MOCK_DBUS_FIFO_NAME: &str = "mock-dbus-fifo";
macro_rules! print_to_path {
($path:expr, $format:expr $(, $arg:expr)*) => {{
let r = OpenOptions::new().write(true).create(true).open($path);
match r {
Err(e) => Err(e),
Ok(mut f) => f.write_all(format!($format $(, $arg)*).as_bytes())
}
}}
}
fn duration_to_millis(duration: &Duration) -> i64 {
duration.as_secs() as i64 * 1000 + duration.subsec_nanos() as i64 / 1_000_000
}
// Writes |string| to file |path|. If |append| is true, seeks to end first.
// If |append| is false, truncates the file first.
fn write_string(string: &str, path: &Path, append: bool) -> Result<()> {
let mut f = OpenOptions::new().write(true).append(append).open(&path)?;
if !append {
f.set_len(0)?;
}
f.write_all(string.as_bytes())?;
Ok(())
}
fn read_nonblocking_pipe(file: &mut File, mut buf: &mut [u8]) -> Result<usize> {
let status = file.read(&mut buf);
let read_bytes = match status {
Ok(n) => n,
Err(_) if errno() == libc::EAGAIN => 0,
Err(_) => return Err("cannot read pipe".into()),
};
Ok(read_bytes)
}
fn non_blocking_select(high_fd: i32, inout_read_fds: &mut libc::fd_set) -> i32 {
let mut null_timeout = libc::timeval {
tv_sec: 0 as libc::time_t,
tv_usec: 0 as libc::suseconds_t,
};
let null = std::ptr::null_mut();
// Safe because we're passing valid values and addresses.
let n = unsafe {
libc::select(
high_fd,
inout_read_fds,
null,
null,
&mut null_timeout as *mut libc::timeval,
)
};
if n < 0 {
panic!("select: {}", strerror(errno()));
}
n
}
fn mkfifo(path: &PathBuf) -> Result<()> {
let path_name = path.to_str().unwrap();
let c_path = std::ffi::CString::new(path_name).unwrap();
// Safe because c_path points to a valid C string.
let status = unsafe { libc::mkfifo(c_path.as_ptr(), 0o644) };
if status < 0 {
Err(format!("mkfifo: {}: {}", path_name, strerror(errno())).into())
} else {
Ok(())
}
}
// The types of events which are generated internally for testing. They
// simulate state changes (for instance, change in the memory pressure level),
// chrome events, and kernel events.
#[derive(Clone, Copy, Debug, PartialEq)]
enum TestEventType {
EnterHighPressure, // enter low available RAM (below margin) state
EnterLowPressure, // enter high available RAM state
EnterMediumPressure, // set enough memory pressure to trigger fast sampling
OomKillBrowser, // fake browser report of OOM kill
TabDiscard, // fake browser report of tab discard
}
// Internally generated event for testing.
#[derive(Debug)]
struct TestEvent {
time: i64,
event_type: TestEventType,
}
impl TestEvent {
fn deliver(&self, paths: &Paths, dbus_fifo: &mut File, low_mem_device: &mut File, time: i64) {
debug!("delivering {:?}", self);
match self.event_type {
TestEventType::EnterLowPressure => {
self.low_mem_notify(LOW_MEM_HIGH_AVAILABLE, &paths, low_mem_device)
}
TestEventType::EnterMediumPressure => {
self.low_mem_notify(LOW_MEM_MEDIUM_AVAILABLE, &paths, low_mem_device)
}
TestEventType::EnterHighPressure => {
self.low_mem_notify(LOW_MEM_LOW_AVAILABLE, &paths, low_mem_device)
}
TestEventType::OomKillBrowser => self.send_signal("oom-kill", time, dbus_fifo),
TestEventType::TabDiscard => self.send_signal("tab-discard", time, dbus_fifo),
}
}
fn low_mem_notify(&self, amount: usize, paths: &Paths, mut low_mem_device: &mut File) {
write_string(&amount.to_string(), &paths.available, false)
.expect("available file: write failed");
if amount == LOW_MEM_LOW_AVAILABLE {
debug!("making low-mem device ready to read");
// Make low-mem device ready-to-read.
write!(low_mem_device, ".").expect("low-mem-device: write failed");
} else {
debug!("clearing low-mem device");
let mut buf = [0; PAGE_SIZE];
read_nonblocking_pipe(&mut low_mem_device, &mut buf)
.expect("low-mem-device: clear failed");
}
}
fn send_signal(&self, signal: &str, time: i64, dbus_fifo: &mut File) {
write!(dbus_fifo, "{} {}\n", signal, time).expect("mock dbus: write failed");
}
}
// Real time mock, for testing only. It removes time races (for better or
// worse) and makes it possible to run the test on build machines which may be
// heavily loaded.
//
// Time is mocked by assuming that CPU speed is infinite and time passes only
// when the program is asleep. Time advances in discrete jumps when we call
// either sleep() or select() with a timeout.
struct MockTimer {
current_time: i64, // the current time
test_events: Vec<TestEvent>, // list events to be delivered
event_index: usize, // index of next event to be delivered
paths: Paths, // for event delivery
dbus_fifo_out: File, // for mock dbus event delivery
low_mem_device: File, // for delivery of low-mem notifications
quit_request: bool, // for termination
}
impl MockTimer {
fn new(test_events: Vec<TestEvent>, paths: Paths, dbus_fifo_out: File) -> MockTimer {
let low_mem_device = OpenOptions::new()
.custom_flags(libc::O_NONBLOCK)
.read(true)
.write(true)
.open(&paths.low_mem_device)
.expect("low-mem-device: cannot setup");
MockTimer {
current_time: 0,
test_events,
event_index: 0,
paths,
dbus_fifo_out,
low_mem_device,
quit_request: false,
}
}
}
impl Timer for MockTimer {
fn now(&self) -> i64 {
self.current_time
}
fn quit_request(&self) -> bool {
self.quit_request
}
// Mock select first checks if any events are pending, then produces events
// that would happen during its sleeping time, and checks if those events
// are delivered.
fn select(
&mut self,
high_fd: i32,
inout_read_fds: &mut libc::fd_set,
timeout: &Duration,
) -> i32 {
// First check for existing active fds (for instance, the low-mem
// device). We must save the original fd_set because when
// non_blocking_select returns 0, the fd_set is cleared.
let saved_inout_read_fds = inout_read_fds.clone();
let n = non_blocking_select(high_fd, inout_read_fds);
if n != 0 {
return n;
}
let timeout_ms = duration_to_millis(&timeout);
let end_time = self.current_time + timeout_ms;
// Assume no events occur and we hit the timeout. Fix later as needed.
self.current_time = end_time;
loop {
if self.event_index == self.test_events.len() {
// No more events to deliver, so no need for further select() calls.
self.quit_request = true;
return 0;
}
// There are still event to be delivered.
let first_event_time = self.test_events[self.event_index].time;
// We interpret the event time to be event.time + epsilon. Thus if
// |first_event_time| is equal to |end_time|, we time out.
if first_event_time >= end_time {
// No event to deliver before the timeout.
debug!("returning because fev = {}", first_event_time);
return 0;
}
// Deliver all events with the time stamp of the first event. (There
// is at least one.)
while {
self.test_events[self.event_index].deliver(
&self.paths,
&mut self.dbus_fifo_out,
&mut self.low_mem_device,
first_event_time,
);
self.event_index += 1;
self.event_index < self.test_events.len()
&& self.test_events[self.event_index].time == first_event_time
} {}
// One or more events were delivered, and some of them may fire a
// select. First restore the original fd_set.
*inout_read_fds = saved_inout_read_fds.clone();
let n = non_blocking_select(high_fd, inout_read_fds);
if n > 0 {
debug!("returning at {} with {} events", first_event_time, n);
self.current_time = first_event_time;
return n;
}
}
}
// Mock sleep produces all events that would happen during that sleep, then
// updates the time.
fn sleep(&mut self, sleep_time: &Duration) {
let start_time = self.current_time;
let end_time = start_time + duration_to_millis(&sleep_time);
while self.event_index < self.test_events.len()
&& self.test_events[self.event_index].time <= end_time
{
self.test_events[self.event_index].deliver(
&self.paths,
&mut self.dbus_fifo_out,
&mut self.low_mem_device,
self.current_time,
);
self.event_index += 1;
}
if self.event_index == self.test_events.len() {
self.quit_request = true;
}
self.current_time = end_time;
}
}
struct MockDbus {
fds: Vec<RawFd>,
fifo_in: File,
fifo_out: Option<File>, // using Option merely to use take()
}
impl Dbus for MockDbus {
fn get_fds(&self) -> &Vec<RawFd> {
&self.fds
}
// Processes any mock chrome events. Events are strings separated by
// newlines sent to the event pipe. We could check if the pipe fired in
// the watcher, but it's less code to just do a non-blocking read.
fn process_dbus_events(
&mut self,
_watcher: &mut FileWatcher,
) -> Result<Vec<(Event_Type, i64)>> {
let mut events: Vec<(Event_Type, i64)> = Vec::new();
let mut buf = [0u8; 4096];
let read_bytes = read_nonblocking_pipe(&mut self.fifo_in, &mut buf)?;
let mock_events = str::from_utf8(&buf[..read_bytes])?.lines();
for mock_event in mock_events {
let mut split_iterator = mock_event.split_whitespace();
let event_type = split_iterator.next().unwrap();
let event_time_string = split_iterator.next().unwrap();
let event_time = event_time_string.parse::<i64>()?;
match event_type {
"tab-discard" => events.push((Event_Type::TAB_DISCARD, event_time)),
"oom-kill" => events.push((Event_Type::OOM_KILL, event_time)),
other => return Err(format!("unexpected mock event {:?}", other).into()),
};
}
Ok(events)
}
}
impl MockDbus {
fn new(fifo_path: &Path) -> Result<MockDbus> {
let fifo_in = OpenOptions::new()
.custom_flags(libc::O_NONBLOCK)
.read(true)
.open(&fifo_path)?;
let fds = vec![fifo_in.as_raw_fd()];
let fifo_out = OpenOptions::new()
.custom_flags(libc::O_NONBLOCK)
.write(true)
.open(&fifo_path)?;
Ok(MockDbus {
fds,
fifo_in,
fifo_out: Some(fifo_out),
})
}
}
pub fn test_loop(_always_poll_fast: bool, paths: &Paths) {
for test_desc in TEST_DESCRIPTORS.iter() {
// Every test run requires a (mock) restart of the daemon.
println!("\n--------------\nrunning test:\n{}", test_desc);
// Clean up log directory.
std::fs::remove_dir_all(&paths.log_directory).expect("cannot remove /var/log/memd");
std::fs::create_dir_all(&paths.log_directory).expect("cannot create /var/log/memd");
let events = events_from_test_descriptor(test_desc);
let mut dbus = Box::new(
MockDbus::new(&paths.testing_root.join(MOCK_DBUS_FIFO_NAME))
.expect("cannot create mock dbus"),
);
let timer = Box::new(MockTimer::new(
events,
paths.clone(),
dbus.fifo_out.take().unwrap(),
));
let mut sampler = Sampler::new(false, &paths, timer, dbus).expect("sampler creation error");
loop {
// Alternate between slow and fast poll.
sampler.slow_poll().expect("slow poll error");
if sampler.quit_request {
break;
}
sampler.fast_poll().expect("fast poll error");
if sampler.quit_request {
break;
}
}
verify_test_results(test_desc, &paths.log_directory)
.expect(&format!("test:{}failed.", test_desc));
println!("test succeeded\n--------------");
}
}
// ================
// Test Descriptors
// ================
//
// Define events and expected result using "ASCII graphics".
//
// The top lines of the test descriptor (all lines except the last one) define
// sequences of events. The last line describes the expected result.
//
// Events are single characters:
//
// M = start medium pressure (fast poll)
// H = start high pressure (low-mem notification)
// L = start low pressure (slow poll)
// <digit> = tab discard
// K = kernel OOM kill
// k = chrome notification of OOM kill
// ' ', . = nop (just wait 1 second)
// | = ignored (no delay), cosmetic only
//
// - each character indicates a 1-second slot
// - events (if any) happen at the beginning of their slot
// - multiple events in the same slot are stacked vertically
//
// Example:
//
// ..H.1..L
// 2
//
// means:
// - wait 2 seconds
// - signal high-memory pressure, wait 1 second
// - wait 1 second
// - signal two tab discard events (named 1 and 2), wait 1 second
// - wait 2 more seconds
// - return to low-memory pressure
//
// The last line describes the expected clip logs. Each log is identified by
// one digit: 0 for memd.clip000.log, 1 for memd.clip001.log etc. The positions
// of the digits correspond to the time span covered by each clip. So a clip
// file whose description is 5 characters long is supposed to contain 5 seconds
// worth of samples.
//
// For readability, the descriptor must start and end with newlines, which are
// removed. Also, indentation (common all-space prefixes) is removed.
#[rustfmt::skip]
const TEST_DESCRIPTORS: &[&str] = &[
// Very simple test: go from slow poll to fast poll and back. No clips
// are collected.
"
.M.L.
.....
",
// Simple test: start fast poll, signal low-mem, signal tab discard.
"
.M...H..1.....L
..00000000001..
",
// Two full disjoint clips. Also tests kernel-reported and chrome-reported OOM
// kills.
"
.M......k.............k.....
...0000000000....1111111111.
",
// Test that clip collection continues for the time span of interest even if
// memory pressure returns quickly to a low level. Note that the
// medium-pressure event (M) is at t = 1s, but the fast poll starts at 2s
// (multiple of 2s slow-poll period).
"
.MH1L.....
..000000..
",
// Several discards, which result in three consecutive clips. Tab discards 1
// and 2 produce an 8-second clip because the first two seconds of data are
// missing. Also see the note above regarding fast poll start.
"
...M.H12..|...3...6..|.7.....L
| 4 |
| 5 |
....000000|0011111111|112222..
",
// Enter low-mem, then exit, then enter it again.
"
.MHM......|......H...|...L
..00000...|.111111111|1...
",
// Discard a tab in slow-poll mode.
"
....1.......
....00000...
",
];
fn trim_descriptor(descriptor: &str) -> Vec<Vec<u8>> {
// Remove vertical bars. Don't check for consistent use because it's easy
// enough to notice visually.
let barless_descriptor: String = descriptor.chars().filter(|c| *c != '|').collect();
// Split string into lines.
let all_lines: Vec<String> = barless_descriptor.split('\n').map(String::from).collect();
// A test descriptor must start and end with empty lines, and have at least
// one line of events, and exactly one line to describe the clip files.
assert!(all_lines.len() >= 4, "invalid test descriptor format");
// Remove first and last line.
let valid_lines = all_lines[1..all_lines.len() - 1].to_vec();
// Find indentation amount. Unwrap() cannot fail because of previous assert.
let indent = valid_lines
.iter()
.map(|s| s.len() - s.trim_left().len())
.min()
.unwrap();
// Remove indentation.
let trimmed_lines: Vec<Vec<u8>> = valid_lines
.iter()
.map(|s| s[indent..].to_string().into_bytes())
.collect();
trimmed_lines
}
fn events_from_test_descriptor(descriptor: &str) -> Vec<TestEvent> {
let all_descriptors = trim_descriptor(descriptor);
let event_sequences = &all_descriptors[..all_descriptors.len() - 1];
let max_length = event_sequences.iter().map(|d| d.len()).max().unwrap();
let mut events = vec![];
for i in 0..max_length {
for seq in event_sequences {
// Each character represents one second. Time unit is milliseconds.
let mut opt_type = None;
if i < seq.len() {
match seq[i] {
b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' => {
opt_type = Some(TestEventType::TabDiscard)
}
b'H' => opt_type = Some(TestEventType::EnterHighPressure),
b'M' => opt_type = Some(TestEventType::EnterMediumPressure),
b'L' => opt_type = Some(TestEventType::EnterLowPressure),
b'k' => opt_type = Some(TestEventType::OomKillBrowser),
b'.' | b' ' | b'|' => {}
x => panic!("unexpected character {} in descriptor '{}'", &x, descriptor),
}
}
if let Some(t) = opt_type {
events.push(TestEvent {
time: i as i64 * 1000,
event_type: t,
});
}
}
}
events
}
// Given a descriptor string for the expected clips, returns a vector of start
// and end time of each clip.
fn expected_clips(descriptor: &[u8]) -> Vec<(i64, i64)> {
let mut time = 0;
let mut clip_start_time = 0;
let mut previous_clip = b'0' - 1;
let mut previous_char = 0u8;
let mut clips = vec![];
for &c in descriptor {
if c != previous_char {
if (previous_char as char).is_digit(10) {
// End of clip.
clips.push((clip_start_time, time));
}
if (c as char).is_digit(10) {
// Start of clip.
clip_start_time = time;
assert_eq!(c, previous_clip + 1, "malformed clip descriptor");
previous_clip = c;
}
}
previous_char = c;
time += 1000;
}
clips
}
// Converts a string starting with a timestamp in seconds (#####.##, with two
// decimal digits) to a timestamp in milliseconds.
fn time_from_sample_string(line: &str) -> Result<i64> {
let mut tokens = line.split(|c: char| !c.is_digit(10));
let seconds = match tokens.next() {
Some(digits) => digits.parse::<i64>().unwrap(),
None => return Err("no digits in string".into()),
};
let centiseconds = match tokens.next() {
Some(digits) => {
if digits.len() == 2 {
digits.parse::<i64>().unwrap()
} else {
return Err("expecting 2 decimals".into());
}
}
None => return Err("expecting at least two groups of digits".into()),
};
Ok(seconds * 1000 + centiseconds * 10)
}
macro_rules! assert_approx_eq {
($actual:expr, $expected: expr, $tolerance: expr, $format:expr $(, $arg:expr)*) => {{
let actual = $actual;
let expected = $expected;
let tolerance = $tolerance;
let expected_min = expected - tolerance;
let expected_max = expected + tolerance;
assert!(actual < expected_max && actual > expected_min,
concat!("(expected: {}, actual: {}) ", $format), expected, actual $(, $arg)*);
}}
}
fn check_clip(clip_times: (i64, i64), clip_path: PathBuf, events: &Vec<TestEvent>) -> Result<()> {
let clip_name = clip_path.to_string_lossy();
let mut clip_file = File::open(&clip_path)?;
let mut file_content = String::new();
clip_file.read_to_string(&mut file_content)?;
debug!("clip {}:\n{}", clip_name, file_content);
let lines = file_content.lines().collect::<Vec<&str>>();
// First line is time stamp. Second line is field names. Check count of
// field names and field values in the third line (don't bother to check
// the other lines).
let name_count = lines[1].split_whitespace().count();
let value_count = lines[2].split_whitespace().count();
assert_eq!(name_count, value_count);
// Check first and last time stamps.
let start_time = time_from_sample_string(&lines[2]).expect("cannot parse first timestamp");
let end_time =
time_from_sample_string(&lines[lines.len() - 1]).expect("cannot parse last timestamp");
let expected_start_time = clip_times.0;
let expected_end_time = clip_times.1;
// Milliseconds of slack allowed on start/stop times. We allow one full
// fast poll period to take care of edge cases. The specs don't need to be
// tight here because it doesn't matter if we collect one fewer sample (or
// an extra one) at each end.
let slack_ms = 101i64;
assert_approx_eq!(
start_time,
expected_start_time,
slack_ms,
"unexpected start time for {}",
clip_name
);
assert_approx_eq!(
end_time,
expected_end_time,
slack_ms,
"unexpected end time for {}",
clip_name
);
// Check sample count. Must keep track of low_mem -> not low_mem transitions.
let mut in_low_mem = false;
let expected_sample_count_from_events: usize = events
.iter()
.map(|e| {
let sample_count_for_event = if e.time <= start_time || e.time > end_time {
0
} else {
match e.event_type {
// These generate 1 sample only when moving out of high pressure.
TestEventType::EnterLowPressure | TestEventType::EnterMediumPressure => {
if in_low_mem {
1
} else {
0
}
}
_ => 1,
}
};
match e.event_type {
TestEventType::EnterHighPressure => in_low_mem = true,
TestEventType::EnterLowPressure | TestEventType::EnterMediumPressure => {
in_low_mem = false
}
_ => {}
}
sample_count_for_event
})
.sum();
// We include samples both at the beginning and end of the range, so we
// need to add 1. Note that here we use the actual sample times, not the
// expected times.
let expected_sample_count_from_timer = ((end_time - start_time) / 100) as usize + 1;
let expected_sample_count =
expected_sample_count_from_events + expected_sample_count_from_timer;
let sample_count = lines.len() - 2;
assert_eq!(
sample_count, expected_sample_count,
"unexpected sample count for {}",
clip_name
);
Ok(())
}
fn verify_test_results(descriptor: &str, log_directory: &PathBuf) -> Result<()> {
let all_descriptors = trim_descriptor(descriptor);
let result_descriptor = &all_descriptors[all_descriptors.len() - 1];
let clips = expected_clips(result_descriptor);
let events = events_from_test_descriptor(descriptor);
// Check that there are no more clips than expected.
let files_count = std::fs::read_dir(log_directory)?.count();
// Subtract one for the memd.parameters file.
assert_eq!(clips.len(), files_count - 1, "wrong number of clip files");
let mut clip_number = 0;
for clip in clips {
let clip_path = log_directory.join(format!("memd.clip{:03}.log", clip_number));
check_clip(clip, clip_path, &events)?;
clip_number += 1;
}
Ok(())
}
fn create_dir_all(path: &Path) -> Result<()> {
let result = std::fs::create_dir_all(path);
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("create_dir_all: {}: {:?}", path.to_string_lossy(), e).into()),
}
}
pub fn teardown_test_environment(paths: &Paths) {
std::fs::remove_dir_all(&paths.testing_root).expect(&format!(
"teardown: could not remove {}",
paths.testing_root.to_str().unwrap()
));
}
pub fn setup_test_environment(paths: &Paths) {
std::fs::create_dir(&paths.testing_root).expect(&format!(
"cannot create {}",
paths.testing_root.to_str().unwrap()
));
mkfifo(&paths.testing_root.join(MOCK_DBUS_FIFO_NAME)).expect("failed to make mock dbus fifo");
create_dir_all(paths.vmstat.parent().unwrap()).expect("cannot create /proc");
create_dir_all(paths.available.parent().unwrap()).expect("cannot create ../chromeos-low-mem");
let sys_vm = paths.testing_root.join("proc/sys/vm");
create_dir_all(&sys_vm).expect("cannot create /proc/sys/vm");
create_dir_all(paths.low_mem_device.parent().unwrap()).expect("cannot create /dev");
let vmstat_content = include_str!("vmstat_content");
let zoneinfo_content = include_str!("zoneinfo_content");
print_to_path!(&paths.vmstat, "{}", vmstat_content).expect("cannot initialize vmstat");
print_to_path!(&paths.zoneinfo, "{}", zoneinfo_content).expect("cannot initialize zoneinfo");
print_to_path!(&paths.available, "{}\n", LOW_MEM_HIGH_AVAILABLE)
.expect("cannot initialize available");
print_to_path!(&paths.runnables, "0.16 0.18 0.22 4/981 8504")
.expect("cannot initialize runnables");
print_to_path!(
&paths.low_mem_margin,
"{} {}",
LOW_MEM_MARGIN,
LOW_MEM_MARGIN * 2
)
.expect("cannot initialize low_mem_margin");
print_to_path!(sys_vm.join("min_filelist_kbytes"), "100000\n")
.expect("cannot initialize min_filelist_kbytes");
print_to_path!(sys_vm.join("min_free_kbytes"), "80000\n")
.expect("cannot initialize min_free_kbytes");
print_to_path!(sys_vm.join("extra_free_kbytes"), "60000\n")
.expect("cannot initialize extra_free_kbytes");
mkfifo(&paths.low_mem_device).expect("could not make mock low-mem device");
}
pub fn read_loadavg() {
// Calling getpid() is always safe.
let temp_file_name = format!("/tmp/memd-loadavg-{}", unsafe { libc::getpid() });
let mut temp_file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&temp_file_name)
.expect("cannot create");
// Unlink file immediately for more reliable cleanup.
std::fs::remove_file(&temp_file_name).expect(&format!("cannot remove"));
temp_file
.write_all("0.42 0.31 1.50 44/1234 56789".as_bytes())
.expect("cannot write");
temp_file
.seek(std::io::SeekFrom::Start(0))
.expect("cannot seek");
assert_eq!(get_runnables(&temp_file).unwrap(), 44);
temp_file
.seek(std::io::SeekFrom::Start(0))
.expect("cannot seek");
temp_file
.write_all("1122.12 25.87 19.51 33/1234 56789".as_bytes())
.expect("cannot write");
temp_file
.seek(std::io::SeekFrom::Start(0))
.expect("cannot seek");
assert_eq!(get_runnables(&temp_file).unwrap(), 33);
}
pub fn queue_loop() {
let mut sq = SampleQueue::new();
let mut file = OpenOptions::new()
.write(true)
.create_new(false)
.open("/dev/null")
.unwrap();
let mut s: Sample = Default::default();
// We'll compare this uptime against the start_time of 0 in |output_from_time|, to ensure that
// we don't stop looping in the array due to uptime.
s.uptime = 1;
s.sample_type = SampleType::EnterLowMem;
sq.samples = [s; SAMPLE_QUEUE_LENGTH];
sq.head = 30;
sq.count = 30;
sq.output_from_time(&mut file, /*start_time=*/ 0).unwrap();
}
pub fn read_vmstat(paths: &Paths) {
setup_test_environment(&paths);
let mut vmstat_values: [u64; VMSTAT_VALUES_COUNT] = [0, 0, 0, 0, 0];
get_vmstats(
&File::open(&paths.vmstat).expect("cannot open vmstat"),
&mut vmstat_values,
)
.expect("get_vmstats failure");
// Check one simple and one accumulated value.
assert_eq!(vmstat_values[1], 678);
assert_eq!(vmstat_values[2], 66);
teardown_test_environment(&paths);
}