Log to file on errors
- Also recover from issue #12 - Make config path management more portable
This commit is contained in:
parent
3dee8bc6f2
commit
c7171781c1
46
main.go
46
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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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],
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user