#include #include #include #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 #include #include #include #include #include #include #include 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_ratio = (f32)system_info.ram_used / system_info.ram_total; 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, &ram_ratio, 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_ratio = (f32)(fs_info.fs[i].bytes_total - fs_info.fs[i].bytes_available) / fs_info.fs[i].bytes_total; gui_slider_text(layout.cell(layout.max_cells_count.x), &space_ratio, 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); } // key and value are views into buffer. Copy them if you need them for a longer lifetime size_t meminfo_extract_next_entry(String *buffer, size_t start, String *key, String *value) { size_t separator = 0; size_t end = 0; for(int i = start; i < buffer->size; i++) { if(buffer->text[i] == ':') { separator = i; } else if(buffer->text[i] == '\n') { end = i; break; } } if(end == 0) // no newline found end = buffer->size; if(key) { key->text = buffer->text + start; key->size = separator - start; } if(value) { // TODO: Check if seaparator is actually found. This will return the wrong data if separator is 0 value->text = buffer->text + separator + 1; value->size = end - (separator + 1); } return end + 1 - start; } String String_trim(String s) { String result; result.text = s.text; result.size = s.size; // Trim front while(result.size > 0 && result.text[0] == ' ') { result.text++; result.size--; } // Trim back while(result.size > 0 && result.text[result.size-1] == ' ') { result.size--; } return result; } 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 { p_file file; if(p_file_init(&file, "/proc/meminfo", P_FILE_READONLY)) { u64 size = p_file_size(&file); if(size == 0) size = 1024; u8 backing_buff[size + 1]; Buffer buffer = { .size = size + 1, .data = backing_buff }; if(p_file_read(&file, &buffer, size + 1)) { String s = { .size = buffer.size, .text = buffer.data }; // Parse meminfo data size_t parsed = 0; while(1) { String key, value; size_t read = meminfo_extract_next_entry(&s, parsed, &key, &value); parsed += read; if(read == 0) break; key = String_trim(key); value = String_trim(value); if(strncmp((const char *)key.text, "MemTotal", key.size) == 0) { // LOG(LOG_INFO, "MemTotal: %.*s\n", value.size, value.text); u64 converted = 0; sscanf((const char *)value.text, "%lu", &converted); system_info.ram_total = converted * 1024; } else if(strncmp((const char *)key.text, "MemAvailable", key.size) == 0) { // LOG(LOG_INFO, "MemAvailable: %.*s\n", value.size, value.text); u64 converted = 0; sscanf((const char *)value.text, "%lu", &converted); system_info.ram_available = converted * 1024; } } } p_file_deinit(&file); } } // 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; system_info.ram_used = system_info.ram_total - 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); }