From d8d7e408146e6d6525db9a9266f6f3d67e3155bd Mon Sep 17 00:00:00 2001 From: simplepad <35456194+simplepad@users.noreply.github.com> Date: Fri, 1 Jul 2022 18:35:41 +0300 Subject: [PATCH 1/8] Add zfs_pools_only option --- src/btop_config.cpp | 3 +++ src/btop_menu.cpp | 7 +++++++ src/linux/btop_collect.cpp | 1 + 3 files changed, 11 insertions(+) diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 6ffc483..6cc2a6c 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -155,6 +155,8 @@ namespace Config { {"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."}, + {"zfs_pools_only", "#* Only show ZFS pools. Setting this to True will hide all datasets, and only show ZFS pools."}, + {"disk_free_priv", "#* Set to true to show available disk space for privileged users."}, {"show_io_stat", "#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view."}, @@ -241,6 +243,7 @@ namespace Config { {"show_disks", true}, {"only_physical", true}, {"use_fstab", true}, + {"zfs_pools_only", false}, {"show_io_stat", true}, {"io_mode", false}, {"base_10_sizes", false}, diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 6dd420c..99972ef 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -469,6 +469,13 @@ namespace Menu { "This also disables only_physical.", "", "True or False."}, + {"zfs_pools_only", + "(Linux) Only show ZFS pools.", + "", + "Setting this to True will hide all datasets,", + "and only show ZFS pools.", + "", + "True or False."}, {"disk_free_priv", "(Linux) Type of available disk space.", "", diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 0c71397..8350dc2 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -851,6 +851,7 @@ namespace Mem { bool filter_exclude = false; auto& use_fstab = Config::getB("use_fstab"); auto& only_physical = Config::getB("only_physical"); + auto& zfs_pools_only = Config::getB("zfs_pools_only"); auto& disks = mem.disks; ifstream diskread; From ed20cb9e0702659a90af3e2b5a4efd6ddf8aa217 Mon Sep 17 00:00:00 2001 From: simplepad <35456194+simplepad@users.noreply.github.com> Date: Sun, 3 Jul 2022 21:04:05 +0300 Subject: [PATCH 2/8] Re-implemented ZFS stat collection to be compatible with new ZFS versions, now it uses files "/proc/spl/kstat/zfs/*pool_name*/objset*". Needs additional work to be compatible with the option "zfs_pools_only". --- src/btop_shared.hpp | 3 ++ src/linux/btop_collect.cpp | 93 ++++++++++++++++++++++++++++++++------ 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index caef8bf..3704c15 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -160,6 +160,9 @@ namespace Mem { //* Draw contents of mem box using as source string draw(const mem_info& mem, const bool force_redraw=false, const bool data_same=false); + + //? * Find the filepath to the specified ZFS object's stat file + string get_zfs_stat_file(const string& device_name, size_t dataset_name_start); } namespace Net { diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 8350dc2..dac7c6d 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -26,6 +26,7 @@ tab-size = 4 #include #include #include +#include #if !(defined(STATIC_BUILD) && defined(__GLIBC__)) #include @@ -925,6 +926,10 @@ namespace Mem { continue; } + //? Skip ZFS datasets if zfs_pools_only option is enabled + size_t zfs_dataset_name_start = 0; + if (fstype == "zfs" && (zfs_dataset_name_start = dev.find('/')) != std::string::npos && zfs_pools_only) continue; + if ((not use_fstab and not only_physical) or (use_fstab and v_contains(fstab, mountpoint)) or (not use_fstab and only_physical and v_contains(fstypes, fstype))) { @@ -949,8 +954,11 @@ namespace Mem { disks.at(mountpoint).stat = "/sys/block/" + devname + "/stat"; break; //? Set ZFS stat filepath - } else if (fs::exists(Shared::procPath.string() + "/spl/kstat/zfs/" + devname + "/io", ec) and access(string(Shared::procPath.string() + "/spl/kstat/zfs/" + devname + "/io").c_str(), R_OK) == 0) { - disks.at(mountpoint).stat = Shared::procPath.string() + "/spl/kstat/zfs/" + devname + "/io"; + } else if (fstype == "zfs") { + disks.at(mountpoint).stat = get_zfs_stat_file(dev, zfs_dataset_name_start); + if (disks.at(mountpoint).stat.empty()) { + Logger::warning("Failed to get ZFS stat file for device " + dev); + } break; } devname.resize(devname.size() - 1); @@ -1020,18 +1028,17 @@ namespace Mem { if (disk.stat.empty() or access(disk.stat.c_str(), R_OK) != 0) continue; diskread.open(disk.stat); if (diskread.good()) { + disk_ios++; //? ZFS Pool Support if (disk.fstype == "zfs") { - disk_ios++; - for (int i = 0; i < 18; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); } - diskread >> sectors_read; // nbytes read - if (disk.io_read.empty()) - disk.io_read.push_back(0); - else - disk.io_read.push_back(max((int64_t)0, (sectors_read - disk.old_io.at(0)))); - disk.old_io.at(0) = sectors_read; - while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front(); + // skip first three lines + for (int i = 0; i < 3; i++) diskread.ignore(numeric_limits::max(), '\n'); + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); + diskread >> io_ticks_write; + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); diskread >> sectors_write; // nbytes written if (disk.io_write.empty()) disk.io_write.push_back(0); @@ -1040,10 +1047,20 @@ namespace Mem { disk.old_io.at(1) = sectors_write; while (cmp_greater(disk.io_write.size(), width * 2)) disk.io_write.pop_front(); - for (int i = 0; i < 2; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); } - diskread >> io_ticks_write; - for (int i = 0; i < 2; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); } + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); diskread >> io_ticks_read; + + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); + diskread >> sectors_read; // nbytes read + if (disk.io_read.empty()) + disk.io_read.push_back(0); + else + disk.io_read.push_back(max((int64_t)0, (sectors_read - disk.old_io.at(0)))); + disk.old_io.at(0) = sectors_read; + while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front(); + io_ticks = io_ticks_write + io_ticks_read; if (disk.io_activity.empty()) disk.io_activity.push_back(0); @@ -1052,7 +1069,6 @@ namespace Mem { disk.old_io.at(2) = io_ticks; while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front(); } else { - disk_ios++; for (int i = 0; i < 2; i++) { diskread >> std::ws; diskread.ignore(SSmax, ' '); } diskread >> sectors_read; if (disk.io_read.empty()) @@ -1095,6 +1111,53 @@ namespace Mem { return mem; } + string get_zfs_stat_file(const string& device_name, size_t dataset_name_start) { + ifstream filestream; + DIR *directory; + struct dirent *entity; + string filename; + string filepath; + string zfs_pool_stat_path; + string pool_name; + if (dataset_name_start != std::string::npos) { // device is a dataset + pool_name = device_name.substr(0, dataset_name_start); + zfs_pool_stat_path = Shared::procPath.string() + "/spl/kstat/zfs/" + pool_name + "/"; + } else { // device is a pool + zfs_pool_stat_path = Shared::procPath.string() + "/spl/kstat/zfs/" + device_name + "/"; + } + string name_compare; + + if ((directory = opendir(zfs_pool_stat_path.c_str())) == nullptr) { + return ""; + } + // looking through all files that start with 'objset' to find the one containing `device_name` object stats + while ((entity = readdir(directory)) != nullptr) { + filename = entity->d_name; + if (filename.find("objset") != std::string::npos) { + filepath = zfs_pool_stat_path + filename; + filestream.open(filepath); + if (filestream.good()) { + // skip first two lines + for (int i = 0; i < 2; i++) filestream.ignore(numeric_limits::max(), '\n'); + // skip characters until '7' is reached, indicating data type 7, next value will be object name + filestream.ignore(numeric_limits::max(), '7'); + filestream >> name_compare; + if (name_compare == device_name) { + filestream.close(); + if (access((filepath).c_str(), R_OK) == 0) { + return filepath; + } else { + return ""; + } + } + } + filestream.close(); + } + } + + return ""; + } + } namespace Net { From 7feff854fd457453b798d3290f94d6e9ae534fb4 Mon Sep 17 00:00:00 2001 From: simplepad <35456194+simplepad@users.noreply.github.com> Date: Mon, 4 Jul 2022 01:28:25 +0300 Subject: [PATCH 3/8] Made ZFS stats collection compatible with zfs_pools_only option. ZFS pool's stat filepath points to the objset-* file when the option is disabled, otherwise it points to the pool's stats directory. Made ZFS total pool stat collection into a separate function for clean code. Also removed an unnecessary variable in the default ZFS stat collection, and changed io_ticks to track the number of reads/writes, reducing unnecessary calculations. --- src/btop_shared.hpp | 7 ++- src/linux/btop_collect.cpp | 115 ++++++++++++++++++++++++++++++++++--- 2 files changed, 111 insertions(+), 11 deletions(-) diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 3704c15..d08eb4c 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -161,8 +161,11 @@ namespace Mem { //* Draw contents of mem box using as source string draw(const mem_info& mem, const bool force_redraw=false, const bool data_same=false); - //? * Find the filepath to the specified ZFS object's stat file - string get_zfs_stat_file(const string& device_name, size_t dataset_name_start); + //?* Find the filepath to the specified ZFS object's stat file + string get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_pools_only); + + //?* Collect total ZFS pool io stats + bool zfs_collect_pool_total_stats(struct disk_info &disk); } namespace Net { diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index dac7c6d..113d418 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -955,7 +955,7 @@ namespace Mem { break; //? Set ZFS stat filepath } else if (fstype == "zfs") { - disks.at(mountpoint).stat = get_zfs_stat_file(dev, zfs_dataset_name_start); + disks.at(mountpoint).stat = get_zfs_stat_file(dev, zfs_dataset_name_start, zfs_pools_only); if (disks.at(mountpoint).stat.empty()) { Logger::warning("Failed to get ZFS stat file for device " + dev); } @@ -966,6 +966,14 @@ namespace Mem { } } + //? If zfs_pools_only option was switched, refresh stat filepath + if (fstype == "zfs" && ((zfs_pools_only && !is_directory(disks.at(mountpoint).stat)) + || (!zfs_pools_only && is_directory(disks.at(mountpoint).stat)))) { + disks.at(mountpoint).stat = get_zfs_stat_file(dev, zfs_dataset_name_start, zfs_pools_only); + if (disks.at(mountpoint).stat.empty()) { + Logger::warning("Failed to get ZFS stat file for device " + dev); + } + } } } //? Remove disks no longer mounted or filtered out @@ -1022,10 +1030,14 @@ namespace Mem { #endif //? Get disks IO - int64_t sectors_read, sectors_write, io_ticks, io_ticks_read, io_ticks_write; + int64_t sectors_read, sectors_write, io_ticks, io_ticks_temp; disk_ios = 0; for (auto& [ignored, disk] : disks) { if (disk.stat.empty() or access(disk.stat.c_str(), R_OK) != 0) continue; + if (disk.fstype == "zfs" && zfs_pools_only && zfs_collect_pool_total_stats(disk)) { + disk_ios++; + continue; + } diskread.open(disk.stat); if (diskread.good()) { disk_ios++; @@ -1035,7 +1047,7 @@ namespace Mem { for (int i = 0; i < 3; i++) diskread.ignore(numeric_limits::max(), '\n'); // skip characters until '4' is reached, indicating data type 4, next value will be out target diskread.ignore(numeric_limits::max(), '4'); - diskread >> io_ticks_write; + diskread >> io_ticks; // skip characters until '4' is reached, indicating data type 4, next value will be out target diskread.ignore(numeric_limits::max(), '4'); @@ -1049,7 +1061,8 @@ namespace Mem { // skip characters until '4' is reached, indicating data type 4, next value will be out target diskread.ignore(numeric_limits::max(), '4'); - diskread >> io_ticks_read; + diskread >> io_ticks_temp; + io_ticks += io_ticks_temp; // skip characters until '4' is reached, indicating data type 4, next value will be out target diskread.ignore(numeric_limits::max(), '4'); @@ -1061,11 +1074,10 @@ namespace Mem { disk.old_io.at(0) = sectors_read; while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front(); - io_ticks = io_ticks_write + io_ticks_read; if (disk.io_activity.empty()) disk.io_activity.push_back(0); else - disk.io_activity.push_back(clamp((long)round((double)(io_ticks - disk.old_io.at(2)) / (uptime - old_uptime) / 10), 0l, 100l)); + disk.io_activity.push_back(max((int64_t)0, (io_ticks - disk.old_io.at(2)))); disk.old_io.at(2) = io_ticks; while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front(); } else { @@ -1111,23 +1123,35 @@ namespace Mem { return mem; } - string get_zfs_stat_file(const string& device_name, size_t dataset_name_start) { + string get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_pools_only) { + string zfs_pool_stat_path; + if (zfs_pools_only) { + zfs_pool_stat_path = Shared::procPath.string() + "/spl/kstat/zfs/" + device_name; + if (access((zfs_pool_stat_path).c_str(), R_OK) == 0) { + return zfs_pool_stat_path; + } else { + Logger::warning("Cant access folder: " + zfs_pool_stat_path); + return ""; + } + } + ifstream filestream; DIR *directory; struct dirent *entity; string filename; string filepath; - string zfs_pool_stat_path; string pool_name; + string name_compare; + if (dataset_name_start != std::string::npos) { // device is a dataset pool_name = device_name.substr(0, dataset_name_start); zfs_pool_stat_path = Shared::procPath.string() + "/spl/kstat/zfs/" + pool_name + "/"; } else { // device is a pool zfs_pool_stat_path = Shared::procPath.string() + "/spl/kstat/zfs/" + device_name + "/"; } - string name_compare; if ((directory = opendir(zfs_pool_stat_path.c_str())) == nullptr) { + Logger::warning("Cant access folder: " + zfs_pool_stat_path); return ""; } // looking through all files that start with 'objset' to find the one containing `device_name` object stats @@ -1147,6 +1171,7 @@ namespace Mem { if (access((filepath).c_str(), R_OK) == 0) { return filepath; } else { + Logger::warning("Cant access file: " + filepath); return ""; } } @@ -1155,9 +1180,81 @@ namespace Mem { } } + Logger::warning("Could not read directory: " + zfs_pool_stat_path); return ""; } + bool zfs_collect_pool_total_stats(struct disk_info &disk) { + ifstream diskread; + DIR *directory; + struct dirent *entity; + string filename; + + int64_t bytes_read, bytes_write, io_ticks, bytes_read_total = 0, bytes_write_total = 0, io_ticks_total = 0; + + if ((directory = opendir(disk.stat.c_str())) == nullptr) { + Logger::warning("Cant access folder: " + disk.stat.string()); + return false; + } + + // looking through all files that start with 'objset' + while ((entity = readdir(directory)) != nullptr) { + filename = entity->d_name; + if (filename.find("objset") != std::string::npos) { + diskread.open(disk.stat / filename); + if (diskread.good()) { + // skip first three lines + for (int i = 0; i < 3; i++) diskread.ignore(numeric_limits::max(), '\n'); + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); + diskread >> io_ticks; + io_ticks_total += io_ticks; + + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); + diskread >> bytes_write; + bytes_write_total += bytes_write; + + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); + diskread >> io_ticks; + io_ticks_total += io_ticks; + + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); + diskread >> bytes_read; + bytes_read_total += bytes_read; + } else { + Logger::warning("Could not read file: " + (disk.stat / filename).string()); + } + diskread.close(); + } + } + + if (disk.io_write.empty()) + disk.io_write.push_back(0); + else + disk.io_write.push_back(max((int64_t)0, (bytes_write_total - disk.old_io.at(1)))); + disk.old_io.at(1) = bytes_write_total; + while (cmp_greater(disk.io_write.size(), width * 2)) disk.io_write.pop_front(); + + if (disk.io_read.empty()) + disk.io_read.push_back(0); + else + disk.io_read.push_back(max((int64_t)0, (bytes_read_total - disk.old_io.at(0)))); + disk.old_io.at(0) = bytes_read_total; + while (cmp_greater(disk.io_read.size(), width * 2)) disk.io_read.pop_front(); + + if (disk.io_activity.empty()) + disk.io_activity.push_back(0); + else + disk.io_activity.push_back(max((int64_t)0, (io_ticks_total - disk.old_io.at(2)))); + disk.old_io.at(2) = io_ticks_total; + while (cmp_greater(disk.io_activity.size(), width * 2)) disk.io_activity.pop_front(); + + return true; + } + } namespace Net { From 30cc42fcd92891336e4df6734282aba3213d0c17 Mon Sep 17 00:00:00 2001 From: simplepad <35456194+simplepad@users.noreply.github.com> Date: Tue, 5 Jul 2022 00:20:33 +0300 Subject: [PATCH 4/8] Move get_zfs_stat_file() and zfs_collect_pool_total_stats() functions declarations to btop_collect.cpp so they aren't included when compiling for macos and freebsd --- src/btop_shared.hpp | 5 ----- src/linux/btop_collect.cpp | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index d08eb4c..07c042e 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -161,11 +161,6 @@ namespace Mem { //* Draw contents of mem box using as source string draw(const mem_info& mem, const bool force_redraw=false, const bool data_same=false); - //?* Find the filepath to the specified ZFS object's stat file - string get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_pools_only); - - //?* Collect total ZFS pool io stats - bool zfs_collect_pool_total_stats(struct disk_info &disk); } namespace Net { diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 113d418..7598d50 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -741,6 +741,12 @@ namespace Mem { vector last_found; const std::regex zfs_size_regex("^size\\s+\\d\\s+(\\d+)"); + //?* Find the filepath to the specified ZFS object's stat file + string get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_pools_only); + + //?* Collect total ZFS pool io stats + bool zfs_collect_pool_total_stats(struct disk_info &disk); + mem_info current_mem {}; uint64_t get_totalMem() { From 4969dd8dc63d2acb81babf20b73554ab2f529039 Mon Sep 17 00:00:00 2001 From: simplepad <35456194+simplepad@users.noreply.github.com> Date: Tue, 5 Jul 2022 01:11:17 +0300 Subject: [PATCH 5/8] Use fs::directory_iterator() instead of readdir() in ZFS functions, use fs::path instead of strings. --- src/linux/btop_collect.cpp | 60 +++++++++++++------------------------- 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 7598d50..d82ef61 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -26,7 +26,6 @@ tab-size = 4 #include #include #include -#include #if !(defined(STATIC_BUILD) && defined(__GLIBC__)) #include @@ -742,7 +741,7 @@ namespace Mem { const std::regex zfs_size_regex("^size\\s+\\d\\s+(\\d+)"); //?* Find the filepath to the specified ZFS object's stat file - string get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_pools_only); + fs::path get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_pools_only); //?* Collect total ZFS pool io stats bool zfs_collect_pool_total_stats(struct disk_info &disk); @@ -1129,43 +1128,33 @@ namespace Mem { return mem; } - string get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_pools_only) { - string zfs_pool_stat_path; + fs::path get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_pools_only) { + fs::path zfs_pool_stat_path; if (zfs_pools_only) { - zfs_pool_stat_path = Shared::procPath.string() + "/spl/kstat/zfs/" + device_name; - if (access((zfs_pool_stat_path).c_str(), R_OK) == 0) { + zfs_pool_stat_path = Shared::procPath / "spl/kstat/zfs" / device_name; + if (access(zfs_pool_stat_path.c_str(), R_OK) == 0) { return zfs_pool_stat_path; } else { - Logger::warning("Cant access folder: " + zfs_pool_stat_path); + Logger::warning("Cant access folder: " + zfs_pool_stat_path.string()); return ""; } } ifstream filestream; - DIR *directory; - struct dirent *entity; string filename; - string filepath; - string pool_name; string name_compare; if (dataset_name_start != std::string::npos) { // device is a dataset - pool_name = device_name.substr(0, dataset_name_start); - zfs_pool_stat_path = Shared::procPath.string() + "/spl/kstat/zfs/" + pool_name + "/"; + zfs_pool_stat_path = Shared::procPath / "spl/kstat/zfs" / device_name.substr(0, dataset_name_start); } else { // device is a pool - zfs_pool_stat_path = Shared::procPath.string() + "/spl/kstat/zfs/" + device_name + "/"; + zfs_pool_stat_path = Shared::procPath / "spl/kstat/zfs" / device_name; } - if ((directory = opendir(zfs_pool_stat_path.c_str())) == nullptr) { - Logger::warning("Cant access folder: " + zfs_pool_stat_path); - return ""; - } // looking through all files that start with 'objset' to find the one containing `device_name` object stats - while ((entity = readdir(directory)) != nullptr) { - filename = entity->d_name; - if (filename.find("objset") != std::string::npos) { - filepath = zfs_pool_stat_path + filename; - filestream.open(filepath); + for (const auto& file: fs::directory_iterator(zfs_pool_stat_path)) { + filename = file.path().filename(); + if (filename.starts_with("objset")) { + filestream.open(file.path()); if (filestream.good()) { // skip first two lines for (int i = 0; i < 2; i++) filestream.ignore(numeric_limits::max(), '\n'); @@ -1174,10 +1163,10 @@ namespace Mem { filestream >> name_compare; if (name_compare == device_name) { filestream.close(); - if (access((filepath).c_str(), R_OK) == 0) { - return filepath; + if (access(file.path().c_str(), R_OK) == 0) { + return file.path(); } else { - Logger::warning("Cant access file: " + filepath); + Logger::warning("Can't access file: " + file.path().string()); return ""; } } @@ -1186,28 +1175,19 @@ namespace Mem { } } - Logger::warning("Could not read directory: " + zfs_pool_stat_path); + Logger::warning("Could not read directory: " + zfs_pool_stat_path.string()); return ""; } bool zfs_collect_pool_total_stats(struct disk_info &disk) { ifstream diskread; - DIR *directory; - struct dirent *entity; - string filename; int64_t bytes_read, bytes_write, io_ticks, bytes_read_total = 0, bytes_write_total = 0, io_ticks_total = 0; - if ((directory = opendir(disk.stat.c_str())) == nullptr) { - Logger::warning("Cant access folder: " + disk.stat.string()); - return false; - } - // looking through all files that start with 'objset' - while ((entity = readdir(directory)) != nullptr) { - filename = entity->d_name; - if (filename.find("objset") != std::string::npos) { - diskread.open(disk.stat / filename); + for (const auto& file: fs::directory_iterator(disk.stat)) { + if ((file.path().filename()).string().starts_with("objset")) { + diskread.open(file.path()); if (diskread.good()) { // skip first three lines for (int i = 0; i < 3; i++) diskread.ignore(numeric_limits::max(), '\n'); @@ -1231,7 +1211,7 @@ namespace Mem { diskread >> bytes_read; bytes_read_total += bytes_read; } else { - Logger::warning("Could not read file: " + (disk.stat / filename).string()); + Logger::warning("Could not read file: " + file.path().string()); } diskread.close(); } From 189cba73e40b3c43381076e038da8929a58dece9 Mon Sep 17 00:00:00 2001 From: simplepad <35456194+simplepad@users.noreply.github.com> Date: Sat, 9 Jul 2022 03:46:15 +0300 Subject: [PATCH 6/8] check if at least one object was read before updating ZFS pool io in zfs_collect_pool_total_stats(), use try-catch to prevent possible crashes from int_64t conversions --- src/linux/btop_collect.cpp | 48 +++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index d82ef61..1ec2a13 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -1182,34 +1182,41 @@ namespace Mem { bool zfs_collect_pool_total_stats(struct disk_info &disk) { ifstream diskread; - int64_t bytes_read, bytes_write, io_ticks, bytes_read_total = 0, bytes_write_total = 0, io_ticks_total = 0; + int64_t bytes_read, bytes_write, io_ticks, bytes_read_total = 0, bytes_write_total = 0, io_ticks_total = 0, objects_read = 0; // looking through all files that start with 'objset' for (const auto& file: fs::directory_iterator(disk.stat)) { if ((file.path().filename()).string().starts_with("objset")) { diskread.open(file.path()); if (diskread.good()) { - // skip first three lines - for (int i = 0; i < 3; i++) diskread.ignore(numeric_limits::max(), '\n'); - // skip characters until '4' is reached, indicating data type 4, next value will be out target - diskread.ignore(numeric_limits::max(), '4'); - diskread >> io_ticks; - io_ticks_total += io_ticks; + try { + // skip first three lines + for (int i = 0; i < 3; i++) diskread.ignore(numeric_limits::max(), '\n'); + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); + diskread >> io_ticks; + io_ticks_total += io_ticks; - // skip characters until '4' is reached, indicating data type 4, next value will be out target - diskread.ignore(numeric_limits::max(), '4'); - diskread >> bytes_write; - bytes_write_total += bytes_write; + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); + diskread >> bytes_write; + bytes_write_total += bytes_write; - // skip characters until '4' is reached, indicating data type 4, next value will be out target - diskread.ignore(numeric_limits::max(), '4'); - diskread >> io_ticks; - io_ticks_total += io_ticks; + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); + diskread >> io_ticks; + io_ticks_total += io_ticks; - // skip characters until '4' is reached, indicating data type 4, next value will be out target - diskread.ignore(numeric_limits::max(), '4'); - diskread >> bytes_read; - bytes_read_total += bytes_read; + // skip characters until '4' is reached, indicating data type 4, next value will be out target + diskread.ignore(numeric_limits::max(), '4'); + diskread >> bytes_read; + bytes_read_total += bytes_read; + } catch (const std::exception& e) { + continue; + } + + // increment read objects counter if no errors were encountered + objects_read++; } else { Logger::warning("Could not read file: " + file.path().string()); } @@ -1217,6 +1224,9 @@ namespace Mem { } } + // if for some reason no objects were read + if (objects_read == 0) return false; + if (disk.io_write.empty()) disk.io_write.push_back(0); else From bc608e862ead1e4ae1816736e20952616c7c61c7 Mon Sep 17 00:00:00 2001 From: simplepad <35456194+simplepad@users.noreply.github.com> Date: Mon, 11 Jul 2022 19:08:25 +0300 Subject: [PATCH 7/8] rename zfs_pools_only option to zfs_hide_datasets, make its description clearer --- src/btop_config.cpp | 4 ++-- src/btop_menu.cpp | 6 ++++-- src/linux/btop_collect.cpp | 24 ++++++++++++------------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 6cc2a6c..01e66d7 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -155,7 +155,7 @@ namespace Config { {"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."}, - {"zfs_pools_only", "#* Only show ZFS pools. Setting this to True will hide all datasets, and only show ZFS pools."}, + {"zfs_hide_datasets", "#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)"}, {"disk_free_priv", "#* Set to true to show available disk space for privileged users."}, @@ -243,7 +243,7 @@ namespace Config { {"show_disks", true}, {"only_physical", true}, {"use_fstab", true}, - {"zfs_pools_only", false}, + {"zfs_hide_datasets", false}, {"show_io_stat", true}, {"io_mode", false}, {"base_10_sizes", false}, diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 99972ef..cb83770 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -469,12 +469,14 @@ namespace Menu { "This also disables only_physical.", "", "True or False."}, - {"zfs_pools_only", - "(Linux) Only show ZFS pools.", + {"zfs_hide_datasets", + "(Linux) Hide ZFS datasets in disks list.", "", "Setting this to True will hide all datasets,", "and only show ZFS pools.", "", + "(IO stats will be calculated per-pool)", + "", "True or False."}, {"disk_free_priv", "(Linux) Type of available disk space.", diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 1ec2a13..3f4ff6e 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -741,7 +741,7 @@ namespace Mem { const std::regex zfs_size_regex("^size\\s+\\d\\s+(\\d+)"); //?* Find the filepath to the specified ZFS object's stat file - fs::path get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_pools_only); + fs::path get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_hide_datasets); //?* Collect total ZFS pool io stats bool zfs_collect_pool_total_stats(struct disk_info &disk); @@ -857,7 +857,7 @@ namespace Mem { bool filter_exclude = false; auto& use_fstab = Config::getB("use_fstab"); auto& only_physical = Config::getB("only_physical"); - auto& zfs_pools_only = Config::getB("zfs_pools_only"); + auto& zfs_hide_datasets = Config::getB("zfs_hide_datasets"); auto& disks = mem.disks; ifstream diskread; @@ -931,9 +931,9 @@ namespace Mem { continue; } - //? Skip ZFS datasets if zfs_pools_only option is enabled + //? Skip ZFS datasets if zfs_hide_datasets option is enabled size_t zfs_dataset_name_start = 0; - if (fstype == "zfs" && (zfs_dataset_name_start = dev.find('/')) != std::string::npos && zfs_pools_only) continue; + if (fstype == "zfs" && (zfs_dataset_name_start = dev.find('/')) != std::string::npos && zfs_hide_datasets) continue; if ((not use_fstab and not only_physical) or (use_fstab and v_contains(fstab, mountpoint)) @@ -960,7 +960,7 @@ namespace Mem { break; //? Set ZFS stat filepath } else if (fstype == "zfs") { - disks.at(mountpoint).stat = get_zfs_stat_file(dev, zfs_dataset_name_start, zfs_pools_only); + disks.at(mountpoint).stat = get_zfs_stat_file(dev, zfs_dataset_name_start, zfs_hide_datasets); if (disks.at(mountpoint).stat.empty()) { Logger::warning("Failed to get ZFS stat file for device " + dev); } @@ -971,10 +971,10 @@ namespace Mem { } } - //? If zfs_pools_only option was switched, refresh stat filepath - if (fstype == "zfs" && ((zfs_pools_only && !is_directory(disks.at(mountpoint).stat)) - || (!zfs_pools_only && is_directory(disks.at(mountpoint).stat)))) { - disks.at(mountpoint).stat = get_zfs_stat_file(dev, zfs_dataset_name_start, zfs_pools_only); + //? If zfs_hide_datasets option was switched, refresh stat filepath + if (fstype == "zfs" && ((zfs_hide_datasets && !is_directory(disks.at(mountpoint).stat)) + || (!zfs_hide_datasets && is_directory(disks.at(mountpoint).stat)))) { + disks.at(mountpoint).stat = get_zfs_stat_file(dev, zfs_dataset_name_start, zfs_hide_datasets); if (disks.at(mountpoint).stat.empty()) { Logger::warning("Failed to get ZFS stat file for device " + dev); } @@ -1039,7 +1039,7 @@ namespace Mem { disk_ios = 0; for (auto& [ignored, disk] : disks) { if (disk.stat.empty() or access(disk.stat.c_str(), R_OK) != 0) continue; - if (disk.fstype == "zfs" && zfs_pools_only && zfs_collect_pool_total_stats(disk)) { + if (disk.fstype == "zfs" && zfs_hide_datasets && zfs_collect_pool_total_stats(disk)) { disk_ios++; continue; } @@ -1128,9 +1128,9 @@ namespace Mem { return mem; } - fs::path get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_pools_only) { + fs::path get_zfs_stat_file(const string& device_name, size_t dataset_name_start, bool zfs_hide_datasets) { fs::path zfs_pool_stat_path; - if (zfs_pools_only) { + if (zfs_hide_datasets) { zfs_pool_stat_path = Shared::procPath / "spl/kstat/zfs" / device_name; if (access(zfs_pool_stat_path.c_str(), R_OK) == 0) { return zfs_pool_stat_path; From a33bab3000ea3fbc7b4179976960862d4653f168 Mon Sep 17 00:00:00 2001 From: simplepad <35456194+simplepad@users.noreply.github.com> Date: Mon, 11 Jul 2022 19:16:19 +0300 Subject: [PATCH 8/8] switch Logger calls to debug() to avoid filling up the logfile --- src/linux/btop_collect.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 3f4ff6e..068a7e2 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -962,7 +962,7 @@ namespace Mem { } else if (fstype == "zfs") { disks.at(mountpoint).stat = get_zfs_stat_file(dev, zfs_dataset_name_start, zfs_hide_datasets); if (disks.at(mountpoint).stat.empty()) { - Logger::warning("Failed to get ZFS stat file for device " + dev); + Logger::debug("Failed to get ZFS stat file for device " + dev); } break; } @@ -976,7 +976,7 @@ namespace Mem { || (!zfs_hide_datasets && is_directory(disks.at(mountpoint).stat)))) { disks.at(mountpoint).stat = get_zfs_stat_file(dev, zfs_dataset_name_start, zfs_hide_datasets); if (disks.at(mountpoint).stat.empty()) { - Logger::warning("Failed to get ZFS stat file for device " + dev); + Logger::debug("Failed to get ZFS stat file for device " + dev); } } } @@ -1135,7 +1135,7 @@ namespace Mem { if (access(zfs_pool_stat_path.c_str(), R_OK) == 0) { return zfs_pool_stat_path; } else { - Logger::warning("Cant access folder: " + zfs_pool_stat_path.string()); + Logger::debug("Cant access folder: " + zfs_pool_stat_path.string()); return ""; } } @@ -1166,7 +1166,7 @@ namespace Mem { if (access(file.path().c_str(), R_OK) == 0) { return file.path(); } else { - Logger::warning("Can't access file: " + file.path().string()); + Logger::debug("Can't access file: " + file.path().string()); return ""; } } @@ -1175,7 +1175,7 @@ namespace Mem { } } - Logger::warning("Could not read directory: " + zfs_pool_stat_path.string()); + Logger::debug("Could not read directory: " + zfs_pool_stat_path.string()); return ""; } @@ -1218,7 +1218,7 @@ namespace Mem { // increment read objects counter if no errors were encountered objects_read++; } else { - Logger::warning("Could not read file: " + file.path().string()); + Logger::debug("Could not read file: " + file.path().string()); } diskread.close(); }