Merge branch 'v3.4.x'
This commit is contained in:
commit
c6af0ab404
@ -23,17 +23,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
a histogram.
|
||||
- Temp widget displays degree symbol (merged from BartWillems, thanks
|
||||
also fleaz)
|
||||
- Support for (device) plugins, and abstracting devices from widgets. This
|
||||
allows adding functionality without adding bulk.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Keys not controlling process widget, #59
|
||||
- The one-column bug, #62
|
||||
|
||||
## [3.3.2] - 2020-02-26
|
||||
|
||||
Bugfix release.
|
||||
|
||||
- Fixes #15, crash caused by battery widget when some accessories have batteries
|
||||
- Fixes #57, colors with dashes in the name not found.
|
||||
### Fixed
|
||||
|
||||
- #15, crash caused by battery widget when some accessories have batteries
|
||||
- #57, colors with dashes in the name not found.
|
||||
- Also, cjbassi/gotop#127 and cjbassi/gotop#130 were released back in v3.1.0.
|
||||
|
||||
## [3.3.1] - 2020-02-18
|
||||
|
@ -4,9 +4,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -14,6 +16,7 @@ import (
|
||||
|
||||
docopt "github.com/docopt/docopt.go"
|
||||
ui "github.com/gizak/termui/v3"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/xxxserxxx/gotop"
|
||||
"github.com/xxxserxxx/gotop/colorschemes"
|
||||
@ -25,12 +28,13 @@ import (
|
||||
|
||||
const (
|
||||
appName = "gotop"
|
||||
version = "3.3.2"
|
||||
version = "3.4.0"
|
||||
|
||||
graphHorizontalScaleDelta = 3
|
||||
defaultUI = "cpu\ndisk/1 2:mem/2\ntemp\nnet procs"
|
||||
minimalUI = "cpu\nmem procs"
|
||||
batteryUI = "cpu/2 batt/1\ndisk/1 2:mem/2\ntemp\nnet procs"
|
||||
procsUI = "cpu 4:procs\ndisk\nmem\nnet"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -41,10 +45,10 @@ var (
|
||||
stderrLogger = log.New(os.Stderr, "", 0)
|
||||
)
|
||||
|
||||
// TODO: Add tab completion for Linux https://gist.github.com/icholy/5314423
|
||||
// TODO: state:merge #135 linux console font (cmatsuoka/console-font)
|
||||
// TODO: state:deferred 157 FreeBSD fixes & Nvidia GPU support (kraust/master). Significant CPU use impact for NVidia changes.
|
||||
// TODO: Virtual devices from Prometeus metrics @feature
|
||||
// TODO: Export Prometheus metrics @feature
|
||||
// TODO: state:merge #167 configuration file (jrswab/configFile111)
|
||||
func parseArgs(conf *gotop.Config) error {
|
||||
usage := `
|
||||
@ -63,11 +67,16 @@ Options:
|
||||
-b, --battery Show battery level widget ('minimal' turns off). (DEPRECATED, use -l battery)
|
||||
-B, --bandwidth=bits Specify the number of bits per seconds.
|
||||
-l, --layout=NAME Name of layout spec file for the UI. Looks first in $XDG_CONFIG_HOME/gotop, then as a path. Use "-" to pipe.
|
||||
-i, --interface=NAME Select network interface [default: all].
|
||||
-i, --interface=NAME Select network interface [default: all]. Several interfaces can be defined using comma separated values. Interfaces can also be ignored using !
|
||||
-x, --export=PORT Enable metrics for export on the specified port.
|
||||
-X, --extensions=NAMES Enables the listed extensions. This is a comma-separated list without the .so suffix. The current and config directories will be searched.
|
||||
|
||||
Several interfaces can be defined using comma separated values.
|
||||
|
||||
Interfaces can also be ignored using !
|
||||
Built-in layouts:
|
||||
default
|
||||
minimal
|
||||
battery
|
||||
kitchensink
|
||||
|
||||
Colorschemes:
|
||||
default
|
||||
@ -115,8 +124,11 @@ Colorschemes:
|
||||
if args["--minimal"].(bool) {
|
||||
conf.Layout = "minimal"
|
||||
}
|
||||
if val, _ := args["--statusbar"]; val != nil {
|
||||
rateStr, _ := args["--rate"].(string)
|
||||
if val, _ := args["--export"]; val != nil {
|
||||
conf.ExportPort = val.(string)
|
||||
}
|
||||
if val, _ := args["--rate"]; val != nil {
|
||||
rateStr, _ := val.(string)
|
||||
rate, err := strconv.ParseFloat(rateStr, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid rate parameter")
|
||||
@ -136,6 +148,10 @@ Colorschemes:
|
||||
if val, _ := args["--interface"]; val != nil {
|
||||
conf.NetInterface, _ = args["--interface"].(string)
|
||||
}
|
||||
if val, _ := args["--extensions"]; val != nil {
|
||||
exs, _ := args["--extensions"].(string)
|
||||
conf.Extensions = strings.Split(exs, ",")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -335,7 +351,7 @@ func makeConfig() gotop.Config {
|
||||
HelpVisible: false,
|
||||
UpdateInterval: time.Second,
|
||||
AverageLoad: false,
|
||||
PercpuLoad: false,
|
||||
PercpuLoad: true,
|
||||
TempScale: w.Celsius,
|
||||
Statusbar: false,
|
||||
NetInterface: w.NET_INTERFACE_ALL,
|
||||
@ -345,6 +361,7 @@ func makeConfig() gotop.Config {
|
||||
return conf
|
||||
}
|
||||
|
||||
// TODO: mpd visualizer widget
|
||||
func main() {
|
||||
// Set up default config
|
||||
conf := makeConfig()
|
||||
@ -379,6 +396,8 @@ func main() {
|
||||
bar = w.NewStatusBar()
|
||||
}
|
||||
|
||||
loadExtensions(conf)
|
||||
|
||||
lstream := getLayout(conf)
|
||||
ly := layout.ParseLayout(lstream)
|
||||
grid, err := layout.Layout(ly, conf)
|
||||
@ -400,6 +419,12 @@ func main() {
|
||||
ui.Render(bar)
|
||||
}
|
||||
|
||||
if conf.ExportPort != "" {
|
||||
go func() {
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
http.ListenAndServe(conf.ExportPort, nil)
|
||||
}()
|
||||
}
|
||||
eventLoop(conf, grid)
|
||||
}
|
||||
|
||||
@ -413,8 +438,9 @@ func getLayout(conf gotop.Config) io.Reader {
|
||||
return strings.NewReader(minimalUI)
|
||||
case "battery":
|
||||
return strings.NewReader(batteryUI)
|
||||
case "procs":
|
||||
return strings.NewReader(procsUI)
|
||||
default:
|
||||
log.Printf("layout = %s", conf.Layout)
|
||||
fp := filepath.Join(conf.ConfigDir, conf.Layout)
|
||||
fin, err := os.Open(fp)
|
||||
if err != nil {
|
||||
@ -426,3 +452,47 @@ func getLayout(conf gotop.Config) io.Reader {
|
||||
return fin
|
||||
}
|
||||
}
|
||||
|
||||
func loadExtensions(conf gotop.Config) {
|
||||
var hasError bool
|
||||
for _, ex := range conf.Extensions {
|
||||
exf := ex + ".so"
|
||||
fn := exf
|
||||
_, err := os.Stat(fn)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
log.Printf("no plugin %s found in current directory", fn)
|
||||
fn = filepath.Join(conf.ConfigDir, exf)
|
||||
_, err = os.Stat(fn)
|
||||
if err != nil || os.IsNotExist(err) {
|
||||
hasError = true
|
||||
log.Printf("no plugin %s found in config directory", fn)
|
||||
continue
|
||||
}
|
||||
}
|
||||
p, err := plugin.Open(fn)
|
||||
if err != nil {
|
||||
hasError = true
|
||||
log.Printf(err.Error())
|
||||
continue
|
||||
}
|
||||
init, err := p.Lookup("Init")
|
||||
if err != nil {
|
||||
hasError = true
|
||||
log.Printf(err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
initFunc, ok := init.(func())
|
||||
if !ok {
|
||||
hasError = true
|
||||
log.Printf(err.Error())
|
||||
continue
|
||||
}
|
||||
initFunc()
|
||||
}
|
||||
if hasError {
|
||||
ui.Close()
|
||||
fmt.Printf("Error initializing requested plugins; check the log file %s\n", filepath.Join(conf.ConfigDir, conf.LogFile))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,7 @@ func init() {
|
||||
|
||||
BattLines: []int{4, 3, 2, 1, 5, 6, 7, 8},
|
||||
|
||||
MainMem: 5,
|
||||
SwapMem: 11,
|
||||
MemLines: []int{5, 11, 4, 3, 2, 1, 6, 7, 8},
|
||||
|
||||
ProcCursor: 4,
|
||||
|
||||
|
@ -11,8 +11,7 @@
|
||||
|
||||
"BattLines": [4, 3, 2, 1, 5, 6, 7, 8],
|
||||
|
||||
"MainMem": 5,
|
||||
"SwapMem": 11,
|
||||
"MemLines": [5, 11, 4, 3, 2, 1, 6, 7, 8],
|
||||
|
||||
"ProcCursor": 4,
|
||||
|
||||
|
@ -12,8 +12,7 @@ func init() {
|
||||
|
||||
BattLines: []int{4, 3, 2, 1, 5, 6, 7, 8},
|
||||
|
||||
MainMem: 5,
|
||||
SwapMem: 3,
|
||||
MemLines: []int{5, 3, 4, 2, 1, 6, 7, 8, 11},
|
||||
|
||||
ProcCursor: 33,
|
||||
|
||||
|
@ -12,8 +12,7 @@ func init() {
|
||||
|
||||
BattLines: []int{81, 70, 208, 197, 249, 141, 221, 186},
|
||||
|
||||
MainMem: 208,
|
||||
SwapMem: 186,
|
||||
MemLines: []int{208, 186, 81, 70, 208, 197, 249, 141, 221, 186},
|
||||
|
||||
ProcCursor: 197,
|
||||
|
||||
|
@ -25,8 +25,7 @@ func init() {
|
||||
|
||||
BattLines: []int{4, 3, 2, 1, 5, 6, 7, 8},
|
||||
|
||||
MainMem: 172, // Orange
|
||||
SwapMem: 221, // yellow
|
||||
MemLines: []int{172, 221, 4, 3, 2, 1, 5, 6, 7, 8},
|
||||
|
||||
ProcCursor: 31, // blue (nord9)
|
||||
|
||||
|
@ -15,8 +15,7 @@ func init() {
|
||||
|
||||
BattLines: []int{61, 33, 37, 64, 125, 160, 166, 136},
|
||||
|
||||
MainMem: 125,
|
||||
SwapMem: 166,
|
||||
MemLines: []int{125, 166, 61, 33, 37, 64, 125, 160, 166, 136},
|
||||
|
||||
ProcCursor: 136,
|
||||
|
||||
|
@ -14,8 +14,7 @@ func init() {
|
||||
|
||||
BattLines: []int{13, 4, 6, 2, 5, 1, 9, 3},
|
||||
|
||||
MainMem: 5,
|
||||
SwapMem: 9,
|
||||
MemLines: []int{5, 9, 13, 4, 6, 2, 1, 3},
|
||||
|
||||
ProcCursor: 4,
|
||||
|
||||
|
@ -14,8 +14,7 @@ func init() {
|
||||
|
||||
BattLines: []int{13, 4, 6, 2, 5, 1, 9, 3},
|
||||
|
||||
MainMem: 5,
|
||||
SwapMem: 9,
|
||||
MemLines: []int{5, 9, 13, 4, 6, 2, 1, 3},
|
||||
|
||||
ProcCursor: 4,
|
||||
|
||||
|
@ -32,8 +32,7 @@ type Colorscheme struct {
|
||||
|
||||
BattLines []int
|
||||
|
||||
MainMem int
|
||||
SwapMem int
|
||||
MemLines []int
|
||||
|
||||
ProcCursor int
|
||||
|
||||
|
@ -12,8 +12,7 @@ func init() {
|
||||
|
||||
BattLines: []int{212, 218, 123, 159, 229, 158, 183, 146},
|
||||
|
||||
MainMem: 201,
|
||||
SwapMem: 97,
|
||||
MemLines: []int{201, 97, 212, 218, 123, 159, 229, 158, 183, 146},
|
||||
|
||||
ProcCursor: 159,
|
||||
|
||||
|
@ -30,6 +30,8 @@ type Config struct {
|
||||
NetInterface string
|
||||
Layout string
|
||||
MaxLogSize int64
|
||||
ExportPort string
|
||||
Extensions []string
|
||||
}
|
||||
|
||||
func Parse(in io.Reader, conf *Config) error {
|
||||
@ -109,6 +111,10 @@ func Parse(in io.Reader, conf *Config) error {
|
||||
return err
|
||||
}
|
||||
conf.MaxLogSize = int64(iv)
|
||||
case "export":
|
||||
conf.ExportPort = kv[1]
|
||||
case "extensions":
|
||||
conf.Extensions = strings.Split(kv[1], ",")
|
||||
}
|
||||
}
|
||||
|
||||
|
34
devices/cpu.go
Normal file
34
devices/cpu.go
Normal file
@ -0,0 +1,34 @@
|
||||
package devices
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
var cpuFuncs []func(map[string]int, time.Duration, bool) map[string]error
|
||||
|
||||
// RegisterCPU adds a new CPU device to the CPU widget. labels returns the
|
||||
// names of the devices; they should be as short as possible, and the indexes
|
||||
// of the returned slice should align with the values returned by the percents
|
||||
// function. The percents function should return the percent CPU usage of the
|
||||
// device(s), sliced over the time duration supplied. If the bool argument to
|
||||
// percents is true, it is expected that the return slice
|
||||
//
|
||||
// labels may be called once and the value cached. This means the number of
|
||||
// cores should not change dynamically.
|
||||
func RegisterCPU(f func(map[string]int, time.Duration, bool) map[string]error) {
|
||||
cpuFuncs = append(cpuFuncs, f)
|
||||
}
|
||||
|
||||
// CPUPercent calculates the percentage of cpu used either per CPU or combined.
|
||||
// Returns one value per cpu, or a single value if percpu is set to false.
|
||||
func UpdateCPU(cpus map[string]int, interval time.Duration, logical bool) {
|
||||
for _, f := range cpuFuncs {
|
||||
errs := f(cpus, interval, logical)
|
||||
if errs != nil {
|
||||
for k, e := range errs {
|
||||
log.Printf("%s: %s", k, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
devices/cpu_cpu.go
Normal file
31
devices/cpu_cpu.go
Normal file
@ -0,0 +1,31 @@
|
||||
package devices
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
psCpu "github.com/shirou/gopsutil/cpu"
|
||||
)
|
||||
|
||||
func init() {
|
||||
f := func(cpus map[string]int, iv time.Duration, l bool) map[string]error {
|
||||
cpuCount, err := psCpu.Counts(l)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
formatString := "CPU%1d"
|
||||
if cpuCount > 10 {
|
||||
formatString = "CPU%02d"
|
||||
}
|
||||
vals, err := psCpu.Percent(iv, l)
|
||||
if err != nil {
|
||||
return map[string]error{"gopsutil": err}
|
||||
}
|
||||
for i := 0; i < len(vals); i++ {
|
||||
key := fmt.Sprintf(formatString, i)
|
||||
cpus[key] = int(vals[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
RegisterCPU(f)
|
||||
}
|
26
devices/devices.go
Normal file
26
devices/devices.go
Normal file
@ -0,0 +1,26 @@
|
||||
package devices
|
||||
|
||||
import "log"
|
||||
|
||||
var shutdownFuncs []func() error
|
||||
|
||||
// RegisterShutdown stores a function to be called by gotop on exit, allowing
|
||||
// extensions to properly release resources. Extensions should register a
|
||||
// shutdown function IFF the extension is using resources that need to be
|
||||
// released. The returned error will be logged, but no other action will be
|
||||
// taken.
|
||||
func RegisterShutdown(f func() error) {
|
||||
shutdownFuncs = append(shutdownFuncs, f)
|
||||
}
|
||||
|
||||
// Shutdown will be called by the `main()` function if gotop is exited
|
||||
// cleanly. It will call all of the registered shutdown functions of devices,
|
||||
// logging all errors but otherwise not responding to them.
|
||||
func Shutdown() {
|
||||
for _, f := range shutdownFuncs {
|
||||
err := f()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
}
|
26
devices/mem.go
Normal file
26
devices/mem.go
Normal file
@ -0,0 +1,26 @@
|
||||
package devices
|
||||
|
||||
import "log"
|
||||
|
||||
var memFuncs []func(map[string]MemoryInfo) map[string]error
|
||||
|
||||
type MemoryInfo struct {
|
||||
Total uint64
|
||||
Used uint64
|
||||
UsedPercent float64
|
||||
}
|
||||
|
||||
func RegisterMem(f func(map[string]MemoryInfo) map[string]error) {
|
||||
memFuncs = append(memFuncs, f)
|
||||
}
|
||||
|
||||
func UpdateMem(mem map[string]MemoryInfo) {
|
||||
for _, f := range memFuncs {
|
||||
errs := f(mem)
|
||||
if errs != nil {
|
||||
for k, e := range errs {
|
||||
log.Printf("%s: %s", k, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
devices/mem_mem.go
Normal file
21
devices/mem_mem.go
Normal file
@ -0,0 +1,21 @@
|
||||
package devices
|
||||
|
||||
import (
|
||||
psMem "github.com/shirou/gopsutil/mem"
|
||||
)
|
||||
|
||||
func init() {
|
||||
mf := func(mems map[string]MemoryInfo) map[string]error {
|
||||
mainMemory, err := psMem.VirtualMemory()
|
||||
if err != nil {
|
||||
return map[string]error{"Main": err}
|
||||
}
|
||||
mems["Main"] = MemoryInfo{
|
||||
Total: mainMemory.Total,
|
||||
Used: mainMemory.Used,
|
||||
UsedPercent: mainMemory.UsedPercent,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
RegisterMem(mf)
|
||||
}
|
44
devices/mem_swap_freebsd.go
Normal file
44
devices/mem_swap_freebsd.go
Normal file
@ -0,0 +1,44 @@
|
||||
// +build freebsd
|
||||
|
||||
package devices
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
mf := func(mems map[string]MemoryInfo) map[string]error {
|
||||
cmd := "swapinfo -k|sed -n '1!p'|awk '{print $2,$3,$5}'"
|
||||
output, err := exec.Command("sh", "-c", cmd).Output()
|
||||
if err != nil {
|
||||
return map[string]error{"swapinfo": err}
|
||||
}
|
||||
|
||||
s := strings.TrimSuffix(string(output), "\n")
|
||||
s = strings.ReplaceAll(s, "\n", " ")
|
||||
ss := strings.Split(s, " ")
|
||||
ss = ss[((len(ss)/3)-1)*3:]
|
||||
|
||||
errors := make(map[string]error)
|
||||
mem := MemoryInfo{}
|
||||
mem.Total, err = strconv.ParseUint(ss[0], 10, 64)
|
||||
if err != nil {
|
||||
errors["swap total"] = err
|
||||
}
|
||||
|
||||
mem.Used, err = strconv.ParseUint(ss[1], 10, 64)
|
||||
if err != nil {
|
||||
errors["swap used"] = err
|
||||
}
|
||||
|
||||
mem.UsedPercent, err = strconv.ParseFloat(strings.TrimSuffix(ss[2], "%"), 64)
|
||||
if err != nil {
|
||||
errors["swap percent"] = err
|
||||
}
|
||||
mems["Swap"] = mem
|
||||
return errors
|
||||
}
|
||||
RegisterMem(mf)
|
||||
}
|
23
devices/mem_swap_other.go
Normal file
23
devices/mem_swap_other.go
Normal file
@ -0,0 +1,23 @@
|
||||
// +build !freebsd
|
||||
|
||||
package devices
|
||||
|
||||
import (
|
||||
psMem "github.com/shirou/gopsutil/mem"
|
||||
)
|
||||
|
||||
func init() {
|
||||
mf := func(mems map[string]MemoryInfo) map[string]error {
|
||||
memory, err := psMem.SwapMemory()
|
||||
if err != nil {
|
||||
return map[string]error{"Swap": err}
|
||||
}
|
||||
mems["Swap"] = MemoryInfo{
|
||||
Total: memory.Total,
|
||||
Used: memory.Used,
|
||||
UsedPercent: memory.UsedPercent,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
RegisterMem(mf)
|
||||
}
|
22
devices/temp.go
Normal file
22
devices/temp.go
Normal file
@ -0,0 +1,22 @@
|
||||
package devices
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
var tempUpdates []func(map[string]int) map[string]error
|
||||
|
||||
func RegisterTemp(update func(map[string]int) map[string]error) {
|
||||
tempUpdates = append(tempUpdates, update)
|
||||
}
|
||||
|
||||
func UpdateTemps(temps map[string]int) {
|
||||
for _, f := range tempUpdates {
|
||||
errs := f(temps)
|
||||
if errs != nil {
|
||||
for k, e := range errs {
|
||||
log.Printf("error updating temp for %s: %s", k, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +1,16 @@
|
||||
// +build darwin
|
||||
|
||||
package widgets
|
||||
package devices
|
||||
|
||||
// #cgo LDFLAGS: -framework IOKit
|
||||
// #include "include/smc.c"
|
||||
import "C"
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
|
||||
type TemperatureStat struct {
|
||||
SensorKey string `json:"sensorKey"`
|
||||
Temperature float64 `json:"sensorTemperature"`
|
||||
func init() {
|
||||
RegisterTemp(update)
|
||||
}
|
||||
|
||||
func SensorsTemperatures() ([]TemperatureStat, error) {
|
||||
func update(temps map[string]int) map[string]error {
|
||||
temperatureKeys := map[string]string{
|
||||
C.AMBIENT_AIR_0: "ambient_air_0",
|
||||
C.AMBIENT_AIR_1: "ambient_air_1",
|
||||
@ -41,34 +35,12 @@ func SensorsTemperatures() ([]TemperatureStat, error) {
|
||||
C.WIRELESS_MODULE: "wireless_module",
|
||||
}
|
||||
|
||||
var temperatures []TemperatureStat
|
||||
|
||||
C.open_smc()
|
||||
defer C.close_smc()
|
||||
|
||||
for key, val := range temperatureKeys {
|
||||
temperatures = append(temperatures, TemperatureStat{
|
||||
SensorKey: val,
|
||||
Temperature: float64(C.get_tmp(C.CString(key), C.CELSIUS)),
|
||||
})
|
||||
temps[val] = int(C.get_tmp(C.CString(key), C.CELSIUS))
|
||||
}
|
||||
return temperatures, nil
|
||||
}
|
||||
|
||||
func (self *TempWidget) update() {
|
||||
sensors, err := SensorsTemperatures()
|
||||
if err != nil {
|
||||
log.Printf("failed to get sensors from CGO: %v", err)
|
||||
return
|
||||
}
|
||||
for _, sensor := range sensors {
|
||||
if sensor.Temperature != 0 {
|
||||
switch self.TempScale {
|
||||
case Fahrenheit:
|
||||
self.Data[sensor.SensorKey] = utils.CelsiusToFahrenheit(int(sensor.Temperature))
|
||||
case Celsius:
|
||||
self.Data[sensor.SensorKey] = int(sensor.Temperature)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
45
devices/temp_freebsd.go
Normal file
45
devices/temp_freebsd.go
Normal file
@ -0,0 +1,45 @@
|
||||
// +build freebsd
|
||||
|
||||
package devices
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTemp(update)
|
||||
}
|
||||
|
||||
var sensorOIDS = map[string]string{
|
||||
"dev.cpu.0.temperature": "CPU 0 ",
|
||||
"hw.acpi.thermal.tz0.temperature": "Thermal zone 0",
|
||||
}
|
||||
|
||||
func update(temps map[string]int) map[string]error {
|
||||
var errors map[string]error
|
||||
|
||||
for k, v := range sensorOIDS {
|
||||
output, err := exec.Command("sysctl", "-n", k).Output()
|
||||
if err != nil {
|
||||
errors[v] = err
|
||||
continue
|
||||
}
|
||||
|
||||
s1 := strings.Replace(string(output), "C", "", 1)
|
||||
s2 := strings.TrimSuffix(s1, "\n")
|
||||
convertedOutput := utils.ConvertLocalizedString(s2)
|
||||
value, err := strconv.ParseFloat(convertedOutput, 64)
|
||||
if err != nil {
|
||||
errors[v] = err
|
||||
continue
|
||||
}
|
||||
|
||||
temps[v] = int(value)
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
@ -1,32 +1,29 @@
|
||||
// +build linux
|
||||
|
||||
package widgets
|
||||
package devices
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
psHost "github.com/shirou/gopsutil/host"
|
||||
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
|
||||
func (self *TempWidget) update() {
|
||||
func init() {
|
||||
RegisterTemp(getTemps)
|
||||
}
|
||||
|
||||
func getTemps(temps map[string]int) map[string]error {
|
||||
sensors, err := psHost.SensorsTemperatures()
|
||||
if err != nil {
|
||||
log.Printf("error received from gopsutil: %v", err)
|
||||
return map[string]error{"psHost": 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 {
|
||||
// removes '_input' from the end of the sensor name
|
||||
label := sensor.SensorKey[:strings.Index(sensor.SensorKey, "_input")]
|
||||
switch self.TempScale {
|
||||
case Fahrenheit:
|
||||
self.Data[label] = utils.CelsiusToFahrenheit(int(sensor.Temperature))
|
||||
case Celsius:
|
||||
self.Data[label] = int(sensor.Temperature)
|
||||
}
|
||||
temps[label] = int(sensor.Temperature)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// +build openbsd
|
||||
|
||||
package widgets
|
||||
package devices
|
||||
|
||||
// loosely based on https://github.com/openbsd/src/blob/master/sbin/sysctl/sysctl.c#L2517
|
||||
|
||||
@ -13,42 +13,13 @@ import (
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
|
||||
func (self *TempWidget) getTemp(mib []C.int, mlen int, snsrdev *C.struct_sensordev, index int) {
|
||||
switch mlen {
|
||||
case 4:
|
||||
k := mib[3]
|
||||
var numt C.int
|
||||
for numt = 0; numt < snsrdev.maxnumt[k]; numt++ {
|
||||
mib[4] = numt
|
||||
self.getTemp(mib, mlen+1, snsrdev, int(numt))
|
||||
}
|
||||
case 5:
|
||||
var snsr C.struct_sensor
|
||||
var slen C.size_t = C.sizeof_struct_sensor
|
||||
|
||||
if v, _ := C.sysctl(&mib[0], 5, unsafe.Pointer(&snsr), &slen, nil, 0); v == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
if slen > 0 && (snsr.flags&C.SENSOR_FINVALID) == 0 {
|
||||
key := C.GoString(&snsrdev.xname[0]) + ".temp" + strconv.Itoa(index)
|
||||
temp := int((snsr.value - 273150000.0) / 1000000.0)
|
||||
|
||||
switch self.TempScale {
|
||||
case Fahrenheit:
|
||||
self.Data[key] = utils.CelsiusToFahrenheit(temp)
|
||||
case Celsius:
|
||||
self.Data[key] = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
func init() {
|
||||
RegisterTemp(update)
|
||||
}
|
||||
|
||||
func (self *TempWidget) update() {
|
||||
func update(temps map[string]int) map[string]error {
|
||||
mib := []C.int{0, 1, 2, 3, 4}
|
||||
|
||||
var snsrdev C.struct_sensordev
|
||||
@ -69,6 +40,33 @@ func (self *TempWidget) update() {
|
||||
break
|
||||
}
|
||||
}
|
||||
self.getTemp(mib, 4, &snsrdev, 0)
|
||||
getTemp(temps, mib, 4, &snsrdev, 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTemp(temps map[string]int, mib []C.int, mlen int, snsrdev *C.struct_sensordev, index int) {
|
||||
switch mlen {
|
||||
case 4:
|
||||
k := mib[3]
|
||||
var numt C.int
|
||||
for numt = 0; numt < snsrdev.maxnumt[k]; numt++ {
|
||||
mib[4] = numt
|
||||
getTemp(temps, mib, mlen+1, snsrdev, int(numt))
|
||||
}
|
||||
case 5:
|
||||
var snsr C.struct_sensor
|
||||
var slen C.size_t = C.sizeof_struct_sensor
|
||||
|
||||
if v, _ := C.sysctl(&mib[0], 5, unsafe.Pointer(&snsr), &slen, nil, 0); v == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
if slen > 0 && (snsr.flags&C.SENSOR_FINVALID) == 0 {
|
||||
key := C.GoString(&snsrdev.xname[0]) + ".temp" + strconv.Itoa(index)
|
||||
temp := int((snsr.value - 273150000.0) / 1000000.0)
|
||||
|
||||
temps[key] = temp
|
||||
}
|
||||
}
|
||||
}
|
24
devices/temp_windows.go
Normal file
24
devices/temp_windows.go
Normal file
@ -0,0 +1,24 @@
|
||||
// +build windows
|
||||
|
||||
package devices
|
||||
|
||||
import (
|
||||
psHost "github.com/shirou/gopsutil/host"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTemp(update)
|
||||
}
|
||||
|
||||
func update(temps map[string]int) map[string]error {
|
||||
sensors, err := psHost.SensorsTemperatures()
|
||||
if err != nil {
|
||||
return map[string]error{"gopsutil": err}
|
||||
}
|
||||
for _, sensor := range sensors {
|
||||
if sensor.Temperature != 0 {
|
||||
temps[sensor.SensorKey] = int(sensor.Temperature)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
22
docs/extensions.md
Normal file
22
docs/extensions.md
Normal file
@ -0,0 +1,22 @@
|
||||
% Plugins
|
||||
|
||||
|
||||
# Extensions
|
||||
|
||||
- Plugins will supply an `Init()` function that will call the appropriate
|
||||
`Register\*()` functions in the `github.com/xxxserxxx/gotop/devices` package.
|
||||
- `devices` will supply:
|
||||
- RegisterCPU (opt)
|
||||
- Counts (req)
|
||||
- Percents (req)
|
||||
- RegisterMem (opt)
|
||||
- RegisterTemp (opt)
|
||||
- RegisterShutdown (opt)
|
||||
|
||||
# gotop
|
||||
|
||||
- Command line -P, comma separated list of plugin .so
|
||||
- gotop will look in `pwd` and then in \$XDG_CONFIG_HOME/gotop
|
||||
- When loaded, gotop will call lib#Init()
|
||||
|
||||
When exited cleanly, gotop will call all registered shutdown functions.
|
42
docs/grid-fill.md
Normal file
42
docs/grid-fill.md
Normal file
@ -0,0 +1,42 @@
|
||||
T is max height in row
|
||||
S(T) is all widgets with height T
|
||||
R(T) is all widgets with height < T
|
||||
X is len(R) > 0 ? 1 : 0
|
||||
C is len(S) + X
|
||||
Make row
|
||||
Make C columns
|
||||
Place S
|
||||
Recurse with R; place result
|
||||
|
||||
|
||||
1 2 3 4 5
|
||||
cpu/2............... mem/1. 6:procs/2..........
|
||||
3:temp/1. 2:disk/2......... |..................
|
||||
|........ |................ |..................
|
||||
|........ power/2.......... |..................
|
||||
net/2............... batt.. |..................
|
||||
|
||||
1 2 3 4 5
|
||||
cpu/2............... 6:procs/2........ mem/1...
|
||||
2:disk/2............ |................ 3:temp/1
|
||||
|................... |................ |.......
|
||||
power/2............. |................ |.......
|
||||
net/2............... |................ batt
|
||||
|
||||
1 2 3 4 5
|
||||
1x2................. 3x2.............. 1x1..... 221 221
|
||||
2x2................. ||||||||||||||||| 3x1..... 21 2x1
|
||||
|||||||||||||||||||| ||||||||||||||||| ||||||||
|
||||
1x1...... 1x1...... 1x2.............. 1x1..... 1121
|
||||
1x2................. 1x2.............. |||||||| 22 22x
|
||||
1x1...... 1x4................................... 14
|
||||
|
||||
initial columns = initial row
|
||||
fill
|
||||
pattern for row
|
||||
does pattern fit columns?
|
||||
yes: place widgets
|
||||
no: new row w/ new columns; fill
|
||||
|
||||
does fit
|
||||
cw < patt_c_w
|
37
docs/releasing.md
Normal file
37
docs/releasing.md
Normal file
@ -0,0 +1,37 @@
|
||||
Current steps for a release:
|
||||
|
||||
### gotop
|
||||
1. Update Version in main.go
|
||||
2. Update CHANGELOG.md
|
||||
3. Tag
|
||||
4. Push everything
|
||||
5. ./make.sh
|
||||
6. Create github release
|
||||
|
||||
### Homebrew
|
||||
1. Change homebrew-gotop
|
||||
```
|
||||
curl --output - -L https://github.com/xxxserxxx/gotop/releases/download/v3.3.2/gotop_3.3.2_linux_amd64.tgz | sha256sum
|
||||
curl --output - -L https://github.com/xxxserxxx/gotop/releases/download/v3.3.2/gotop_3.3.2_darwin_amd64.tgz | sha256sum
|
||||
```
|
||||
|
||||
### AUR
|
||||
1. Update aur/PKGBUILD
|
||||
2. namcap PKGBUILD
|
||||
3. makepkg
|
||||
4. makepkg -g
|
||||
5. makepkg --printsrcinfo \> .SRCINFO
|
||||
6. Commit everything
|
||||
7. push
|
||||
```
|
||||
curl -L https://github.com/xxxserxxx/gotop/archive/v3.3.2.tar.gz | sha256sum
|
||||
```
|
||||
|
||||
### AUR-BIN
|
||||
1. Update aur-bin/PKGBUILD
|
||||
2. namcap PKGBUILD
|
||||
3. makepkg
|
||||
4. makepkg -g
|
||||
5. makepkg --printsrcinfo \> .SRCINFO
|
||||
6. Commit everything
|
||||
7. push aur-bin
|
4
go.mod
4
go.mod
@ -1,4 +1,4 @@
|
||||
module github.com/xxxserxxx/gotop
|
||||
module github.com/xxxserxxx/gotop/v3
|
||||
|
||||
require (
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
|
||||
@ -15,4 +15,4 @@ require (
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
|
||||
)
|
||||
|
||||
go 1.12
|
||||
go 1.13
|
||||
|
32
go.sum
32
go.sum
@ -1,7 +1,5 @@
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@ -14,29 +12,21 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd h1:XtfPmj9tQRilnrEmI1HjQhxXWRhEM+m8CACtaMJE/kM=
|
||||
github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd/go.mod h1:vjcQJUZJYD3MeVGhtZXSMnCHfUNZxsyYzJt90eCYxK4=
|
||||
github.com/cjbassi/drawille-go v0.1.0/go.mod h1:vjcQJUZJYD3MeVGhtZXSMnCHfUNZxsyYzJt90eCYxK4=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/distatus/battery v0.9.0 h1:8NS5o00/j3Oh2xgocA6pQROTp5guoR+s8CZlWzHC4QM=
|
||||
github.com/distatus/battery v0.9.0/go.mod h1:gGO7GxHTi1zlRT+cAj8uGG0/8HFiqAeH0TJvoipnuPs=
|
||||
github.com/distatus/battery v0.10.0 h1:YbizvmV33mqqC1fPCAEaQGV3bBhfYOfM+2XmL+mvt5o=
|
||||
github.com/distatus/battery v0.10.0/go.mod h1:STnSvFLX//eEpkaN7qWRxCWxrWOcssTDgnG4yqq9BRE=
|
||||
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815 h1:HMAfwOa33y82IaQEKQDfUCiwNlxtM1iw7HLM9ru0RNc=
|
||||
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:l7JNRynTRuqe45tpIyItHNqZWTxywYjp87MWTOnU5cg=
|
||||
github.com/gizak/termui/v3 v3.0.0 h1:NYTUG6ig/sJK05O5FyhWemwlVPO8ilNpvS/PgRtrKAE=
|
||||
github.com/gizak/termui/v3 v3.0.0/go.mod h1:uinu2dMdtMI+FTIdEFUJQT5y+KShnhQRshvPblXq3lY=
|
||||
github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
|
||||
github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@ -44,6 +34,7 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@ -57,15 +48,11 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
@ -76,9 +63,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
||||
github.com/nsf/termbox-go v0.0.0-20200204031403-4d2b513ad8be h1:yzmWtPyxEUIKdZg4RcPq64MfS8NA6A5fNOJgYhpR9EQ=
|
||||
github.com/nsf/termbox-go v0.0.0-20200204031403-4d2b513ad8be/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@ -99,20 +85,17 @@ github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLk
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/shirou/gopsutil v2.18.11+incompatible h1:PMFTKnFTr/YTRW5rbLK4vWALV3a+IGXse5nvhSjztmg=
|
||||
github.com/shirou/gopsutil v2.18.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
|
||||
github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045 h1:Pn8fQdvx+z1avAi7fdM2kRYWQNxGlavNDSyzrQg2SsU=
|
||||
golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@ -125,21 +108,18 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||
|
@ -31,17 +31,22 @@ var widgetNames []string = []string{"cpu", "disk", "mem", "temp", "net", "procs"
|
||||
|
||||
func Layout(wl layout, c gotop.Config) (*MyGrid, error) {
|
||||
rowDefs := wl.Rows
|
||||
uiRows := make([]ui.GridItem, 0)
|
||||
uiRows := make([][]interface{}, 0)
|
||||
numRows := countNumRows(wl.Rows)
|
||||
var uiRow ui.GridItem
|
||||
var uiRow []interface{}
|
||||
maxHeight := 0
|
||||
heights := make([]int, 0)
|
||||
var h int
|
||||
for len(rowDefs) > 0 {
|
||||
uiRow, rowDefs = processRow(c, numRows, rowDefs)
|
||||
h, uiRow, rowDefs = processRow(c, numRows, rowDefs)
|
||||
maxHeight += h
|
||||
uiRows = append(uiRows, uiRow)
|
||||
heights = append(heights, h)
|
||||
}
|
||||
rgs := make([]interface{}, 0)
|
||||
for _, ur := range uiRows {
|
||||
ur.HeightRatio = ur.HeightRatio / float64(numRows)
|
||||
rgs = append(rgs, ur)
|
||||
for i, ur := range uiRows {
|
||||
rh := float64(heights[i]) / float64(maxHeight)
|
||||
rgs = append(rgs, ui.NewRow(rh, ur...))
|
||||
}
|
||||
grid := &MyGrid{ui.NewGrid(), nil, nil}
|
||||
grid.Set(rgs...)
|
||||
@ -58,10 +63,10 @@ func Layout(wl layout, c gotop.Config) (*MyGrid, error) {
|
||||
// if there's a row span widget in the row; in this case, it'll consume as many
|
||||
// rows as the largest row span object in the row, and produce an uber-row
|
||||
// containing all that stuff. It returns a slice without the consumed elements.
|
||||
func processRow(c gotop.Config, numRows int, rowDefs [][]widgetRule) (ui.GridItem, [][]widgetRule) {
|
||||
func processRow(c gotop.Config, numRows int, rowDefs [][]widgetRule) (int, []interface{}, [][]widgetRule) {
|
||||
// Recursive function #3. See the comment in deepFindProc.
|
||||
if len(rowDefs) < 1 {
|
||||
return ui.GridItem{}, [][]widgetRule{}
|
||||
return 0, nil, [][]widgetRule{}
|
||||
}
|
||||
// The height of the tallest widget in this row; the number of rows that
|
||||
// will be consumed, and the overall height of the row that will be
|
||||
@ -86,9 +91,10 @@ func processRow(c gotop.Config, numRows int, rowDefs [][]widgetRule) (ui.GridIte
|
||||
columns = append(columns, make([]interface{}, 0))
|
||||
}
|
||||
colHeights := make([]int, numCols)
|
||||
outer:
|
||||
for i, row := range processing {
|
||||
// A definition may fill up the columns before all rows are consumed,
|
||||
// e.g. wid1/2 wid2/2. This block checks for that and, if it occurs,
|
||||
// e.g. cpu/2 net/2. This block checks for that and, if it occurs,
|
||||
// prepends the remaining rows to the "remainder" return value.
|
||||
full := true
|
||||
for _, ch := range colHeights {
|
||||
@ -101,16 +107,25 @@ func processRow(c gotop.Config, numRows int, rowDefs [][]widgetRule) (ui.GridIte
|
||||
rowDefs = append(processing[i:], rowDefs...)
|
||||
break
|
||||
}
|
||||
// Not all rows have been consumed, so go ahead and place the row's widgets in columns
|
||||
for _, wid := range row {
|
||||
for j, ch := range colHeights {
|
||||
if ch+wid.Height <= maxHeight {
|
||||
widget := makeWidget(c, wid)
|
||||
columns[j] = append(columns[j], ui.NewRow(float64(wid.Height)/float64(maxHeight), widget))
|
||||
colHeights[j] += wid.Height
|
||||
// Not all rows have been consumed, so go ahead and place the row's
|
||||
// widgets in columns
|
||||
for w, widg := range row {
|
||||
placed := false
|
||||
for k := w; k < len(colHeights); k++ { // there are enough columns
|
||||
ch := colHeights[k]
|
||||
if ch+widg.Height <= maxHeight {
|
||||
widget := makeWidget(c, widg)
|
||||
columns[k] = append(columns[k], ui.NewRow(float64(widg.Height)/float64(maxHeight), widget))
|
||||
colHeights[k] += widg.Height
|
||||
placed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// If all columns are full, break out, return the row, and continue processing
|
||||
if !placed {
|
||||
rowDefs = append(processing[i:], rowDefs...)
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
var uiColumns []interface{}
|
||||
@ -120,11 +135,15 @@ func processRow(c gotop.Config, numRows int, rowDefs [][]widgetRule) (ui.GridIte
|
||||
}
|
||||
}
|
||||
|
||||
return ui.NewRow(1.0/float64(numRows), uiColumns...), rowDefs
|
||||
return maxHeight, uiColumns, rowDefs
|
||||
}
|
||||
|
||||
type Metric interface {
|
||||
EnableMetric()
|
||||
}
|
||||
|
||||
func makeWidget(c gotop.Config, widRule widgetRule) interface{} {
|
||||
var w interface{}
|
||||
var w Metric
|
||||
switch widRule.Widget {
|
||||
case "cpu":
|
||||
cpu := widgets.NewCpuWidget(c.UpdateInterval, c.GraphHorizontalScale, c.AverageLoad, c.PercpuLoad)
|
||||
@ -145,11 +164,19 @@ func makeWidget(c gotop.Config, widRule widgetRule) interface{} {
|
||||
}
|
||||
w = cpu
|
||||
case "disk":
|
||||
w = widgets.NewDiskWidget()
|
||||
dw := widgets.NewDiskWidget()
|
||||
w = dw
|
||||
case "mem":
|
||||
m := widgets.NewMemWidget(c.UpdateInterval, c.GraphHorizontalScale)
|
||||
m.LineColors["Main"] = ui.Color(c.Colorscheme.MainMem)
|
||||
m.LineColors["Swap"] = ui.Color(c.Colorscheme.SwapMem)
|
||||
var i int
|
||||
for key, _ := range m.Data {
|
||||
if i >= len(c.Colorscheme.MemLines) {
|
||||
i = 0
|
||||
}
|
||||
color := c.Colorscheme.MemLines[i]
|
||||
m.LineColors[key] = ui.Color(color)
|
||||
i++
|
||||
}
|
||||
w = m
|
||||
case "temp":
|
||||
t := widgets.NewTempWidget(c.TempScale)
|
||||
@ -185,10 +212,17 @@ func makeWidget(c gotop.Config, widRule widgetRule) interface{} {
|
||||
i++
|
||||
}
|
||||
w = b
|
||||
case "power":
|
||||
b := widgets.NewBatteryGauge()
|
||||
b.BarColor = ui.Color(c.Colorscheme.ProcCursor)
|
||||
w = b
|
||||
default:
|
||||
log.Printf("Invalid widget name %s. Must be one of %v", widRule.Widget, widgetNames)
|
||||
return ui.NewBlock()
|
||||
}
|
||||
if c.ExportPort != "" {
|
||||
w.EnableMetric()
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
|
@ -101,6 +101,33 @@ func TestParsing(t *testing.T) {
|
||||
assert.Equal(t, 1, l.Rows[1][0].Height)
|
||||
assert.Equal(t, 1.0, l.Rows[1][0].Weight)
|
||||
}},
|
||||
{"cpu/2 mem/1 6:procs\n3:temp/1 2:disk/2\npower\nnet procs", func(l layout) {
|
||||
assert.Equal(t, 4, len(l.Rows))
|
||||
// First row
|
||||
assert.Equal(t, 3, len(l.Rows[0]))
|
||||
assert.Equal(t, 1, l.Rows[0][0].Height)
|
||||
assert.Equal(t, 0.5, l.Rows[0][0].Weight)
|
||||
assert.Equal(t, 1, l.Rows[0][1].Height)
|
||||
assert.Equal(t, 0.25, l.Rows[0][1].Weight)
|
||||
assert.Equal(t, 6, l.Rows[0][2].Height)
|
||||
assert.Equal(t, 0.25, l.Rows[0][2].Weight)
|
||||
// Second row
|
||||
assert.Equal(t, 2, len(l.Rows[1]))
|
||||
assert.Equal(t, 3, l.Rows[1][0].Height)
|
||||
assert.Equal(t, 1/3.0, l.Rows[1][0].Weight)
|
||||
assert.Equal(t, 2, l.Rows[1][1].Height)
|
||||
assert.Equal(t, 2/3.0, l.Rows[1][1].Weight)
|
||||
// Third row
|
||||
assert.Equal(t, 1, len(l.Rows[2]))
|
||||
assert.Equal(t, 1, l.Rows[2][0].Height)
|
||||
assert.Equal(t, 1.0, l.Rows[2][0].Weight)
|
||||
// Fourth row
|
||||
assert.Equal(t, 2, len(l.Rows[3]))
|
||||
assert.Equal(t, 1, l.Rows[3][0].Height)
|
||||
assert.Equal(t, 0.5, l.Rows[3][0].Weight)
|
||||
assert.Equal(t, 1, l.Rows[3][1].Height)
|
||||
assert.Equal(t, 0.5, l.Rows[3][1].Weight)
|
||||
}},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
4
layouts/kitchensink
Normal file
4
layouts/kitchensink
Normal file
@ -0,0 +1,4 @@
|
||||
3:cpu/2 3:mem/1
|
||||
4:temp/1 3:disk/2
|
||||
power
|
||||
3:net 3:procs
|
4
layouts/many_columns_test
Normal file
4
layouts/many_columns_test
Normal file
@ -0,0 +1,4 @@
|
||||
cpu/2 mem/1 6:procs/2
|
||||
3:temp/1 2:disk/2
|
||||
power
|
||||
net procs
|
1
make.sh
1
make.sh
@ -92,6 +92,7 @@ function cdarwinz() {
|
||||
cd darwin
|
||||
else
|
||||
cd darwin
|
||||
git checkout -- .
|
||||
git pull
|
||||
fi
|
||||
export CGO_ENABLED=1
|
||||
|
22
termui/gauge.go
Normal file
22
termui/gauge.go
Normal file
@ -0,0 +1,22 @@
|
||||
package termui
|
||||
|
||||
import (
|
||||
. "github.com/gizak/termui/v3"
|
||||
gizak "github.com/gizak/termui/v3/widgets"
|
||||
)
|
||||
|
||||
// LineGraph implements a line graph of data points.
|
||||
type Gauge struct {
|
||||
*gizak.Gauge
|
||||
}
|
||||
|
||||
func NewGauge() *Gauge {
|
||||
return &Gauge{
|
||||
Gauge: gizak.NewGauge(),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Gauge) Draw(buf *Buffer) {
|
||||
self.Gauge.Draw(buf)
|
||||
self.Gauge.SetRect(self.Min.X, self.Min.Y, self.Inner.Dx(), self.Inner.Dy())
|
||||
}
|
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/distatus/battery"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
ui "github.com/xxxserxxx/gotop/termui"
|
||||
)
|
||||
@ -15,6 +16,7 @@ import (
|
||||
type BatteryWidget struct {
|
||||
*ui.LineGraph
|
||||
updateInterval time.Duration
|
||||
metric []prometheus.Gauge
|
||||
}
|
||||
|
||||
func NewBatteryWidget(horizontalScale int) *BatteryWidget {
|
||||
@ -41,6 +43,25 @@ func NewBatteryWidget(horizontalScale int) *BatteryWidget {
|
||||
return self
|
||||
}
|
||||
|
||||
func (b *BatteryWidget) EnableMetric() {
|
||||
bats, err := battery.GetAll()
|
||||
if err != nil {
|
||||
log.Printf("error setting up metrics: %v", err)
|
||||
return
|
||||
}
|
||||
b.metric = make([]prometheus.Gauge, len(bats))
|
||||
for i, bat := range bats {
|
||||
gauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "gotop",
|
||||
Subsystem: "battery",
|
||||
Name: fmt.Sprintf("%d", i),
|
||||
})
|
||||
gauge.Set(bat.Current / bat.Full)
|
||||
b.metric[i] = gauge
|
||||
prometheus.MustRegister(gauge)
|
||||
}
|
||||
}
|
||||
|
||||
func makeId(i int) string {
|
||||
return "Batt" + strconv.Itoa(i)
|
||||
}
|
||||
@ -74,8 +95,12 @@ func (self *BatteryWidget) update() {
|
||||
}
|
||||
for i, battery := range batteries {
|
||||
id := makeId(i)
|
||||
percentFull := math.Abs(battery.Current/battery.Full) * 100.0
|
||||
perc := battery.Current / battery.Full
|
||||
percentFull := math.Abs(perc) * 100.0
|
||||
self.Data[id] = append(self.Data[id], percentFull)
|
||||
self.Labels[id] = fmt.Sprintf("%3.0f%% %.0f/%.0f", percentFull, math.Abs(battery.Current), math.Abs(battery.Full))
|
||||
if self.metric != nil {
|
||||
self.metric[i].Set(perc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
86
widgets/batterygauge.go
Normal file
86
widgets/batterygauge.go
Normal file
@ -0,0 +1,86 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/distatus/battery"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
. "github.com/xxxserxxx/gotop/termui"
|
||||
)
|
||||
|
||||
type BatteryGauge struct {
|
||||
*Gauge
|
||||
metric prometheus.Gauge
|
||||
}
|
||||
|
||||
func NewBatteryGauge() *BatteryGauge {
|
||||
self := &BatteryGauge{Gauge: NewGauge()}
|
||||
self.Title = " Power Level "
|
||||
|
||||
self.update()
|
||||
|
||||
go func() {
|
||||
for range time.NewTicker(time.Second).C {
|
||||
self.Lock()
|
||||
self.update()
|
||||
self.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (b *BatteryGauge) EnableMetric() {
|
||||
bats, err := battery.GetAll()
|
||||
if err != nil {
|
||||
log.Printf("error setting up metrics: %v", err)
|
||||
return
|
||||
}
|
||||
mx := 0.0
|
||||
cu := 0.0
|
||||
for _, bat := range bats {
|
||||
mx += bat.Full
|
||||
cu += bat.Current
|
||||
gauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "gotop",
|
||||
Subsystem: "battery",
|
||||
Name: "total",
|
||||
})
|
||||
gauge.Set(cu / mx)
|
||||
b.metric = gauge
|
||||
prometheus.MustRegister(gauge)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BatteryGauge) update() {
|
||||
bats, err := battery.GetAll()
|
||||
if err != nil {
|
||||
log.Printf("error setting up batteries: %v", err)
|
||||
return
|
||||
}
|
||||
mx := 0.0
|
||||
cu := 0.0
|
||||
charging := "%d%% ⚡%s"
|
||||
rate := 0.0
|
||||
for _, bat := range bats {
|
||||
mx += bat.Full
|
||||
cu += bat.Current
|
||||
if rate < bat.ChargeRate {
|
||||
rate = bat.ChargeRate
|
||||
}
|
||||
if bat.State == battery.Charging {
|
||||
charging = "%d%% 🔌%s"
|
||||
}
|
||||
}
|
||||
tn := (mx - cu) / rate
|
||||
d, _ := time.ParseDuration(fmt.Sprintf("%fh", tn))
|
||||
self.Percent = int((cu / mx) * 100.0)
|
||||
self.Label = fmt.Sprintf(charging, self.Percent, d.Truncate(time.Minute))
|
||||
if self.metric != nil {
|
||||
self.metric.Set(cu / mx)
|
||||
}
|
||||
}
|
103
widgets/cpu.go
103
widgets/cpu.go
@ -6,7 +6,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
psCpu "github.com/shirou/gopsutil/cpu"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/xxxserxxx/gotop/devices"
|
||||
|
||||
ui "github.com/xxxserxxx/gotop/termui"
|
||||
)
|
||||
@ -17,26 +18,19 @@ type CpuWidget struct {
|
||||
ShowAverageLoad bool
|
||||
ShowPerCpuLoad bool
|
||||
updateInterval time.Duration
|
||||
formatString string
|
||||
updateLock sync.Mutex
|
||||
metric map[string]prometheus.Gauge
|
||||
}
|
||||
|
||||
var cpuLabels []string
|
||||
|
||||
func NewCpuWidget(updateInterval time.Duration, horizontalScale int, showAverageLoad bool, showPerCpuLoad bool) *CpuWidget {
|
||||
cpuCount, err := psCpu.Counts(false)
|
||||
if err != nil {
|
||||
log.Printf("failed to get CPU count from gopsutil: %v", err)
|
||||
}
|
||||
formatString := "CPU%1d"
|
||||
if cpuCount > 10 {
|
||||
formatString = "CPU%02d"
|
||||
}
|
||||
self := &CpuWidget{
|
||||
LineGraph: ui.NewLineGraph(),
|
||||
CpuCount: cpuCount,
|
||||
CpuCount: len(cpuLabels),
|
||||
updateInterval: updateInterval,
|
||||
ShowAverageLoad: showAverageLoad,
|
||||
ShowPerCpuLoad: showPerCpuLoad,
|
||||
formatString: formatString,
|
||||
}
|
||||
self.Title = " CPU Usage "
|
||||
self.HorizontalScale = horizontalScale
|
||||
@ -54,9 +48,10 @@ func NewCpuWidget(updateInterval time.Duration, horizontalScale int, showAverage
|
||||
}
|
||||
|
||||
if self.ShowPerCpuLoad {
|
||||
for i := 0; i < int(self.CpuCount); i++ {
|
||||
key := fmt.Sprintf(formatString, i)
|
||||
self.Data[key] = []float64{0}
|
||||
cpus := make(map[string]int)
|
||||
devices.UpdateCPU(cpus, self.updateInterval, self.ShowPerCpuLoad)
|
||||
for k, v := range cpus {
|
||||
self.Data[k] = []float64{float64(v)}
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,6 +66,30 @@ func NewCpuWidget(updateInterval time.Duration, horizontalScale int, showAverage
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CpuWidget) EnableMetric() {
|
||||
if self.ShowAverageLoad {
|
||||
self.metric = make(map[string]prometheus.Gauge)
|
||||
self.metric["AVRG"] = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Subsystem: "cpu",
|
||||
Name: "avg",
|
||||
})
|
||||
} else {
|
||||
cpus := make(map[string]int)
|
||||
devices.UpdateCPU(cpus, self.updateInterval, self.ShowPerCpuLoad)
|
||||
self.metric = make(map[string]prometheus.Gauge)
|
||||
for key, perc := range cpus {
|
||||
gauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "gotop",
|
||||
Subsystem: "cpu",
|
||||
Name: key,
|
||||
})
|
||||
gauge.Set(float64(perc))
|
||||
prometheus.MustRegister(gauge)
|
||||
self.metric[key] = gauge
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CpuWidget) Scale(i int) {
|
||||
b.LineGraph.HorizontalScale = i
|
||||
}
|
||||
@ -78,37 +97,41 @@ func (b *CpuWidget) Scale(i int) {
|
||||
func (self *CpuWidget) update() {
|
||||
if self.ShowAverageLoad {
|
||||
go func() {
|
||||
percent, err := psCpu.Percent(self.updateInterval, false)
|
||||
if err != nil {
|
||||
log.Printf("failed to get average CPU usage percent from gopsutil: %v. self.updateInterval: %v. percpu: %v", err, self.updateInterval, false)
|
||||
} else {
|
||||
self.Lock()
|
||||
defer self.Unlock()
|
||||
self.updateLock.Lock()
|
||||
defer self.updateLock.Unlock()
|
||||
self.Data["AVRG"] = append(self.Data["AVRG"], percent[0])
|
||||
self.Labels["AVRG"] = fmt.Sprintf("%3.0f%%", percent[0])
|
||||
cpus := make(map[string]int)
|
||||
devices.UpdateCPU(cpus, self.updateInterval, false)
|
||||
self.Lock()
|
||||
defer self.Unlock()
|
||||
self.updateLock.Lock()
|
||||
defer self.updateLock.Unlock()
|
||||
var val float64
|
||||
for _, v := range cpus {
|
||||
val = float64(v)
|
||||
break
|
||||
}
|
||||
self.Data["AVRG"] = append(self.Data["AVRG"], val)
|
||||
self.Labels["AVRG"] = fmt.Sprintf("%3.0f%%", val)
|
||||
if self.metric != nil {
|
||||
self.metric["AVRG"].Set(val)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if self.ShowPerCpuLoad {
|
||||
go func() {
|
||||
percents, err := psCpu.Percent(self.updateInterval, true)
|
||||
if err != nil {
|
||||
log.Printf("failed to get CPU usage percents from gopsutil: %v. self.updateInterval: %v. percpu: %v", err, self.updateInterval, true)
|
||||
} else {
|
||||
if len(percents) != int(self.CpuCount) {
|
||||
log.Printf("error: number of CPU usage percents from gopsutil doesn't match CPU count. percents: %v. self.Count: %v", percents, self.CpuCount)
|
||||
} else {
|
||||
self.Lock()
|
||||
defer self.Unlock()
|
||||
self.updateLock.Lock()
|
||||
defer self.updateLock.Unlock()
|
||||
for i, percent := range percents {
|
||||
key := fmt.Sprintf(self.formatString, i)
|
||||
self.Data[key] = append(self.Data[key], percent)
|
||||
self.Labels[key] = fmt.Sprintf("%3.0f%%", percent)
|
||||
cpus := make(map[string]int)
|
||||
devices.UpdateCPU(cpus, self.updateInterval, true)
|
||||
self.Lock()
|
||||
defer self.Unlock()
|
||||
self.updateLock.Lock()
|
||||
defer self.updateLock.Unlock()
|
||||
for key, percent := range cpus {
|
||||
self.Data[key] = append(self.Data[key], float64(percent))
|
||||
self.Labels[key] = fmt.Sprintf("%d%%", percent)
|
||||
if self.metric != nil {
|
||||
if self.metric[key] == nil {
|
||||
log.Printf("no metrics for %s", key)
|
||||
} else {
|
||||
self.metric[key].Set(float64(percent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
psDisk "github.com/shirou/gopsutil/disk"
|
||||
|
||||
ui "github.com/xxxserxxx/gotop/termui"
|
||||
@ -28,6 +29,7 @@ type DiskWidget struct {
|
||||
*ui.Table
|
||||
updateInterval time.Duration
|
||||
Partitions map[string]*Partition
|
||||
metric map[string]prometheus.Gauge
|
||||
}
|
||||
|
||||
func NewDiskWidget() *DiskWidget {
|
||||
@ -60,6 +62,21 @@ func NewDiskWidget() *DiskWidget {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *DiskWidget) EnableMetric() {
|
||||
self.metric = make(map[string]prometheus.Gauge)
|
||||
for key, part := range self.Partitions {
|
||||
gauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "gotop",
|
||||
Subsystem: "disk",
|
||||
Name: strings.ReplaceAll(key, "/", ":"),
|
||||
//Name: strings.Replace(strings.Replace(part.Device, "/dev/", "", -1), "mapper/", "", -1),
|
||||
})
|
||||
gauge.Set(float64(part.UsedPercent) / 100.0)
|
||||
prometheus.MustRegister(gauge)
|
||||
self.metric[key] = gauge
|
||||
}
|
||||
}
|
||||
|
||||
func (self *DiskWidget) update() {
|
||||
partitions, err := psDisk.Partitions(false)
|
||||
if err != nil {
|
||||
@ -158,5 +175,12 @@ func (self *DiskWidget) update() {
|
||||
self.Rows[i][3] = partition.Free
|
||||
self.Rows[i][4] = partition.BytesReadRecently
|
||||
self.Rows[i][5] = partition.BytesWrittenRecently
|
||||
if self.metric != nil {
|
||||
if self.metric[key] == nil {
|
||||
log.Printf("ERROR: missing metric %s", key)
|
||||
} else {
|
||||
self.metric[key].Set(float64(partition.UsedPercent) / 100.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
102
widgets/mem.go
102
widgets/mem.go
@ -5,8 +5,9 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
psMem "github.com/shirou/gopsutil/mem"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/xxxserxxx/gotop/devices"
|
||||
ui "github.com/xxxserxxx/gotop/termui"
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
@ -14,15 +15,62 @@ import (
|
||||
type MemWidget struct {
|
||||
*ui.LineGraph
|
||||
updateInterval time.Duration
|
||||
metrics map[string]prometheus.Gauge
|
||||
}
|
||||
|
||||
type MemoryInfo struct {
|
||||
Total uint64
|
||||
Used uint64
|
||||
UsedPercent float64
|
||||
func NewMemWidget(updateInterval time.Duration, horizontalScale int) *MemWidget {
|
||||
self := &MemWidget{
|
||||
LineGraph: ui.NewLineGraph(),
|
||||
updateInterval: updateInterval,
|
||||
}
|
||||
self.Title = " Memory Usage "
|
||||
self.HorizontalScale = horizontalScale
|
||||
mems := make(map[string]devices.MemoryInfo)
|
||||
devices.UpdateMem(mems)
|
||||
for name, mem := range mems {
|
||||
log.Printf("setting %s to %v", name, mem)
|
||||
self.Data[name] = []float64{0}
|
||||
self.renderMemInfo(name, mem)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for range time.NewTicker(self.updateInterval).C {
|
||||
self.Lock()
|
||||
devices.UpdateMem(mems)
|
||||
for label, mi := range mems {
|
||||
log.Printf(" updating %s to %v", label, mi)
|
||||
self.renderMemInfo(label, mi)
|
||||
if self.metrics != nil && self.metrics[label] != nil {
|
||||
self.metrics[label].Set(mi.UsedPercent)
|
||||
}
|
||||
}
|
||||
self.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MemWidget) renderMemInfo(line string, memoryInfo MemoryInfo) {
|
||||
func (b *MemWidget) EnableMetric() {
|
||||
b.metrics = make(map[string]prometheus.Gauge)
|
||||
mems := make(map[string]devices.MemoryInfo)
|
||||
devices.UpdateMem(mems)
|
||||
for l, mem := range mems {
|
||||
b.metrics[l] = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "gotop",
|
||||
Subsystem: "memory",
|
||||
Name: l,
|
||||
})
|
||||
b.metrics[l].Set(mem.UsedPercent)
|
||||
prometheus.MustRegister(b.metrics[l])
|
||||
}
|
||||
}
|
||||
|
||||
func (b *MemWidget) Scale(i int) {
|
||||
b.LineGraph.HorizontalScale = i
|
||||
}
|
||||
|
||||
func (self *MemWidget) renderMemInfo(line string, memoryInfo devices.MemoryInfo) {
|
||||
self.Data[line] = append(self.Data[line], memoryInfo.UsedPercent)
|
||||
memoryTotalBytes, memoryTotalMagnitude := utils.ConvertBytes(memoryInfo.Total)
|
||||
memoryUsedBytes, memoryUsedMagnitude := utils.ConvertBytes(memoryInfo.Used)
|
||||
@ -34,45 +82,3 @@ func (self *MemWidget) renderMemInfo(line string, memoryInfo MemoryInfo) {
|
||||
memoryTotalMagnitude,
|
||||
)
|
||||
}
|
||||
|
||||
func (self *MemWidget) updateMainMemory() {
|
||||
mainMemory, err := psMem.VirtualMemory()
|
||||
if err != nil {
|
||||
log.Printf("failed to get main memory info from gopsutil: %v", err)
|
||||
} else {
|
||||
self.renderMemInfo("Main", MemoryInfo{
|
||||
Total: mainMemory.Total,
|
||||
Used: mainMemory.Used,
|
||||
UsedPercent: mainMemory.UsedPercent,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func NewMemWidget(updateInterval time.Duration, horizontalScale int) *MemWidget {
|
||||
self := &MemWidget{
|
||||
LineGraph: ui.NewLineGraph(),
|
||||
updateInterval: updateInterval,
|
||||
}
|
||||
self.Title = " Memory Usage "
|
||||
self.HorizontalScale = horizontalScale
|
||||
self.Data["Main"] = []float64{0}
|
||||
self.Data["Swap"] = []float64{0}
|
||||
|
||||
self.updateMainMemory()
|
||||
self.updateSwapMemory()
|
||||
|
||||
go func() {
|
||||
for range time.NewTicker(self.updateInterval).C {
|
||||
self.Lock()
|
||||
self.updateMainMemory()
|
||||
self.updateSwapMemory()
|
||||
self.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (b *MemWidget) Scale(i int) {
|
||||
b.LineGraph.HorizontalScale = i
|
||||
}
|
||||
|
@ -1,61 +0,0 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
|
||||
func convert(s []string) (MemoryInfo, error) {
|
||||
total, err := strconv.ParseUint(s[0], 10, 64)
|
||||
if err != nil {
|
||||
return MemoryInfo{}, fmt.Errorf("int converion failed %v", err)
|
||||
}
|
||||
|
||||
used, err := strconv.ParseUint(s[1], 10, 64)
|
||||
if err != nil {
|
||||
return MemoryInfo{}, fmt.Errorf("int converion failed %v", err)
|
||||
}
|
||||
|
||||
percentage, err := strconv.ParseFloat(strings.TrimSuffix(s[2], "%"), 64)
|
||||
if err != nil {
|
||||
return MemoryInfo{}, fmt.Errorf("float converion failed %v", err)
|
||||
}
|
||||
|
||||
return MemoryInfo{
|
||||
Total: total * utils.KB,
|
||||
Used: used * utils.KB,
|
||||
UsedPercent: percentage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func gatherSwapInfo() (MemoryInfo, error) {
|
||||
cmd := "swapinfo -k|sed -n '1!p'|awk '{print $2,$3,$5}'"
|
||||
output, err := exec.Command("sh", "-c", cmd).Output()
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
return MemoryInfo{}, fmt.Errorf("command failed %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
ss := strings.Split(strings.TrimSuffix(string(output), "\n"), " ")
|
||||
|
||||
return convert(ss)
|
||||
}
|
||||
|
||||
func (self *MemWidget) updateSwapMemory() {
|
||||
swapMemory, err := gatherSwapInfo()
|
||||
if err != nil {
|
||||
log.Printf("failed to get swap memory info from gopsutil: %v", err)
|
||||
} else {
|
||||
self.renderMemInfo("Swap", MemoryInfo{
|
||||
Total: swapMemory.Total,
|
||||
Used: swapMemory.Used,
|
||||
UsedPercent: swapMemory.UsedPercent,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
// +build !freebsd
|
||||
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
psMem "github.com/shirou/gopsutil/mem"
|
||||
)
|
||||
|
||||
func (self *MemWidget) updateSwapMemory() {
|
||||
swapMemory, err := psMem.SwapMemory()
|
||||
if err != nil {
|
||||
log.Printf("failed to get swap memory info from gopsutil: %v", err)
|
||||
} else {
|
||||
self.renderMemInfo("Swap", MemoryInfo{
|
||||
Total: swapMemory.Total,
|
||||
Used: swapMemory.Used,
|
||||
UsedPercent: swapMemory.UsedPercent,
|
||||
})
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
psNet "github.com/shirou/gopsutil/net"
|
||||
|
||||
ui "github.com/xxxserxxx/gotop/termui"
|
||||
@ -25,6 +26,8 @@ type NetWidget struct {
|
||||
totalBytesRecv uint64
|
||||
totalBytesSent uint64
|
||||
NetInterface []string
|
||||
sentMetric prometheus.Counter
|
||||
recvMetric prometheus.Counter
|
||||
}
|
||||
|
||||
// TODO: state:merge #169 % option for network use (jrswab/networkPercentage)
|
||||
@ -59,6 +62,22 @@ func NewNetWidget(netInterface string) *NetWidget {
|
||||
return self
|
||||
}
|
||||
|
||||
func (b *NetWidget) EnableMetric() {
|
||||
b.recvMetric = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "gotop",
|
||||
Subsystem: "net",
|
||||
Name: "recv",
|
||||
})
|
||||
prometheus.MustRegister(b.recvMetric)
|
||||
|
||||
b.sentMetric = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "gotop",
|
||||
Subsystem: "net",
|
||||
Name: "sent",
|
||||
})
|
||||
prometheus.MustRegister(b.sentMetric)
|
||||
}
|
||||
|
||||
func (self *NetWidget) update() {
|
||||
interfaces, err := psNet.IOCounters(true)
|
||||
if err != nil {
|
||||
@ -115,6 +134,10 @@ func (self *NetWidget) update() {
|
||||
|
||||
self.Lines[0].Data = append(self.Lines[0].Data, int(recentBytesRecv))
|
||||
self.Lines[1].Data = append(self.Lines[1].Data, int(recentBytesSent))
|
||||
if self.sentMetric != nil {
|
||||
self.sentMetric.Add(float64(recentBytesSent))
|
||||
self.recvMetric.Add(float64(recentBytesRecv))
|
||||
}
|
||||
}
|
||||
|
||||
// used in later calls to update
|
||||
|
@ -100,6 +100,10 @@ func NewProcWidget() *ProcWidget {
|
||||
return self
|
||||
}
|
||||
|
||||
func (p *ProcWidget) EnableMetric() {
|
||||
// There's (currently) no metric for this
|
||||
}
|
||||
|
||||
func (self *ProcWidget) SetEditingFilter(editing bool) {
|
||||
self.entry.SetEditing(editing)
|
||||
}
|
||||
|
@ -7,16 +7,17 @@ import (
|
||||
"time"
|
||||
|
||||
ui "github.com/gizak/termui/v3"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/xxxserxxx/gotop/devices"
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
|
||||
type TempScale int
|
||||
type TempScale rune
|
||||
|
||||
const (
|
||||
Celsius TempScale = 0
|
||||
Fahrenheit = 1
|
||||
Disabled = 2
|
||||
Celsius TempScale = 'C'
|
||||
Fahrenheit = 'F'
|
||||
)
|
||||
|
||||
type TempWidget struct {
|
||||
@ -27,6 +28,7 @@ type TempWidget struct {
|
||||
TempLowColor ui.Color
|
||||
TempHighColor ui.Color
|
||||
TempScale TempScale
|
||||
tempsMetric map[string]prometheus.Gauge
|
||||
}
|
||||
|
||||
// TODO: state:deferred 156 Added temperatures for NVidia GPUs (azak-azkaran/master). Crashes on non-nvidia machines.
|
||||
@ -57,6 +59,20 @@ func NewTempWidget(tempScale TempScale) *TempWidget {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *TempWidget) EnableMetric() {
|
||||
self.tempsMetric = make(map[string]prometheus.Gauge)
|
||||
for k, v := range self.Data {
|
||||
gauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "gotop",
|
||||
Subsystem: "temp",
|
||||
Name: k,
|
||||
})
|
||||
gauge.Set(float64(v))
|
||||
prometheus.MustRegister(gauge)
|
||||
self.tempsMetric[k] = gauge
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Draw method instead of inheriting from a generic Widget.
|
||||
func (self *TempWidget) Draw(buf *ui.Buffer) {
|
||||
self.Block.Draw(buf)
|
||||
@ -85,20 +101,26 @@ func (self *TempWidget) Draw(buf *ui.Buffer) {
|
||||
image.Pt(self.Inner.Min.X, self.Inner.Min.Y+y),
|
||||
)
|
||||
|
||||
// TODO: state:merge #184 or #177 degree symbol (BartWillems/master, fleaz/master)
|
||||
switch self.TempScale {
|
||||
case Fahrenheit:
|
||||
buf.SetString(
|
||||
fmt.Sprintf("%3dF", self.Data[key]),
|
||||
ui.NewStyle(fg),
|
||||
image.Pt(self.Inner.Max.X-4, self.Inner.Min.Y+y),
|
||||
)
|
||||
case Celsius:
|
||||
buf.SetString(
|
||||
fmt.Sprintf("%3dC", self.Data[key]),
|
||||
ui.NewStyle(fg),
|
||||
image.Pt(self.Inner.Max.X-4, self.Inner.Min.Y+y),
|
||||
)
|
||||
if self.tempsMetric != nil {
|
||||
self.tempsMetric[key].Set(float64(self.Data[key]))
|
||||
}
|
||||
temperature := fmt.Sprintf("%3d°%c", self.Data[key], self.TempScale)
|
||||
|
||||
buf.SetString(
|
||||
temperature,
|
||||
ui.NewStyle(fg),
|
||||
image.Pt(self.Inner.Max.X-(len(temperature)-1), self.Inner.Min.Y+y),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TempWidget) update() {
|
||||
devices.UpdateTemps(self.Data)
|
||||
for name, val := range self.Data {
|
||||
if self.TempScale == Fahrenheit {
|
||||
self.Data[name] = utils.CelsiusToFahrenheit(val)
|
||||
} else {
|
||||
self.Data[name] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,73 +0,0 @@
|
||||
// +build freebsd
|
||||
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
|
||||
var sensorOIDS = map[string]string{
|
||||
"dev.cpu.0.temperature": "CPU 0 ",
|
||||
"hw.acpi.thermal.tz0.temperature": "Thermal zone 0",
|
||||
}
|
||||
|
||||
type sensorMeasurement struct {
|
||||
name string
|
||||
temperature float64
|
||||
}
|
||||
|
||||
func removeUnusedChars(s string) string {
|
||||
s1 := strings.Replace(s, "C", "", 1)
|
||||
s2 := strings.TrimSuffix(s1, "\n")
|
||||
return s2
|
||||
}
|
||||
|
||||
func refineOutput(output []byte) (float64, error) {
|
||||
convertedOutput := utils.ConvertLocalizedString(removeUnusedChars(string(output)))
|
||||
value, err := strconv.ParseFloat(convertedOutput, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func collectSensors() ([]sensorMeasurement, error) {
|
||||
var measurements []sensorMeasurement
|
||||
for k, v := range sensorOIDS {
|
||||
output, err := exec.Command("sysctl", "-n", k).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute 'sysctl' command: %v", err)
|
||||
}
|
||||
|
||||
value, err := refineOutput(output)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute 'sysctl' command: %v", err)
|
||||
}
|
||||
|
||||
measurements = append(measurements, sensorMeasurement{v, value})
|
||||
|
||||
}
|
||||
return measurements, nil
|
||||
|
||||
}
|
||||
|
||||
func (self *TempWidget) update() {
|
||||
sensors, err := collectSensors()
|
||||
if err != nil {
|
||||
log.Printf("error received from gopsutil: %v", err)
|
||||
}
|
||||
for _, sensor := range sensors {
|
||||
switch self.TempScale {
|
||||
case Fahrenheit:
|
||||
self.Data[sensor.name] = utils.CelsiusToFahrenheit(int(sensor.temperature))
|
||||
case Celsius:
|
||||
self.Data[sensor.name] = int(sensor.temperature)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
psHost "github.com/shirou/gopsutil/host"
|
||||
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
|
||||
func (self *TempWidget) update() {
|
||||
sensors, err := psHost.SensorsTemperatures()
|
||||
if err != nil {
|
||||
log.Printf("failed to get sensors from gopsutil: %v", err)
|
||||
return
|
||||
}
|
||||
for _, sensor := range sensors {
|
||||
if sensor.Temperature != 0 {
|
||||
switch self.TempScale {
|
||||
case Fahrenheit:
|
||||
self.Data[sensor.SensorKey] = utils.CelsiusToFahrenheit(int(sensor.Temperature))
|
||||
case Celsius:
|
||||
self.Data[sensor.SensorKey] = int(sensor.Temperature)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user