#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(); 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; }; 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; }; struct Network_Device { char name[128]; NMDeviceType type; NMDeviceState state; }; struct Network_Info { s32 device_count; Network_Device devices[32]; }; 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]; }; 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]; }; System_Info system_info; Network_Info network_info; Virt_Info virt_info; FS_Info fs_info; App_Data app_data; 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; } void load_image(const char *filename, u8 **data, s32 *width, s32 *height, s32 *channels) { stbi_set_flip_vertically_on_load(false); *data = stbi_load(filename, width, height, channels, 4); } 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; 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); // Input bool exit = process_input(); if(exit) break; // GUI r_framebuffer_select(&r_render_state.framebuffer_SCREEN); r_clear({0,0,0,0}); gui_frame_begin(engine.time); process_gui(); gui_frame_end(); 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) { 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_window() { Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{40,6}*engine.gui_scaling, 3, 4, 0.2*engine.gui_scaling); gui_window_start(Rect{0.1*engine.gui_scaling, 0.1*engine.gui_scaling, layout.window_size.x, layout.window_size.y}, 0xabcdef01); // Host date and time gui_text_aligned(layout.cell(), system_info.hostname, GUI_ALIGN_LEFT); gui_text_aligned(layout.cell(), system_info.cached_time_string, GUI_ALIGN_CENTER); gui_text_aligned(layout.cell(), system_info.cached_date_string, GUI_ALIGN_RIGHT); // Load, Memory, Uptime struct sysinfo sys_info; sysinfo(&sys_info); char uptime[128] = "Uptime: "; seconds_to_duration_text(uptime + strlen("Uptime: "), sys_info.uptime); 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); char ram[128]; snprintf(ram, 128, "RAM: %.2f/%.2f GiB", system_info.ram_used / (1024.0*1024.0*1024.0), system_info.ram_total / (1024.0*1024.0*1024.0)); layout.row(2); gui_text_aligned(layout.cell(), processors, GUI_ALIGN_LEFT); gui_text_aligned(layout.cell(), load, GUI_ALIGN_CENTER); gui_text_aligned(layout.cell(), uptime, GUI_ALIGN_RIGHT); gui_text_aligned(layout.cell(), ram, GUI_ALIGN_LEFT); gui_window_end(); } void network_window() { Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{40,18}*engine.gui_scaling, 3, 12, 0.2*engine.gui_scaling); gui_window_start(Rect{0.1*engine.gui_scaling, 7*engine.gui_scaling, layout.window_size.x, layout.window_size.y}, 0xabcdef02); Gui_Context *ctx = &global_gui_state.default_context; for(s32 i = 0; i < network_info.device_count; i++) { Network_Device *device = &network_info.devices[i]; gui_button(layout.cell(), device->name); gui_id_stack_push(ctx, gui_id_from_pointer(ctx, device->name)); switch(device->type) { case NM_DEVICE_TYPE_ETHERNET: gui_button(layout.cell(), "ETHERNET"); break; case NM_DEVICE_TYPE_WIFI: gui_button(layout.cell(), "WIFI"); break; case NM_DEVICE_TYPE_TUN: gui_button(layout.cell(), "TAP or TUN"); break; case NM_DEVICE_TYPE_BRIDGE: gui_button(layout.cell(), "BRIDGE"); break; case NM_DEVICE_TYPE_VLAN: gui_button(layout.cell(), "VLAN"); break; case NM_DEVICE_TYPE_WIREGUARD: gui_button(layout.cell(), "WIREGUARD"); break; default: gui_button(layout.cell(), ""); break; } switch(device->state) { case NM_DEVICE_STATE_UNKNOWN: gui_button(layout.cell(), "UNKNOWN"); break; case NM_DEVICE_STATE_UNMANAGED: gui_button(layout.cell(), "UNMANAGED"); break; case NM_DEVICE_STATE_UNAVAILABLE: gui_button(layout.cell(), "UNAVAILABLE"); break; case NM_DEVICE_STATE_DISCONNECTED: gui_button(layout.cell(), "DISCONNECTED"); break; case NM_DEVICE_STATE_PREPARE: gui_button(layout.cell(), "PREPARE"); break; case NM_DEVICE_STATE_CONFIG: gui_button(layout.cell(), "CONFIG"); break; case NM_DEVICE_STATE_NEED_AUTH: gui_button(layout.cell(), "NEED_AUTH"); break; case NM_DEVICE_STATE_IP_CONFIG: gui_button(layout.cell(), "IP_CONFIG"); break; case NM_DEVICE_STATE_IP_CHECK: gui_button(layout.cell(), "IP_CHECK"); break; case NM_DEVICE_STATE_SECONDARIES: gui_button(layout.cell(), "SECONDARIES"); break; case NM_DEVICE_STATE_ACTIVATED: gui_button(layout.cell(), "ACTIVATED"); break; case NM_DEVICE_STATE_DEACTIVATING: gui_button(layout.cell(), "DEACTIVATING"); break; case NM_DEVICE_STATE_FAILED: gui_button(layout.cell(), "FAILED"); break; default: gui_button(layout.cell(), ""); break; } gui_id_stack_pop(ctx); layout.row(); } gui_window_end(); } void vm_window() { Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{40,6}*engine.gui_scaling, 4, 4, 0.2*engine.gui_scaling); gui_window_start(Rect{0.1*engine.gui_scaling, 26*engine.gui_scaling, layout.window_size.x, layout.window_size.y}, 0xabcdef03); Gui_Context *ctx = &global_gui_state.default_context; Gui_Style old_style = ctx->style; for(s32 i = 0; i < virt_info.domain_count; i++) { Virt_Domain *domain = &virt_info.domains[i]; // Name and state switch(domain->state) { case VIR_DOMAIN_NOSTATE: ctx->style.button_color = old_style.button_color; break; case VIR_DOMAIN_RUNNING: ctx->style.button_color = v4{0,1,0,1}; break; case VIR_DOMAIN_BLOCKED: case VIR_DOMAIN_SHUTOFF: case VIR_DOMAIN_CRASHED: ctx->style.button_color = v4{1,0,0,1}; break; case VIR_DOMAIN_SHUTDOWN: ctx->style.button_color = v4{0,.6,0,1}; break; case VIR_DOMAIN_PAUSED: case VIR_DOMAIN_PMSUSPENDED: ctx->style.button_color = v4{.7,.7,0,1}; break; default: ctx->style.button_color = old_style.button_color; } gui_button(layout.cell(), domain->name); gui_id_stack_push(ctx, gui_id_from_pointer(ctx, domain->name)); ctx->style = old_style; // CPU usage char cpu[128]; snprintf(cpu, 128, "%.2f%%", domain->cpu_usage * 100); gui_button(layout.cell(), cpu); char ram[128]; snprintf(ram, 128, "%.2f GB", domain->ram_total / (1024.0*1024.0*1024.0)); gui_button(layout.cell(), ram); char cpu_count[128]; snprintf(cpu_count, 128, "%hd vCPU", domain->vcpus); gui_button(layout.cell(), cpu_count); gui_id_stack_pop(ctx); layout.row(); } ctx->style = old_style; gui_window_end(); } void fs_window() { Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{16,30}*engine.gui_scaling, 2, 20, 0.2*engine.gui_scaling); gui_window_start(Rect{41*engine.gui_scaling, 0.1*engine.gui_scaling, layout.window_size.x, layout.window_size.y}, 0xabcdef04); Gui_Context *ctx = &global_gui_state.default_context; for(s32 i = 0; i < fs_info.fs_count; i++) { gui_id_stack_push(ctx, gui_id_from_pointer(ctx, fs_info.fs[i].device)); gui_button(layout.cell(2), fs_info.fs[i].device); gui_button(layout.cell(1), fs_info.fs[i].directory); gui_button(layout.cell(1), fs_info.fs[i].type); 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); gui_button(layout.cell(1), space); gui_button(layout.cell(1), percentage); gui_id_stack_pop(ctx); } 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; 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); } 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); 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); } } void process_gui() { collect_new_data_if_needed(); system_info_window(); network_window(); vm_window(); fs_window(); } 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(); } void app_deinit() { virConnectClose(virt_connection); }