311 lines
8.6 KiB
C
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;
|
|
}
|