Log to file on errors

- Also recover from issue #12
- Make config path management more portable
This commit is contained in:
Caleb Bassi 2018-12-04 21:44:25 -08:00
parent 3dee8bc6f2
commit c7171781c1
12 changed files with 148 additions and 74 deletions

46
main.go
View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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],

View File

@ -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),

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {