diff options
author | Sean E. Russell <ser@ser1.net> | 2021-09-20 15:30:14 -0500 |
---|---|---|
committer | Sean E. Russell <ser@ser1.net> | 2021-09-23 10:32:01 -0500 |
commit | e1600ac7fb365b89ccbd7e157a685c9cbb795973 (patch) | |
tree | 10eee322f3a0936ae0d6acb094abd7e30d5cf6b1 | |
parent | f7bb764e7c59c9ef7bb577180c2720436e34e329 (diff) |
Refactoring to remove the remnants of the plugin design. Adds headless.
Updates docs
-rw-r--r-- | CHANGELOG.md | 9 | ||||
-rw-r--r-- | cmd/gotop/main.go | 406 | ||||
-rw-r--r-- | config.go | 81 | ||||
-rw-r--r-- | devices/battery.go | 79 | ||||
-rw-r--r-- | devices/cpu.go | 75 | ||||
-rw-r--r-- | devices/cpu_cpu.go | 34 | ||||
-rw-r--r-- | devices/cpu_linux.go | 4 | ||||
-rw-r--r-- | devices/cpu_other.go | 3 | ||||
-rw-r--r-- | devices/devices.go | 159 | ||||
-rw-r--r-- | devices/disk.go | 163 | ||||
-rw-r--r-- | devices/mem.go | 52 | ||||
-rw-r--r-- | devices/mem_mem.go | 21 | ||||
-rw-r--r-- | devices/mem_swap_freebsd.go | 52 | ||||
-rw-r--r-- | devices/mem_swap_other.go | 28 | ||||
-rw-r--r-- | devices/metrics.go (renamed from widgets/metrics.go) | 12 | ||||
-rw-r--r-- | devices/net.go | 173 | ||||
-rw-r--r-- | devices/nvidia.go | 210 | ||||
-rw-r--r-- | devices/remote.go | 348 | ||||
-rw-r--r-- | devices/temp.go | 74 | ||||
-rw-r--r-- | devices/temp_darwin.go | 32 | ||||
-rw-r--r-- | devices/temp_freebsd.go | 47 | ||||
-rw-r--r-- | devices/temp_linux.go | 47 | ||||
-rw-r--r-- | devices/temp_linwin.go | 46 | ||||
-rw-r--r-- | devices/temp_nix.go | 24 | ||||
-rw-r--r-- | devices/temp_openbsd.go | 34 | ||||
-rw-r--r-- | devices/temp_windows.go | 39 | ||||
-rw-r--r-- | dicts/de_DE.toml | 2 | ||||
-rw-r--r-- | dicts/en_US.toml | 2 | ||||
-rw-r--r-- | dicts/eo.toml | 2 | ||||
-rw-r--r-- | dicts/es.toml | 2 | ||||
-rw-r--r-- | dicts/fr.toml | 2 | ||||
-rw-r--r-- | dicts/ru_RU.toml | 2 | ||||
-rw-r--r-- | dicts/tt_TT.toml | 2 | ||||
-rw-r--r-- | dicts/zh_CN.toml | 2 | ||||
-rw-r--r-- | docs/remote-monitoring.md | 50 | ||||
-rw-r--r-- | go.mod | 9 | ||||
-rw-r--r-- | go.sum | 32 | ||||
-rw-r--r-- | layout/layout.go | 165 | ||||
-rw-r--r-- | layout/layout_test.go | 22 | ||||
-rw-r--r-- | layout/parser.go | 4 | ||||
-rw-r--r-- | tui/ui.go | 318 | ||||
-rw-r--r-- | widgets/battery.go | 94 | ||||
-rw-r--r-- | widgets/batterygauge.go | 83 | ||||
-rw-r--r-- | widgets/cpu.go | 112 | ||||
-rw-r--r-- | widgets/disk.go | 169 | ||||
-rw-r--r-- | widgets/drawille-go/LICENSE.md (renamed from termui/drawille-go/LICENSE.md) | 0 | ||||
-rw-r--r-- | widgets/drawille-go/README.md (renamed from termui/drawille-go/README.md) | 0 | ||||
-rw-r--r-- | widgets/drawille-go/drawille.go (renamed from termui/drawille-go/drawille.go) | 0 | ||||
-rw-r--r-- | widgets/entry.go (renamed from termui/entry.go) | 16 | ||||
-rw-r--r-- | widgets/gauge.go (renamed from termui/gauge.go) | 12 | ||||
-rw-r--r-- | widgets/linegraph.go (renamed from termui/linegraph.go) | 33 | ||||
-rw-r--r-- | widgets/linegraph_test.go (renamed from termui/linegraph_test.go) | 2 | ||||
-rw-r--r-- | widgets/mem.go | 76 | ||||
-rw-r--r-- | widgets/net.go | 134 | ||||
-rw-r--r-- | widgets/proc.go | 29 | ||||
-rw-r--r-- | widgets/proc_freebsd.go | 2 | ||||
-rw-r--r-- | widgets/proc_linux.go | 3 | ||||
-rw-r--r-- | widgets/proc_windows.go | 4 | ||||
-rw-r--r-- | widgets/sparkline.go (renamed from termui/sparkline.go) | 36 | ||||
-rw-r--r-- | widgets/statusbar.go | 14 | ||||
-rw-r--r-- | widgets/table.go (renamed from termui/table.go) | 24 | ||||
-rw-r--r-- | widgets/temp.go | 93 | ||||
-rw-r--r-- | widgets/widgets.go | 13 |
63 files changed, 2120 insertions, 1697 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 663e323..e2839a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,16 +14,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 > - **Security**: in case of vulnerabilities. -## [4.x.y] +## [4.x.y] PENDING ### Added - Headless mode -- run gotop without UI, for more practical `-x` +- Adds ability to disable all local sensors (for creating remote-only monitors). + See [Remote Monitoring](docs/remote-monitoring.md). ### Changed - Moved all metrics from widgets to devices, where they should have been in the first place. +- ca. 22% less CPU use, *on my systems* +- Refactored to remove all of the code that I added to support plugins. It was + confusing, and not doing anything since there wasn't a good way to solve the + Go plugin problem. This would also have reduced the LOC of the project, except + that some non-device-ified widgets were device-ified. ## [4.1.2] 2021-07-20 diff --git a/cmd/gotop/main.go b/cmd/gotop/main.go index 724dbe4..176761e 100644 --- a/cmd/gotop/main.go +++ b/cmd/gotop/main.go @@ -4,24 +4,21 @@ import ( "bufio" "flag" "fmt" - "io" "io/fs" "io/ioutil" "log" "net/http" + "net/url" "os" "os/signal" "path/filepath" "sort" "strings" - "syscall" "time" //_ "net/http/pprof" - "github.com/VictoriaMetrics/metrics" - jj "github.com/cloudfoundry-attic/jibber_jabber" - ui "github.com/gizak/termui/v3" + "github.com/cloudfoundry-attic/jibber_jabber" "github.com/shibukawa/configdir" "github.com/xxxserxxx/lingo/v2" "github.com/xxxserxxx/opflag" @@ -29,19 +26,11 @@ import ( "github.com/xxxserxxx/gotop/v4" "github.com/xxxserxxx/gotop/v4/colorschemes" "github.com/xxxserxxx/gotop/v4/devices" - "github.com/xxxserxxx/gotop/v4/layout" "github.com/xxxserxxx/gotop/v4/logging" - w "github.com/xxxserxxx/gotop/v4/widgets" + "github.com/xxxserxxx/gotop/v4/tui" ) -const ( - graphHorizontalScaleDelta = 3 - defaultUI = "2:cpu\ndisk/1 2:mem/2\ntemp\n2:net 2: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" - kitchensink = "3:cpu/2 3:mem/1\n4:temp/1 3:disk/2\npower\n3:net 3:procs" -) +// TODO add build flags to disable TUI (headless builds) var ( // Version of the program; set during build from git tags @@ -49,13 +38,10 @@ var ( // BuildDate when the program was compiled; set during build BuildDate = "Hadean" conf gotop.Config - help *w.HelpMenu - bar *w.StatusBar stderrLogger = log.New(os.Stderr, "", 0) tr lingo.Translations ) -// TODO disable local devices func parseArgs() error { cds := conf.ConfigDir.QueryFolders(configdir.All) cpaths := make([]string, len(cds)) @@ -73,16 +59,20 @@ func parseArgs() error { opflag.BoolVarP(&conf.Statusbar, "statusbar", "s", conf.Statusbar, tr.Value("args.statusbar")) opflag.DurationVarP(&conf.UpdateInterval, "rate", "r", conf.UpdateInterval, tr.Value("args.rate")) opflag.StringVarP(&conf.Layout, "layout", "l", conf.Layout, tr.Value("args.layout")) - opflag.StringVarP(&conf.NetInterface, "interface", "i", "all", tr.Value("args.net")) + ifaces := opflag.String("interface", "", tr.Value("args.net")) opflag.StringVarP(&conf.ExportPort, "export", "x", conf.ExportPort, tr.Value("args.export")) opflag.BoolVarP(&conf.Mbps, "mbps", "", conf.Mbps, tr.Value("args.mbps")) opflag.BoolVar(&conf.Test, "test", conf.Test, tr.Value("args.test")) opflag.StringP("", "C", "", tr.Value("args.conffile")) opflag.BoolVarP(&conf.Nvidia, "nvidia", "", conf.Nvidia, "Enable NVidia GPU support") - // TODO Add a disable-local-sensors + remoteName := opflag.String("remote-name", "", "Remote: name of remote gotop") + remoteURL := opflag.String("remote-url", "", "Remote: URL of remote gotop") + remoteRefresh := opflag.Duration("remote-refresh", 0, "Remote: Frequency to refresh data, in seconds") + opflag.BoolVarP(&conf.NoLocal, "no-local", "", false, "Disable local(host) sensors") opflag.BoolVarP(&conf.Headless, "headless", "", conf.Headless, "Disable user interface") list := opflag.String("list", "", tr.Value("args.list")) wc := opflag.Bool("write-config", false, tr.Value("args.write")) + devices := opflag.String("devices", "", tr.Value("args.devices")) opflag.SortFlags = false opflag.Usage = func() { fmt.Fprintf(os.Stderr, tr.Value("usage", os.Args[0])) @@ -102,12 +92,21 @@ func parseArgs() error { if err != nil { return err } + if *devices != "" { + conf.Devices = strings.Split("devices", ",") + if len(conf.Devices) == 0 { + conf.Devices = gotop.AllDevices() + } + } conf.Colorscheme = cs if *fahrenheit { conf.TempScale = 'F' } else { conf.TempScale = 'C' } + if *ifaces != "" { + conf.NetInterface = strings.Split(*ifaces, ",") + } if *list != "" { switch *list { case "layouts": @@ -155,6 +154,25 @@ func parseArgs() error { if conf.Nvidia { conf.ExtensionVars["nvidia"] = "true" } + if *remoteURL != "" { + if u, e := url.Parse(*remoteURL); e == nil { + r := gotop.Remote{} + r.URL = *remoteURL + if remoteName != nil { + r.Name = *remoteName + } else { + r.Name = u.Hostname() + } + if remoteRefresh != nil { + r.Refresh = *remoteRefresh + } else { + r.Refresh = 5 * time.Second + } + conf.Remotes[r.Name] = r + } else { + fmt.Println(e) + } + } if *wc { path, err := conf.Write() if err != nil { @@ -167,225 +185,31 @@ func parseArgs() error { return nil } -func setDefaultTermuiColors(c gotop.Config) { - ui.Theme.Default = ui.NewStyle(ui.Color(c.Colorscheme.Fg), ui.Color(c.Colorscheme.Bg)) - ui.Theme.Block.Title = ui.NewStyle(ui.Color(c.Colorscheme.BorderLabel), ui.Color(c.Colorscheme.Bg)) - ui.Theme.Block.Border = ui.NewStyle(ui.Color(c.Colorscheme.BorderLine), ui.Color(c.Colorscheme.Bg)) -} - -func eventLoop(c gotop.Config, grid *layout.MyGrid) { - drawTicker := time.NewTicker(c.UpdateInterval).C - - // handles kill signal sent to gotop - sigTerm := make(chan os.Signal, 2) - signal.Notify(sigTerm, os.Interrupt, syscall.SIGTERM) - - uiEvents := ui.PollEvents() - - previousKey := "" - - for { - select { - case <-sigTerm: - return - case <-drawTicker: - if !c.HelpVisible { - ui.Render(grid) - if c.Statusbar { - ui.Render(bar) - } - } - case e := <-uiEvents: - if grid.Proc != nil && grid.Proc.HandleEvent(e) { - ui.Render(grid.Proc) - break - } - switch e.ID { - case "q", "<C-c>": - return - case "?": - c.HelpVisible = !c.HelpVisible - case "<Resize>": - payload := e.Payload.(ui.Resize) - termWidth, termHeight := payload.Width, payload.Height - if c.Statusbar { - grid.SetRect(0, 0, termWidth, termHeight-1) - bar.SetRect(0, termHeight-1, termWidth, termHeight) - } else { - grid.SetRect(0, 0, payload.Width, payload.Height) - } - help.Resize(payload.Width, payload.Height) - ui.Clear() - } - - if c.HelpVisible { - switch e.ID { - case "?": - ui.Clear() - ui.Render(help) - case "<Escape>": - c.HelpVisible = false - ui.Render(grid) - case "<Resize>": - ui.Render(help) - } - } else { - switch e.ID { - case "?": - ui.Render(grid) - case "h": - c.GraphHorizontalScale += graphHorizontalScaleDelta - for _, item := range grid.Lines { - item.Scale(c.GraphHorizontalScale) - } - ui.Render(grid) - case "l": - if c.GraphHorizontalScale > graphHorizontalScaleDelta { - c.GraphHorizontalScale -= graphHorizontalScaleDelta - for _, item := range grid.Lines { - item.Scale(c.GraphHorizontalScale) - ui.Render(item) - } - } - case "b": - if grid.Net != nil { - grid.Net.Mbps = !grid.Net.Mbps - } - case "<Resize>": - ui.Render(grid) - if c.Statusbar { - ui.Render(bar) - } - case "<MouseLeft>": - if grid.Proc != nil { - payload := e.Payload.(ui.Mouse) - grid.Proc.HandleClick(payload.X, payload.Y) - ui.Render(grid.Proc) - } - case "k", "<Up>", "<MouseWheelUp>": - if grid.Proc != nil { - grid.Proc.ScrollUp() - ui.Render(grid.Proc) - } - case "j", "<Down>", "<MouseWheelDown>": - if grid.Proc != nil { - grid.Proc.ScrollDown() - ui.Render(grid.Proc) - } - case "<Home>": - if grid.Proc != nil { - grid.Proc.ScrollTop() - ui.Render(grid.Proc) - } - case "g": - if grid.Proc != nil { - if previousKey == "g" { - grid.Proc.ScrollTop() - ui.Render(grid.Proc) - } - } - case "G", "<End>": - if grid.Proc != nil { - grid.Proc.ScrollBottom() - ui.Render(grid.Proc) - } - case "<C-d>": - if grid.Proc != nil { - grid.Proc.ScrollHalfPageDown() - ui.Render(grid.Proc) - } - case "<C-u>": - if grid.Proc != nil { - grid.Proc.ScrollHalfPageUp() - ui.Render(grid.Proc) - } - case "<C-f>": - if grid.Proc != nil { - grid.Proc.ScrollPageDown() - ui.Render(grid.Proc) - } - case "<C-b>": - if grid.Proc != nil { - grid.Proc.ScrollPageUp() - ui.Render(grid.Proc) - } - case "d": - if grid.Proc != nil { - if previousKey == "d" { - grid.Proc.KillProc("SIGTERM") - } - } - case "3": - if grid.Proc != nil { - if previousKey == "d" { - grid.Proc.KillProc("SIGQUIT") - } - } - case "9": - if grid.Proc != nil { - if previousKey == "d" { - grid.Proc.KillProc("SIGKILL") - } - } - case "<Tab>": - if grid.Proc != nil { - grid.Proc.ToggleShowingGroupedProcs() - ui.Render(grid.Proc) - } - case "m", "c", "p": - if grid.Proc != nil { - grid.Proc.ChangeProcSortMethod(w.ProcSortMethod(e.ID)) - ui.Render(grid.Proc) - } - case "/": - if grid.Proc != nil { - grid.Proc.SetEditingFilter(true) - ui.Render(grid.Proc) - } - } - - if previousKey == e.ID { - previousKey = "" - } else { - previousKey = e.ID - } - } - - } - } -} - -// FIXME CPU use regression -// TODO add CPU freq func main() { - // TODO: Make this an option, for performance testing //go func() { - // log.Fatal(http.ListenAndServe(":7777", nil)) + // log.Fatal(http.ListenAndServe(":7777", http.DefaultServeMux)) //}() - // This is just to make sure gotop returns a useful exit code, but also - // executes all defer statements and so cleans up before exit. Sort of - // annoying work-around for a lack of a clean way to exit Go programs - // with exit codes. - ec := run() - if ec > 0 { - if ec < 2 { - logpath := filepath.Join(conf.ConfigDir.QueryCacheFolder().Path, logging.LOGFILE) - fmt.Println(tr.Value("error.checklog", logpath)) - bs, _ := ioutil.ReadFile(logpath) - fmt.Println(string(bs)) + var ec int + defer func() { + if ec > 0 { + if ec < 2 { + logpath := filepath.Join(conf.ConfigDir.QueryCacheFolder().Path, logging.LOGFILE) + fmt.Println(tr.Value("error.checklog", logpath)) + bs, _ := ioutil.ReadFile(logpath) + fmt.Println(string(bs)) + } } - } - os.Exit(ec) -} + os.Exit(ec) + }() -func run() int { ling, err := lingo.New("en_US", ".", gotop.Dicts) if err != nil { fmt.Printf("failed to load language files: %s\n", err) - return 2 + ec = 2 + return } - lang, err := jj.DetectIETF() + lang, err := jibber_jabber.DetectIETF() if err != nil { lang = "en_US" } @@ -407,43 +231,46 @@ func run() int { err = conf.Load() if err != nil { fmt.Println(tr.Value("error.configparse", err.Error())) - return 2 + ec = 2 + return } // Override with command line arguments err = parseArgs() if err != nil { fmt.Println(tr.Value("error.cliparse", err.Error())) - return 2 + ec = 2 + return } logfile, err := logging.New(conf) if err != nil { fmt.Println(tr.Value("logsetup", err.Error())) - return 2 + ec = 2 + return } defer logfile.Close() // device initialization errors do not stop execution - for _, err := range devices.Startup(conf.ExtensionVars) { - stderrLogger.Print(err) - } + // Build a list of requested devices + devs := make(map[string]bool) - lstream, err := getLayout(conf) - if err != nil { - stderrLogger.Print(err) - return 1 + if len(conf.Remotes) > 0 { + devs["remote"] = true + } + if conf.Nvidia { + devs["nvidia"] = true } - ly := layout.ParseLayout(lstream) if conf.Test { - return runTests(conf) + ec = runTests(conf) + return } // TODO https://godoc.org/github.com/VictoriaMetrics/metrics#Set if conf.ExportPort != "" { go func() { http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) { - metrics.WritePrometheus(w, true) + conf.Metrics.WritePrometheus(w) }) http.ListenAndServe(conf.ExportPort, nil) }() @@ -452,79 +279,46 @@ func run() int { if conf.Headless { if conf.ExportPort == "" { fmt.Fprintln(os.Stdout, "metrics not being exported; did you forget --export?") + ec = 1 + return + } + devs := make([]string, len(conf.Devices)) + // First, check the layout; each widget has a default associated device + if !conf.NoLocal { + for i, dn := range conf.Devices { + devs[i] = dn + } } + devInsts, errs := devices.Startup(devs, conf) + for _, err := range errs { + stderrLogger.Print(err) + ec = 1 + return + } + devices.Spawn(devInsts, conf) // No TUI; just wait for user to interrupt + fmt.Println("gotop running... press ^c to exit") c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c - return 0 - } - - if err = ui.Init(); err != nil { - stderrLogger.Print(err) - return 1 - } - defer ui.Close() - - setDefaultTermuiColors(conf) // done before initializing widgets to allow inheriting colors - help = w.NewHelpMenu(tr) - if conf.Statusbar { - bar = w.NewStatusBar() - } - - grid, err := layout.Layout(ly, conf) - if err != nil { - stderrLogger.Print(err) - return 1 - } - - termWidth, termHeight := ui.TerminalDimensions() - if conf.Statusbar { - grid.SetRect(0, 0, termWidth, termHeight-1) } else { - grid.SetRect(0, 0, termWidth, termHeight) - } - help.Resize(termWidth, termHeight) - - ui.Render(grid) - if conf.Statusbar { - bar.SetRect(0, termHeight-1, termWidth, termHeight) - ui.Render(bar) - } - - eventLoop(conf, grid) - return 0 -} - -func getLayout(conf gotop.Config) (io.Reader, error) { - switch conf.Layout { - case "-": - return os.Stdin, nil - case "default": - return strings.NewReader(defaultUI), nil - case "minimal": - return strings.NewReader(minimalUI), nil - case "battery": - return strings.NewReader(batteryUI), nil - case "procs": - return strings.NewReader(procsUI), nil - case "kitchensink": - return strings.NewReader(kitchensink), nil - default: - folder := conf.ConfigDir.QueryFolderContainsFile(conf.Layout) - if folder == nil { - paths := make([]string, 0) - for _, d := range conf.ConfigDir.QueryFolders(configdir.Existing) { - paths = append(paths, d.Path) - } - return nil, fmt.Errorf(tr.Value("error.findlayout", conf.Layout, strings.Join(paths, ", "))) + ui, err := tui.New(conf) + if err != nil { + stderrLogger.Print(err) + ec = 1 + return } - lo, err := folder.ReadFile(conf.Layout) + defer ui.ShutdownUI() + err = ui.LoopUI() if err != nil { - return nil, err + stderrLogger.Print(err) + ec = 1 + return } - return strings.NewReader(string(lo)), nil } + + ec = 0 + return } func runTests(_ gotop.Config) int { @@ -533,7 +327,7 @@ func runTests(_ gotop.Config) int { } func listDevices() { - ms := devices.Domains + ms := devices.Domains() sort.Strings(ms) for _, m := range ms { fmt.Printf("%s:\n", m) @@ -14,15 +14,15 @@ import ( "strings" "time" + "github.com/VictoriaMetrics/metrics" "github.com/shibukawa/configdir" "github.com/xxxserxxx/gotop/v4/colorschemes" - "github.com/xxxserxxx/gotop/v4/widgets" "github.com/xxxserxxx/lingo/v2" ) -// TODO: load colorschemes, layouts, languages from online repo +// TODO: load (additional) colorschemes, layouts, languages from online repo, if user requests +// TODO utility program for benchmarking between versions -// FIXME github action uses old(er) Go version that doesn't have embed //go:embed "dicts/*.toml" var Dicts embed.FS @@ -38,11 +38,12 @@ type Config struct { AverageLoad bool PercpuLoad bool Statusbar bool - TempScale widgets.TempScale - NetInterface string + TempScale TempScale + NetInterface []string Layout string MaxLogSize int64 ExportPort string + Metrics *metrics.Set Mbps bool Temps []string Test bool @@ -52,9 +53,28 @@ type Config struct { Nvidia bool NvidiaRefresh time.Duration Headless bool + Devices []string + Remotes map[string]Remote + NoLocal bool +} + +type Remote struct { + Name string + URL string + Refresh time.Duration +} + +type TempScale rune + +const ( + Celsius TempScale = 'C' + Fahrenheit = 'F' +) + +func AllDevices() []string { + return []string{"batt", "cpu", "disk", "mem", "net", "temp"} } -// FIXME parsing can't handle blank lines func NewConfig() Config { cd := configdir.New("", "gotop") cd.LocalPath, _ = filepath.Abs(".") @@ -65,12 +85,15 @@ func NewConfig() Config { UpdateInterval: time.Second, AverageLoad: false, |