From 9e1b63be3250661124babf270f8a13326ef0d2f0 Mon Sep 17 00:00:00 2001 From: "Sean E. Russell" Date: Thu, 16 Apr 2020 13:28:18 -0500 Subject: Closes #46, option to display network traffic as mbps. This can be set on the command line with --mbps, or toggled while running with `b` --- CHANGELOG.md | 13 ++++-- README.md | 17 +------- cmd/gotop/main.go | 18 +++++--- config.go | 3 ++ layout/layout.go | 40 +++++++++++------ layout/parser.go | 108 ++++++++++++++++++++++++---------------------- layouts/many_columns_test | 41 ++++++++++++++++-- widgets/batterygauge.go | 1 - widgets/help.go | 3 ++ widgets/net.go | 31 +++++++++---- 10 files changed, 173 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d854f55..ba003ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,9 +18,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Adds support for system-wide configurations. This improves support for package maintainers. -- Help function to print key bindings -- Help prints locations of config files (color schemes & layouts) -- Help prints location of logs +- Help function to print key bindings. +- Help prints locations of config files (color schemes & layouts). +- Help prints location of logs. +- CLI option to scale out (#84). +- Ability to report network traffic rates as mbps (#46). ### Changed @@ -31,6 +33,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - configdir, logdir, and logfile options in the config file are no longer used. gotop looks for a configuration file, layouts, and colorschemes in the following order: command-line; `pwd`; user-home, and finally a system-wide path. The paths depend on the OS and whether XDG is in use. +### Fixed + +- Help & statusbar don't obey theme (#47). +- Fix help text layout. + ## [3.5.1] - 2020-04-09 This is a bug fix release. diff --git a/README.md b/README.md index 31e31ea..5b3ce0f 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ Move `gotop` to somewhere in your `$PATH`. - `h`: scale in - `l`: scale out - `?`: toggles keybind help menu +- `b`: toggles display of network traffic in mbps or TX (or RX) per second ### Mouse @@ -207,21 +208,7 @@ build massive edifices, you're in for disappointment. ### CLI Options -`-c`, `--color=NAME` Set a colorscheme. -`-m`, `--minimal` Only show CPU, Mem and Process widgets. (DEPRECATED, use `-l minimal`) -`-r`, `--rate=RATE` Number of times per second to update CPU and Mem widgets [default: 1]. -`-V`, `--version` Print version and exit. -`-p`, `--percpu` Show each CPU in the CPU widget. -`-a`, `--averagecpu` Show average CPU in the CPU widget. -`-f`, `--fahrenheit` Show temperatures in fahrenheit. -`-s`, `--statusbar` Show a statusbar with the time. -`-b`, `--battery` Show battery level widget (`minimal` turns off). (DEPRECATED, use `-l battery`) -`-i`, `--interface=NAME` Select network interface [default: all]. -`-l`, `--layout=NAME` Choose a layout. gotop searches for a file by NAME in \$XDG_CONFIG_HOME/gotop, then relative to the current path. "-" reads a layout from stdin, allowing for simple, one-off layouts such as `echo net | gotop -l -` - -Several interfaces can be defined using comma separated values. - -Interfaces can also be ignored using `!` +Run `gotop -h` to see the list of all command line options. ## More screen shots diff --git a/cmd/gotop/main.go b/cmd/gotop/main.go index 5dbcd3e..5728128 100644 --- a/cmd/gotop/main.go +++ b/cmd/gotop/main.go @@ -74,12 +74,13 @@ 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]. Several interfaces can be defined using comma separated values. Interfaces can also be ignored using ! + -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. - --test Runs tests and exits with success/failure code - --print-paths List out the paths that gotop will look for gotop.conf, layouts, color schemes, and extensions - --print-keys Show the keyboard bindings + -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. + --mbps Net widget shows mb(its)ps for RX/TX instead of scaled bytes per second. + --test Runs tests and exits with success/failure code. + --print-paths List out the paths that gotop will look for gotop.conf, layouts, color schemes, and extensions. + --print-keys Show the keyboard bindings. Built-in layouts: default @@ -184,6 +185,9 @@ Log files are stored in %s } conf.GraphHorizontalScale = scl } + if args["--mbps"].(bool) { + conf.Mbps = true + } if args["--print-paths"].(bool) { paths := make([]string, 0) for _, d := range conf.ConfigDir.QueryFolders(configdir.All) { @@ -308,6 +312,10 @@ func eventLoop(c gotop.Config, grid *layout.MyGrid) { ui.Render(item) } } + case "b": + if grid.Net != nil { + grid.Net.Mbps = !grid.Net.Mbps + } case "": ui.Render(grid) if statusbar { diff --git a/config.go b/config.go index 41b5278..3d1ae38 100644 --- a/config.go +++ b/config.go @@ -33,6 +33,7 @@ type Config struct { MaxLogSize int64 ExportPort string Extensions []string + Mbps bool Test bool } @@ -131,6 +132,8 @@ func (conf *Config) Load() error { conf.ExportPort = kv[1] case "extensions": conf.Extensions = strings.Split(kv[1], ",") + case "mbps": + conf.Mbps = true } } diff --git a/layout/layout.go b/layout/layout.go index 5cd8f24..9fc4452 100644 --- a/layout/layout.go +++ b/layout/layout.go @@ -25,6 +25,7 @@ type MyGrid struct { *ui.Grid Lines []widgets.Scalable Proc *widgets.ProcWidget + Net *widgets.NetWidget } var widgetNames []string = []string{"cpu", "disk", "mem", "temp", "net", "procs", "batt"} @@ -48,10 +49,25 @@ func Layout(wl layout, c gotop.Config) (*MyGrid, error) { rh := float64(heights[i]) / float64(maxHeight) rgs = append(rgs, ui.NewRow(rh, ur...)) } - grid := &MyGrid{ui.NewGrid(), nil, nil} + grid := &MyGrid{ui.NewGrid(), nil, nil, nil} grid.Set(rgs...) grid.Lines = deepFindScalable(rgs) - grid.Proc = deepFindProc(uiRows) + res := deepFindWidget(uiRows, func(gs interface{}) interface{} { + p, ok := gs.(*widgets.ProcWidget) + if ok { + return p + } + return nil + }) + grid.Proc, _ = res.(*widgets.ProcWidget) + res = deepFindWidget(uiRows, func(gs interface{}) interface{} { + p, ok := gs.(*widgets.NetWidget) + if ok { + return p + } + return nil + }) + grid.Net, _ = res.(*widgets.NetWidget) return grid, nil } @@ -189,6 +205,7 @@ func makeWidget(c gotop.Config, widRule widgetRule) interface{} { n.Lines[0].TitleColor = ui.Color(c.Colorscheme.BorderLabel) n.Lines[1].LineColor = ui.Color(c.Colorscheme.Sparkline) n.Lines[1].TitleColor = ui.Color(c.Colorscheme.BorderLabel) + n.Mbps = c.Mbps w = n case "procs": p := widgets.NewProcWidget() @@ -267,20 +284,19 @@ func countMaxHeight(rs [][]widgetRule) int { return ttl } -// deepFindProc looks in the UI widget tree for the ProcWidget, -// and returns it if found or nil if not. -func deepFindProc(gs interface{}) *widgets.ProcWidget { +// deepFindWidget looks in the UI widget tree for a widget, and returns it if found or nil if not. +func deepFindWidget(gs interface{}, test func(v interface{}) interface{}) interface{} { // Recursive function #1. Recursion is OK here because the number // of UI elements, even in a very complex UI, is going to be // relatively small. t, ok := gs.(ui.GridItem) if ok { - return deepFindProc(t.Entry) + return deepFindWidget(t.Entry, test) } es, ok := gs.([]ui.GridItem) if ok { for _, g := range es { - v := deepFindProc(g) + v := deepFindWidget(g, test) if v != nil { return v } @@ -289,7 +305,7 @@ func deepFindProc(gs interface{}) *widgets.ProcWidget { fs, ok := gs.([]interface{}) if ok { for _, g := range fs { - v := deepFindProc(g) + v := deepFindWidget(g, test) if v != nil { return v } @@ -298,17 +314,13 @@ func deepFindProc(gs interface{}) *widgets.ProcWidget { fs2, ok := gs.([][]interface{}) if ok { for _, g := range fs2 { - v := deepFindProc(g) + v := deepFindWidget(g, test) if v != nil { return v } } } - p, ok := gs.(*widgets.ProcWidget) - if ok { - return p - } - return nil + return test(gs) } // deepFindScalable looks in the UI widget tree for Scalable widgets, diff --git a/layout/parser.go b/layout/parser.go index 024fabb..f35171e 100644 --- a/layout/parser.go +++ b/layout/parser.go @@ -8,64 +8,68 @@ import ( "strings" ) -// The syntax for the layout specification is: -// ``` -// (rowspan:)?widget(/weight)? -// ``` -// 1. Each line is a row -// 2. Empty lines are skipped -// 3. Spaces are compressed -// 4. Legal widget names are: cpu, disk, mem, temp, batt, net, procs -// 5. Names are not case sensitive -// 4. The simplest row is a single widget, by name, e.g. -// ``` -// cpu -// ``` -// 5. Widgets with no weights have a weight of 1. -// 6. If multiple widgets are put on a row with no weights, they will all have -// the same width. -// 7. Weights are integers -// 8. A widget will have a width proportional to its weight divided by the -// total weight count of the row. E.g., -// ``` -// cpu net -// disk/2 mem/4 -// ``` -// The first row will have two widgets: the CPU and network widgets; each -// will be 50% of the total width wide. The second row will have two -// widgets: disk and memory; the first will be 2/6 ~= 33% wide, and the -// second will be 5/7 ~= 67% wide (or, memory will be twice as wide as disk). -// 9. If prefixed by a number and colon, the widget will span that number of -// rows downward. E.g. -// ``` -// 2:cpu -// mem -// ``` -// The CPU widget will be twice as high as the memory widget. Similarly, -// ``` -// mem 2:cpu -// net -// ``` -// memory and network will be in the same row as CPU, one over the other, -// and each half as high as CPU. -// 10. Negative, 0, or non-integer weights will be recorded as "1". Same for row spans. -// 11. Unrecognized widgets will cause the application to abort. -// 12. In rows with multi-row spanning widgets **and** weights, weights in -// lower rows are ignored. Put the weight on the widgets in that row, not -// in later (spanned) rows. -// 13. Widgets are filled in top down, left-to-right order. -// 14. The larges row span in a row defines the top-level row span; all smaller -// row spans constitude sub-rows in the row. For example, `cpu mem/3 net/5` -// means that net/5 will be 5 rows tall overall, and mem will compose 3 of -// them. If following rows do not have enough widgets to fill the gaps, -// spacers will be used. +/********************************************************************************** +The syntax for the layout specification is: +``` +(rowspan:)?widget(/weight)? +``` +1. Each line is a row +2. Empty lines are skipped +3. Spaces are compressed +4. Legal widget names are: cpu, disk, mem, temp, batt, net, procs +5. Names are not case sensitive +4. The simplest row is a single widget, by name, e.g. + ``` + cpu + ``` +5. Widgets with no weights have a weight of 1. +6. If multiple widgets are put on a row with no weights, they will all have + the same width. +7. Weights are integers +8. A widget will have a width proportional to its weight divided by the + total weight count of the row. E.g., + ``` + cpu net + disk/2 mem/4 + ``` + The first row will have two widgets: the CPU and network widgets; each + will be 50% of the total width wide. The second row will have two + widgets: disk and memory; the first will be 2/6 ~= 33% wide, and the + second will be 5/7 ~= 67% wide (or, memory will be twice as wide as disk). +9. If prefixed by a number and colon, the widget will span that number of + rows downward. E.g. + ``` + 2:cpu + mem + ``` + The CPU widget will be twice as high as the memory widget. Similarly, + ``` + mem 2:cpu + net + ``` + memory and network will be in the same row as CPU, one over the other, + and each half as high as CPU. +10. Negative, 0, or non-integer weights will be recorded as "1". Same for row spans. +11. Unrecognized widgets will cause the application to abort. +12. In rows with multi-row spanning widgets **and** weights, weights in + lower rows are ignored. Put the weight on the widgets in that row, not + in later (spanned) rows. +13. Widgets are filled in top down, left-to-right order. +14. The larges row span in a row defines the top-level row span; all smaller + row spans constitude sub-rows in the row. For example, `cpu mem/3 net/5` + means that net/5 will be 5 rows tall overall, and mem will compose 3 of + them. If following rows do not have enough widgets to fill the gaps, + spacers will be used. +15. Lines beginning with "#" will be ignored. It must be the first character of + the line. +**********************************************************************************/ func ParseLayout(i io.Reader) layout { r := bufio.NewScanner(i) rv := layout{Rows: make([][]widgetRule, 0)} var lineNo int for r.Scan() { l := strings.TrimSpace(r.Text()) - if l == "" { + if l == "" || l[0] == '#' { continue } row := make([]widgetRule, 0) diff --git a/layouts/many_columns_test b/layouts/many_columns_test index f2d57e3..7525713 100644 --- a/layouts/many_columns_test +++ b/layouts/many_columns_test @@ -1,4 +1,39 @@ -cpu/2 mem/1 6:procs/2 +# This should look like: +# +# + + + + + + +# +-------------------+---------+-------------------+ + +# | CPU | MEM | PROCS | +# +---------+---------+---------+ | + +# | TEMPS | DISK | | +# | | | | + +# | | | | +# | +-------------------+ | + +# | | POWER | | +# +---------+-------------------+ | + +# | NET | | +# +-----------------------------+-------------------+ + +# + +# Not all of the weights are necessary (e.g., power, net) nor +# the spacing, but it makes the layout easier to visualize. + +cpu/2 mem/1 5:procs/2 3:temp/1 2:disk/2 -power -net procs + power/2 +net/3 + +# + + + + + + +# +===============================+=====================+ +# ⋮+-------------------+---------+⋮+-------------------+⋮+ +# ⋮| CPU | MEM |⋮| PROCS |⋮ +# ⋮+---------+---------+---------+⋮| |⋮+ +# ⋮| TEMPS | DISK |⋮| |⋮ +# ⋮| | |⋮| |⋮+ +# ⋮| | |⋮| |⋮ +# ⋮| +-------------------+⋮| |⋮+ +# ⋮| | POWER |⋮| |⋮ +# ⋮+---------+-------------------+⋮| |⋮+ +# ⋮| NET |⋮| |⋮ +# ⋮+-----------------------------+⋮+-------------------+⋮+ +# +===============================+=====================+ +# diff --git a/widgets/batterygauge.go b/widgets/batterygauge.go index d447713..534c1cf 100644 --- a/widgets/batterygauge.go +++ b/widgets/batterygauge.go @@ -12,7 +12,6 @@ import ( . "github.com/xxxserxxx/gotop/v3/termui" ) -// FIXME 3.5.1 is 0% always type BatteryGauge struct { *Gauge metric prometheus.Gauge diff --git a/widgets/help.go b/widgets/help.go index 8aab35d..012ca9d 100644 --- a/widgets/help.go +++ b/widgets/help.go @@ -40,6 +40,9 @@ Process filtering: CPU and Mem graph scaling: - h: scale in - l: scale out + +Network: + - b: toggle between mbps and scaled bytes per second ` type HelpMenu struct { diff --git a/widgets/net.go b/widgets/net.go index 3f03936..249d0c2 100644 --- a/widgets/net.go +++ b/widgets/net.go @@ -28,6 +28,7 @@ type NetWidget struct { NetInterface []string sentMetric prometheus.Counter recvMetric prometheus.Counter + Mbps bool } // TODO: state:merge #169 % option for network use (jrswab/networkPercentage) @@ -144,19 +145,31 @@ func (self *NetWidget) update() { self.totalBytesRecv = totalBytesRecv self.totalBytesSent = totalBytesSent + rx, tx := "RX/s", "TX/s" + if self.Mbps { + rx, tx = "mbps", "mbps" + } + format := " %s: %9.1f %2s/s" + + var total, recent uint64 + var label, unitRecent, rate string + var recentConverted float64 // render widget titles for i := 0; i < 2; i++ { - total, label, recent := func() (uint64, string, uint64) { - if i == 0 { - return totalBytesRecv, "RX", recentBytesRecv - } - return totalBytesSent, "TX", recentBytesSent - }() + if i == 0 { + total, label, rate, recent = totalBytesRecv, "RX", rx, recentBytesRecv + } else { + total, label, rate, recent = totalBytesSent, "TX", tx, recentBytesSent + } - recentConverted, unitRecent := utils.ConvertBytes(uint64(recent)) - totalConverted, unitTotal := utils.ConvertBytes(uint64(total)) + totalConverted, unitTotal := utils.ConvertBytes(total) + if self.Mbps { + recentConverted, unitRecent, format = float64(recent)*0.000008, "", " %s: %11.3f %2s" + } else { + recentConverted, unitRecent = utils.ConvertBytes(recent) + } self.Lines[i].Title1 = fmt.Sprintf(" Total %s: %5.1f %s", label, totalConverted, unitTotal) - self.Lines[i].Title2 = fmt.Sprintf(" %s/s: %9.1f %2s/s", label, recentConverted, unitRecent) + self.Lines[i].Title2 = fmt.Sprintf(format, rate, recentConverted, unitRecent) } } -- cgit v1.2.3