feat(fs): New filesystem module

Module that displays details about
mounted filesystems, #84

Closes #153
This commit is contained in:
Michael Carlberg 2016-11-13 06:09:51 +01:00
parent ed5b7a508a
commit 9a0df75a91
9 changed files with 343 additions and 3 deletions

View File

@ -3,7 +3,7 @@
#
set(MODULES_LEFT "bspwm i3 mpd")
set(MODULES_CENTER "")
set(MODULES_CENTER "filesystem")
set(MODULES_RIGHT "backlight volume memory cpu wlan eth battery temperature date powermenu")
# Strip disabled modules {{{

View File

@ -41,7 +41,7 @@ font-1 = unifont:size=6;-2
font-2 = siji:pixelsize=10;0
modules-left = bspwm i3 mpd
modules-center =
modules-center = filesystem
modules-right = backlight volume memory cpu wlan eth battery temperature date powermenu
tray-position = right
@ -52,6 +52,24 @@ tray-padding = 4
;wm-restack = bspwm
[module/filesystem]
type = internal/fs
interval = 25
disk-0 = /
disk-1 = /home
disk-2 = /invalid/mountpoint
;fixed-values = true
;spacing = 4
label-mounted = %mountpoint%: %percentage_free%
label-unmounted = %mountpoint%: not mounted
label-unmounted-foreground = #55
[module/bspwm]
type = internal/bspwm
ws-icon-default = x

View File

@ -52,6 +52,24 @@ tray-padding = 4
;wm-restack = bspwm
[module/filesystem]
type = internal/fs
interval = 25
disk-0 = /
disk-1 = /home
disk-2 = /invalid/mountpoint
;fixed-values = true
;spacing = 4
label-mounted = %mountpoint%: %percentage_free%
label-unmounted = %mountpoint%: not mounted
label-unmounted-foreground = #55
[module/bspwm]
type = internal/bspwm
ws-icon-default = x

77
include/modules/fs.hpp Normal file
View File

@ -0,0 +1,77 @@
#pragma once
#include "components/config.hpp"
#include "config.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/progressbar.hpp"
#include "drawtypes/ramp.hpp"
#include "modules/meta.hpp"
LEMONBUDDY_NS
namespace modules {
/**
* Filesystem structure
*/
struct fs_disk {
string mountpoint;
bool mounted = false;
string type;
string fsname;
unsigned long long bytes_free = 0;
unsigned long long bytes_used = 0;
unsigned long long bytes_total = 0;
float percentage_free = 0;
float percentage_used = 0;
string percentage_free_s;
string percentage_used_s;
explicit fs_disk(const string& mountpoint, bool mounted = false)
: mountpoint(mountpoint), mounted(mounted) {}
};
using fs_disk_t = unique_ptr<fs_disk>;
/**
* Module used to display filesystem stats.
*/
class fs_module : public timer_module<fs_module> {
public:
using timer_module::timer_module;
void setup();
bool update();
string get_format() const;
string get_output();
bool build(builder* builder, string tag) const;
private:
static constexpr auto FORMAT_MOUNTED = "format-mounted";
static constexpr auto FORMAT_UNMOUNTED = "format-unmounted";
static constexpr auto TAG_LABEL_MOUNTED = "<label-mounted>";
static constexpr auto TAG_LABEL_UNMOUNTED = "<label-unmounted>";
static constexpr auto TAG_BAR_USED = "<bar-used>";
static constexpr auto TAG_BAR_FREE = "<bar-free>";
static constexpr auto TAG_RAMP_CAPACITY = "<ramp-capacity>";
label_t m_labelmounted;
label_t m_labelunmounted;
progressbar_t m_barused;
progressbar_t m_barfree;
ramp_t m_rampcapacity;
vector<string> m_mounts;
vector<fs_disk_t> m_disks;
bool m_fixed = false;
int m_spacing = 2;
// used while formatting output
size_t m_index = 0;
};
}
LEMONBUDDY_NS_END

36
include/utils/mtab.hpp Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <mntent.h>
#include "common.hpp"
LEMONBUDDY_NS
namespace mtab_util {
/**
* Wrapper for reading mtab entries
*/
class reader {
public:
explicit reader() {
if ((m_ptr = setmntent("/etc/mtab", "r")) == nullptr) {
throw system_error("Failed to read mtab");
}
}
~reader() {
if (m_ptr != nullptr) {
endmntent(m_ptr);
}
}
bool next(mntent** dst) {
return (*dst = getmntent(m_ptr)) != nullptr;
}
protected:
FILE* m_ptr = nullptr;
};
}
LEMONBUDDY_NS_END

View File

@ -28,6 +28,8 @@ namespace string_util {
vector<string>& split_into(string s, char delim, vector<string>& container);
vector<string> split(const string& s, char delim);
size_t find_nth(string haystack, size_t pos, string needle, size_t nth);
string floatval(float value, int decimals = 2, bool fixed = false, string locale = "");
string filesize(unsigned long long bytes, int decimals = 2, bool fixed = false, string locale = "");
string from_stream(const std::basic_ostream<char>& os);
hash_type hash(string src);
}

View File

@ -9,6 +9,7 @@
#include "modules/counter.hpp"
#include "modules/cpu.hpp"
#include "modules/date.hpp"
#include "modules/fs.hpp"
#include "modules/memory.hpp"
#include "modules/menu.hpp"
#include "modules/script.hpp"
@ -343,6 +344,8 @@ void controller::bootstrap_modules() {
module.reset(new cpu_module(bar, m_log, m_conf, module_name));
else if (type == "internal/date")
module.reset(new date_module(bar, m_log, m_conf, module_name));
else if (type == "internal/fs")
module.reset(new fs_module(bar, m_log, m_conf, module_name));
else if (type == "internal/memory")
module.reset(new memory_module(bar, m_log, m_conf, module_name));
else if (type == "internal/i3")

150
src/modules/fs.cpp Normal file
View File

@ -0,0 +1,150 @@
#include <sys/statvfs.h>
#include "modules/fs.hpp"
#include "utils/math.hpp"
#include "utils/mtab.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
namespace modules {
/**
* Bootstrap the module by reading config values and
* setting up required components
*/
void fs_module::setup() {
m_mounts = m_conf.get_list<string>(name(), "disk");
m_fixed = m_conf.get<bool>(name(), "fixed-values", m_fixed);
m_spacing = m_conf.get<int>(name(), "spacing", m_spacing);
m_interval = chrono::duration<double>(m_conf.get<float>(name(), "interval", 30));
// Add formats and elements
m_formatter->add(
FORMAT_MOUNTED, TAG_LABEL_MOUNTED, {TAG_LABEL_MOUNTED, TAG_BAR_FREE, TAG_BAR_USED, TAG_RAMP_CAPACITY});
m_formatter->add(
FORMAT_UNMOUNTED, TAG_LABEL_UNMOUNTED, {TAG_LABEL_UNMOUNTED, TAG_BAR_FREE, TAG_BAR_USED, TAG_RAMP_CAPACITY});
if (m_formatter->has(TAG_LABEL_MOUNTED))
m_labelmounted = load_optional_label(m_conf, name(), TAG_LABEL_MOUNTED, "%mountpoint% %percentage_free%");
if (m_formatter->has(TAG_LABEL_UNMOUNTED))
m_labelunmounted = load_optional_label(m_conf, name(), TAG_LABEL_UNMOUNTED, "%mountpoint% is not mounted");
if (m_formatter->has(TAG_BAR_FREE))
m_barfree = load_progressbar(m_bar, m_conf, name(), TAG_BAR_FREE);
if (m_formatter->has(TAG_BAR_USED))
m_barused = load_progressbar(m_bar, m_conf, name(), TAG_BAR_USED);
if (m_formatter->has(TAG_RAMP_CAPACITY))
m_rampcapacity = load_ramp(m_conf, name(), TAG_RAMP_CAPACITY);
}
/**
* Update disk values by reading mtab entries
*/
bool fs_module::update() {
m_disks.clear();
struct statvfs buffer;
struct mntent* mount = nullptr;
for (auto&& mountpoint : m_mounts) {
m_disks.emplace_back(new fs_disk{mountpoint, false});
if (statvfs(mountpoint.c_str(), &buffer) == -1) {
continue;
}
auto mtab = make_unique<mtab_util::reader>();
auto& disk = m_disks.back();
while (mtab->next(&mount)) {
if (strncmp(mount->mnt_dir, mountpoint.c_str(), strlen(mount->mnt_dir)) != 0) {
continue;
}
disk->mounted = true;
disk->mountpoint = mount->mnt_dir;
disk->type = mount->mnt_type;
disk->fsname = mount->mnt_fsname;
auto b_total = buffer.f_bsize * buffer.f_blocks;
auto b_free = buffer.f_bsize * buffer.f_bfree;
auto b_used = b_total - b_free;
disk->bytes_total = b_total;
disk->bytes_free = b_free;
disk->bytes_used = b_used;
disk->percentage_free = math_util::percentage<unsigned long long, float>(b_free, 0, b_total);
disk->percentage_used = math_util::percentage<unsigned long long, float>(b_used, 0, b_total);
disk->percentage_free_s = string_util::floatval(disk->percentage_free, 2, m_fixed, m_bar.locale);
disk->percentage_used_s = string_util::floatval(disk->percentage_used, 2, m_fixed, m_bar.locale);
}
}
return true;
}
/**
* Generate the module output
*/
string fs_module::get_output() {
string output;
for (m_index = 0; m_index < m_disks.size(); ++m_index) {
if (!output.empty())
m_builder->space(m_spacing);
output += timer_module::get_output();
}
return output;
}
/**
* Select format based on fs state
*/
string fs_module::get_format() const {
return m_disks[m_index]->mounted ? FORMAT_MOUNTED : FORMAT_UNMOUNTED;
}
/**
* Output content using configured format tags
*/
bool fs_module::build(builder* builder, string tag) const {
auto& disk = m_disks[m_index];
if (tag == TAG_BAR_FREE) {
builder->node(m_barfree->output(disk->percentage_free));
} else if (tag == TAG_BAR_USED) {
builder->node(m_barused->output(disk->percentage_used));
} else if (tag == TAG_RAMP_CAPACITY) {
builder->node(m_rampcapacity->get_by_percentage(disk->percentage_free));
} else if (tag == TAG_LABEL_MOUNTED || tag == TAG_LABEL_UNMOUNTED) {
label_t label;
if (tag == TAG_LABEL_MOUNTED)
label = m_labelmounted->clone();
else
label = m_labelunmounted->clone();
label->reset_tokens();
label->replace_token("%mountpoint%", disk->mountpoint);
label->replace_token("%type%", disk->type);
label->replace_token("%fsname%", disk->fsname);
label->replace_token("%percentage_free%", disk->percentage_free_s + "%");
label->replace_token("%percentage_used%", disk->percentage_used_s + "%");
label->replace_token("%total%", string_util::filesize(disk->bytes_total, 1, m_fixed, m_bar.locale));
label->replace_token("%free%", string_util::filesize(disk->bytes_free, 2, m_fixed, m_bar.locale));
label->replace_token("%used%", string_util::filesize(disk->bytes_used, 2, m_fixed, m_bar.locale));
builder->node(label);
} else {
return false;
}
return true;
}
}
LEMONBUDDY_NS_END

View File

@ -157,12 +157,48 @@ namespace string_util {
return find_nth(haystack, found_pos + 1, needle, nth - 1);
}
/**
* Create a float value string
*/
string floatval(float value, int decimals, bool fixed, string locale) {
stringstream ss;
ss.precision(decimals);
if (!locale.empty())
ss.imbue(std::locale(locale.c_str()));
if (fixed)
ss << std::fixed;
ss << value;
return ss.str();
}
/**
* Format a filesize string
*/
string filesize(unsigned long long bytes, int decimals, bool fixed, string locale) {
vector<string> suffixes{"TB", "GB", "MB"};
string suffix{"KB"};
while ((bytes /= 1000) > 999) {
suffix = suffixes.back();
suffixes.pop_back();
}
stringstream ss;
ss.precision(decimals);
if (!locale.empty())
ss.imbue(std::locale(locale.c_str()));
if (fixed)
ss << std::fixed;
ss << bytes << " " << suffix;
return ss.str();
}
/**
* Get the resulting string from a ostream/
*
* Example usage:
* @code cpp
* string_util::from_stream(std::stringstream() << ...);
* string_util::from_stream(stringstream() << ...);
* @endcode
*/
string from_stream(const std::basic_ostream<char>& os) {