Files
ulandscape-sysinfo/ulandscape-sysinfo.c
2025-09-25 23:47:27 -05:00

311 lines
8.6 KiB
C

#include <ctype.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sysinfo.h>
#include <time.h>
#include <utmp.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <sys/vfs.h>
#include <unistd.h>
typedef struct {
char *label;
char *value;
} OutputEntry;
OutputEntry *output_entries = NULL;
int output_count = 0;
int max_label_length = 0;
void add_output_entry(char *label, char *value) {
OutputEntry *entry = malloc(sizeof(OutputEntry));
if (entry == NULL) {
perror("Error allocating memory for output entry");
return;
}
entry->label = malloc(strlen(label) + 1);
if (entry->label == NULL) {
perror("Error allocating memory for output entry label");
return;
}
strcpy(entry->label, label);
entry->value = malloc(strlen(value) + 1);
if (entry->value == NULL) {
perror("Error allocating memory for output entry value");
return;
}
strcpy(entry->value, value);
output_entries =
realloc(output_entries, sizeof(OutputEntry) * output_count);
if (output_entries == NULL) {
perror("Error allocating memory for output entries");
return;
}
output_entries =
realloc(output_entries, sizeof(OutputEntry) * (output_count + 1));
if (output_entries == NULL) {
perror("Error allocating memory for output entries");
return;
}
output_entries[output_count] = *entry;
output_count++;
if (strlen(label) > max_label_length) {
max_label_length = strlen(label);
}
}
typedef struct {
long long mem_total;
long long mem_available;
long long swap_total;
long long swap_free;
} BasicMemInfo;
// returns NULL on error
int get_basic_memory_info(BasicMemInfo *meminfo) {
FILE *fp;
char *line = NULL;
size_t len = 0;
ssize_t read;
char key[64];
uint64_t value;
char unit[16];
// get available ram and swap info via /proc/meminfo
fp = fopen("/proc/meminfo", "r");
if (fp == NULL) {
perror("Error opening /proc/meminfo");
return 1;
}
uint8_t count = 0;
while ((read = getline(&line, &len, fp)) != -1) {
if (sscanf(line, "%63[^:]: %ld %15s", key, &value, unit) == 3) {
char *p = key + strlen(key) - 1;
while (p >= key && *p == ' ') {
*p = '\0';
p--;
}
// convert all kilobytes to bytes
if (strcmp(key, "MemAvailable") == 0) {
meminfo->mem_available = value * 1024;
count++;
} else if (strcmp(key, "MemTotal") == 0) {
meminfo->mem_total = value * 1024;
count++;
} else if (strcmp(key, "SwapTotal") == 0) {
meminfo->swap_total = value * 1024;
count++;
} else if (strcmp(key, "SwapFree") == 0) {
meminfo->swap_free = value * 1024;
count++;
}
// Check if we found all values from /proc/meminfo
if (count == 4) {
break;
}
}
}
if (line)
free(line);
fclose(fp);
if (count != 4) {
printf("Error parsing /proc/meminfo\n");
return 1;
}
return 0;
}
int count_processes(void) {
int count = 0;
DIR *dir = opendir("/proc");
if (dir == NULL) {
perror("Error opening /proc");
return -1;
}
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_type == DT_DIR) {
bool is_pid = true;
for (int i = 0; ent->d_name[i] != '\0'; i++) {
// check if the name is a number (PID)
if (!isdigit(ent->d_name[i])) {
is_pid = false;
break;
}
}
count++;
}
}
closedir(dir);
return count;
}
int main(void) {
// TODO: should we cache this like the shell script does?
double loadavg[1] = {0};
getloadavg(loadavg, 1);
time_t curtime;
time(&curtime);
struct statfs sf;
if (statfs("/", &sf) != 0) {
perror("Error calling statfs");
return 1;
}
uint64_t total_bytes = (uint64_t)sf.f_blocks * sf.f_frsize;
uint64_t free_bytes = (uint64_t)sf.f_bfree * sf.f_frsize;
uint64_t available_bytes =
(uint64_t)sf.f_bavail * sf.f_frsize; // For non-root users
uint64_t used_bytes = total_bytes - free_bytes;
float formatted_total_space = total_bytes;
char *unit = "B";
if (formatted_total_space > (uint64_t)1 << 40) {
formatted_total_space /= (uint64_t)1 << 40;
unit = "TiB";
} else if (formatted_total_space > (uint64_t)1 << 30) {
formatted_total_space /= (uint64_t)1 << 30;
unit = "GiB";
} else if (formatted_total_space > (uint64_t)1 << 20) {
formatted_total_space /= (uint64_t)1 << 20;
unit = "MiB";
} else if (formatted_total_space > (uint64_t)1 << 10) {
formatted_total_space /= (uint64_t)1 << 10;
unit = "KiB";
}
BasicMemInfo meminfo = {0};
if (get_basic_memory_info(&meminfo) != 0) {
return 1;
}
int processes = count_processes();
// count logged in users
int users = 0;
struct utmp *ut;
setutent();
while ((ut = getutent()) != NULL) {
if (ut->ut_type == USER_PROCESS) {
users++;
}
}
endutent();
printf("\n System information as of %s\n", ctime(&curtime));
char value_buffer[128];
char label_buffer[128];
snprintf(value_buffer, 128, "%.2f", loadavg[0]);
add_output_entry("System load", value_buffer);
snprintf(value_buffer, 128, "%.2f%% of %.2f%s",
(float)((float)used_bytes * 100.0 / (float)total_bytes),
formatted_total_space, unit);
add_output_entry("Usage of /", value_buffer);
snprintf(value_buffer, 128, "%d%%",
(uint8_t)(((float)(meminfo.mem_total - meminfo.mem_available) /
(float)meminfo.mem_total) *
100.0));
add_output_entry("Memory usage", value_buffer);
snprintf(value_buffer, 128, "%d%%",
(uint8_t)(((float)(meminfo.swap_total - meminfo.swap_free) /
(float)meminfo.swap_total) *
100.0));
add_output_entry("Swap usage", value_buffer);
snprintf(value_buffer, 128, "%d", processes);
add_output_entry("Processes", value_buffer);
snprintf(value_buffer, 128, "%d", users);
add_output_entry("Users", value_buffer);
struct ifaddrs *ifaddr, *ifa;
int family, s;
char host[128];
getifaddrs(&ifaddr);
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) {
continue;
}
if (ifa->ifa_addr->sa_family == AF_INET ||
ifa->ifa_addr->sa_family == AF_INET6) {
family = ifa->ifa_addr->sa_family;
if (family == AF_INET || family == AF_INET6) {
s = getnameinfo(ifa->ifa_addr,
(family == AF_INET)
? sizeof(struct sockaddr_in)
: sizeof(struct sockaddr_in6),
host, 128, NULL, 0, NI_NUMERICHOST);
if (s != 0) {
printf("Error calling getnameinfo: %s\n", gai_strerror(s));
continue;
}
if (strcmp(ifa->ifa_name, "lo") == 0) {
continue;
}
if (strncmp(ifa->ifa_name, "docker", 6) == 0 ||
strncmp(ifa->ifa_name, "br-", 3) ==
0 || // Docker bridge interfaces
strncmp(ifa->ifa_name, "veth", 4) ==
0 || // Docker/VirtualBox interfaces
strncmp(ifa->ifa_name, "virbr", 5) == 0 ||
strcmp(ifa->ifa_name, "lo") ==
0) { // libvirt bridge interfaces
continue; // Skip container interfaces
}
snprintf(label_buffer, 128, "%s address for %s",
(family == AF_INET) ? "IPv4" : "IPv6", ifa->ifa_name);
snprintf(value_buffer, 128, "%s", host);
add_output_entry(label_buffer, value_buffer);
}
}
}
for (int i = 0; i < output_count; i++) {
printf(" %s:%*s%s\n", output_entries[i].label,
(int)((max_label_length + 1) - strlen(output_entries[i].label)),
"", output_entries[i].value);
}
printf("\n");
return 0;
}