master
  1package get
  2
  3/*
  4#cgo linux LDFLAGS: ./lib/libsystem.a
  5#cgo darwin LDFLAGS: ./lib/libsystem.a -framework IOKit
  6#cgo windows LDFLAGS: ./lib/libsystem.a
  7#include "../../lib/system.h"
  8#include <stdlib.h>
  9*/
 10import "C"
 11import (
 12	"fmt"
 13	"math"
 14	"os/user"
 15	"strconv"
 16	"strings"
 17
 18	"github.com/distatus/battery"
 19	"github.com/shirou/gopsutil/v4/cpu"
 20
 21	"github.com/kahnwong/swissknife/configs/color"
 22	"github.com/shirou/gopsutil/v4/disk"
 23	"github.com/shirou/gopsutil/v4/host"
 24	"github.com/shirou/gopsutil/v4/mem"
 25)
 26
 27type batteryStruct struct {
 28	BatteryCurrent        float64
 29	BatteryFull           float64
 30	BatteryDesignCapacity float64
 31	BatteryCycleCount     uint64
 32	BatteryTimeToEmpty    uint64
 33}
 34
 35func SysInfo() error {
 36	// host
 37	username, err := getUsername()
 38	if err != nil {
 39		return err
 40	}
 41	hostInfo, err := getHostInfo()
 42	if err != nil {
 43		return err
 44	}
 45	fmt.Printf("%s@%s\n", color.Green(username), color.Green(hostInfo.Hostname))
 46	fmt.Println(strings.Repeat("-", len(username)+len(hostInfo.Hostname)+1))
 47
 48	// os
 49	fmt.Printf("%s: %s %s\n", color.Green("OS"), hostInfo.Platform, hostInfo.PlatformVersion)
 50
 51	// cpu
 52	cpuModel, cpuThreads, err := getCpuInfo()
 53	if err != nil {
 54		return err
 55	}
 56	fmt.Printf("%s: %s (%v)\n", color.Green("CPU"), cpuModel, cpuThreads)
 57
 58	// memory
 59	memoryUsed, memoryTotal, memoryUsedPercent, err := getMemoryInfo()
 60	if err != nil {
 61		return err
 62	}
 63	fmt.Printf("%s: %.2f GB / %v GB (%s)\n",
 64		color.Green("Memory"),
 65		convertKBtoGB(memoryUsed, true), convertKBtoGB(memoryTotal, false),
 66		color.Blue(strconv.Itoa(memoryUsedPercent)+"%"),
 67	)
 68
 69	// disk
 70	diskUsed, diskTotal, diskUsedPercent, err := getDiskInfo()
 71	if err != nil {
 72		return err
 73	}
 74	fmt.Printf("%s: %v GB / %v GB (%s)\n",
 75		color.Green("Disk"),
 76		convertKBtoGiB(diskUsed), convertKBtoGiB(diskTotal),
 77		color.Blue(strconv.Itoa(diskUsedPercent)+"%"),
 78	)
 79
 80	// battery
 81	batteryInfo, err := getBatteryInfo()
 82	if err != nil {
 83		return err
 84	}
 85
 86	// only print battery info if is a laptop
 87	if batteryInfo.BatteryFull > 0 {
 88		batteryPercent := convertToPercent(batteryInfo.BatteryCurrent / batteryInfo.BatteryFull)
 89		batteryHealth := convertToPercent(batteryInfo.BatteryFull / batteryInfo.BatteryDesignCapacity)
 90		batteryFormat := fmt.Sprintf("%v%%", batteryPercent)
 91		var batteryPercentStr string
 92		if batteryPercent > 80 {
 93			batteryPercentStr = color.Green(batteryFormat)
 94		} else if batteryPercent > 60 {
 95			batteryPercentStr = color.Yellow(batteryFormat)
 96		} else if batteryPercent > 30 {
 97			batteryPercentStr = color.Red(batteryFormat)
 98		} else {
 99			batteryPercentStr = batteryFormat
100		}
101
102		// convert BatteryTimeToEmpty from second to hour
103		var batteryTimeToEmptyFormatted string
104		if batteryInfo.BatteryTimeToEmpty > 0 {
105			hours := batteryInfo.BatteryTimeToEmpty / 3600
106			minutes := (batteryInfo.BatteryTimeToEmpty % 3600) / 60
107			batteryTimeToEmptyFormatted = fmt.Sprintf("%02d:%02d", hours, minutes)
108		} else {
109			batteryTimeToEmptyFormatted = "--:--"
110		}
111
112		fmt.Printf(
113			"%s: %s (Health: %s, Cycles: %s, Time Remaining: %s)\n",
114			color.Green("Battery"), batteryPercentStr,
115			color.Blue(strconv.Itoa(batteryHealth)+"%"),
116			color.Blue(strconv.Itoa(int(batteryInfo.BatteryCycleCount))),
117			color.Blue(batteryTimeToEmptyFormatted),
118		)
119	}
120	return nil
121}
122
123// ---- functions ----
124func getUsername() (string, error) {
125	username, err := user.Current()
126	if err != nil {
127		return "", fmt.Errorf("failed to get current user info: %w", err)
128	}
129	return username.Username, nil
130}
131
132func getHostInfo() (*host.InfoStat, error) {
133	hostStat, err := host.Info()
134	if err != nil {
135		return nil, fmt.Errorf("failed to get host info: %w", err)
136	}
137	return hostStat, nil
138}
139
140func getCpuInfo() (string, int, error) {
141	cpuStat, err := cpu.Info()
142	if err != nil {
143		return "", 0, fmt.Errorf("failed to get cpu info: %w", err)
144	}
145	cpuThreads, err := cpu.Counts(true)
146	if err != nil {
147		return "", 0, fmt.Errorf("failed to get cpu threads info: %w", err)
148	}
149
150	return cpuStat[0].ModelName, cpuThreads, nil
151}
152
153func getMemoryInfo() (uint64, uint64, int, error) {
154	vmStat, err := mem.VirtualMemory()
155	if err != nil {
156		return 0, 0, 0, fmt.Errorf("failed to get memory info: %w", err)
157	}
158	return vmStat.Used, vmStat.Total, convertToPercent(float64(vmStat.Used) / float64(vmStat.Total)), nil
159}
160
161func getDiskInfo() (uint64, uint64, int, error) {
162	diskStat, err := disk.Usage("/")
163	if err != nil {
164		return 0, 0, 0, fmt.Errorf("failed to get disk info: %w", err)
165	}
166	return diskStat.Used, diskStat.Total, convertToPercent(float64(diskStat.Used) / float64(diskStat.Total)), nil
167}
168
169func getBatteryInfo() (batteryStruct, error) {
170	// battery
171	batteries, err := battery.GetAll()
172	if err != nil {
173		if !strings.Contains(err.Error(), "no such file or directory") {
174			return batteryStruct{}, fmt.Errorf("error getting battery info: %w", err)
175		}
176		// ignore this happens on [linux on mac devices]
177	}
178
179	//// charge stats
180	var batteryCurrent float64
181	var batteryFull float64
182	var batteryDesignCapacity float64
183	if len(batteries) > 0 {
184		batteryCurrent = batteries[0].Current
185		batteryFull = batteries[0].Full
186		batteryDesignCapacity = batteries[0].Design
187	}
188
189	//// cycle count
190	var batteryCycleCount uint64
191	result := C.battery_cycle_count()
192
193	switch result.error {
194	case C.BATTERY_SUCCESS:
195		batteryCycleCount = uint64(result.cycle_count)
196	case C.BATTERY_NO_BATTERY:
197		batteryCycleCount = 0
198	case C.BATTERY_NO_CYCLE_COUNT:
199		batteryCycleCount = 0
200	case C.BATTERY_MANAGER_ERROR:
201		return batteryStruct{}, fmt.Errorf("battery manager error")
202	default:
203		return batteryStruct{}, fmt.Errorf("unknown error occurred")
204	}
205
206	//// time to empty
207	var batteryTimeToEmpty uint64
208	resultTimeToEmpty := C.battery_time_to_empty()
209
210	switch result.error {
211	case C.BATTERY_TIME_TO_EMPTY_SUCCESS:
212		batteryTimeToEmpty = uint64(resultTimeToEmpty.time_to_empty_seconds)
213	case C.BATTERY_TIME_TO_EMPTY_NO_BATTERY:
214		batteryTimeToEmpty = 0
215	case C.BATTERY_TIME_TO_EMPTY_NO_TIME_TO_EMPTY:
216		batteryTimeToEmpty = 0
217	case C.BATTERY_TIME_TO_EMPTY_MANAGER_ERROR:
218		return batteryStruct{}, fmt.Errorf("battery manager error")
219	default:
220		return batteryStruct{}, fmt.Errorf("unknown error occurred")
221	}
222
223	// return
224	return batteryStruct{
225		BatteryCurrent:        batteryCurrent,
226		BatteryFull:           batteryFull,
227		BatteryDesignCapacity: batteryDesignCapacity,
228		BatteryCycleCount:     batteryCycleCount,
229		BatteryTimeToEmpty:    batteryTimeToEmpty,
230	}, nil
231}
232
233// ---- utils ----
234func convertKBtoGiB(v uint64) float64 {
235	return math.Round(float64(v)/float64(1024)/float64(1024)/float64(1024)*100) / 100
236}
237
238func convertKBtoGB(v uint64, isFloat bool) float64 {
239	gb := (float64(v) / float64(1024) / float64(1024) / float64(1000) * 100) / 100
240	if isFloat {
241		return gb
242	} else {
243		return math.Round(gb)
244	}
245}
246
247func convertToPercent(v float64) int {
248	return int(math.Round(v * 100))
249}