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

Port memory printing on OpenBSD #493

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ On debian-based systems, the following line will install all requirements:
apt-get install autoconf libconfuse-dev libyajl-dev libasound2-dev libiw-dev asciidoc libpulse-dev libnl-genl-3-dev meson
```

On OpenBSD, the following line will install the requirements:
```bash
pkg_add autoconf libconfuse libyajl asciidoc pluseaudio meson
```

## Upstream

i3status is developed at https://github.com/i3/i3status
Expand Down
7 changes: 4 additions & 3 deletions man/i3status.man
Original file line number Diff line number Diff line change
Expand Up @@ -467,8 +467,8 @@ starting from %cpu0. This feature is currently not supported in FreeBSD.

=== Memory

Gets the memory usage from system on a Linux system from +/proc/meminfo+. Other
systems are currently not supported.
Gets the memory usage from system on Linux from +/proc/meminfo+, on OpenBSD
from sysctl. Other systems are currently not supported.

As format placeholders, +total+, +used+, +free+, +available+ and +shared+ are
available. These will print human readable values. It's also possible to prefix
Expand All @@ -495,7 +495,8 @@ decimals can be configured via the +decimals+ option. If no such option is
given the converted format placeholder will have one decimal.

As Linux' meminfo doesn't expose the overall memory in use, there are multiple
methods to distinguish the actually used memory.
methods to distinguish the actually used memory. The methods are availabe on
OpenBSD as well.

*Example memory_used_method*: +memavailable+ ("total memory" - "MemAvailable", matches +free+ command)

Expand Down
4 changes: 1 addition & 3 deletions src/print_cpu_usage.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,12 @@ struct cpu_usage {
long system;
long idle;
long total;
int spin;
#else
int user;
int nice;
int system;
int idle;
#if defined(__OpenBSD__)
int spin;
#endif
int total;
#endif
};
Expand Down
204 changes: 133 additions & 71 deletions src/print_mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,31 @@
#include <stdlib.h>
#include <yajl/yajl_gen.h>
#include <yajl/yajl_version.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include "i3status.h"

#define MAX_DECIMALS 4
#define STRING_SIZE ((sizeof "1023. TiB") + MAX_DECIMALS)

#define BINARY_BASE 1024UL

#if defined(__linux__)
static const char *const iec_symbols[] = {"B", "KiB", "MiB", "GiB", "TiB"};
#define MAX_EXPONENT ((sizeof iec_symbols / sizeof *iec_symbols) - 1)
#endif

#if defined(__linux__)
struct print_mem_info {
unsigned long ram_total;
unsigned long ram_free;
unsigned long ram_used;
unsigned long ram_available;
unsigned long ram_buffers;
unsigned long ram_cached;
unsigned long ram_shared;
char *output_color;
};

/*
* Prints the given amount of bytes in a human readable manner.
*
Expand All @@ -40,9 +52,11 @@ static int print_bytes_human(char *outwalk, unsigned long bytes, const char *uni
static int print_percentage(char *outwalk, float percent) {
return snprintf(outwalk, STRING_SIZE, "%.1f%s", percent, pct_mark);
}
#endif

#if defined(__linux__)
static unsigned long page2byte (unsigned pageCnt) {
return pageCnt * getpagesize ();
}

/*
* Convert a string to its absolute representation based on the total
* memory of `mem_total`.
Expand Down Expand Up @@ -84,40 +98,57 @@ static unsigned long memory_absolute(const char *mem_amount, const unsigned long

return amount;
}
#endif

void print_memory(memory_ctx_t *ctx) {
char *outwalk = ctx->buf;
static void handle_used_method(struct print_mem_info *minfo, memory_ctx_t *ctx) {
if (BEGINS_WITH(ctx->memory_used_method, "memavailable")) {
minfo->ram_used = minfo->ram_total - minfo->ram_available;
} else if (BEGINS_WITH(ctx->memory_used_method, "classical")) {
minfo->ram_used = minfo->ram_total - minfo->ram_free - minfo->ram_buffers - minfo->ram_cached;
} else {
die("Unexpected value: memory_used_method = %s", ctx->memory_used_method);
}
}

#if defined(__linux__)
const char *selected_format = ctx->format;
const char *output_color = NULL;
static void handle_threshold(struct print_mem_info *minfo, memory_ctx_t *ctx) {
if (ctx->threshold_degraded) {
const unsigned long threshold = memory_absolute(ctx->threshold_degraded, minfo->ram_total);
if (minfo->ram_available < threshold) {
minfo->output_color = "color_degraded";
}
}

if (ctx->threshold_critical) {
const unsigned long threshold = memory_absolute(ctx->threshold_critical, minfo->ram_total);
if (minfo->ram_available < threshold) {
minfo->output_color = "color_bad";
}
}
}

static void get_memory_info(struct print_mem_info *minfo, memory_ctx_t *ctx) {
char *outwalk = ctx->buf;
#if defined(__linux__)
int unread_fields = 6;
unsigned long ram_total;
unsigned long ram_free;
unsigned long ram_available;
unsigned long ram_buffers;
unsigned long ram_cached;
unsigned long ram_shared;

minfo->output_color = NULL

FILE *file = fopen("/proc/meminfo", "r");
if (!file) {
goto error;
}
for (char line[128]; fgets(line, sizeof line, file);) {
if (BEGINS_WITH(line, "MemTotal:")) {
ram_total = strtoul(line + strlen("MemTotal:"), NULL, 10);
minfo->ram_total = strtoul(line + strlen("MemTotal:"), NULL, 10);
} else if (BEGINS_WITH(line, "MemFree:")) {
ram_free = strtoul(line + strlen("MemFree:"), NULL, 10);
minfo->ram_free = strtoul(line + strlen("MemFree:"), NULL, 10);
} else if (BEGINS_WITH(line, "MemAvailable:")) {
ram_available = strtoul(line + strlen("MemAvailable:"), NULL, 10);
minfo->ram_available = strtoul(line + strlen("MemAvailable:"), NULL, 10);
} else if (BEGINS_WITH(line, "Buffers:")) {
ram_buffers = strtoul(line + strlen("Buffers:"), NULL, 10);
minfo->ram_buffers = strtoul(line + strlen("Buffers:"), NULL, 10);
} else if (BEGINS_WITH(line, "Cached:")) {
ram_cached = strtoul(line + strlen("Cached:"), NULL, 10);
minfo->ram_cached = strtoul(line + strlen("Cached:"), NULL, 10);
} else if (BEGINS_WITH(line, "Shmem:")) {
ram_shared = strtoul(line + strlen("Shmem:"), NULL, 10);
minfo->ram_shared = strtoul(line + strlen("Shmem:"), NULL, 10);
} else {
continue;
}
Expand All @@ -132,43 +163,69 @@ void print_memory(memory_ctx_t *ctx) {
}

// Values are in kB, convert them to B.
ram_total *= 1024UL;
ram_free *= 1024UL;
ram_available *= 1024UL;
ram_buffers *= 1024UL;
ram_cached *= 1024UL;
ram_shared *= 1024UL;
minfo->ram_total *= 1024UL;
minfo->ram_free *= 1024UL;
minfo->ram_available *= 1024UL;
minfo->ram_buffers *= 1024UL;
minfo->ram_cached *= 1024UL;
minfo->ram_shared *= 1024UL;

unsigned long ram_used;
if (BEGINS_WITH(ctx->memory_used_method, "memavailable")) {
ram_used = ram_total - ram_available;
} else if (BEGINS_WITH(ctx->memory_used_method, "classical")) {
ram_used = ram_total - ram_free - ram_buffers - ram_cached;
} else {
die("Unexpected value: memory_used_method = %s", ctx->memory_used_method);
}
handle_used_method(minfo, ctx);
handle_threshold(minfo, ctx);
error:
OUTPUT_FULL_TEXT("can't read memory");
fputs("i3status: Cannot read system memory using /proc/meminfo\n", stderr);

if (ctx->threshold_degraded) {
const unsigned long threshold = memory_absolute(ctx->threshold_degraded, ram_total);
if (ram_available < threshold) {
output_color = "color_degraded";
}
}
#elif defined(__OpenBSD__)
int uvm_mib [] = { CTL_VM, VM_UVMEXP };
int bcstats_mib[] = {CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT};
int malloc_mib[] = {CTL_KERN, KERN_MALLOCSTATS, KERN_MALLOC_KMEMSTATS, M_SHM};
struct uvmexp our_uvmexp;
struct bcachestats bcstats;
struct kmemstats kmemstats;
size_t size = sizeof (our_uvmexp);

if (ctx->threshold_critical) {
const unsigned long threshold = memory_absolute(ctx->threshold_critical, ram_total);
if (ram_available < threshold) {
output_color = "color_bad";
}
if (sysctl(uvm_mib, 2, &our_uvmexp, &size, NULL, 0) < 0) {
OUTPUT_FULL_TEXT("");
fputs("i3status: cannot get memory info (uvm)!\n", stderr);
return;
}

if (output_color) {
START_COLOR(output_color);
size = sizeof(bcstats);
if (sysctl(bcstats_mib, 3, &bcstats, &size, NULL, 0) == -1) {
OUTPUT_FULL_TEXT("");
fputs("i3status: cannot get memory info (bcachestats)!\n", stderr);
return;
}

if (ctx->format_degraded)
selected_format = ctx->format_degraded;
}
size = sizeof(kmemstats);
if (sysctl(malloc_mib, 4, &kmemstats, &size, NULL, 0) == -1) {
OUTPUT_FULL_TEXT("");
fputs("i3status: cannot get memory info (malloc)!\n", stderr);
return;
}

minfo->ram_total = page2byte(our_uvmexp.npages);
minfo->ram_free = page2byte(our_uvmexp.free + our_uvmexp.inactive);
minfo->ram_used = page2byte(our_uvmexp.npages - our_uvmexp.free - our_uvmexp.inactive);
minfo->ram_available = minfo->ram_free;
minfo->ram_buffers = page2byte(bcstats.numbufpages);
minfo->ram_cached = 0;
minfo->ram_shared = kmemstats.ks_memuse;
minfo->output_color = NULL;

handle_used_method(minfo, ctx);
handle_threshold(minfo, ctx);
#else
// For FreeBSD implementation see: https://bal0n.es/git/slstatus/file/components/ram.c.html
OUTPUT_FULL_TEXT("");
fputs("i3status: Memory status information is not supported on this system\n", stderr);
#endif
}

static void print_formatted_memory(struct print_mem_info *minfo, memory_ctx_t *ctx) {
char *outwalk = ctx->buf;
const char *selected_format = ctx->format;
char string_ram_total[STRING_SIZE];
char string_ram_used[STRING_SIZE];
char string_ram_free[STRING_SIZE];
Expand All @@ -179,15 +236,22 @@ void print_memory(memory_ctx_t *ctx) {
char string_percentage_used[STRING_SIZE];
char string_percentage_shared[STRING_SIZE];

print_bytes_human(string_ram_total, ram_total, ctx->unit, ctx->decimals);
print_bytes_human(string_ram_used, ram_used, ctx->unit, ctx->decimals);
print_bytes_human(string_ram_free, ram_free, ctx->unit, ctx->decimals);
print_bytes_human(string_ram_available, ram_available, ctx->unit, ctx->decimals);
print_bytes_human(string_ram_shared, ram_shared, ctx->unit, ctx->decimals);
print_percentage(string_percentage_free, 100.0 * ram_free / ram_total);
print_percentage(string_percentage_available, 100.0 * ram_available / ram_total);
print_percentage(string_percentage_used, 100.0 * ram_used / ram_total);
print_percentage(string_percentage_shared, 100.0 * ram_shared / ram_total);
if (minfo->output_color) {
START_COLOR(minfo->output_color);

if (ctx->format_degraded)
selected_format = ctx->format_degraded;
}

print_bytes_human(string_ram_total, minfo->ram_total, ctx->unit, ctx->decimals);
print_bytes_human(string_ram_used, minfo->ram_used, ctx->unit, ctx->decimals);
print_bytes_human(string_ram_free, minfo->ram_free, ctx->unit, ctx->decimals);
print_bytes_human(string_ram_available, minfo->ram_available, ctx->unit, ctx->decimals);
print_bytes_human(string_ram_shared, minfo->ram_shared, ctx->unit, ctx->decimals);
print_percentage(string_percentage_free, 100.0 * minfo->ram_free / minfo->ram_total);
print_percentage(string_percentage_available, 100.0 * minfo->ram_available / minfo->ram_total);
print_percentage(string_percentage_used, 100.0 * minfo->ram_used / minfo->ram_total);
print_percentage(string_percentage_shared, 100.0 * minfo->ram_shared / minfo->ram_total);

placeholder_t placeholders[] = {
{.name = "%total", .value = string_ram_total},
Expand All @@ -205,17 +269,15 @@ void print_memory(memory_ctx_t *ctx) {
OUTPUT_FORMATTED;
free(formatted);

if (output_color)
if (minfo->output_color)
END_COLOR;

OUTPUT_FULL_TEXT(ctx->buf);
}

return;
error:
OUTPUT_FULL_TEXT("can't read memory");
fputs("i3status: Cannot read system memory using /proc/meminfo\n", stderr);
#else
OUTPUT_FULL_TEXT("");
fputs("i3status: Memory status information is not supported on this system\n", stderr);
#endif
void print_memory(memory_ctx_t *ctx) {
struct print_mem_info minfo;

get_memory_info(&minfo, ctx);
print_formatted_memory(&minfo, ctx);
}