2023-09-26 19:40:16 +02:00
# 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>
2023-09-27 01:42:49 +02:00
# include <libvirt/libvirt.h>
2023-09-27 23:56:31 +02:00
# include <mntent.h>
# include <sys/statvfs.h>
# include <sys/stat.h>
2023-09-26 19:40:16 +02:00
bool process_input ( ) ; // Returns true when the program needs to exit
void process_gui ( ) ;
void app_init ( ) ;
void app_deinit ( ) ;
2023-09-30 02:39:12 +02:00
enum Section_Status
{
SECTION_STATUS_NONE ,
SECTION_STATUS_OK ,
SECTION_STATUS_WARNING ,
SECTION_STATUS_ERROR
} ;
2023-09-27 01:42:49 +02:00
struct App_Data
{
2023-09-27 23:56:31 +02:00
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 ;
2023-09-27 01:42:49 +02:00
f64 glib_iteration_delta = 0.1 ;
f64 system_sample_delta = 0.2 ;
f64 network_sample_delta = 0.2 ;
f64 virt_sample_delta = 0.5 ;
2023-09-27 23:56:31 +02:00
f64 fs_sample_delta = 1.0 ;
2023-09-28 23:30:12 +02:00
bool show_grid = false ;
2023-09-27 01:42:49 +02:00
} ;
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 ;
2023-09-30 02:39:12 +02:00
Section_Status status ;
2023-09-27 01:42:49 +02:00
} ;
struct Network_Device
{
char name [ 128 ] ;
NMDeviceType type ;
NMDeviceState state ;
} ;
struct Network_Info
{
s32 device_count ;
Network_Device devices [ 32 ] ;
2023-09-30 02:39:12 +02:00
Section_Status status ;
2023-09-27 01:42:49 +02:00
} ;
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 ] ;
2023-09-30 02:39:12 +02:00
Section_Status status ;
2023-09-27 01:42:49 +02:00
} ;
2023-09-27 23:56:31 +02:00
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 ] ;
2023-09-30 02:39:12 +02:00
Section_Status status ;
2023-09-27 23:56:31 +02:00
} ;
2023-10-01 22:53:08 +02:00
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 ) ;
}
2023-09-27 01:42:49 +02:00
System_Info system_info ;
Network_Info network_info ;
Virt_Info virt_info ;
2023-09-27 23:56:31 +02:00
FS_Info fs_info ;
2023-09-27 01:42:49 +02:00
App_Data app_data ;
2023-09-30 02:39:12 +02:00
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 ;
2023-09-27 01:42:49 +02:00
2023-09-26 19:40:16 +02:00
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 ;
}
2023-09-30 02:39:12 +02:00
r_texture load_image_monochrome ( const char * filename )
2023-09-26 19:40:16 +02:00
{
2023-09-30 02:39:12 +02:00
s32 width , height , channels ;
2023-09-26 19:40:16 +02:00
stbi_set_flip_vertically_on_load ( false ) ;
2023-09-30 02:39:12 +02:00
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 ) ;
2023-09-26 19:40:16 +02:00
}
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 ;
2023-09-28 10:55:40 +02:00
f32 max_render_time = 1.0 / 30.0 ;
f64 processing_start = 0 ;
2023-09-26 19:40:16 +02:00
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 ) ;
2023-09-28 10:55:40 +02:00
// @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 ;
2023-09-26 19:40:16 +02:00
// 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 )
{
2023-09-28 23:30:12 +02:00
case KEY_G :
case KEY_F2 : {
if ( event . key . pressed )
app_data . show_grid = ! app_data . show_grid ;
} break ;
2023-09-26 19:40:16 +02:00
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 ;
}
2023-09-28 23:30:12 +02:00
void system_info_gui ( Gui_Layout_Grid * grid )
2023-09-26 19:40:16 +02:00
{
2023-09-30 02:39:12 +02:00
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 ) ;
2023-09-26 19:40:16 +02:00
2023-09-30 02:39:12 +02:00
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 ) ;
2023-09-30 03:00:50 +02:00
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 ( ) ;
2023-09-30 02:39:12 +02:00
// Window with cpu n., load, ram
2023-09-30 03:00:50 +02:00
r = grid - > rect_at ( { 4 , 1 } , { 2 , 3 } ) ;
2023-09-30 18:28:35 +02:00
layout = gui_layout_grid_create_by_divisions ( v2 { 0 , 0 } , r . size , 4 , 3 , 0.2 * engine . gui_scaling ) ;
2023-09-30 02:39:12 +02:00
style_select ( ctx , system_info . status ) ;
gui_window_start ( r , 0xabcdef01 ) ;
style_select ( ctx , SECTION_STATUS_NONE ) ;
2023-09-26 19:40:16 +02:00
2023-09-27 01:42:49 +02:00
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 ) ;
2023-09-30 02:39:12 +02:00
// slider for ram?
2023-09-30 18:28:35 +02:00
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 ) ) ;
2024-09-27 19:46:39 +02:00
f32 ram_ratio = ( f32 ) system_info . ram_used / system_info . ram_total ;
2023-09-30 18:28:35 +02:00
gui_text_aligned ( layout . cell ( layout . max_cells_count . x ) , processors , GUI_ALIGN_LEFT ) ;
2023-09-26 19:40:16 +02:00
2023-09-30 18:28:35 +02:00
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 ) ;
2024-09-27 19:46:39 +02:00
gui_slider_text ( ram_rect , & ram_ratio , ram ) ;
2023-09-30 18:28:35 +02:00
gui_text_aligned ( layout . cell ( layout . max_cells_count . x ) , load , GUI_ALIGN_LEFT ) ;
2023-09-26 19:40:16 +02:00
gui_window_end ( ) ;
}
2023-09-28 23:30:12 +02:00
void network_gui ( Gui_Layout_Grid * grid )
2023-09-26 19:40:16 +02:00
{
2023-09-30 02:39:12 +02:00
Gui_Context * ctx = & global_gui_state . default_context ;
2023-09-30 03:00:50 +02:00
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 ) ;
2023-09-30 02:39:12 +02:00
style_select ( ctx , network_info . status ) ;
2023-09-28 23:30:12 +02:00
gui_window_start ( r , 0xabcdef02 ) ;
2023-09-30 02:39:12 +02:00
style_select ( ctx , SECTION_STATUS_NONE ) ;
2023-09-26 19:40:16 +02:00
2023-09-30 02:39:12 +02:00
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 } ) ;
2023-09-30 03:00:50 +02:00
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 ) ;
2023-09-27 01:42:49 +02:00
for ( s32 i = 0 ; i < network_info . device_count ; i + + )
2023-09-26 19:40:16 +02:00
{
2023-09-27 01:42:49 +02:00
Network_Device * device = & network_info . devices [ i ] ;
2023-09-26 19:40:16 +02:00
2023-09-30 02:39:12 +02:00
if ( device - > type ! = NM_DEVICE_TYPE_ETHERNET & & device - > type ! = NM_DEVICE_TYPE_WIFI & & device - > type ! = NM_DEVICE_TYPE_WIREGUARD )
continue ;
2023-09-26 19:40:16 +02:00
{
2023-09-30 02:39:12 +02:00
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 ;
}
2023-09-26 19:40:16 +02:00
2023-09-30 02:39:12 +02:00
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 ) ;
2023-09-26 19:40:16 +02:00
}
}
gui_window_end ( ) ;
}
2023-09-28 23:30:12 +02:00
void vm_gui ( Gui_Layout_Grid * grid )
2023-09-26 19:40:16 +02:00
{
2023-09-30 02:39:12 +02:00
Gui_Context * ctx = & global_gui_state . default_context ;
2023-09-30 03:00:50 +02:00
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 ) ;
2023-09-30 02:39:12 +02:00
style_select ( ctx , virt_info . status ) ;
2023-09-28 23:30:12 +02:00
gui_window_start ( r , 0xabcdef03 ) ;
2023-09-30 02:39:12 +02:00
style_select ( ctx , SECTION_STATUS_NONE ) ;
2023-09-26 19:40:16 +02:00
2023-09-30 03:00:50 +02:00
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 ) ;
2023-09-27 01:42:49 +02:00
for ( s32 i = 0 ; i < virt_info . domain_count ; i + + )
{
Virt_Domain * domain = & virt_info . domains [ i ] ;
2023-09-30 03:00:50 +02:00
// Status
Section_Status status = SECTION_STATUS_NONE ;
2023-09-27 01:42:49 +02:00
switch ( domain - > state )
{
case VIR_DOMAIN_NOSTATE :
2023-09-30 03:00:50 +02:00
status = SECTION_STATUS_NONE ;
2023-09-27 01:42:49 +02:00
break ;
case VIR_DOMAIN_RUNNING :
2023-09-30 03:00:50 +02:00
status = SECTION_STATUS_OK ;
2023-09-27 01:42:49 +02:00
break ;
case VIR_DOMAIN_BLOCKED :
case VIR_DOMAIN_SHUTOFF :
case VIR_DOMAIN_CRASHED :
2023-09-30 03:00:50 +02:00
status = SECTION_STATUS_ERROR ;
2023-09-27 01:42:49 +02:00
break ;
case VIR_DOMAIN_SHUTDOWN :
2023-09-30 03:00:50 +02:00
status = SECTION_STATUS_ERROR ;
2023-09-27 01:42:49 +02:00
break ;
case VIR_DOMAIN_PAUSED :
case VIR_DOMAIN_PMSUSPENDED :
2023-09-30 03:00:50 +02:00
status = SECTION_STATUS_WARNING ;
2023-09-27 01:42:49 +02:00
break ;
default :
2023-09-30 03:00:50 +02:00
status = SECTION_STATUS_NONE ;
2023-09-27 01:42:49 +02:00
}
2023-09-30 03:00:50 +02:00
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 ) ;
2023-09-27 01:42:49 +02:00
2023-09-30 03:00:50 +02:00
gui_text ( layout . cell ( 2 ) , domain - > name ) ;
2023-09-27 01:42:49 +02:00
2023-09-26 19:40:16 +02:00
2023-09-27 01:42:49 +02:00
char ram [ 128 ] ; snprintf ( ram , 128 , " %.2f GB " , domain - > ram_total / ( 1024.0 * 1024.0 * 1024.0 ) ) ;
2023-09-30 03:00:50 +02:00
gui_text ( layout . cell ( 1 ) , ram ) ;
2023-09-27 01:42:49 +02:00
char cpu_count [ 128 ] ; snprintf ( cpu_count , 128 , " %hd vCPU " , domain - > vcpus ) ;
2023-09-30 03:00:50 +02:00
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 ) ;
2023-09-27 01:42:49 +02:00
layout . row ( ) ;
}
2023-09-26 19:40:16 +02:00
gui_window_end ( ) ;
}
2023-09-27 01:42:49 +02:00
2023-09-28 23:30:12 +02:00
void fs_gui ( Gui_Layout_Grid * grid )
2023-09-27 23:56:31 +02:00
{
2023-09-30 02:39:12 +02:00
Gui_Context * ctx = & global_gui_state . default_context ;
2023-09-30 03:00:50 +02:00
Rect r = grid - > rect_at ( { 4 , 5 } , { 2 , grid - > max_cells_count . y - 5 } ) ;
2023-10-01 22:53:08 +02:00
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 ) ;
2023-09-30 02:39:12 +02:00
style_select ( ctx , fs_info . status ) ;
2023-09-28 23:30:12 +02:00
gui_window_start ( r , 0xabcdef04 ) ;
2023-09-30 02:39:12 +02:00
style_select ( ctx , SECTION_STATUS_NONE ) ;
2023-09-27 23:56:31 +02:00
2023-09-30 03:00:50 +02:00
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 } ) ;
2023-09-30 18:28:35 +02:00
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 ) ;
2023-09-30 03:00:50 +02:00
2023-09-27 23:56:31 +02:00
for ( s32 i = 0 ; i < fs_info . fs_count ; i + + )
{
2023-10-01 22:53:08 +02:00
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 ) ;
2023-09-30 18:28:35 +02:00
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 ) ;
2023-09-27 23:56:31 +02:00
2023-09-30 18:28:35 +02:00
layout . row ( 2 ) ;
2023-09-27 23:56:31 +02:00
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 ) ;
2024-09-27 19:46:39 +02:00
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 ) ;
2023-09-30 18:28:35 +02:00
gui_text_aligned ( layout . cell ( layout . max_cells_count . x ) , percentage , GUI_ALIGN_CENTER ) ;
2023-09-27 23:56:31 +02:00
}
gui_window_end ( ) ;
}
2023-09-27 01:42:49 +02:00
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 ) ;
}
2024-09-24 16:45:04 +02:00
// 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 ;
}
2023-09-27 01:42:49 +02:00
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
2024-09-24 16:45:04 +02:00
{
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 ;
2023-09-27 01:42:49 +02:00
2023-09-30 02:39:12 +02:00
// Status
system_info . status = SECTION_STATUS_NONE ;
2023-09-27 01:42:49 +02:00
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 ) ;
}
2023-09-30 02:39:12 +02:00
// 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 ;
}
}
}
2023-09-27 01:42:49 +02:00
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 ) ;
2023-09-30 02:39:12 +02:00
// 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 ;
}
}
2023-09-27 01:42:49 +02:00
app_data . virt_sample_t = engine . time ;
}
2023-09-27 23:56:31 +02:00
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 ) ;
2023-09-30 02:39:12 +02:00
2023-10-01 22:53:08 +02:00
// Sort by mount directory
qsort ( fs_info . fs , fs_info . fs_count , sizeof ( FS_Entry ) , fs_cmp ) ;
2023-09-30 02:39:12 +02:00
fs_info . status = SECTION_STATUS_NONE ;
2023-09-27 23:56:31 +02:00
}
2023-09-27 01:42:49 +02:00
}
2023-09-26 19:40:16 +02:00
void process_gui ( )
{
2023-09-27 01:42:49 +02:00
collect_new_data_if_needed ( ) ;
2023-09-28 23:30:12 +02:00
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 ) ;
2023-09-27 01:42:49 +02:00
2023-09-28 23:30:12 +02:00
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
}
}
2023-09-26 19:40:16 +02:00
}
2023-09-27 01:42:49 +02:00
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 ( ) ;
2023-09-30 02:39:12 +02:00
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 " ) ;
2023-09-27 01:42:49 +02:00
}
void app_deinit ( )
{
virConnectClose ( virt_connection ) ;
}