916 lines
25 KiB
C++
916 lines
25 KiB
C++
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include "platform.h"
|
|
#include "render/render.h"
|
|
#include "enginestate.h"
|
|
#include "lib/types.h"
|
|
#include "lib/math.h"
|
|
#include "gui/gui.h"
|
|
#include "debug/logger.h"
|
|
#include "debug/log_viewer.h"
|
|
#include "gui/layout.h"
|
|
#include "stb_image.h"
|
|
|
|
#include <time.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/sysinfo.h>
|
|
#include <NetworkManager.h>
|
|
#include <libvirt/libvirt.h>
|
|
#include <mntent.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/stat.h>
|
|
|
|
|
|
bool process_input(); // Returns true when the program needs to exit
|
|
void process_gui();
|
|
void app_init();
|
|
void app_deinit();
|
|
|
|
enum Section_Status
|
|
{
|
|
SECTION_STATUS_NONE,
|
|
SECTION_STATUS_OK,
|
|
SECTION_STATUS_WARNING,
|
|
SECTION_STATUS_ERROR
|
|
};
|
|
|
|
struct App_Data
|
|
{
|
|
f64 glib_iteration_t = -1;
|
|
f64 system_sample_t = -1;
|
|
f64 network_sample_t = -1;
|
|
f64 virt_sample_t = -1;
|
|
f64 fs_sample_t = -1;
|
|
|
|
f64 glib_iteration_delta = 0.1;
|
|
f64 system_sample_delta = 0.2;
|
|
f64 network_sample_delta = 0.2;
|
|
f64 virt_sample_delta = 0.5;
|
|
f64 fs_sample_delta = 1.0;
|
|
|
|
bool show_grid = false;
|
|
};
|
|
|
|
struct System_Info
|
|
{
|
|
char hostname[128];
|
|
char kernel[128];
|
|
time_t time;
|
|
char cached_date_string[128];
|
|
char cached_time_string[128];
|
|
|
|
s32 cpus_total;
|
|
s32 cpus_active;
|
|
|
|
f32 load[3];
|
|
s64 uptime;
|
|
|
|
u64 ram_total;
|
|
u64 ram_used;
|
|
u64 ram_available;
|
|
|
|
Section_Status status;
|
|
};
|
|
|
|
struct Network_Device
|
|
{
|
|
char name[128];
|
|
NMDeviceType type;
|
|
NMDeviceState state;
|
|
};
|
|
|
|
struct Network_Info
|
|
{
|
|
s32 device_count;
|
|
Network_Device devices[32];
|
|
|
|
Section_Status status;
|
|
};
|
|
|
|
struct Virt_Domain
|
|
{
|
|
char name[128];
|
|
s32 state;
|
|
|
|
u32 vcpus;
|
|
f32 cpu_usage;
|
|
u64 prev_cpuTime;
|
|
|
|
u64 ram_total;
|
|
u64 ram_used;
|
|
u64 ram_available;
|
|
};
|
|
|
|
struct Virt_Info
|
|
{
|
|
s32 domain_count;
|
|
Virt_Domain domains[32];
|
|
|
|
Section_Status status;
|
|
};
|
|
|
|
struct FS_Entry
|
|
{
|
|
char device[256];
|
|
char directory[256];
|
|
char type[128];
|
|
u64 bytes_total;
|
|
u64 bytes_available;
|
|
};
|
|
|
|
struct FS_Info
|
|
{
|
|
s32 fs_count;
|
|
FS_Entry fs[32];
|
|
|
|
Section_Status status;
|
|
};
|
|
|
|
int fs_cmp(const void* a, const void *b)
|
|
{
|
|
FS_Entry *fs_a = (FS_Entry*)a;
|
|
FS_Entry *fs_b = (FS_Entry*)b;
|
|
return strcmp(fs_a->directory, fs_b->directory);
|
|
}
|
|
|
|
System_Info system_info;
|
|
Network_Info network_info;
|
|
Virt_Info virt_info;
|
|
FS_Info fs_info;
|
|
App_Data app_data;
|
|
|
|
Gui_Style style_default;
|
|
void style_select(Gui_Context *ctx, Section_Status status)
|
|
{
|
|
|
|
switch(status)
|
|
{
|
|
case SECTION_STATUS_NONE: {
|
|
ctx->style.button_color = style_default.button_color;
|
|
ctx->style.button_color_hovered = style_default.button_color_hovered;
|
|
ctx->style.window_background_color = style_default.window_background_color;
|
|
ctx->style.window_background_color_inactive = style_default.window_background_color_inactive;
|
|
ctx->style.window_border_color = style_default.window_border_color;
|
|
ctx->style.window_border_color_inactive = style_default.window_border_color_inactive;
|
|
} break;
|
|
case SECTION_STATUS_OK: {
|
|
ctx->style.button_color_hovered = ctx->style.button_color = v4{0,1,0,1};
|
|
ctx->style.window_background_color_inactive = ctx->style.window_background_color = v4{0,.6,0,1};
|
|
ctx->style.window_border_color_inactive = ctx->style.window_border_color = v4{0,1,0,1};
|
|
} break;
|
|
case SECTION_STATUS_WARNING: {
|
|
ctx->style.button_color_hovered = ctx->style.button_color = v4{1,.8,0,1};
|
|
ctx->style.window_background_color_inactive = ctx->style.window_background_color = v4{.6,.5,0,1};
|
|
ctx->style.window_border_color_inactive = ctx->style.window_border_color = v4{1,.8,0,1};
|
|
} break;
|
|
case SECTION_STATUS_ERROR: {
|
|
ctx->style.button_color_hovered = ctx->style.button_color = v4{1,0,0,1};
|
|
ctx->style.window_background_color_inactive = ctx->style.window_background_color = v4{0.6,0,0,1};
|
|
ctx->style.window_border_color_inactive = ctx->style.window_border_color = v4{1,0,0,1};
|
|
} break;
|
|
}
|
|
}
|
|
|
|
// Icons
|
|
r_texture icon_disk;
|
|
r_texture icon_lan;
|
|
r_texture icon_ram;
|
|
r_texture icon_vpn;
|
|
r_texture icon_wifi;
|
|
r_texture icon_cpu;
|
|
|
|
|
|
u32 seconds_to_duration_text(char *text, f64 seconds, bool show_millis = false)
|
|
{
|
|
u32 written = 0;
|
|
u32 days = seconds / (24 * 3600);
|
|
seconds -= days * (24 * 3600);
|
|
u32 hours = seconds / 3600;
|
|
seconds -= hours * 3600;
|
|
u32 minutes = seconds / 60;
|
|
seconds -= minutes * 60;
|
|
|
|
if(days)
|
|
written += sprintf(text + written, "%s%dd", (written ? " " : ""), days);
|
|
if(days || hours)
|
|
written += sprintf(text + written, "%s%dh", (written ? " " : ""), hours);
|
|
if(days || hours || minutes)
|
|
written += sprintf(text + written, "%s%dm", (written ? " " : ""), minutes);
|
|
if(days || hours || minutes || seconds)
|
|
{
|
|
if(show_millis)
|
|
written += sprintf(text + written, "%s%.3lfs", (written ? " " : ""), seconds);
|
|
else
|
|
written += sprintf(text + written, "%s%.0lfs", (written ? " " : ""), seconds);
|
|
}
|
|
|
|
return written;
|
|
}
|
|
|
|
|
|
r_texture load_image_monochrome(const char *filename)
|
|
{
|
|
s32 width, height, channels;
|
|
stbi_set_flip_vertically_on_load(false);
|
|
u8 *data = stbi_load(filename, &width, &height, &channels, 1);
|
|
|
|
for(u64 i = 0; i < (width*height); i++) // Flipped colors
|
|
data[i] = 0xFF - data[i];
|
|
|
|
return r_texture_create(data, {width, height}, R_TEXTURE_ALPHA);
|
|
}
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
bool status;
|
|
|
|
LOG_INIT();
|
|
p_init(true);
|
|
|
|
LogViewer log_viewer = LogViewer::Init(&global_logger);
|
|
|
|
p_window_open();
|
|
p_window_name((char*)"Server Monitor");
|
|
|
|
r_init();
|
|
gui_init();
|
|
|
|
log_viewer.Print_New_Messages_On_Console();
|
|
|
|
|
|
app_init();
|
|
|
|
|
|
f64 start = p_time();
|
|
f64 prev_t = 0;
|
|
f32 max_render_time = 1.0 / 30.0;
|
|
f64 processing_start = 0;
|
|
while(1)
|
|
{
|
|
// Engine
|
|
engine.time = p_time() - start;
|
|
engine.delta_t = engine.time - prev_t;
|
|
//LOG(LOG_INFO, "Frame time: %.3lf ms FPS: %.2lf", 1000*delta_t, 1/delta_t);
|
|
r_time_update(engine.time);
|
|
|
|
// @Hack: @Performance: Reduced framerate to use less resources. Text rendering is highly inefficient right now. Maybe it will improve later and this will not be necessary anymore.
|
|
f64 processing_time = engine.time - processing_start;
|
|
if(processing_time < max_render_time)
|
|
p_wait(maximum(0, (max_render_time - 1.02*processing_time) * 1000));
|
|
processing_start = p_time() - start;
|
|
|
|
|
|
// Input
|
|
bool exit = process_input();
|
|
if(exit)
|
|
break;
|
|
|
|
// GUI
|
|
r_framebuffer_select(&r_render_state.framebuffer_SCREEN);
|
|
r_clear({0,0,0,0});
|
|
|
|
process_gui();
|
|
|
|
log_viewer.Print_New_Messages_On_Console();
|
|
r_swap();
|
|
prev_t = engine.time;
|
|
}
|
|
|
|
app_deinit();
|
|
gui_deinit();
|
|
p_window_close();
|
|
|
|
LOG_DEINIT();
|
|
p_deinit();
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
bool process_input()
|
|
{
|
|
// Events
|
|
Event event;
|
|
while(p_next_event(&event))
|
|
{
|
|
gui_handle_event(&event);
|
|
|
|
switch(event.type)
|
|
{
|
|
case EVENT_QUIT: {
|
|
return true;
|
|
};
|
|
case EVENT_MOUSE_MOVE:
|
|
{
|
|
|
|
} break;
|
|
case EVENT_KEY:
|
|
{
|
|
switch(event.key.key_code)
|
|
{
|
|
case KEY_G:
|
|
case KEY_F2: {
|
|
if(event.key.pressed)
|
|
app_data.show_grid = !app_data.show_grid;
|
|
} break;
|
|
default: {
|
|
// Other keys. Put default to suppress warnings
|
|
} break;
|
|
}
|
|
} break;
|
|
case EVENT_RESIZE:
|
|
{
|
|
//LOG(LOG_DEBUG, "New size: %u %u", signal.resize.width, signal.resize.height);
|
|
s32 width = event.resize.width;
|
|
s32 height = event.resize.height;
|
|
r_size_update(width, height);
|
|
|
|
global_gui_state.default_context.width = width;
|
|
global_gui_state.default_context.height = height;
|
|
|
|
engine.gui_scaling = minimum(width, height) * 0.03;
|
|
global_gui_state.default_context.style.font_size = engine.gui_scaling;
|
|
global_gui_state.default_context.style.button_radius = engine.gui_scaling / 10;
|
|
|
|
} break;
|
|
case EVENT_FOCUS:
|
|
{
|
|
} break;
|
|
case EVENT_UNFOCUS:
|
|
{
|
|
} break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
void system_info_gui(Gui_Layout_Grid *grid)
|
|
{
|
|
Gui_Context *ctx = &global_gui_state.default_context;
|
|
|
|
// No window info
|
|
// Clock and date
|
|
system_info.time = time(NULL);
|
|
struct tm *time_info = localtime(&system_info.time);
|
|
char clock[64], timezone[64];
|
|
strftime(clock, 128, "%H:%M:%S", time_info);
|
|
strftime(timezone, 128, "%Z", time_info);
|
|
|
|
f32 old_font_size = ctx->style.font_size;
|
|
ctx->style.font_size *= 3;
|
|
gui_text_aligned(grid->rect_at({2,1}, {2,2}), clock, GUI_ALIGN_CENTER);
|
|
ctx->style.font_size = old_font_size;
|
|
gui_text_aligned(grid->rect_at({2,3}, {2,1}), timezone, GUI_ALIGN_CENTER);
|
|
gui_text_aligned(grid->rect_at({2,0}, {2,1}), system_info.cached_date_string, GUI_ALIGN_CENTER);
|
|
|
|
// Host
|
|
gui_text_aligned(grid->rect_at({0,0}, {2,1}), system_info.hostname, GUI_ALIGN_LEFT);
|
|
// Uptime
|
|
char uptime[128] = "Uptime: "; seconds_to_duration_text(uptime + strlen("Uptime: "), system_info.uptime);
|
|
gui_text_aligned(grid->rect_at({4,0}, {2,1}), uptime, GUI_ALIGN_RIGHT);
|
|
|
|
|
|
Rect r;
|
|
Gui_Layout_Grid layout;
|
|
|
|
|
|
// Window reserved for future system info
|
|
r = grid->rect_at({0,1}, {2,3});
|
|
layout = gui_layout_grid_create_by_divisions(v2{0,0}, r.size, 2, 3, 0.2*engine.gui_scaling);
|
|
|
|
style_select(ctx, system_info.status);
|
|
gui_window_start(r, 0xabc23401);
|
|
style_select(ctx, SECTION_STATUS_NONE);
|
|
|
|
gui_window_end();
|
|
|
|
|
|
// Window with cpu n., load, ram
|
|
r = grid->rect_at({4,1}, {2,3});
|
|
layout = gui_layout_grid_create_by_divisions(v2{0,0}, r.size, 4, 3, 0.2*engine.gui_scaling);
|
|
|
|
style_select(ctx, system_info.status);
|
|
gui_window_start(r, 0xabcdef01);
|
|
style_select(ctx, SECTION_STATUS_NONE);
|
|
|
|
|
|
char load[128]; snprintf(load, 128, "Load: %.2f %.2f %.2f", system_info.load[0], system_info.load[1], system_info.load[2]);
|
|
|
|
char processors[128]; snprintf(processors, 128, "CPUs: %d/%d", system_info.cpus_active, system_info.cpus_total);
|
|
|
|
// slider for ram?
|
|
char ram[128]; snprintf(ram, 128, "%.2f/%.2f GiB", system_info.ram_used / (1024.0*1024.0*1024.0), system_info.ram_total / (1024.0*1024.0*1024.0));
|
|
f32 ram_value = system_info.ram_used;
|
|
|
|
gui_text_aligned(layout.cell(layout.max_cells_count.x), processors, GUI_ALIGN_LEFT);
|
|
|
|
v2 ram_text_size = gui_text_compute_size("RAM: ");
|
|
Rect ram_rect = layout.cell(layout.max_cells_count.x);
|
|
ram_rect.position.x += ram_text_size.x;
|
|
ram_rect.size.x -= ram_text_size.x;
|
|
gui_text_aligned(layout.rect(), "RAM: ", GUI_ALIGN_LEFT);
|
|
gui_slider_text(ram_rect, 0, system_info.ram_total, &ram_value, ram);
|
|
|
|
gui_text_aligned(layout.cell(layout.max_cells_count.x), load, GUI_ALIGN_LEFT);
|
|
|
|
gui_window_end();
|
|
}
|
|
|
|
void network_gui(Gui_Layout_Grid *grid)
|
|
{
|
|
Gui_Context *ctx = &global_gui_state.default_context;
|
|
|
|
Rect r = grid->rect_at({0,5}, {2,grid->max_cells_count.y - 5});
|
|
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, r.size, 2, grid->max_cells_count.y - 5, 0.2*engine.gui_scaling);
|
|
|
|
style_select(ctx, network_info.status);
|
|
gui_window_start(r, 0xabcdef02);
|
|
style_select(ctx, SECTION_STATUS_NONE);
|
|
|
|
gui_text_aligned(layout.cell(GUI_LAYOUT_MAX_CELLS), "Network", GUI_ALIGN_CENTER);
|
|
layout.row();
|
|
|
|
r = layout.rect_at({0,1}, layout.max_cells_count - v2s{0,1});
|
|
layout = gui_layout_grid_create_by_divisions(r.position, r.size, layout.max_cells_count.x, layout.max_cells_count.y/3, 0.2*engine.gui_scaling);
|
|
|
|
for(s32 i = 0; i < network_info.device_count; i++)
|
|
{
|
|
Network_Device *device = &network_info.devices[i];
|
|
|
|
if(device->type != NM_DEVICE_TYPE_ETHERNET && device->type != NM_DEVICE_TYPE_WIFI && device->type != NM_DEVICE_TYPE_WIREGUARD)
|
|
continue;
|
|
|
|
{
|
|
Section_Status status = SECTION_STATUS_OK;
|
|
if(device->state == NM_DEVICE_STATE_PREPARE ||
|
|
device->state == NM_DEVICE_STATE_CONFIG ||
|
|
device->state == NM_DEVICE_STATE_IP_CONFIG ||
|
|
device->state == NM_DEVICE_STATE_IP_CHECK)
|
|
{
|
|
// Bump status
|
|
if(status < SECTION_STATUS_WARNING)
|
|
status = SECTION_STATUS_WARNING;
|
|
}
|
|
else if(device->state != NM_DEVICE_STATE_ACTIVATED)
|
|
{
|
|
// Bump status
|
|
if(status < SECTION_STATUS_ERROR)
|
|
status = SECTION_STATUS_ERROR;
|
|
}
|
|
|
|
Rect r = layout.cell();
|
|
style_select(ctx, status);
|
|
gui_panel(r);
|
|
style_select(ctx, SECTION_STATUS_NONE);
|
|
|
|
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(r.position, r.size, 9, 4, 0.1*engine.gui_scaling);
|
|
|
|
const char *type_text = "";
|
|
r_texture *icon = &icon_lan;
|
|
switch(device->type)
|
|
{
|
|
case NM_DEVICE_TYPE_ETHERNET: {
|
|
icon = &icon_lan;
|
|
type_text = "Ethernet";
|
|
} break;
|
|
case NM_DEVICE_TYPE_WIFI: {
|
|
icon = &icon_wifi;
|
|
type_text = "WiFi";
|
|
} break;
|
|
case NM_DEVICE_TYPE_TUN: {
|
|
icon = &icon_vpn;
|
|
type_text = "TAP/TUN";
|
|
} break;
|
|
case NM_DEVICE_TYPE_BRIDGE: {
|
|
icon = &icon_lan;
|
|
type_text = "Bridge";
|
|
} break;
|
|
case NM_DEVICE_TYPE_VLAN: {
|
|
icon = &icon_lan;
|
|
type_text = "vlan";
|
|
} break;
|
|
case NM_DEVICE_TYPE_WIREGUARD: {
|
|
icon = &icon_vpn;
|
|
type_text = "VPN";
|
|
} break;
|
|
default: {
|
|
icon = &icon_lan;
|
|
type_text = "";
|
|
} break;
|
|
}
|
|
|
|
const char *state_text = "";
|
|
switch(device->state)
|
|
{
|
|
case NM_DEVICE_STATE_UNKNOWN: state_text = "UNKNOWN"; break;
|
|
case NM_DEVICE_STATE_UNMANAGED: state_text = "UNMANAGED"; break;
|
|
case NM_DEVICE_STATE_UNAVAILABLE: state_text = "UNAVAILABLE"; break;
|
|
case NM_DEVICE_STATE_DISCONNECTED: state_text = "DISCONNECTED"; break;
|
|
case NM_DEVICE_STATE_PREPARE: state_text = "PREPARE"; break;
|
|
case NM_DEVICE_STATE_CONFIG: state_text = "CONFIG"; break;
|
|
case NM_DEVICE_STATE_NEED_AUTH: state_text = "NEED_AUTH"; break;
|
|
case NM_DEVICE_STATE_IP_CONFIG: state_text = "IP_CONFIG"; break;
|
|
case NM_DEVICE_STATE_IP_CHECK: state_text = "IP_CHECK"; break;
|
|
case NM_DEVICE_STATE_SECONDARIES: state_text = "SECONDARIES"; break;
|
|
case NM_DEVICE_STATE_ACTIVATED: state_text = "ACTIVATED"; break;
|
|
case NM_DEVICE_STATE_DEACTIVATING: state_text = "DEACTIVATING"; break;
|
|
case NM_DEVICE_STATE_FAILED: state_text = "FAILED"; break;
|
|
default: gui_button(layout.cell(), ""); break;
|
|
}
|
|
|
|
Rect icon_r = layout.cell(1);
|
|
icon_r.position += (icon_r.size - v2{1,1}*minimum(icon_r.size.x, icon_r.size.y)) * .5;
|
|
icon_r.size = v2{1,1} * minimum(icon_r.size.x, icon_r.size.y);
|
|
gui_image(icon_r, icon);
|
|
gui_text(layout.cell(layout.max_cells_count.x - layout.cursor.x), device->name);
|
|
|
|
layout.cell(1);
|
|
gui_text(layout.cell(layout.max_cells_count.x - layout.cursor.x), type_text);
|
|
|
|
layout.row(2);
|
|
layout.cell(1);
|
|
gui_text(layout.cell(layout.max_cells_count.x - layout.cursor.x), state_text);
|
|
}
|
|
}
|
|
gui_window_end();
|
|
}
|
|
|
|
void vm_gui(Gui_Layout_Grid *grid)
|
|
{
|
|
Gui_Context *ctx = &global_gui_state.default_context;
|
|
|
|
Rect r = grid->rect_at({2,5}, {2,grid->max_cells_count.y - 5});
|
|
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, r.size, 2, grid->max_cells_count.y - 5, 0.2*engine.gui_scaling);
|
|
|
|
style_select(ctx, virt_info.status);
|
|
gui_window_start(r, 0xabcdef03);
|
|
style_select(ctx, SECTION_STATUS_NONE);
|
|
|
|
gui_text_aligned(layout.cell(GUI_LAYOUT_MAX_CELLS), "Virtual machines", GUI_ALIGN_CENTER);
|
|
layout.row();
|
|
|
|
r = layout.rect_at({0,1}, layout.max_cells_count - v2s{0,1});
|
|
layout = gui_layout_grid_create_by_divisions(r.position, r.size, layout.max_cells_count.x, layout.max_cells_count.y/4, 0.2*engine.gui_scaling);
|
|
|
|
|
|
|
|
for(s32 i = 0; i < virt_info.domain_count; i++)
|
|
{
|
|
Virt_Domain *domain = &virt_info.domains[i];
|
|
|
|
// Status
|
|
Section_Status status = SECTION_STATUS_NONE;
|
|
switch(domain->state)
|
|
{
|
|
case VIR_DOMAIN_NOSTATE:
|
|
status = SECTION_STATUS_NONE;
|
|
break;
|
|
case VIR_DOMAIN_RUNNING:
|
|
status = SECTION_STATUS_OK;
|
|
break;
|
|
case VIR_DOMAIN_BLOCKED:
|
|
case VIR_DOMAIN_SHUTOFF:
|
|
case VIR_DOMAIN_CRASHED:
|
|
status = SECTION_STATUS_ERROR;
|
|
break;
|
|
case VIR_DOMAIN_SHUTDOWN:
|
|
status = SECTION_STATUS_ERROR;
|
|
break;
|
|
case VIR_DOMAIN_PAUSED:
|
|
case VIR_DOMAIN_PMSUSPENDED:
|
|
status = SECTION_STATUS_WARNING;
|
|
break;
|
|
default:
|
|
status = SECTION_STATUS_NONE;
|
|
}
|
|
|
|
Rect r = layout.cell();
|
|
style_select(ctx, status);
|
|
gui_panel(r);
|
|
style_select(ctx, SECTION_STATUS_NONE);
|
|
|
|
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(r.position, r.size, 2, 4, 0.1*engine.gui_scaling);
|
|
|
|
gui_text(layout.cell(2), domain->name);
|
|
|
|
|
|
char ram[128]; snprintf(ram, 128, "%.2f GB", domain->ram_total / (1024.0*1024.0*1024.0));
|
|
gui_text(layout.cell(1), ram);
|
|
|
|
char cpu_count[128]; snprintf(cpu_count, 128, "%hd vCPU", domain->vcpus);
|
|
gui_text(layout.cell(1), cpu_count);
|
|
|
|
layout.row(2);
|
|
char cpu[128]; snprintf(cpu, 128, "CPU: %.2f%%", domain->cpu_usage * 100);
|
|
gui_text(layout.cell(2), cpu);
|
|
|
|
layout.row();
|
|
}
|
|
|
|
gui_window_end();
|
|
}
|
|
|
|
|
|
void fs_gui(Gui_Layout_Grid *grid)
|
|
{
|
|
Gui_Context *ctx = &global_gui_state.default_context;
|
|
|
|
Rect r = grid->rect_at({4,5}, {2,grid->max_cells_count.y - 5});
|
|
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, r.size, 2, grid->max_cells_count.y - 5, 0.2*engine.gui_scaling);
|
|
|
|
style_select(ctx, fs_info.status);
|
|
gui_window_start(r, 0xabcdef04);
|
|
style_select(ctx, SECTION_STATUS_NONE);
|
|
|
|
gui_text_aligned(layout.cell(GUI_LAYOUT_MAX_CELLS), "Storage", GUI_ALIGN_CENTER);
|
|
layout.row();
|
|
|
|
r = layout.rect_at({0,1}, layout.max_cells_count - v2s{0,1});
|
|
layout = gui_layout_grid_create_by_divisions(r.position, r.size, layout.max_cells_count.x, layout.max_cells_count.y/3, 0.2*engine.gui_scaling);
|
|
|
|
|
|
|
|
for(s32 i = 0; i < fs_info.fs_count; i++)
|
|
{
|
|
s32 container_size = 2;
|
|
if(strcmp(fs_info.fs[i].directory, "/boot") == 0 || strcmp(fs_info.fs[i].directory, "/boot/efi") == 0)
|
|
container_size = 1;
|
|
|
|
Rect r = layout.cell(container_size);
|
|
gui_panel(r);
|
|
|
|
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(r.position, r.size, 8, 4, 0.1*engine.gui_scaling);
|
|
|
|
Rect icon_r = layout.cell(1);
|
|
icon_r.position += (icon_r.size - v2{1,1}*minimum(icon_r.size.x, icon_r.size.y)) * .5;
|
|
icon_r.size = v2{1,1} * minimum(icon_r.size.x, icon_r.size.y);
|
|
|
|
gui_image(icon_r, &icon_disk);
|
|
gui_text(layout.cell(layout.max_cells_count.x - layout.cursor.x), fs_info.fs[i].directory);
|
|
|
|
|
|
layout.row(2);
|
|
char space[64]; snprintf(space, 64, "%.1f/%.1f GiB", (f32)(fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available) / (1024*1024*1024), (f32)fs_info.fs[i].bytes_total / (1024*1024*1024));
|
|
char percentage[64]; snprintf(percentage, 64, "%.1f%%", (f32)(fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available) / fs_info.fs[i].bytes_total * 100);
|
|
f32 space_value = fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available;
|
|
gui_slider_text(layout.cell(layout.max_cells_count.x), 0, fs_info.fs[i].bytes_total, &space_value, space);
|
|
gui_text_aligned(layout.cell(layout.max_cells_count.x), percentage, GUI_ALIGN_CENTER);
|
|
}
|
|
|
|
gui_window_end();
|
|
}
|
|
|
|
|
|
NMClient *nmclient;
|
|
virConnectPtr virt_connection;
|
|
|
|
void collect_static_data()
|
|
{
|
|
// Hostname
|
|
struct utsname host_info;
|
|
uname(&host_info);
|
|
|
|
system_info.hostname[0] = 0;
|
|
if(host_info.nodename[0])
|
|
strncpy(system_info.hostname, host_info.nodename, 128);
|
|
snprintf(system_info.kernel, 128, "%s %s", host_info.sysname, host_info.release);
|
|
}
|
|
|
|
void collect_new_data_if_needed()
|
|
{
|
|
if(engine.time - app_data.glib_iteration_t >= app_data.glib_iteration_delta)
|
|
{
|
|
g_main_context_iteration(NULL, false);
|
|
app_data.glib_iteration_t = engine.time;
|
|
}
|
|
|
|
|
|
if(engine.time - app_data.system_sample_t >= app_data.system_sample_delta)
|
|
{
|
|
// Clock
|
|
system_info.time = time(NULL);
|
|
struct tm *time_info = localtime(&system_info.time);
|
|
strftime(system_info.cached_date_string, 128, "%a %e %b %Y", time_info);
|
|
strftime(system_info.cached_time_string, 128, "%H:%M:%S %Z", time_info);
|
|
|
|
// CPU count
|
|
system_info.cpus_total = get_nprocs();
|
|
system_info.cpus_active = get_nprocs_conf();
|
|
|
|
// System info for later
|
|
struct sysinfo sys_info;
|
|
sysinfo(&sys_info);
|
|
// Uptime
|
|
system_info.uptime = sys_info.uptime;
|
|
// Load
|
|
system_info.load;
|
|
for(int i = 0; i < 3; i++)
|
|
system_info.load[i] = round((f32)sys_info.loads[i] / (1 << SI_LOAD_SHIFT) * 100) / 100;
|
|
|
|
// Memory
|
|
system_info.ram_total = sys_info.totalram * sys_info.mem_unit;
|
|
system_info.ram_available = (sys_info.freeram + sys_info.bufferram) * sys_info.mem_unit;
|
|
system_info.ram_used = sys_info.totalram * sys_info.mem_unit - system_info.ram_available;
|
|
|
|
|
|
// Status
|
|
system_info.status = SECTION_STATUS_NONE;
|
|
|
|
app_data.system_sample_t = engine.time;
|
|
}
|
|
|
|
|
|
if(engine.time - app_data.network_sample_t >= app_data.network_sample_delta)
|
|
{
|
|
const GPtrArray *devices = nm_client_get_devices(nmclient);
|
|
network_info.device_count = devices->len;
|
|
for(int i = 0; i < devices->len; i++)
|
|
{
|
|
NMDevice *device = (NMDevice*)devices->pdata[i];
|
|
|
|
strncpy(network_info.devices[i].name, nm_device_get_iface(device), 128);
|
|
network_info.devices[i].type = nm_device_get_device_type(device);
|
|
network_info.devices[i].state = nm_device_get_state(device);
|
|
}
|
|
|
|
// Update section status
|
|
network_info.status = SECTION_STATUS_OK;
|
|
for(int i = 0; i < network_info.device_count; i++)
|
|
{
|
|
Network_Device *device = &network_info.devices[i];
|
|
if(device->type == NM_DEVICE_TYPE_ETHERNET || device->type == NM_DEVICE_TYPE_WIREGUARD)
|
|
{
|
|
if(device->state == NM_DEVICE_STATE_PREPARE ||
|
|
device->state == NM_DEVICE_STATE_CONFIG ||
|
|
device->state == NM_DEVICE_STATE_IP_CONFIG ||
|
|
device->state == NM_DEVICE_STATE_IP_CHECK)
|
|
{
|
|
// Bump status
|
|
if(network_info.status < SECTION_STATUS_WARNING)
|
|
network_info.status = SECTION_STATUS_WARNING;
|
|
}
|
|
else if(device->state != NM_DEVICE_STATE_ACTIVATED)
|
|
{
|
|
// Bump status
|
|
if(network_info.status < SECTION_STATUS_ERROR)
|
|
network_info.status = SECTION_STATUS_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
app_data.network_sample_t = engine.time;
|
|
}
|
|
|
|
|
|
if(engine.time - app_data.virt_sample_t >= app_data.virt_sample_delta)
|
|
{
|
|
virDomainPtr *domains;
|
|
virt_info.domain_count = virConnectListAllDomains(virt_connection, &domains, 0);
|
|
|
|
for(int i = 0; i < virt_info.domain_count; i++)
|
|
{
|
|
Virt_Domain *dom = &virt_info.domains[i];
|
|
// Name
|
|
strncpy(dom->name, virDomainGetName(domains[i]), 128);
|
|
|
|
// State
|
|
virDomainInfo info;
|
|
int res = virDomainGetInfo(domains[i], &info);
|
|
dom->state = info.state;
|
|
|
|
// CPU
|
|
dom->vcpus = info.nrVirtCpu;
|
|
|
|
dom->cpu_usage = (info.cpuTime - dom->prev_cpuTime) / ((engine.time - app_data.virt_sample_t) * 1e9 * dom->vcpus);
|
|
dom->prev_cpuTime = info.cpuTime;
|
|
|
|
dom->ram_total = info.memory * 1024; // mem * 1000 or mem * 1024 ??
|
|
dom->ram_used = dom->ram_total;
|
|
dom->ram_available = 0;
|
|
}
|
|
|
|
p_free(domains);
|
|
|
|
// Update section status
|
|
virt_info.status = SECTION_STATUS_OK;
|
|
for(int i = 0; i < virt_info.domain_count; i++)
|
|
{
|
|
Virt_Domain *domain = &virt_info.domains[i];
|
|
if(domain->state == VIR_DOMAIN_PAUSED ||
|
|
domain->state == VIR_DOMAIN_PMSUSPENDED)
|
|
{
|
|
// Bump status
|
|
if(virt_info.status < SECTION_STATUS_WARNING)
|
|
virt_info.status = SECTION_STATUS_WARNING;
|
|
}
|
|
else if(domain->state != VIR_DOMAIN_RUNNING)
|
|
{
|
|
// Bump status
|
|
if(virt_info.status < SECTION_STATUS_ERROR)
|
|
virt_info.status = SECTION_STATUS_ERROR;
|
|
}
|
|
}
|
|
|
|
app_data.virt_sample_t = engine.time;
|
|
}
|
|
|
|
|
|
if(engine.time - app_data.fs_sample_t >= app_data.fs_sample_delta)
|
|
{
|
|
FILE *file = setmntent("/proc/mounts", "r");
|
|
|
|
fs_info.fs_count = 0;
|
|
for(struct mntent *ent = getmntent(file); ent != NULL; ent = getmntent(file))
|
|
{
|
|
strncpy(fs_info.fs[fs_info.fs_count].device, ent->mnt_fsname, 256);
|
|
strncpy(fs_info.fs[fs_info.fs_count].directory, ent->mnt_dir, 256);
|
|
strncpy(fs_info.fs[fs_info.fs_count].type, ent->mnt_type, 128);
|
|
fs_info.fs[fs_info.fs_count].bytes_total = 0;
|
|
fs_info.fs[fs_info.fs_count].bytes_available = 0;
|
|
|
|
struct stat s;
|
|
if(stat(ent->mnt_fsname, &s) != 0 || !S_ISBLK(s.st_mode))
|
|
continue;
|
|
|
|
struct statvfs svfs;
|
|
if(statvfs(ent->mnt_dir, &svfs) == 0)
|
|
{
|
|
fs_info.fs[fs_info.fs_count].bytes_total = svfs.f_frsize * svfs.f_blocks;
|
|
fs_info.fs[fs_info.fs_count].bytes_available = svfs.f_frsize * svfs.f_bavail;
|
|
}
|
|
fs_info.fs_count++;
|
|
}
|
|
|
|
endmntent(file);
|
|
|
|
// Sort by mount directory
|
|
qsort(fs_info.fs, fs_info.fs_count, sizeof(FS_Entry), fs_cmp);
|
|
|
|
fs_info.status = SECTION_STATUS_NONE;
|
|
}
|
|
}
|
|
|
|
void process_gui()
|
|
{
|
|
collect_new_data_if_needed();
|
|
|
|
gui_frame_begin(engine.time);
|
|
|
|
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{r_render_state.width,r_render_state.height} , 6, 22, 0.5*engine.gui_scaling);
|
|
|
|
|
|
|
|
system_info_gui(&layout);
|
|
network_gui(&layout);
|
|
vm_gui(&layout);
|
|
fs_gui(&layout);
|
|
|
|
gui_frame_end();
|
|
|
|
if(app_data.show_grid)
|
|
{
|
|
layout.cursor = {0,0};
|
|
while(layout.cursor.y < layout.max_cells_count.y)
|
|
{
|
|
r_2d_immediate_rectangle_outline(layout.cell(), {0,1,0,1});
|
|
char coords[32]; snprintf(coords, 32, "(%d, %d)", layout.cursor.x - 1, layout.cursor.y);
|
|
gui_text_aligned(layout.rect(), coords, GUI_ALIGN_CENTER); // abuse: used after gui_frame_end
|
|
}
|
|
}
|
|
}
|
|
|
|
void app_init()
|
|
{
|
|
app_data.system_sample_t = 0;
|
|
app_data.network_sample_t = 0;
|
|
app_data.virt_sample_t = 0;
|
|
app_data.glib_iteration_t = 0;
|
|
|
|
nmclient = nm_client_new(NULL, NULL);
|
|
virt_connection = virConnectOpenReadOnly("qemu:///system");
|
|
|
|
collect_static_data();
|
|
|
|
style_default = global_gui_state.default_context.style;
|
|
|
|
// Icons
|
|
icon_disk = load_image_monochrome("assets/icons/harddisk-symbolic.png");
|
|
icon_lan = load_image_monochrome("assets/icons/lan-symbolic.png");
|
|
icon_ram = load_image_monochrome("assets/icons/memory-symbolic.png");
|
|
icon_vpn = load_image_monochrome("assets/icons/network-vpn-symbolic.png");
|
|
icon_wifi = load_image_monochrome("assets/icons/network-wireless-symbolic.png");
|
|
icon_cpu = load_image_monochrome("assets/icons/processor-symbolic.png");
|
|
}
|
|
|
|
void app_deinit()
|
|
{
|
|
virConnectClose(virt_connection);
|
|
}
|