import options from 'options'; import { GPU_Stat } from 'lib/types/gpustat'; import { dependencies } from 'lib/utils'; import { BoxWidget } from 'lib/types/widget'; import { GenericResourceMetrics } from 'lib/types/customModules/generic'; const { terminal } = options; const { enable_gpu } = options.menus.dashboard.stats; const Stats = (): BoxWidget => { const divide = ([total, free]: number[]): number => free / total; const formatSizeInGB = (sizeInKB: number): number => Number((sizeInKB / 1024 ** 2).toFixed(2)); const cpu = Variable(0, { poll: [ 2000, 'top -b -n 1', (out): number => { if (typeof out !== 'string') { return 0; } const cpuOut = out.split('\n').find((line) => line.includes('Cpu(s)')); if (cpuOut === undefined) { return 0; } const freeCpu = parseFloat(cpuOut.split(/\s+/)[1].replace(',', '.')); return divide([100, freeCpu]); }, ], }); const ram = Variable( { total: 0, used: 0, percentage: 0 }, { poll: [ 2000, 'free', (out): GenericResourceMetrics => { if (typeof out !== 'string') { return { total: 0, used: 0, percentage: 0 }; } const ramOut = out.split('\n').find((line) => line.includes('Mem:')); if (ramOut === undefined) { return { total: 0, used: 0, percentage: 0 }; } const [totalRam, usedRam] = ramOut.split(/\s+/).splice(1, 2).map(Number); return { percentage: divide([totalRam, usedRam]), total: formatSizeInGB(totalRam), used: formatSizeInGB(usedRam), }; }, ], }, ); const gpu = Variable(0); const GPUStat = Widget.Box({ child: enable_gpu.bind('value').as((gpStat) => { if (!gpStat || !dependencies('gpustat')) { return Widget.Box(); } return Widget.Box({ vertical: true, children: [ Widget.Box({ class_name: 'stat gpu', hexpand: true, vpack: 'center', setup: (self) => { const getGpuUsage = (): void => { if (!enable_gpu.value) { gpu.value = 0; return; } Utils.execAsync('gpustat --json') .then((out) => { if (typeof out !== 'string') { return 0; } try { const data = JSON.parse(out); const totalGpu = 100; const usedGpu = data.gpus.reduce((acc: number, gpu: GPU_Stat) => { return acc + gpu['utilization.gpu']; }, 0) / data.gpus.length; gpu.value = divide([totalGpu, usedGpu]); } catch (e) { console.error('Error getting GPU stats:', e); gpu.value = 0; } }) .catch((err) => { console.error(`An error occurred while fetching GPU stats: ${err}`); }); }; self.poll(2000, getGpuUsage); Utils.merge([gpu.bind('value'), enable_gpu.bind('value')], (gpu, enableGpu) => { if (!enableGpu) { return (self.children = []); } return (self.children = [ Widget.Button({ on_primary_click: terminal.bind('value').as((term) => { return (): void => { App.closeWindow('dashboardmenu'); Utils.execAsync(`bash -c "${term} -e btop"`).catch( (err) => `Failed to open btop: ${err}`, ); }; }), child: Widget.Label({ class_name: 'txt-icon', label: '󰢮', }), }), Widget.Button({ on_primary_click: terminal.bind('value').as((term) => { return (): void => { App.closeWindow('dashboardmenu'); Utils.execAsync(`bash -c "${term} -e btop"`).catch( (err) => `Failed to open btop: ${err}`, ); }; }), child: Widget.LevelBar({ class_name: 'stats-bar', hexpand: true, vpack: 'center', value: gpu, }), }), ]); }); }, }), Widget.Box({ hpack: 'end', children: Utils.merge([gpu.bind('value'), enable_gpu.bind('value')], (gpuUsed, enableGpu) => { if (!enableGpu) { return []; } return [ Widget.Label({ class_name: 'stat-value gpu', label: `${Math.floor(gpuUsed * 100)}%`, }), ]; }), }), ], }); }), }); const storage = Variable( { total: 0, used: 0, percentage: 0 }, { poll: [ 2000, 'df -B1 /', (out): GenericResourceMetrics => { if (typeof out !== 'string') { return { total: 0, used: 0, percentage: 0 }; } const dfOut = out.split('\n').find((line) => line.startsWith('/')); if (dfOut === undefined) { return { total: 0, used: 0, percentage: 0 }; } const parts = dfOut.split(/\s+/); const size = parseInt(parts[1], 10); const used = parseInt(parts[2], 10); const sizeInGB = formatSizeInGB(size); const usedInGB = formatSizeInGB(used); return { total: Math.floor(sizeInGB / 1000), used: Math.floor(usedInGB / 1000), percentage: divide([size, used]), }; }, ], }, ); return Widget.Box({ class_name: 'dashboard-card stats-container', vertical: true, vpack: 'fill', hpack: 'fill', expand: true, children: [ Widget.Box({ vertical: true, children: [ Widget.Box({ class_name: 'stat cpu', hexpand: true, vpack: 'center', children: [ Widget.Button({ on_primary_click: terminal.bind('value').as((term) => { return () => { App.closeWindow('dashboardmenu'); Utils.execAsync(`bash -c "${term} -e btop"`).catch( (err) => `Failed to open btop: ${err}`, ); }; }), child: Widget.Label({ class_name: 'txt-icon', label: '', }), }), Widget.Button({ on_primary_click: terminal.bind('value').as((term) => { return () => { App.closeWindow('dashboardmenu'); Utils.execAsync(`bash -c "${term} -e btop"`).catch( (err) => `Failed to open btop: ${err}`, ); }; }), child: Widget.LevelBar({ class_name: 'stats-bar', hexpand: true, vpack: 'center', bar_mode: 'continuous', max_value: 1, value: cpu.bind('value'), }), }), ], }), Widget.Label({ hpack: 'end', class_name: 'stat-value cpu', label: cpu.bind('value').as((v) => `${Math.floor(v * 100)}%`), }), ], }), Widget.Box({ vertical: true, children: [ Widget.Box({ class_name: 'stat ram', vpack: 'center', hexpand: true, children: [ Widget.Button({ on_primary_click: terminal.bind('value').as((term) => { return () => { App.closeWindow('dashboardmenu'); Utils.execAsync(`bash -c "${term} -e btop"`).catch( (err) => `Failed to open btop: ${err}`, ); }; }), child: Widget.Label({ class_name: 'txt-icon', label: '', }), }), Widget.Button({ on_primary_click: terminal.bind('value').as((term) => { return () => { App.closeWindow('dashboardmenu'); Utils.execAsync(`bash -c "${term} -e btop"`).catch( (err) => `Failed to open btop: ${err}`, ); }; }), child: Widget.LevelBar({ class_name: 'stats-bar', hexpand: true, vpack: 'center', value: ram.bind('value').as((v) => v.percentage), }), }), ], }), Widget.Label({ hpack: 'end', class_name: 'stat-value ram', label: ram.bind('value').as((v) => `${v.used}/${v.total} GB`), }), ], }), GPUStat, Widget.Box({ vertical: true, children: [ Widget.Box({ class_name: 'stat storage', hexpand: true, vpack: 'center', children: [ Widget.Button({ on_primary_click: terminal.bind('value').as((term) => { return () => { App.closeWindow('dashboardmenu'); Utils.execAsync(`bash -c "${term} -e btop"`).catch( (err) => `Failed to open btop: ${err}`, ); }; }), child: Widget.Label({ class_name: 'txt-icon', label: '󰋊', }), }), Widget.Button({ on_primary_click: terminal.bind('value').as((term) => { return () => { App.closeWindow('dashboardmenu'); Utils.execAsync(`bash -c "${term} -e btop"`).catch( (err) => `Failed to open btop: ${err}`, ); }; }), child: Widget.LevelBar({ class_name: 'stats-bar', hexpand: true, vpack: 'center', value: storage.bind('value').as((v) => v.percentage), }), }), ], }), Widget.Label({ hpack: 'end', class_name: 'stat-value storage', label: storage.bind('value').as((v) => `${v.used}/${v.total} GB`), }), ], }), ], }); }; export { Stats };