Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: migrate disk collection code off of heim, remove heim #1064

Merged
merged 39 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
acd79e5
refactor: fix some refresh code
ClementTsang Mar 9, 2023
e58746d
remove async from the freebsd code
ClementTsang Mar 11, 2023
7754d02
some file/implementation organization
ClementTsang Mar 14, 2023
577bf72
more restructuring
ClementTsang Mar 16, 2023
5e1592c
Some other fixes
ClementTsang Mar 19, 2023
b2594ce
remove futures
ClementTsang Mar 19, 2023
48158e7
ready for some big changes?
ClementTsang Mar 23, 2023
4d5f250
big changes
ClementTsang Mar 26, 2023
69675dc
linux io + reads
ClementTsang Apr 1, 2023
ecf0ac2
use lossy conversion for mount point
ClementTsang Apr 1, 2023
8724780
add windows refresh
ClementTsang Apr 1, 2023
794eda3
so long heim, and thanks for all the fish
ClementTsang Apr 1, 2023
c53a32e
fix filter behaviour, remove string allocation when reading lines
ClementTsang Apr 3, 2023
042c530
rename unix -> system for more accurate file struct representation
ClementTsang Apr 4, 2023
7e0a2be
fix freebsd
ClementTsang Apr 4, 2023
37f4fd7
port generic unix partition code
ClementTsang Apr 5, 2023
b10d124
add bindings and fix errors
ClementTsang Apr 5, 2023
ad1d8f9
finish macOS bindings for I/O
ClementTsang Apr 5, 2023
9c8df52
disable conform check, this seems to... make disk I/O work on macOS?????
ClementTsang Apr 5, 2023
c027da9
fix linux
ClementTsang Apr 5, 2023
32e38ae
add safety comments
ClementTsang Apr 5, 2023
2628254
more comments
ClementTsang Apr 5, 2023
72485bc
update changelog
ClementTsang Apr 5, 2023
6d6bec0
changelog
ClementTsang Apr 5, 2023
096190d
We're going full 0.9.0 for this
ClementTsang Apr 5, 2023
e92cba7
update lock
ClementTsang Apr 5, 2023
c396659
fix some typing
ClementTsang Apr 6, 2023
eff2f16
bleh
ClementTsang Apr 6, 2023
461cde4
some file management
ClementTsang Apr 6, 2023
7f0c669
hoist out get_disk_usage
ClementTsang Apr 6, 2023
12559df
fix some stuff for Windows
ClementTsang Apr 6, 2023
30ea987
typing and remove dead code allow lint
ClementTsang Apr 6, 2023
0f4287b
unify typing
ClementTsang Apr 6, 2023
661c578
fix
ClementTsang Apr 6, 2023
111133c
fix 2
ClementTsang Apr 6, 2023
a30aac1
macOS fix
ClementTsang Apr 7, 2023
4d22589
Add bindings file for windows
ClementTsang Apr 9, 2023
0765343
add windows implementation
ClementTsang Apr 10, 2023
e5d241a
fix macos
ClementTsang Apr 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix filter behaviour, remove string allocation when reading lines
  • Loading branch information
ClementTsang committed Apr 9, 2023
commit c53a32ef5b92dfa2b6ce39b32ba192ab4649b95a
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- [#1021](https://github.com/ClementTsang/bottom/pull/1021): Fix selected text background colour being wrong if only the foreground colour was set.
- [#1037](https://github.com/ClementTsang/bottom/pull/1037): Fix `is_list_ignored` accepting all results if set to `false`.
- [#1064](https://github.com/ClementTsang/bottom/pull/1064): Disk name/mount filter was fixed to not just drop entries if one filter wasn't set.

## Features

Expand All @@ -25,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#1036](https://github.com/ClementTsang/bottom/pull/1036): Migrate away from heim for memory information; Linux
platforms will also now try to use `MemAvailable` to determine used memory if supported.
- [#1041](https://github.com/ClementTsang/bottom/pull/1041): Migrate away from heim for network information.
- [#1064](https://github.com/ClementTsang/bottom/pull/1064): Removed the Heim dependency.

## Other

Expand Down
109 changes: 109 additions & 0 deletions src/app/data_harvester/disks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use std::collections::HashMap;

use crate::app::filter::Filter;

cfg_if::cfg_if! {
if #[cfg(target_os = "freebsd")] {
pub mod freebsd;
Expand Down Expand Up @@ -37,3 +39,110 @@ pub struct IoData {
}

pub type IoHarvest = HashMap<String, Option<IoData>>;

/// Whether to keep the current disk entry given the filters, disk name, and disk mount.
/// Precedence ordering in the case where name and mount filters disagree, "allow"
/// takes precedence over "deny".
///
/// For implementation, we do this as follows:
///
/// 1. Is the entry allowed through any filter? That is, does it match an entry in a
/// filter where `is_list_ignored` is `false`? If so, we always keep this entry.
/// 2. Is the entry denied through any filter? That is, does it match an entry in a
/// filter where `is_list_ignored` is `true`? If so, we always deny this entry.
/// 3. Anything else is allowed.
pub(self) fn keep_disk_entry(
disk_name: &str, mount_point: &str, disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
) -> bool {
match (disk_filter, mount_filter) {
(Some(d), Some(m)) => match (d.is_list_ignored, m.is_list_ignored) {
(true, true) => !(d.has_match(disk_name) || m.has_match(mount_point)),
(true, false) => {
if m.has_match(mount_point) {
true
} else {
d.keep_entry(disk_name)
}
}
(false, true) => {
if d.has_match(disk_name) {
true
} else {
m.keep_entry(mount_point)
}
}
(false, false) => d.has_match(disk_name) || m.has_match(mount_point),
},
(Some(d), None) => d.keep_entry(disk_name),
(None, Some(m)) => m.keep_entry(mount_point),
(None, None) => true,
}
}

#[cfg(test)]
mod test {
use regex::Regex;

use crate::app::filter::Filter;

use super::keep_disk_entry;

fn run_filter(disk_filter: &Option<Filter>, mount_filter: &Option<Filter>) -> Vec<usize> {
let targets = [
("/dev/nvme0n1p1", "/boot"),
("/dev/nvme0n1p2", "/"),
("/dev/nvme0n1p3", "/home"),
("/dev/sda1", "/mnt/test"),
("/dev/sda2", "/mnt/boot"),
];

targets
.into_iter()
.enumerate()
.filter_map(|(itx, (name, mount))| {
if keep_disk_entry(name, mount, disk_filter, mount_filter) {
Some(itx)
} else {
None
}
})
.collect()
}

#[test]
fn test_keeping_disk_entry() {
let disk_ignore = Some(Filter {
is_list_ignored: true,
list: vec![Regex::new("nvme").unwrap()],
});

let disk_keep = Some(Filter {
is_list_ignored: false,
list: vec![Regex::new("nvme").unwrap()],
});

let mount_ignore = Some(Filter {
is_list_ignored: true,
list: vec![Regex::new("boot").unwrap()],
});

let mount_keep = Some(Filter {
is_list_ignored: false,
list: vec![Regex::new("boot").unwrap()],
});

assert_eq!(run_filter(&None, &None), vec![0, 1, 2, 3, 4]);

assert_eq!(run_filter(&disk_ignore, &None), vec![3, 4]);
assert_eq!(run_filter(&disk_keep, &None), vec![0, 1, 2]);

assert_eq!(run_filter(&None, &mount_ignore), vec![1, 2, 3]);
assert_eq!(run_filter(&None, &mount_keep), vec![0, 4]);

assert_eq!(run_filter(&disk_ignore, &mount_ignore), vec![3]);
assert_eq!(run_filter(&disk_keep, &mount_ignore), vec![0, 1, 2, 3]);

assert_eq!(run_filter(&disk_ignore, &mount_keep), vec![0, 3, 4]);
assert_eq!(run_filter(&disk_keep, &mount_keep), vec![0, 1, 2, 4]);
}
}
40 changes: 3 additions & 37 deletions src/app/data_harvester/disks/freebsd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@ use std::io;

use serde::Deserialize;

use crate::{
app::Filter,
data_harvester::{
deserialize_xo,
disks::{DiskHarvest, IoHarvest},
},
utils::error,
};
use super::{keep_disk_entry, DiskHarvest, IoData, IoHarvest};
use crate::{app::Filter, data_harvester::deserialize_xo, utils::error};

#[derive(Deserialize, Debug, Default)]
#[serde(rename_all = "kebab-case")]
Expand Down Expand Up @@ -49,21 +43,7 @@ pub fn get_disk_usage(
.filesystem
.into_iter()
.filter_map(|disk| {
// Precedence ordering in the case where name and mount filters disagree, "allow"
// takes precedence over "deny".
//
// For implementation, we do this as follows:
//
// 1. Is the entry allowed through any filter? That is, does it match an entry in a
// filter where `is_list_ignored` is `false`? If so, we always keep this entry.
// 2. Is the entry denied through any filter? That is, does it match an entry in a
// filter where `is_list_ignored` is `true`? If so, we always deny this entry.
// 3. Anything else is allowed.
let filter_check_map =
[(disk_filter, &disk.name), (mount_filter, &disk.mounted_on)];
if matches_allow_list(filter_check_map.as_slice())
|| !matches_ignore_list(filter_check_map.as_slice())
{
if keep_disk_entry(&disk.name, &disk.mounted_on, disk_filter, mount_filter) {
Some(DiskHarvest {
free_space: disk.available_blocks * 1024,
used_space: disk.used_blocks * 1024,
Expand All @@ -81,20 +61,6 @@ pub fn get_disk_usage(
Ok(vec_disks)
}

fn matches_allow_list(filter_check_map: &[(&Option<Filter>, &String)]) -> bool {
filter_check_map.iter().any(|(filter, text)| match filter {
Some(f) if !f.is_list_ignored => f.list.iter().any(|r| r.is_match(text)),
Some(_) | None => false,
})
}

fn matches_ignore_list(filter_check_map: &[(&Option<Filter>, &String)]) -> bool {
filter_check_map.iter().any(|(filter, text)| match filter {
Some(f) if f.is_list_ignored => f.list.iter().any(|r| r.is_match(text)),
Some(_) | None => false,
})
}

fn get_disk_info() -> io::Result<StorageSystemInformation> {
// TODO: Ideally we don't have to shell out to a new program.
let output = std::process::Command::new("df")
Expand Down
44 changes: 5 additions & 39 deletions src/app/data_harvester/disks/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ cfg_if::cfg_if! {
}
}

use super::{keep_disk_entry, DiskHarvest, IoData, IoHarvest};
use crate::app::Filter;
use crate::data_harvester::disks::{DiskHarvest, IoData, IoHarvest};

/// Returns the I/O usage of certain mount points.
pub fn get_io_usage() -> anyhow::Result<IoHarvest> {
let mut io_hash: HashMap<String, Option<IoData>> = HashMap::new();

for io in io_stats()?.flatten() {
for io in io_stats()?.into_iter().flatten() {
let mount_point = io.device_name().to_string_lossy();

io_hash.insert(
Expand All @@ -39,6 +40,7 @@ pub fn get_io_usage() -> anyhow::Result<IoHarvest> {
Ok(io_hash)
}

/// Returns the disk usage of the mounted (and for now, physical) disks.
pub fn get_disk_usage(
disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
) -> anyhow::Result<Vec<DiskHarvest>> {
Expand All @@ -55,43 +57,7 @@ pub fn get_disk_usage(
// 2. Is the entry denied through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `true`? If so, we always deny this entry.
// 3. Anything else is allowed.

let filter_check_map = [(disk_filter, &name), (mount_filter, &mount_point)];

// This represents case 1. That is, if there is a match in an allowing list - if there is, then
// immediately allow it!
let matches_allow_list = filter_check_map.iter().any(|(filter, text)| {
if let Some(filter) = filter {
if !filter.is_list_ignored {
for r in &filter.list {
if r.is_match(text) {
return true;
}
}
}
}
false
});

let to_keep = if matches_allow_list {
true
} else {
// If it doesn't match an allow list, then check if it is denied.
// That is, if it matches in a reject filter, then reject. Otherwise, we always keep it.
!filter_check_map.iter().any(|(filter, text)| {
if let Some(filter) = filter {
if filter.is_list_ignored {
for r in &filter.list {
if r.is_match(text) {
return true;
}
}
}
}
false
})
};

if to_keep {
if keep_disk_entry(&name, &mount_point, disk_filter, mount_filter) {
// The usage line can fail in some cases (for example, if you use Void Linux + LUKS,
// see https://github.com/ClementTsang/bottom/issues/419 for details).
if let Ok(usage) = partition.usage() {
Expand Down
32 changes: 20 additions & 12 deletions src/app/data_harvester/disks/unix/linux/io_counters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const DISK_SECTOR_SIZE: u64 = 512;

#[allow(dead_code)]
#[derive(Debug, Default)]
pub struct IoStats {
pub struct IoCounters {
name: String,
read_count: u64,
read_merged_count: u64,
Expand All @@ -35,7 +35,7 @@ pub struct IoStats {
write_time_secs: u64,
}

impl IoStats {
impl IoCounters {
pub(crate) fn device_name(&self) -> &OsStr {
OsStr::new(&self.name)
}
Expand All @@ -49,7 +49,7 @@ impl IoStats {
}
}

impl FromStr for IoStats {
impl FromStr for IoCounters {
type Err = anyhow::Error;

/// Converts a `&str` to an [`IoStats`].
Expand All @@ -60,7 +60,7 @@ impl FromStr for IoStats {
///
/// https://www.kernel.org/doc/Documentation/iostats.txt
/// https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
fn from_str(s: &str) -> anyhow::Result<IoStats> {
fn from_str(s: &str) -> anyhow::Result<IoCounters> {
fn next_part<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Result<&'a str, io::Error> {
iter.next()
.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))
Expand All @@ -81,7 +81,7 @@ impl FromStr for IoStats {
let write_bytes = next_part(&mut parts)?.parse::<u64>()? * DISK_SECTOR_SIZE;
let write_time_secs = next_part(&mut parts)?.parse()?;

Ok(IoStats {
Ok(IoCounters {
name,
read_count,
read_merged_count,
Expand All @@ -96,13 +96,21 @@ impl FromStr for IoStats {
}

/// Returns an iterator of disk I/O stats. Pulls data from `/proc/diskstats`.
pub fn io_stats() -> anyhow::Result<impl Iterator<Item = anyhow::Result<IoStats>>> {
pub fn io_stats() -> anyhow::Result<Vec<anyhow::Result<IoCounters>>> {
const PROC_DISKSTATS: &str = "/proc/diskstats";

Ok(BufReader::new(File::open(PROC_DISKSTATS)?)
.lines()
.map(|line| match line {
Ok(line) => IoStats::from_str(&line),
Err(err) => Err(err.into()),
}))
let mut results = vec![];
let mut reader = BufReader::new(File::open(PROC_DISKSTATS)?);
let mut line = String::new();

while let Ok(bytes) = reader.read_line(&mut line) {
if bytes > 0 {
results.push(IoCounters::from_str(&line));
line.clear();
} else {
break;
}
}

Ok(results)
}
Loading