diff --git a/main.go b/main.go index 7a5fe2f7..7aa5bda0 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,11 @@ package main import ( "encoding/json" - "fmt" "io/ioutil" + "log" "os" "os/signal" + "path/filepath" "sort" "strconv" "sync" @@ -32,6 +33,8 @@ var ( widgetCount = 6 fahrenheit = false configDir = getConfigDir() + logPath = filepath.Join(configDir, "errors.log") + stderrLogger = log.New(os.Stderr, "", 0) cpu *w.CPU mem *w.Mem @@ -63,7 +66,10 @@ Colorschemes: monokai ` - args, _ := docopt.ParseArgs(usage, os.Args[1:], version) + args, err := docopt.ParseArgs(usage, os.Args[1:], version) + if err != nil { + panic(err) + } if val, _ := args["--color"]; val != nil { handleColorscheme(val.(string)) @@ -77,7 +83,10 @@ Colorschemes: } rateStr, _ := args["--rate"].(string) - rate, _ := strconv.ParseFloat(rateStr, 64) + rate, err := strconv.ParseFloat(rateStr, 64) + if err != nil { + stderrLogger.Fatalf("error: invalid rate parameter") + } if rate < 1 { interval = time.Second * time.Duration(1/rate) } else { @@ -104,24 +113,22 @@ func handleColorscheme(cs string) { func getConfigDir() string { globalConfigDir := os.Getenv("XDG_CONFIG_HOME") if globalConfigDir == "" { - globalConfigDir = os.ExpandEnv("$HOME") + "/.config" + globalConfigDir = filepath.Join(os.ExpandEnv("$HOME"), ".config") } - return globalConfigDir + "/gotop" + return filepath.Join(globalConfigDir, "gotop") } // getCustomColorscheme tries to read a custom json colorscheme from {configDir}/{name}.json func getCustomColorscheme(name string) colorschemes.Colorscheme { - filePath := configDir + "/" + name + ".json" + filePath := filepath.Join(configDir, name+".json") dat, err := ioutil.ReadFile(filePath) if err != nil { - fmt.Fprintf(os.Stderr, "error: colorscheme not recognized\n") - os.Exit(1) + stderrLogger.Fatalf("error: colorscheme not recognized") } var colorscheme colorschemes.Colorscheme err = json.Unmarshal(dat, &colorscheme) if err != nil { - fmt.Fprintf(os.Stderr, "error: could not parse colorscheme\n") - os.Exit(1) + stderrLogger.Fatalf("error: could not parse colorscheme") } return colorscheme } @@ -334,6 +341,23 @@ func eventLoop() { } func main() { + // make the config directory + err := os.MkdirAll(configDir, 0755) + if err != nil { + stderrLogger.Fatalf("failed to make the configuration directory: %v", err) + } + // open the log file + lf, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660) + if err != nil { + stderrLogger.Fatalf("failed to open log file: %v", err) + } + defer lf.Close() + + // log time, filename, and line number + log.SetFlags(log.Ltime | log.Lshortfile) + // log to file + log.SetOutput(lf) + cliArguments() termuiColors() // need to do this before initializing widgets so that they can inherit the colors initWidgets() @@ -341,7 +365,7 @@ func main() { help = w.NewHelpMenu() // inits termui - err := ui.Init() + err = ui.Init() if err != nil { panic(err) } diff --git a/src/utils/utils.go b/src/utils/utils.go index f3e11efd..fe2a4a9e 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -1,10 +1,7 @@ package utils import ( - "fmt" "math" - - ui "github.com/cjbassi/termui" ) var ( @@ -55,14 +52,3 @@ func Max(a, b int) int { } return b } - -func Error(issue, diagnostics string) { - ui.Close() - fmt.Println("Error caught. Exiting program.") - fmt.Println() - fmt.Println("Issue with " + issue + ".") - fmt.Println() - fmt.Println("Diagnostics:\n" + diagnostics) - fmt.Println() - panic(1) -} diff --git a/src/widgets/cpu.go b/src/widgets/cpu.go index cd417334..caa3d692 100644 --- a/src/widgets/cpu.go +++ b/src/widgets/cpu.go @@ -2,9 +2,9 @@ package widgets import ( "fmt" + "log" "time" - "github.com/cjbassi/gotop/src/utils" ui "github.com/cjbassi/termui" psCPU "github.com/shirou/gopsutil/cpu" ) @@ -19,7 +19,10 @@ type CPU struct { } func NewCPU(interval time.Duration, zoom int, average bool, percpu bool) *CPU { - count, _ := psCPU.Counts(false) + count, err := psCPU.Counts(false) + if err != nil { + log.Printf("failed to get CPU count from gopsutil: %v", err) + } formatString := "CPU%1d" if count > 10 { formatString = "CPU%02d" @@ -70,7 +73,10 @@ func NewCPU(interval time.Duration, zoom int, average bool, percpu bool) *CPU { func (self *CPU) update() { if self.Average { go func() { - percent, _ := psCPU.Percent(self.interval, false) + percent, err := psCPU.Percent(self.interval, false) + if err != nil { + log.Printf("failed to get average CPU usage percent from gopsutil: %v. self.interval: %v. percpu: %v", err, self.interval, false) + } self.Data["AVRG"] = append(self.Data["AVRG"], percent[0]) self.Labels["AVRG"] = fmt.Sprintf("%3.0f%%", percent[0]) }() @@ -78,17 +84,12 @@ func (self *CPU) update() { if self.PerCPU { go func() { - percents, _ := psCPU.Percent(self.interval, true) + percents, err := psCPU.Percent(self.interval, true) + if err != nil { + log.Printf("failed to get CPU usage percents from gopsutil: %v. self.interval: %v. percpu: %v", err, self.interval, true) + } if len(percents) != self.Count { - count, _ := psCPU.Counts(false) - utils.Error("CPU percentages", - fmt.Sprint( - "self.Count: ", self.Count, "\n", - "gopsutil.Counts(): ", count, "\n", - "len(percents): ", len(percents), "\n", - "percents: ", percents, "\n", - "self.interval: ", self.interval, - )) + log.Printf("error: number of CPU usage percents from gopsutil doesn't match CPU count. percents: %v. self.Count: %v", percents, self.Count) } for i := 0; i < self.Count; i++ { k := fmt.Sprintf(self.formatString, i) diff --git a/src/widgets/disk.go b/src/widgets/disk.go index 98904de1..b2316201 100644 --- a/src/widgets/disk.go +++ b/src/widgets/disk.go @@ -2,6 +2,7 @@ package widgets import ( "fmt" + "log" "sort" "strings" "time" @@ -52,7 +53,10 @@ func NewDisk() *Disk { } func (self *Disk) update() { - Partitions, _ := psDisk.Partitions(false) + Partitions, err := psDisk.Partitions(false) + if err != nil { + log.Printf("failed to get disk partitions from gopsutil: %v", err) + } // add partition if it's new for _, Part := range Partitions { @@ -93,13 +97,19 @@ func (self *Disk) update() { // updates partition info for _, Part := range self.Partitions { - usage, _ := psDisk.Usage(Part.Mount) + usage, err := psDisk.Usage(Part.Mount) + if err != nil { + log.Printf("failed to get partition usage statistics from gopsutil: %v. Part.Mount: %v", err, Part.Mount) + } Part.UsedPercent = int(usage.UsedPercent) Free, Mag := utils.ConvertBytes(usage.Free) Part.Free = fmt.Sprintf("%3d%s", uint64(Free), Mag) - ret, _ := psDisk.IOCounters("/dev/" + Part.Device) + ret, err := psDisk.IOCounters("/dev/" + Part.Device) + if err != nil { + log.Printf("failed to get partition read/write info from gopsutil: %v. Part.Device: %v", err, Part.Device) + } data := ret[Part.Device] curRead, curWrite := data.ReadBytes, data.WriteBytes if Part.TotalRead != 0 { // if this isn't the first update diff --git a/src/widgets/mem.go b/src/widgets/mem.go index 63a6a789..fa3b8247 100644 --- a/src/widgets/mem.go +++ b/src/widgets/mem.go @@ -2,6 +2,7 @@ package widgets import ( "fmt" + "log" "time" "github.com/cjbassi/gotop/src/utils" @@ -37,8 +38,14 @@ func NewMem(interval time.Duration, zoom int) *Mem { } func (self *Mem) update() { - main, _ := psMem.VirtualMemory() - swap, _ := psMem.SwapMemory() + main, err := psMem.VirtualMemory() + if err != nil { + log.Printf("failed to get main memory info from gopsutil: %v", err) + } + swap, err := psMem.SwapMemory() + if err != nil { + log.Printf("failed to get swap memory info from gopsutil: %v", err) + } self.Data["Main"] = append(self.Data["Main"], main.UsedPercent) self.Data["Swap"] = append(self.Data["Swap"], swap.UsedPercent) diff --git a/src/widgets/net.go b/src/widgets/net.go index 9e928da8..2dc58155 100644 --- a/src/widgets/net.go +++ b/src/widgets/net.go @@ -2,6 +2,7 @@ package widgets import ( "fmt" + "log" "time" "github.com/cjbassi/gotop/src/utils" @@ -45,7 +46,10 @@ func NewNet() *Net { func (self *Net) update() { // `false` causes psutil to group all network activity - interfaces, _ := psNet.IOCounters(false) + interfaces, err := psNet.IOCounters(false) + if err != nil { + log.Printf("failed to get network activity from gopsutil: %v", err) + } curRecvTotal := interfaces[0].BytesRecv curSentTotal := interfaces[0].BytesSent var recvRecent uint64 @@ -55,22 +59,19 @@ func (self *Net) update() { recvRecent = curRecvTotal - self.prevRecvTotal sentRecent = curSentTotal - self.prevSentTotal + if int(recvRecent) < 0 { + log.Printf("error: negative value for recently received network data from gopsutil. recvRecent: %v", recvRecent) + // recover from error + recvRecent = 0 + } + if int(sentRecent) < 0 { + log.Printf("error: negative value for recently sent network data from gopsutil. sentRecent: %v", sentRecent) + // recover from error + sentRecent = 0 + } + self.Lines[0].Data = append(self.Lines[0].Data, int(recvRecent)) self.Lines[1].Data = append(self.Lines[1].Data, int(sentRecent)) - - if int(recvRecent) < 0 || int(sentRecent) < 0 { - utils.Error("net data", - fmt.Sprint( - "curRecvTotal: ", curRecvTotal, "\n", - "curSentTotal: ", curSentTotal, "\n", - "self.prevRecvTotal: ", self.prevRecvTotal, "\n", - "self.prevSentTotal: ", self.prevSentTotal, "\n", - "recvRecent: ", recvRecent, "\n", - "sentRecent: ", sentRecent, "\n", - "int(recvRecent): ", int(recvRecent), "\n", - "int(sentRecent): ", int(sentRecent), - )) - } } // used in later calls to update diff --git a/src/widgets/proc.go b/src/widgets/proc.go index f6e4ec56..dc04216f 100644 --- a/src/widgets/proc.go +++ b/src/widgets/proc.go @@ -2,6 +2,7 @@ package widgets import ( "fmt" + "log" "os/exec" "sort" "strconv" @@ -37,7 +38,10 @@ type Proc struct { } func NewProc() *Proc { - cpuCount, _ := psCPU.Counts(false) + cpuCount, err := psCPU.Counts(false) + if err != nil { + log.Printf("failed to get CPU count from gopsutil: %v", err) + } self := &Proc{ Table: ui.NewTable(), interval: time.Second, diff --git a/src/widgets/proc_unix.go b/src/widgets/proc_unix.go index b422737f..b973f3a2 100644 --- a/src/widgets/proc_unix.go +++ b/src/widgets/proc_unix.go @@ -3,6 +3,7 @@ package widgets import ( + "log" "os/exec" "strconv" "strings" @@ -11,7 +12,7 @@ import ( func (self *Proc) update() { processes := Processes() // have to iterate like this in order to actually change the value - for i, _ := range processes { + for i := range processes { processes[i].CPU /= self.cpuCount } @@ -22,15 +23,27 @@ func (self *Proc) update() { } func Processes() []Process { - output, _ := exec.Command("ps", "-axo", "pid,comm,pcpu,pmem,args").Output() + output, err := exec.Command("ps", "-axo", "pid,comm,pcpu,pmem,args").Output() + if err != nil { + log.Printf("failed to execute 'ps' command: %v", err) + } // converts to []string and removes the header strOutput := strings.Split(strings.TrimSpace(string(output)), "\n")[1:] processes := []Process{} for _, line := range strOutput { split := strings.Fields(line) - pid, _ := strconv.Atoi(split[0]) - cpu, _ := strconv.ParseFloat(split[2], 64) - mem, _ := strconv.ParseFloat(split[3], 64) + pid, err := strconv.Atoi(split[0]) + if err != nil { + log.Printf("failed to convert first field to int: %v. split: %v", err, split) + } + cpu, err := strconv.ParseFloat(split[2], 64) + if err != nil { + log.Printf("failed to convert third field to float: %v. split: %v", err, split) + } + mem, err := strconv.ParseFloat(split[3], 64) + if err != nil { + log.Printf("failed to convert fourth field to float: %v. split: %v", err, split) + } process := Process{ PID: pid, Command: split[1], diff --git a/src/widgets/proc_windows.go b/src/widgets/proc_windows.go index 4b362db7..aef97afb 100644 --- a/src/widgets/proc_windows.go +++ b/src/widgets/proc_windows.go @@ -1,17 +1,31 @@ package widgets import ( + "log" + psProc "github.com/shirou/gopsutil/process" ) func (self *Proc) update() { - psProcesses, _ := psProc.Processes() + psProcesses, err := psProc.Processes() + if err != nil { + log.Printf("failed to get processes from gopsutil: %v", err) + } processes := make([]Process, len(psProcesses)) for i, psProcess := range psProcesses { pid := psProcess.Pid - command, _ := psProcess.Name() - cpu, _ := psProcess.CPUPercent() - mem, _ := psProcess.MemoryPercent() + command, err := psProcess.Name() + if err != nil { + log.Printf("failed to get process command from gopsutil: %v. psProcess: %v. i: %v. pid: %v", err, psProcess, i, pid) + } + cpu, err := psProcess.CPUPercent() + if err != nil { + log.Printf("failed to get process cpu usage from gopsutil: %v. psProcess: %v. i: %v. pid: %v", err, psProcess, i, pid) + } + mem, err := psProcess.MemoryPercent() + if err != nil { + log.Printf("failed to get process memeory usage from gopsutil: %v. psProcess: %v. i: %v. pid: %v", err, psProcess, i, pid) + } processes[i] = Process{ int(pid), diff --git a/src/widgets/temp_darwin.go b/src/widgets/temp_darwin.go index 0a660872..9343debe 100644 --- a/src/widgets/temp_darwin.go +++ b/src/widgets/temp_darwin.go @@ -1,11 +1,13 @@ -// TODO do we need to add '+build cgo'? - package widgets // #cgo LDFLAGS: -framework IOKit // #include "include/smc.c" import "C" -import "github.com/cjbassi/gotop/src/utils" +import ( + "log" + + "github.com/cjbassi/gotop/src/utils" +) type TemperatureStat struct { SensorKey string `json:"sensorKey"` @@ -52,7 +54,10 @@ func SensorsTemperatures() ([]TemperatureStat, error) { } func (self *Temp) update() { - sensors, _ := SensorsTemperatures() + sensors, err := SensorsTemperatures() + if err != nil { + log.Printf("failed to get sensors from CGO: %v", err) + } for _, sensor := range sensors { if sensor.Temperature != 0 { if self.Fahrenheit { diff --git a/src/widgets/temp_linux.go b/src/widgets/temp_linux.go index 40c1978a..13664039 100644 --- a/src/widgets/temp_linux.go +++ b/src/widgets/temp_linux.go @@ -1,6 +1,7 @@ package widgets import ( + "log" "strings" "github.com/cjbassi/gotop/src/utils" @@ -8,7 +9,10 @@ import ( ) func (self *Temp) update() { - sensors, _ := psHost.SensorsTemperatures() + sensors, err := psHost.SensorsTemperatures() + if err != nil { + log.Printf("failed to get sensors from gopsutil: %v", err) + } for _, sensor := range sensors { // only sensors with input in their name are giving us live temp info if strings.Contains(sensor.SensorKey, "input") && sensor.Temperature != 0 { diff --git a/src/widgets/temp_windows.go b/src/widgets/temp_windows.go index 7ac35d58..c217552f 100644 --- a/src/widgets/temp_windows.go +++ b/src/widgets/temp_windows.go @@ -1,12 +1,17 @@ package widgets import ( + "log" + "github.com/cjbassi/gotop/src/utils" psHost "github.com/shirou/gopsutil/host" ) func (self *Temp) update() { - sensors, _ := psHost.SensorsTemperatures() + sensors, err := psHost.SensorsTemperatures() + if err != nil { + log.Printf("failed to get sensors from gopsutil: %v", err) + } for _, sensor := range sensors { if sensor.Temperature != 0 { if self.Fahrenheit {