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
This commit is contained in:
parent
d22c36e719
commit
9e1b63be32
13
CHANGELOG.md
13
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.
|
||||
|
17
README.md
17
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
|
||||
|
||||
|
@ -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 "<Resize>":
|
||||
ui.Render(grid)
|
||||
if statusbar {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
108
layout/parser.go
108
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)
|
||||
|
@ -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 |⋮| |⋮
|
||||
# ⋮+-----------------------------+⋮+-------------------+⋮+
|
||||
# +===============================+=====================+
|
||||
#
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user