Refactor
This commit is contained in:
parent
3a804eb666
commit
71e10bb6be
4
go.sum
4
go.sum
|
@ -10,7 +10,9 @@ github.com/gizak/termui v0.0.0-20190124041613-958a28575d74 h1:gQbT+IgWIflxp7EQax
|
|||
github.com/gizak/termui v0.0.0-20190124041613-958a28575d74/go.mod h1:hJmkzz29zwvMdxina9wLc5fWN7bZuougH5YR93VrJtA=
|
||||
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/google/pprof v0.0.0-20190109223431-e84dfd68c163 h1:beB+Da4k9B1zmgag78k3k1Bx4L/fdWr5FwNa0f8RxmY=
|
||||
github.com/google/pprof v0.0.0-20190109223431-e84dfd68c163/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
|
@ -36,7 +38,9 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+D
|
|||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M=
|
||||
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/sys v0.0.0-20190116161447-11f53e031339 h1:g/Jesu8+QLnA0CPzF3E1pURg0Byr7i6jLoX5sqjcAh0=
|
||||
golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
73
main.go
73
main.go
|
@ -44,19 +44,19 @@ var (
|
|||
minimalMode = false
|
||||
averageLoad = false
|
||||
percpuLoad = false
|
||||
fahrenheit = false
|
||||
tempScale = w.Celcius
|
||||
battery = false
|
||||
statusbar = false
|
||||
|
||||
renderLock sync.RWMutex
|
||||
|
||||
cpu *w.CPU
|
||||
batt *w.Batt
|
||||
mem *w.Mem
|
||||
proc *w.Proc
|
||||
net *w.Net
|
||||
disk *w.Disk
|
||||
temp *w.Temp
|
||||
cpu *w.CpuWidget
|
||||
batt *w.BatteryWidget
|
||||
mem *w.MemWidget
|
||||
proc *w.ProcWidget
|
||||
net *w.NetWidget
|
||||
disk *w.DiskWidget
|
||||
temp *w.TempWidget
|
||||
help *w.HelpMenu
|
||||
grid *ui.Grid
|
||||
bar *w.StatusBar
|
||||
|
@ -134,7 +134,10 @@ Colorschemes:
|
|||
} else {
|
||||
updateInterval = time.Second / time.Duration(rate)
|
||||
}
|
||||
fahrenheit, _ = args["--fahrenheit"].(bool)
|
||||
fahrenheit, _ := args["--fahrenheit"].(bool)
|
||||
if fahrenheit {
|
||||
tempScale = w.Fahrenheit
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -221,8 +224,8 @@ func setDefaultTermuiColors() {
|
|||
}
|
||||
|
||||
func setWidgetColors() {
|
||||
mem.LineColor["Main"] = ui.Color(colorscheme.MainMem)
|
||||
mem.LineColor["Swap"] = ui.Color(colorscheme.SwapMem)
|
||||
mem.LineColors["Main"] = ui.Color(colorscheme.MainMem)
|
||||
mem.LineColors["Swap"] = ui.Color(colorscheme.SwapMem)
|
||||
|
||||
proc.CursorColor = ui.Color(colorscheme.ProcCursor)
|
||||
|
||||
|
@ -238,7 +241,7 @@ func setWidgetColors() {
|
|||
i = 0
|
||||
}
|
||||
c := colorscheme.CPULines[i]
|
||||
cpu.LineColor[v] = ui.Color(c)
|
||||
cpu.LineColors[v] = ui.Color(c)
|
||||
i++
|
||||
}
|
||||
|
||||
|
@ -256,13 +259,13 @@ func setWidgetColors() {
|
|||
i = 0
|
||||
}
|
||||
c := colorscheme.BattLines[i]
|
||||
batt.LineColor[v] = ui.Color(c)
|
||||
batt.LineColors[v] = ui.Color(c)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
temp.TempLow = ui.Color(colorscheme.TempLow)
|
||||
temp.TempHigh = ui.Color(colorscheme.TempHigh)
|
||||
temp.TempLowColor = ui.Color(colorscheme.TempLow)
|
||||
temp.TempHighColor = ui.Color(colorscheme.TempHigh)
|
||||
|
||||
net.Lines[0].LineColor = ui.Color(colorscheme.Sparkline)
|
||||
net.Lines[0].TitleColor = ui.Color(colorscheme.BorderLabel)
|
||||
|
@ -272,17 +275,17 @@ func setWidgetColors() {
|
|||
}
|
||||
|
||||
func initWidgets() {
|
||||
cpu = w.NewCPU(&renderLock, updateInterval, graphHorizontalScale, averageLoad, percpuLoad)
|
||||
mem = w.NewMem(&renderLock, updateInterval, graphHorizontalScale)
|
||||
proc = w.NewProc(&renderLock)
|
||||
cpu = w.NewCpuWidget(&renderLock, updateInterval, graphHorizontalScale, averageLoad, percpuLoad)
|
||||
mem = w.NewMemWidget(&renderLock, updateInterval, graphHorizontalScale)
|
||||
proc = w.NewProcWidget(&renderLock)
|
||||
help = w.NewHelpMenu()
|
||||
if !minimalMode {
|
||||
if battery {
|
||||
batt = w.NewBatt(&renderLock, graphHorizontalScale)
|
||||
batt = w.NewBatteryWidget(&renderLock, graphHorizontalScale)
|
||||
}
|
||||
net = w.NewNet(&renderLock)
|
||||
disk = w.NewDisk(&renderLock)
|
||||
temp = w.NewTemp(&renderLock, fahrenheit)
|
||||
net = w.NewNetWidget(&renderLock)
|
||||
disk = w.NewDiskWidget(&renderLock)
|
||||
temp = w.NewTempWidget(&renderLock, tempScale)
|
||||
}
|
||||
if statusbar {
|
||||
bar = w.NewStatusBar()
|
||||
|
@ -367,46 +370,46 @@ func eventLoop() {
|
|||
}
|
||||
case "<MouseLeft>":
|
||||
payload := e.Payload.(ui.Mouse)
|
||||
proc.Click(payload.X, payload.Y)
|
||||
proc.HandleClick(payload.X, payload.Y)
|
||||
render(proc)
|
||||
case "k", "<Up>", "<MouseWheelUp>":
|
||||
proc.Up()
|
||||
proc.ScrollUp()
|
||||
render(proc)
|
||||
case "j", "<Down>", "<MouseWheelDown>":
|
||||
proc.Down()
|
||||
proc.ScrollDown()
|
||||
render(proc)
|
||||
case "<Home>":
|
||||
proc.Top()
|
||||
proc.ScrollTop()
|
||||
render(proc)
|
||||
case "g":
|
||||
if previousKey == "g" {
|
||||
proc.Top()
|
||||
proc.ScrollTop()
|
||||
render(proc)
|
||||
}
|
||||
case "G", "<End>":
|
||||
proc.Bottom()
|
||||
proc.ScrollBottom()
|
||||
render(proc)
|
||||
case "<C-d>":
|
||||
proc.HalfPageDown()
|
||||
proc.ScrollHalfPageDown()
|
||||
render(proc)
|
||||
case "<C-u>":
|
||||
proc.HalfPageUp()
|
||||
proc.ScrollHalfPageUp()
|
||||
render(proc)
|
||||
case "<C-f>":
|
||||
proc.PageDown()
|
||||
proc.ScrollPageDown()
|
||||
render(proc)
|
||||
case "<C-b>":
|
||||
proc.PageUp()
|
||||
proc.ScrollPageUp()
|
||||
render(proc)
|
||||
case "d":
|
||||
if previousKey == "d" {
|
||||
proc.Kill()
|
||||
proc.KillProc()
|
||||
}
|
||||
case "<Tab>":
|
||||
proc.Tab()
|
||||
proc.ToggleShowingGroupedProcs()
|
||||
render(proc)
|
||||
case "m", "c", "p":
|
||||
proc.ChangeSort(e)
|
||||
proc.ChangeProcSortMethod(w.ProcSortMethod(e.ID))
|
||||
render(proc)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,6 @@ import (
|
|||
"syscall"
|
||||
)
|
||||
|
||||
func StderrToLogfile(lf *os.File) {
|
||||
syscall.Dup3(int(lf.Fd()), 2, 0)
|
||||
func StderrToLogfile(logfile *os.File) {
|
||||
syscall.Dup3(int(logfile.Fd()), 2, 0)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,6 @@ import (
|
|||
"syscall"
|
||||
)
|
||||
|
||||
func StderrToLogfile(lf *os.File) {
|
||||
syscall.Dup2(int(lf.Fd()), 2)
|
||||
func StderrToLogfile(logfile *os.File) {
|
||||
syscall.Dup2(int(logfile.Fd()), 2)
|
||||
}
|
||||
|
|
|
@ -11,21 +11,26 @@ import (
|
|||
// LineGraph implements a line graph of data points.
|
||||
type LineGraph struct {
|
||||
*Block
|
||||
Data map[string][]float64
|
||||
LineColor map[string]Color
|
||||
HorizontalScale int
|
||||
Labels map[string]string
|
||||
|
||||
Data map[string][]float64
|
||||
Labels map[string]string
|
||||
|
||||
HorizontalScale int
|
||||
|
||||
LineColors map[string]Color
|
||||
DefaultLineColor Color
|
||||
}
|
||||
|
||||
func NewLineGraph() *LineGraph {
|
||||
return &LineGraph{
|
||||
Block: NewBlock(),
|
||||
Data: make(map[string][]float64),
|
||||
LineColor: make(map[string]Color),
|
||||
Labels: make(map[string]string),
|
||||
Block: NewBlock(),
|
||||
|
||||
Data: make(map[string][]float64),
|
||||
Labels: make(map[string]string),
|
||||
|
||||
HorizontalScale: 5,
|
||||
|
||||
LineColors: make(map[string]Color),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +58,7 @@ func (self *LineGraph) Draw(buf *Buffer) {
|
|||
for i := len(seriesList) - 1; i >= 0; i-- {
|
||||
seriesName := seriesList[i]
|
||||
seriesData := self.Data[seriesName]
|
||||
seriesLineColor, ok := self.LineColor[seriesName]
|
||||
seriesLineColor, ok := self.LineColors[seriesName]
|
||||
if !ok {
|
||||
seriesLineColor = self.DefaultLineColor
|
||||
}
|
||||
|
@ -110,7 +115,7 @@ func (self *LineGraph) Draw(buf *Buffer) {
|
|||
if i+2 > self.Inner.Dy() {
|
||||
continue
|
||||
}
|
||||
seriesLineColor, ok := self.LineColor[seriesName]
|
||||
seriesLineColor, ok := self.LineColors[seriesName]
|
||||
if !ok {
|
||||
seriesLineColor = self.DefaultLineColor
|
||||
}
|
||||
|
|
|
@ -16,31 +16,31 @@ type Sparkline struct {
|
|||
LineColor Color
|
||||
}
|
||||
|
||||
// Sparklines is a renderable widget which groups together the given sparklines.
|
||||
type Sparklines struct {
|
||||
// SparklineGroup is a renderable widget which groups together the given sparklines.
|
||||
type SparklineGroup struct {
|
||||
*Block
|
||||
Lines []*Sparkline
|
||||
}
|
||||
|
||||
// Add appends a given Sparkline to the *Sparklines.
|
||||
func (self *Sparklines) Add(sl Sparkline) {
|
||||
// Add appends a given Sparkline to the *SparklineGroup.
|
||||
func (self *SparklineGroup) Add(sl Sparkline) {
|
||||
self.Lines = append(self.Lines, &sl)
|
||||
}
|
||||
|
||||
// NewSparkline returns an unrenderable single sparkline that intended to be added into a Sparklines.
|
||||
// NewSparkline returns an unrenderable single sparkline that intended to be added into a SparklineGroup.
|
||||
func NewSparkline() *Sparkline {
|
||||
return &Sparkline{}
|
||||
}
|
||||
|
||||
// NewSparklines return a new *Sparklines with given Sparklines, you can always add a new Sparkline later.
|
||||
func NewSparklines(ss ...*Sparkline) *Sparklines {
|
||||
return &Sparklines{
|
||||
// NewSparklineGroup return a new *SparklineGroup with given Sparklines, you can always add a new Sparkline later.
|
||||
func NewSparklineGroup(ss ...*Sparkline) *SparklineGroup {
|
||||
return &SparklineGroup{
|
||||
Block: NewBlock(),
|
||||
Lines: ss,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Sparklines) Draw(buf *Buffer) {
|
||||
func (self *SparklineGroup) Draw(buf *Buffer) {
|
||||
self.Block.Draw(buf)
|
||||
|
||||
lc := len(self.Lines) // lineCount
|
||||
|
|
|
@ -14,37 +14,30 @@ type Table struct {
|
|||
Header []string
|
||||
Rows [][]string
|
||||
|
||||
ColWidths []int
|
||||
CellXPos []int // column position
|
||||
ColResizer func() // for widgets that inherit a Table and want to overload the ColResize method
|
||||
Gap int // gap between columns
|
||||
PadLeft int
|
||||
ColWidths []int
|
||||
ColGap int
|
||||
PadLeft int
|
||||
|
||||
Cursor bool
|
||||
ShowCursor bool
|
||||
CursorColor Color
|
||||
|
||||
UniqueCol int // the column used to identify the selected item
|
||||
UniqueCol int // the column used to uniquely identify each table row
|
||||
SelectedItem string // used to keep the cursor on the correct item if the data changes
|
||||
SelectedRow int
|
||||
TopRow int // used to indicate where in the table we are scrolled at
|
||||
|
||||
ColResizer func()
|
||||
}
|
||||
|
||||
// NewTable returns a new Table instance
|
||||
func NewTable() *Table {
|
||||
self := &Table{
|
||||
Block: NewBlock(),
|
||||
// CursorColor: Theme.TableCursor,
|
||||
return &Table{
|
||||
Block: NewBlock(),
|
||||
SelectedRow: 0,
|
||||
TopRow: 0,
|
||||
UniqueCol: 0,
|
||||
ColResizer: func() {},
|
||||
}
|
||||
self.ColResizer = self.ColResize
|
||||
return self
|
||||
}
|
||||
|
||||
// ColResize is the default column resizer, but can be overriden.
|
||||
// ColResize calculates the width of each column.
|
||||
func (self *Table) ColResize() {
|
||||
}
|
||||
|
||||
func (self *Table) Draw(buf *Buffer) {
|
||||
|
@ -53,12 +46,12 @@ func (self *Table) Draw(buf *Buffer) {
|
|||
self.ColResizer()
|
||||
|
||||
// finds exact column starting position
|
||||
self.CellXPos = []int{}
|
||||
colXPos := []int{}
|
||||
cur := 1 + self.PadLeft
|
||||
for _, w := range self.ColWidths {
|
||||
self.CellXPos = append(self.CellXPos, cur)
|
||||
colXPos = append(colXPos, cur)
|
||||
cur += w
|
||||
cur += self.Gap
|
||||
cur += self.ColGap
|
||||
}
|
||||
|
||||
// prints header
|
||||
|
@ -68,13 +61,13 @@ func (self *Table) Draw(buf *Buffer) {
|
|||
continue
|
||||
}
|
||||
// don't render column if it doesn't fit in widget
|
||||
if width > (self.Inner.Dx()-self.CellXPos[i])+1 {
|
||||
if width > (self.Inner.Dx()-colXPos[i])+1 {
|
||||
continue
|
||||
}
|
||||
buf.SetString(
|
||||
h,
|
||||
NewStyle(Theme.Default.Fg, ColorClear, ModifierBold),
|
||||
image.Pt(self.Inner.Min.X+self.CellXPos[i]-1, self.Inner.Min.Y),
|
||||
image.Pt(self.Inner.Min.X+colXPos[i]-1, self.Inner.Min.Y),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -90,7 +83,7 @@ func (self *Table) Draw(buf *Buffer) {
|
|||
|
||||
// prints cursor
|
||||
style := NewStyle(Theme.Default.Fg)
|
||||
if self.Cursor {
|
||||
if self.ShowCursor {
|
||||
if (self.SelectedItem == "" && rowNum == self.SelectedRow) || (self.SelectedItem != "" && self.SelectedItem == row[self.UniqueCol]) {
|
||||
style.Fg = self.CursorColor
|
||||
style.Modifier = ModifierReverse
|
||||
|
@ -115,24 +108,22 @@ func (self *Table) Draw(buf *Buffer) {
|
|||
continue
|
||||
}
|
||||
// don't render column if width is greater than distance to end of widget
|
||||
if width > (self.Inner.Dx()-self.CellXPos[i])+1 {
|
||||
if width > (self.Inner.Dx()-colXPos[i])+1 {
|
||||
continue
|
||||
}
|
||||
r := TrimString(row[i], width)
|
||||
buf.SetString(
|
||||
r,
|
||||
style,
|
||||
image.Pt(self.Inner.Min.X+self.CellXPos[i]-1, self.Inner.Min.Y+y-1),
|
||||
image.Pt(self.Inner.Min.X+colXPos[i]-1, self.Inner.Min.Y+y-1),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// Cursor Movement //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// Scrolling ///////////////////////////////////////////////////////////////////
|
||||
|
||||
// calcPos is used to calculate the cursor position and the current view.
|
||||
// calcPos is used to calculate the cursor position and the current view into the table.
|
||||
func (self *Table) calcPos() {
|
||||
self.SelectedItem = ""
|
||||
|
||||
|
@ -151,49 +142,47 @@ func (self *Table) calcPos() {
|
|||
}
|
||||
}
|
||||
|
||||
func (self *Table) Up() {
|
||||
func (self *Table) ScrollUp() {
|
||||
self.SelectedRow--
|
||||
self.calcPos()
|
||||
}
|
||||
|
||||
func (self *Table) Down() {
|
||||
func (self *Table) ScrollDown() {
|
||||
self.SelectedRow++
|
||||
self.calcPos()
|
||||
}
|
||||
|
||||
func (self *Table) Top() {
|
||||
func (self *Table) ScrollTop() {
|
||||
self.SelectedRow = 0
|
||||
self.calcPos()
|
||||
}
|
||||
|
||||
func (self *Table) Bottom() {
|
||||
func (self *Table) ScrollBottom() {
|
||||
self.SelectedRow = len(self.Rows) - 1
|
||||
self.calcPos()
|
||||
}
|
||||
|
||||
// The number of lines in a page is equal to the height of the widgeself.
|
||||
|
||||
func (self *Table) HalfPageUp() {
|
||||
func (self *Table) ScrollHalfPageUp() {
|
||||
self.SelectedRow = self.SelectedRow - (self.Inner.Dy()-2)/2
|
||||
self.calcPos()
|
||||
}
|
||||
|
||||
func (self *Table) HalfPageDown() {
|
||||
func (self *Table) ScrollHalfPageDown() {
|
||||
self.SelectedRow = self.SelectedRow + (self.Inner.Dy()-2)/2
|
||||
self.calcPos()
|
||||
}
|
||||
|
||||
func (self *Table) PageUp() {
|
||||
func (self *Table) ScrollPageUp() {
|
||||
self.SelectedRow -= (self.Inner.Dy() - 2)
|
||||
self.calcPos()
|
||||
}
|
||||
|
||||
func (self *Table) PageDown() {
|
||||
func (self *Table) ScrollPageDown() {
|
||||
self.SelectedRow += (self.Inner.Dy() - 2)
|
||||
self.calcPos()
|
||||
}
|
||||
|
||||
func (self *Table) Click(x, y int) {
|
||||
func (self *Table) HandleClick(x, y int) {
|
||||
x = x - self.Min.X
|
||||
y = y - self.Min.Y
|
||||
if (x > 0 && x <= self.Inner.Dx()) && (y > 0 && y <= self.Inner.Dy()) {
|
||||
|
|
|
@ -46,7 +46,7 @@ func ConvertBytes(b uint64) (float64, string) {
|
|||
}
|
||||
}
|
||||
|
||||
func Max(a, b int) int {
|
||||
func MaxInt(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
|
|
|
@ -13,25 +13,26 @@ import (
|
|||
ui "github.com/cjbassi/gotop/src/termui"
|
||||
)
|
||||
|
||||
type Batt struct {
|
||||
type BatteryWidget struct {
|
||||
*ui.LineGraph
|
||||
interval time.Duration
|
||||
updateInterval time.Duration
|
||||
}
|
||||
|
||||
func NewBatt(renderLock *sync.RWMutex, horizontalScale int) *Batt {
|
||||
self := &Batt{
|
||||
LineGraph: ui.NewLineGraph(),
|
||||
interval: time.Minute,
|
||||
func NewBatteryWidget(renderLock *sync.RWMutex, horizontalScale int) *BatteryWidget {
|
||||
self := &BatteryWidget{
|
||||
LineGraph: ui.NewLineGraph(),
|
||||
updateInterval: time.Minute,
|
||||
}
|
||||
self.Title = " Battery Status "
|
||||
self.HorizontalScale = horizontalScale
|
||||
|
||||
// intentional duplicate
|
||||
// adds 2 datapoints to the graph, otherwise the dot is difficult to see
|
||||
self.update()
|
||||
self.update()
|
||||
|
||||
go func() {
|
||||
for range time.NewTicker(self.interval).C {
|
||||
for range time.NewTicker(self.updateInterval).C {
|
||||
renderLock.RLock()
|
||||
self.update()
|
||||
renderLock.RUnlock()
|
||||
|
@ -41,20 +42,20 @@ func NewBatt(renderLock *sync.RWMutex, horizontalScale int) *Batt {
|
|||
return self
|
||||
}
|
||||
|
||||
func mkId(i int) string {
|
||||
func makeId(i int) string {
|
||||
return "Batt" + strconv.Itoa(i)
|
||||
}
|
||||
|
||||
func (self *Batt) update() {
|
||||
batts, err := battery.GetAll()
|
||||
func (self *BatteryWidget) update() {
|
||||
batteries, err := battery.GetAll()
|
||||
if err != nil {
|
||||
log.Printf("failed to get battery info from system: %v", err)
|
||||
log.Printf("failed to get battery info: %v", err)
|
||||
return
|
||||
}
|
||||
for i, b := range batts {
|
||||
n := mkId(i)
|
||||
pc := math.Abs(b.Current/b.Full) * 100.0
|
||||
self.Data[n] = append(self.Data[n], pc)
|
||||
self.Labels[n] = fmt.Sprintf("%3.0f%% %.0f/%.0f", pc, math.Abs(b.Current), math.Abs(b.Full))
|
||||
for i, battery := range batteries {
|
||||
id := makeId(i)
|
||||
percentFull := math.Abs(battery.Current/battery.Full) * 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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,66 +6,66 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
psCPU "github.com/shirou/gopsutil/cpu"
|
||||
psCpu "github.com/shirou/gopsutil/cpu"
|
||||
|
||||
ui "github.com/cjbassi/gotop/src/termui"
|
||||
)
|
||||
|
||||
type CPU struct {
|
||||
type CpuWidget struct {
|
||||
*ui.LineGraph
|
||||
Count int // number of cores
|
||||
Average bool // show average load
|
||||
PerCPU bool // show per-core load
|
||||
interval time.Duration
|
||||
formatString string
|
||||
renderLock *sync.RWMutex
|
||||
updateLock sync.Mutex
|
||||
CpuCount int
|
||||
ShowAverageLoad bool
|
||||
ShowPerCpuLoad bool
|
||||
updateInterval time.Duration
|
||||
formatString string
|
||||
renderLock *sync.RWMutex
|
||||
updateLock sync.Mutex
|
||||
}
|
||||
|
||||
func NewCPU(renderLock *sync.RWMutex, interval time.Duration, horizontalScale int, average bool, percpu bool) *CPU {
|
||||
count, err := psCPU.Counts(false)
|
||||
func NewCpuWidget(renderLock *sync.RWMutex, 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 count > 10 {
|
||||
if cpuCount > 10 {
|
||||
formatString = "CPU%02d"
|
||||
}
|
||||
self := &CPU{
|
||||
LineGraph: ui.NewLineGraph(),
|
||||
Count: count,
|
||||
interval: interval,
|
||||
Average: average,
|
||||
PerCPU: percpu,
|
||||
formatString: formatString,
|
||||
renderLock: renderLock,
|
||||
self := &CpuWidget{
|
||||
LineGraph: ui.NewLineGraph(),
|
||||
CpuCount: cpuCount,
|
||||
updateInterval: updateInterval,
|
||||
ShowAverageLoad: showAverageLoad,
|
||||
ShowPerCpuLoad: showPerCpuLoad,
|
||||
formatString: formatString,
|
||||
renderLock: renderLock,
|
||||
}
|
||||
self.Title = " CPU Usage "
|
||||
self.HorizontalScale = horizontalScale
|
||||
|
||||
if !(self.Average || self.PerCPU) {
|
||||
if self.Count <= 8 {
|
||||
self.PerCPU = true
|
||||
if !(self.ShowAverageLoad || self.ShowPerCpuLoad) {
|
||||
if self.CpuCount <= 8 {
|
||||
self.ShowPerCpuLoad = true
|
||||
} else {
|
||||
self.Average = true
|
||||
self.ShowAverageLoad = true
|
||||
}
|
||||
}
|
||||
|
||||
if self.Average {
|
||||
if self.ShowAverageLoad {
|
||||
self.Data["AVRG"] = []float64{0}
|
||||
}
|
||||
|
||||
if self.PerCPU {
|
||||
for i := 0; i < self.Count; i++ {
|
||||
k := fmt.Sprintf(formatString, i)
|
||||
self.Data[k] = []float64{0}
|
||||
if self.ShowPerCpuLoad {
|
||||
for i := 0; i < int(self.CpuCount); i++ {
|
||||
key := fmt.Sprintf(formatString, i)
|
||||
self.Data[key] = []float64{0}
|
||||
}
|
||||
}
|
||||
|
||||
self.update()
|
||||
|
||||
go func() {
|
||||
for range time.NewTicker(self.interval).C {
|
||||
for range time.NewTicker(self.updateInterval).C {
|
||||
self.update()
|
||||
}
|
||||
}()
|
||||
|
@ -73,12 +73,12 @@ func NewCPU(renderLock *sync.RWMutex, interval time.Duration, horizontalScale in
|
|||
return self
|
||||
}
|
||||
|
||||
func (self *CPU) update() {
|
||||
if self.Average {
|
||||
func (self *CpuWidget) update() {
|
||||
if self.ShowAverageLoad {
|
||||
go func() {
|
||||
percent, err := psCPU.Percent(self.interval, false)
|
||||
percent, err := psCpu.Percent(self.updateInterval, false)
|
||||
if err != nil {
|
||||
log.Printf("failed to get average CPU usage percent from gopsutil: %v. self.interval: %v. percpu: %v", err, self.interval, false)
|
||||
log.Printf("failed to get average CPU usage percent from gopsutil: %v. self.updateInterval: %v. percpu: %v", err, self.updateInterval, false)
|
||||
} else {
|
||||
self.renderLock.RLock()
|
||||
defer self.renderLock.RUnlock()
|
||||
|
@ -90,23 +90,23 @@ func (self *CPU) update() {
|
|||
}()
|
||||
}
|
||||
|
||||
if self.PerCPU {
|
||||
if self.ShowPerCpuLoad {
|
||||
go func() {
|
||||
percents, err := psCPU.Percent(self.interval, true)
|
||||
percents, err := psCpu.Percent(self.updateInterval, true)
|
||||
if err != nil {
|
||||
log.Printf("failed to get CPU usage percents from gopsutil: %v. self.interval: %v. percpu: %v", err, self.interval, true)
|
||||
log.Printf("failed to get CPU usage percents from gopsutil: %v. self.updateInterval: %v. percpu: %v", err, self.updateInterval, true)
|
||||
} else {
|
||||
if len(percents) != self.Count {
|
||||
log.Printf("error: number of CPU usage percents from gopsutil doesn't match CPU count. percents: %v. self.Count: %v", percents, self.Count)
|
||||
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.renderLock.RLock()
|
||||
defer self.renderLock.RUnlock()
|
||||
self.updateLock.Lock()
|
||||
defer self.updateLock.Unlock()
|
||||
for i, percent := range percents {
|
||||
k := fmt.Sprintf(self.formatString, i)
|
||||
self.Data[k] = append(self.Data[k], percent)
|
||||
self.Labels[k] = fmt.Sprintf("%3.0f%%", percent)
|
||||
key := fmt.Sprintf(self.formatString, i)
|
||||
self.Data[key] = append(self.Data[key], percent)
|
||||
self.Labels[key] = fmt.Sprintf("%3.0f%%", percent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,37 +15,43 @@ import (
|
|||
)
|
||||
|
||||
type Partition struct {
|
||||
Device string
|
||||
Mount string
|
||||
TotalRead uint64
|
||||
TotalWrite uint64
|
||||
CurRead string
|
||||
CurWrite string
|
||||
UsedPercent int
|
||||
Free string
|
||||
Device string
|
||||
MountPoint string
|
||||
BytesRead uint64
|
||||
BytesWritten uint64
|
||||
BytesReadRecently string
|
||||
BytesWrittenRecently string
|
||||
UsedPercent uint32
|
||||
Free string
|
||||
}
|
||||
|
||||
type Disk struct {
|
||||
type DiskWidget struct {
|
||||
*ui.Table
|
||||
interval time.Duration
|
||||
Partitions map[string]*Partition
|
||||
updateInterval time.Duration
|
||||
Partitions map[string]*Partition
|
||||
}
|
||||
|
||||
func NewDisk(renderLock *sync.RWMutex) *Disk {
|
||||
self := &Disk{
|
||||
Table: ui.NewTable(),
|
||||
interval: time.Second,
|
||||
Partitions: make(map[string]*Partition),
|
||||
func NewDiskWidget(renderLock *sync.RWMutex) *DiskWidget {
|
||||
self := &DiskWidget{
|
||||
Table: ui.NewTable(),
|
||||
updateInterval: time.Second,
|
||||
Partitions: make(map[string]*Partition),
|
||||
}
|
||||
self.Title = " Disk Usage "
|
||||
self.Header = []string{"Disk", "Mount", "Used", "Free", "R/s", "W/s"}
|
||||
self.Gap = 2
|
||||
self.ColResizer = self.ColResize
|
||||
self.ColGap = 2
|
||||
self.ColResizer = func() {
|
||||
self.ColWidths = []int{
|
||||
utils.MaxInt(4, (self.Inner.Dx()-29)/2),
|
||||
utils.MaxInt(5, (self.Inner.Dx()-29)/2),
|
||||
4, 5, 5, 5,
|
||||
}
|
||||
}
|
||||
|
||||
self.update()
|
||||
|
||||
go func() {
|
||||
for range time.NewTicker(self.interval).C {
|
||||
for range time.NewTicker(self.updateInterval).C {
|
||||
renderLock.RLock()
|
||||
self.update()
|
||||
renderLock.RUnlock()
|
||||
|
@ -55,86 +61,87 @@ func NewDisk(renderLock *sync.RWMutex) *Disk {
|
|||
return self
|
||||
}
|
||||
|
||||
func (self *Disk) update() {
|
||||
Partitions, err := psDisk.Partitions(false)
|
||||
func (self *DiskWidget) update() {
|
||||
partitions, err := psDisk.Partitions(false)
|
||||
if err != nil {
|
||||
log.Printf("failed to get disk partitions from gopsutil: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// add partition if it's new
|
||||
for _, Part := range Partitions {
|
||||
for _, partition := range partitions {
|
||||
// don't show loop devices
|
||||
if strings.HasPrefix(Part.Device, "/dev/loop") {
|
||||
if strings.HasPrefix(partition.Device, "/dev/loop") {
|
||||
continue
|
||||
}
|
||||
// don't show docker container filesystems
|
||||
if strings.HasPrefix(Part.Mountpoint, "/var/lib/docker/") {
|
||||
if strings.HasPrefix(partition.Mountpoint, "/var/lib/docker/") {
|
||||
continue
|
||||
}
|
||||
// check if partition doesn't already exist in our list
|
||||
if _, ok := self.Partitions[Part.Device]; !ok {
|
||||
self.Partitions[Part.Device] = &Partition{
|
||||
Device: Part.Device,
|
||||
Mount: Part.Mountpoint,
|
||||
if _, ok := self.Partitions[partition.Device]; !ok {
|
||||
self.Partitions[partition.Device] = &Partition{
|
||||
Device: partition.Device,
|
||||
MountPoint: partition.Mountpoint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete a partition if it no longer exists
|
||||
todelete := []string{}
|
||||
for key := range self.Partitions {
|
||||
toDelete := []string{}
|
||||
for device := range self.Partitions {
|
||||
exists := false
|
||||
for _, Part := range Partitions {
|
||||
if key == Part.Device {
|
||||
for _, partition := range partitions {
|
||||
if device == partition.Device {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
todelete = append(todelete, key)
|
||||
toDelete = append(toDelete, device)
|
||||
}
|
||||
}
|
||||
for _, val := range todelete {
|
||||
delete(self.Partitions, val)
|
||||
for _, device := range toDelete {
|
||||
delete(self.Partitions, device)
|
||||
}
|
||||
|
||||
// updates partition info
|
||||
for _, Part := range self.Partitions {
|
||||
usage, err := psDisk.Usage(Part.Mount)
|
||||
for _, partition := range self.Partitions {
|
||||
usage, err := psDisk.Usage(partition.MountPoint)
|
||||
if err != nil {
|
||||
log.Printf("failed to get partition usage statistics from gopsutil: %v. Part: %v", err, Part)
|
||||
log.Printf("failed to get partition usage statistics from gopsutil: %v. partition: %v", err, partition)
|
||||
continue
|
||||
}
|
||||
Part.UsedPercent = int(usage.UsedPercent)
|
||||
partition.UsedPercent = uint32(usage.UsedPercent)
|
||||
|
||||
Free, Mag := utils.ConvertBytes(usage.Free)
|
||||
Part.Free = fmt.Sprintf("%3d%s", uint64(Free), Mag)
|
||||
bytesFree, magnitudeFree := utils.ConvertBytes(usage.Free)
|
||||
partition.Free = fmt.Sprintf("%3d%s", uint64(bytesFree), magnitudeFree)
|
||||
|
||||
ret, err := psDisk.IOCounters(Part.Device)
|
||||
ioCounters, err := psDisk.IOCounters(partition.Device)
|
||||
if err != nil {
|
||||
log.Printf("failed to get partition read/write info from gopsutil: %v. Part: %v", err, Part)
|
||||
log.Printf("failed to get partition read/write info from gopsutil: %v. partition: %v", err, partition)
|
||||
continue
|
||||
}
|
||||
data := ret[strings.Replace(Part.Device, "/dev/", "", -1)]
|
||||
curRead, curWrite := data.ReadBytes, data.WriteBytes
|
||||
if Part.TotalRead != 0 { // if this isn't the first update
|
||||
readRecent := curRead - Part.TotalRead
|
||||
writeRecent := curWrite - Part.TotalWrite
|
||||
ioCounter := ioCounters[strings.Replace(partition.Device, "/dev/", "", -1)]
|
||||
bytesRead, bytesWritten := ioCounter.ReadBytes, ioCounter.WriteBytes
|
||||
if partition.BytesRead != 0 { // if this isn't the first update
|
||||
bytesReadRecently := bytesRead - partition.BytesRead
|
||||
bytesWrittenRecently := bytesWritten - partition.BytesWritten
|
||||
|
||||
readFloat, unitRead := utils.ConvertBytes(readRecent)
|
||||
writeFloat, unitWrite := utils.ConvertBytes(writeRecent)
|
||||
readRecent, writeRecent = uint64(readFloat), uint64(writeFloat)
|
||||
Part.CurRead = fmt.Sprintf("%d%s", readRecent, unitRead)
|
||||
Part.CurWrite = fmt.Sprintf("%d%s", writeRecent, unitWrite)
|
||||
readFloat, readMagnitude := utils.ConvertBytes(bytesReadRecently)
|
||||
writeFloat, writeMagnitude := utils.ConvertBytes(bytesWrittenRecently)
|
||||
bytesReadRecently, bytesWrittenRecently = uint64(readFloat), uint64(writeFloat)
|
||||
partition.BytesReadRecently = fmt.Sprintf("%d%s", bytesReadRecently, readMagnitude)
|
||||
partition.BytesWrittenRecently = fmt.Sprintf("%d%s", bytesWrittenRecently, writeMagnitude)
|
||||
} else {
|
||||
Part.CurRead = fmt.Sprintf("%d%s", 0, "B")
|
||||
Part.CurWrite = fmt.Sprintf("%d%s", 0, "B")
|
||||
partition.BytesReadRecently = fmt.Sprintf("%d%s", 0, "B")
|
||||
partition.BytesWrittenRecently = fmt.Sprintf("%d%s", 0, "B")
|
||||
}
|
||||
Part.TotalRead, Part.TotalWrite = curRead, curWrite
|
||||
partition.BytesRead, partition.BytesWritten = bytesRead, bytesWritten
|
||||
}
|
||||
|
||||
// converts self.Partitions into self.Rows which is a [][]String
|
||||
|
||||
sortedPartitions := []string{}
|
||||
for seriesName := range self.Partitions {
|
||||
sortedPartitions = append(sortedPartitions, seriesName)
|
||||
|
@ -142,31 +149,15 @@ func (self *Disk) update() {
|
|||
sort.Strings(sortedPartitions)
|
||||
|
||||
self.Rows = make([][]string, len(self.Partitions))
|
||||
|
||||
for i, key := range sortedPartitions {
|
||||
Part := self.Partitions[key]
|
||||
partition := self.Partitions[key]
|
||||
self.Rows[i] = make([]string, 6)
|
||||
self.Rows[i][0] = strings.Replace(strings.Replace(Part.Device, "/dev/", "", -1), "mapper/", "", -1)
|
||||
self.Rows[i][1] = Part.Mount
|
||||
self.Rows[i][2] = fmt.Sprintf("%d%%", Part.UsedPercent)
|
||||
self.Rows[i][3] = Part.Free
|
||||
self.Rows[i][4] = Part.CurRead
|
||||
self.Rows[i][5] = Part.CurWrite
|
||||
}
|
||||
}
|
||||
|
||||
// ColResize overrides the default ColResize in the termui table.
|
||||
func (self *Disk) ColResize() {
|
||||
self.ColWidths = []int{
|
||||
utils.Max(4, (self.Inner.Dx()-29)/2),
|
||||
utils.Max(5, (self.Inner.Dx()-29)/2),
|
||||
4, 5, 5, 5,
|
||||
}
|
||||
|
||||
self.CellXPos = []int{}
|
||||
cur := 1
|
||||
for _, w := range self.ColWidths {
|
||||
self.CellXPos = append(self.CellXPos, cur)
|
||||
cur += w
|
||||
cur += self.Gap
|
||||
self.Rows[i][0] = strings.Replace(strings.Replace(partition.Device, "/dev/", "", -1), "mapper/", "", -1)
|
||||
self.Rows[i][1] = partition.MountPoint
|
||||
self.Rows[i][2] = fmt.Sprintf("%d%%", partition.UsedPercent)
|
||||
self.Rows[i][3] = partition.Free
|
||||
self.Rows[i][4] = partition.BytesReadRecently
|
||||
self.Rows[i][5] = partition.BytesWrittenRecently
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,9 +57,9 @@ func (self *HelpMenu) Draw(buf *ui.Buffer) {
|
|||
self.Block.Draw(buf)
|
||||
|
||||
for y, line := range strings.Split(KEYBINDS, "\n") {
|
||||
for x, char := range line {
|
||||
for x, rune := range line {
|
||||
buf.SetCell(
|
||||
ui.NewCell(char, ui.NewStyle(7)),
|
||||
ui.NewCell(rune, ui.NewStyle(7)),
|
||||
image.Pt(self.Inner.Min.X+x, self.Inner.Min.Y+y-1),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,15 +12,15 @@ import (
|
|||
"github.com/cjbassi/gotop/src/utils"
|
||||
)
|
||||
|
||||
type Mem struct {
|
||||
type MemWidget struct {
|
||||
*ui.LineGraph
|
||||
interval time.Duration
|
||||
updateInterval time.Duration
|
||||
}
|
||||
|
||||
func NewMem(renderLock *sync.RWMutex, interval time.Duration, horizontalScale int) *Mem {
|
||||
self := &Mem{
|
||||
LineGraph: ui.NewLineGraph(),
|
||||
interval: interval,
|
||||
func NewMemWidget(renderLock *sync.RWMutex, updateInterval time.Duration, horizontalScale int) *MemWidget {
|
||||
self := &MemWidget{
|
||||
LineGraph: ui.NewLineGraph(),
|
||||
updateInterval: updateInterval,
|
||||
}
|
||||
self.Title = " Memory Usage "
|
||||
self.HorizontalScale = horizontalScale
|
||||
|
@ -30,7 +30,7 @@ func NewMem(renderLock *sync.RWMutex, interval time.Duration, horizontalScale in
|
|||
self.update()
|
||||
|
||||
go func() {
|
||||
for range time.NewTicker(self.interval).C {
|
||||
for range time.NewTicker(self.updateInterval).C {
|
||||
renderLock.RLock()
|
||||
self.update()
|
||||
renderLock.RUnlock()
|
||||
|
@ -40,24 +40,36 @@ func NewMem(renderLock *sync.RWMutex, interval time.Duration, horizontalScale in
|
|||
return self
|
||||
}
|
||||
|
||||
func (self *Mem) update() {
|
||||
main, err := psMem.VirtualMemory()
|
||||
func (self *MemWidget) update() {
|
||||
mainMemory, err := psMem.VirtualMemory()
|
||||
if err != nil {
|
||||
log.Printf("failed to get main memory info from gopsutil: %v", err)
|
||||
} else {
|
||||
self.Data["Main"] = append(self.Data["Main"], main.UsedPercent)
|
||||
mainTotalBytes, mainTotalMagnitude := utils.ConvertBytes(main.Total)
|
||||
mainUsedBytes, mainUsedMagnitude := utils.ConvertBytes(main.Used)
|
||||
self.Labels["Main"] = fmt.Sprintf("%3.0f%% %5.1f%s/%.0f%s", main.UsedPercent, mainUsedBytes, mainUsedMagnitude, mainTotalBytes, mainTotalMagnitude)
|
||||
self.Data["Main"] = append(self.Data["Main"], mainMemory.UsedPercent)
|
||||
mainMemoryTotalBytes, mainMemoryTotalMagnitude := utils.ConvertBytes(mainMemory.Total)
|
||||
mainMemoryUsedBytes, mainMemoryUsedMagnitude := utils.ConvertBytes(mainMemory.Used)
|
||||
self.Labels["Main"] = fmt.Sprintf("%3.0f%% %5.1f%s/%.0f%s",
|
||||
mainMemory.UsedPercent,
|
||||
mainMemoryUsedBytes,
|
||||
mainMemoryUsedMagnitude,
|
||||
mainMemoryTotalBytes,
|
||||
mainMemoryTotalMagnitude,
|
||||
)
|
||||
}
|
||||
|
||||
swap, err := psMem.SwapMemory()
|
||||
swapMemory, err := psMem.SwapMemory()
|
||||
if err != nil {
|
||||
log.Printf("failed to get swap memory info from gopsutil: %v", err)
|
||||
} else {
|
||||
self.Data["Swap"] = append(self.Data["Swap"], swap.UsedPercent)
|
||||
swapTotalBytes, swapTotalMagnitude := utils.ConvertBytes(swap.Total)
|
||||
swapUsedBytes, swapUsedMagnitude := utils.ConvertBytes(swap.Used)
|
||||
self.Labels["Swap"] = fmt.Sprintf("%3.0f%% %5.1f%s/%.0f%s", swap.UsedPercent, swapUsedBytes, swapUsedMagnitude, swapTotalBytes, swapTotalMagnitude)
|
||||
self.Data["Swap"] = append(self.Data["Swap"], swapMemory.UsedPercent)
|
||||
swapMemoryTotalBytes, swapMemoryTotalMagnitude := utils.ConvertBytes(swapMemory.Total)
|
||||
swapMemoryUsedBytes, swapMemoryUsedMagnitude := utils.ConvertBytes(swapMemory.Used)
|
||||
self.Labels["Swap"] = fmt.Sprintf("%3.0f%% %5.1f%s/%.0f%s",
|
||||
swapMemory.UsedPercent,
|
||||
swapMemoryUsedBytes,
|
||||
swapMemoryUsedMagnitude,
|
||||
swapMemoryTotalBytes,
|
||||
swapMemoryTotalMagnitude,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,33 +12,33 @@ import (
|
|||
"github.com/cjbassi/gotop/src/utils"
|
||||
)
|
||||
|
||||
type Net struct {
|
||||
*ui.Sparklines
|
||||
interval time.Duration
|
||||
type NetWidget struct {
|
||||
*ui.SparklineGroup
|
||||
updateInterval time.Duration
|
||||
|
||||
// used to calculate recent network activity
|
||||
prevRecvTotal uint64
|
||||
prevSentTotal uint64
|
||||
totalBytesRecv uint64
|
||||
totalBytesSent uint64
|
||||
}
|
||||
|
||||
func NewNet(renderLock *sync.RWMutex) *Net {
|
||||
recv := ui.NewSparkline()
|
||||
recv.Data = []int{}
|
||||
func NewNetWidget(renderLock *sync.RWMutex) *NetWidget {
|
||||
recvSparkline := ui.NewSparkline()
|
||||
recvSparkline.Data = []int{}
|
||||
|
||||
sent := ui.NewSparkline()
|
||||
sent.Data = []int{}
|
||||
sentSparkline := ui.NewSparkline()
|
||||
sentSparkline.Data = []int{}
|
||||
|
||||
spark := ui.NewSparklines(recv, sent)
|
||||
self := &Net{
|
||||
Sparklines: spark,
|
||||
interval: time.Second,
|
||||
spark := ui.NewSparklineGroup(recvSparkline, sentSparkline)
|
||||
self := &NetWidget{
|
||||
SparklineGroup: spark,
|
||||
updateInterval: time.Second,
|
||||
}
|
||||
self.Title = " Network Usage "
|
||||
|
||||
self.update()
|
||||
|
||||
go func() {
|
||||
for range time.NewTicker(self.interval).C {
|
||||
for range time.NewTicker(self.updateInterval).C {
|
||||
renderLock.RLock()
|
||||
self.update()
|
||||
renderLock.RUnlock()
|
||||
|
@ -48,60 +48,62 @@ func NewNet(renderLock *sync.RWMutex) *Net {
|
|||
return self
|
||||
}
|
||||
|
||||
func (self *Net) update() {
|
||||
func (self *NetWidget) update() {
|
||||
interfaces, err := psNet.IOCounters(true)
|
||||
if err != nil {
|
||||
log.Printf("failed to get network activity from gopsutil: %v", err)
|
||||
return
|
||||
}
|
||||
var curRecvTotal uint64
|
||||
var curSentTotal uint64
|
||||
|
||||
var totalBytesRecv uint64
|
||||
var totalBytesSent uint64
|
||||
for _, _interface := range interfaces {
|
||||
// ignore VPN interface
|
||||
if _interface.Name != "tun0" {
|
||||
curRecvTotal += _interface.BytesRecv
|
||||
curSentTotal += _interface.BytesSent
|
||||
totalBytesRecv += _interface.BytesRecv
|
||||
totalBytesSent += _interface.BytesSent
|
||||
}
|
||||
}
|
||||
var recvRecent uint64
|
||||
var sentRecent uint64
|
||||
|
||||
if self.prevRecvTotal != 0 { // if this isn't the first update
|
||||
recvRecent = curRecvTotal - self.prevRecvTotal
|
||||
sentRecent = curSentTotal - self.prevSentTotal
|
||||
var recentBytesRecv uint64
|
||||
var recentBytesSent uint64
|
||||
|
||||
if int(recvRecent) < 0 {
|
||||
log.Printf("error: negative value for recently received network data from gopsutil. recvRecent: %v", recvRecent)
|
||||
if self.totalBytesRecv != 0 { // if this isn't the first update
|
||||
recentBytesRecv = totalBytesRecv - self.totalBytesRecv
|
||||
recentBytesSent = totalBytesSent - self.totalBytesSent
|
||||
|
||||
if int(recentBytesRecv) < 0 {
|
||||
log.Printf("error: negative value for recently received network data from gopsutil. recentBytesRecv: %v", recentBytesRecv)
|
||||
// recover from error
|
||||
recvRecent = 0
|
||||
recentBytesRecv = 0
|
||||
}
|
||||
if int(sentRecent) < 0 {
|
||||
log.Printf("error: negative value for recently sent network data from gopsutil. sentRecent: %v", sentRecent)
|
||||
if int(recentBytesSent) < 0 {
|
||||
log.Printf("error: negative value for recently sent network data from gopsutil. recentBytesSent: %v", recentBytesSent)
|
||||
// recover from error
|
||||
sentRecent = 0
|
||||
recentBytesSent = 0
|
||||
}
|
||||
|
||||
self.Lines[0].Data = append(self.Lines[0].Data, int(recvRecent))
|
||||
self.Lines[1].Data = append(self.Lines[1].Data, int(sentRecent))
|
||||
self.Lines[0].Data = append(self.Lines[0].Data, int(recentBytesRecv))
|
||||
self.Lines[1].Data = append(self.Lines[1].Data, int(recentBytesSent))
|
||||
}
|
||||
|
||||
// used in later calls to update
|
||||
self.prevRecvTotal = curRecvTotal
|
||||
self.prevSentTotal = curSentTotal
|
||||
self.totalBytesRecv = totalBytesRecv
|
||||
self.totalBytesSent = totalBytesSent
|
||||
|
||||
// render widget titles
|
||||
for i := 0; i < 2; i++ {
|
||||
total, label, recent := func() (uint64, string, uint64) {
|
||||
if i == 0 {
|
||||
return curRecvTotal, "RX", recvRecent
|
||||
return totalBytesRecv, "RX", recentBytesRecv
|
||||
}
|
||||
return curSentTotal, "Tx", sentRecent
|
||||
return totalBytesSent, "Tx", recentBytesSent
|
||||
}()
|
||||
|
||||
recentConv, unitRecent := utils.ConvertBytes(uint64(recent))
|
||||
totalConv, unitTotal := utils.ConvertBytes(uint64(total))
|
||||
recentConverted, unitRecent := utils.ConvertBytes(uint64(recent))
|
||||
totalConverted, unitTotal := utils.ConvertBytes(uint64(total))
|
||||
|
||||
self.Lines[i].Title1 = fmt.Sprintf(" Total %s: %5.1f %s", label, totalConv, unitTotal)
|
||||
self.Lines[i].Title2 = fmt.Sprintf(" %s/s: %9.1f %2s/s", label, recentConv, unitRecent)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gizak/termui"
|
||||
psCPU "github.com/shirou/gopsutil/cpu"
|
||||
|
||||
ui "github.com/cjbassi/gotop/src/termui"
|
||||
|
@ -17,56 +16,67 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
UP = "▲"
|
||||
DOWN = "▼"
|
||||
UP_ARROW = "▲"
|
||||
DOWN_ARROW = "▼"
|
||||
)
|
||||
|
||||
// Process represents each process.
|
||||
type Process struct {
|
||||
PID int
|
||||
Command string
|
||||
CPU float64
|
||||
Mem float64
|
||||
Args string
|
||||
}
|
||||
type ProcSortMethod string
|
||||
|
||||
const (
|
||||
ProcSortCpu ProcSortMethod = "c"
|
||||
ProcSortMem = "m"
|
||||
ProcSortPid = "p"
|
||||
)
|
||||
|
||||
type Proc struct {
|
||||
*ui.Table
|
||||
cpuCount float64
|
||||
interval time.Duration
|
||||
sortMethod string
|
||||
groupedProcs []Process
|
||||
ungroupedProcs []Process
|
||||
group bool
|
||||
Pid int
|
||||
CommandName string
|
||||
FullCommand string
|
||||
Cpu float64
|
||||
Mem float64
|
||||
}
|
||||
|
||||
func NewProc(renderLock *sync.RWMutex) *Proc {
|
||||
type ProcWidget struct {
|
||||
*ui.Table
|
||||
cpuCount float64
|
||||
updateInterval time.Duration
|
||||
sortMethod ProcSortMethod
|
||||
groupedProcs []Proc
|
||||
ungroupedProcs []Proc
|
||||
showGroupedProcs bool
|
||||
}
|
||||
|
||||
func NewProcWidget(renderLock *sync.RWMutex) *ProcWidget {
|
||||
cpuCount, err := psCPU.Counts(false)
|
||||
if err != nil {
|
||||
log.Printf("failed to get CPU count from gopsutil: %v", err)
|
||||
}
|
||||
self := &Proc{
|
||||
Table: ui.NewTable(),
|
||||
interval: time.Second,
|
||||
cpuCount: float64(cpuCount),
|
||||
sortMethod: "c",
|
||||
group: true,
|
||||
self := &ProcWidget{
|
||||
Table: ui.NewTable(),
|
||||
updateInterval: time.Second,
|
||||
cpuCount: float64(cpuCount),
|
||||
sortMethod: ProcSortCpu,
|
||||
showGroupedProcs: true,
|
||||
}
|
||||
self.Title = " Processes "
|
||||
self.ColResizer = self.ColResize
|
||||
self.Cursor = true
|
||||
self.Gap = 3
|
||||
self.ShowCursor = true
|
||||
self.ColGap = 3
|
||||
self.PadLeft = 2
|
||||
self.ColResizer = func() {
|
||||
self.ColWidths = []int{
|
||||
5, utils.MaxInt(self.Inner.Dx()-26, 10), 4, 4,
|
||||
}
|
||||
}
|
||||
|
||||
self.UniqueCol = 0
|
||||
if self.group {
|
||||
if self.showGroupedProcs {
|
||||
self.UniqueCol = 1
|
||||
}
|
||||
|
||||
self.update()
|
||||
|
||||
go func() {
|
||||
for range time.NewTicker(self.interval).C {
|
||||
for range time.NewTicker(self.updateInterval).C {
|
||||
renderLock.RLock()
|
||||
self.update()
|
||||
renderLock.RUnlock()
|
||||
|
@ -76,120 +86,104 @@ func NewProc(renderLock *sync.RWMutex) *Proc {
|
|||
return self
|
||||
}
|
||||
|
||||
// Sort sorts either the grouped or ungrouped []Process based on the sortMethod.
|
||||
func (self *ProcWidget) update() {
|
||||
procs, err := getProcs()
|
||||
if err != nil {
|
||||
log.Printf("failed to retrieve processes: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// can't iterate on the entries directly since we can't modify them that way
|
||||
for i := range procs {
|
||||
procs[i].Cpu /= self.cpuCount
|
||||
}
|
||||
|
||||
self.ungroupedProcs = procs
|
||||
self.groupedProcs = groupProcs(procs)
|
||||
|
||||
self.sortProcs()
|
||||
self.convertProcsToTableRows()
|
||||
}
|
||||
|
||||
// sortProcs sorts either the grouped or ungrouped []Process based on the sortMethod.
|
||||
// Called with every update, when the sort method is changed, and when processes are grouped and ungrouped.
|
||||
func (self *Proc) Sort() {
|
||||
func (self *ProcWidget) sortProcs() {
|
||||
self.Header = []string{"Count", "Command", "CPU%", "Mem%"}
|
||||
|
||||
if !self.group {
|
||||
if !self.showGroupedProcs {
|
||||
self.Header[0] = "PID"
|
||||
}
|
||||
|
||||
processes := &self.ungroupedProcs
|
||||
if self.group {
|
||||
processes = &self.groupedProcs
|
||||
var procs *[]Proc
|
||||
if self.showGroupedProcs {
|
||||
procs = &self.groupedProcs
|
||||
} else {
|
||||
procs = &self.ungroupedProcs
|
||||
}
|
||||
|
||||
switch self.sortMethod {
|
||||
case "c":
|
||||
sort.Sort(sort.Reverse(ProcessByCPU(*processes)))
|
||||
self.Header[2] += DOWN
|
||||
case "p":
|
||||
if self.group {
|
||||
sort.Sort(sort.Reverse(ProcessByPID(*processes)))
|
||||
case ProcSortCpu:
|
||||
sort.Sort(sort.Reverse(SortProcsByCpu(*procs)))
|
||||
self.Header[2] += DOWN_ARROW
|
||||
case ProcSortPid:
|
||||
if self.showGroupedProcs {
|
||||
sort.Sort(sort.Reverse(SortProcsByPid(*procs)))
|
||||
} else {
|
||||
sort.Sort(ProcessByPID(*processes))
|
||||
sort.Sort(SortProcsByPid(*procs))
|
||||
}
|
||||
self.Header[0] += DOWN
|
||||
case "m":
|
||||
sort.Sort(sort.Reverse(ProcessByMem(*processes)))
|
||||
self.Header[3] += DOWN
|
||||
}
|
||||
|
||||
self.Rows = FieldsToStrings(*processes, self.group)
|
||||
}
|
||||
|
||||
// ColResize overrides the default ColResize in the termui table.
|
||||
func (self *Proc) ColResize() {
|
||||
self.ColWidths = []int{
|
||||
5, utils.Max(self.Inner.Dx()-26, 10), 4, 4,
|
||||
self.Header[0] += DOWN_ARROW
|
||||
case ProcSortMem:
|
||||
sort.Sort(sort.Reverse(SortProcsByMem(*procs)))
|
||||
self.Header[3] += DOWN_ARROW
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Proc) ChangeSort(e termui.Event) {
|
||||
if self.sortMethod != e.ID {
|
||||
self.sortMethod = e.ID
|
||||
self.Top()
|
||||
self.Sort()
|
||||
// convertProcsToTableRows converts a []Proc to a [][]string and sets it to the table Rows
|
||||
func (self *ProcWidget) convertProcsToTableRows() {
|
||||
var procs *[]Proc
|
||||
if self.showGroupedProcs {
|
||||
procs = &self.groupedProcs
|
||||
} else {
|
||||
procs = &self.ungroupedProcs
|
||||
}
|
||||
strings := make([][]string, len(*procs))
|
||||
for i := range *procs {
|
||||
strings[i] = make([]string, 4)
|
||||
strings[i][0] = strconv.Itoa(int((*procs)[i].Pid))
|
||||
if self.showGroupedProcs {
|
||||
strings[i][1] = (*procs)[i].CommandName
|
||||
} else {
|
||||
strings[i][1] = (*procs)[i].FullCommand
|
||||
}
|
||||
strings[i][2] = fmt.Sprintf("%4s", strconv.FormatFloat((*procs)[i].Cpu, 'f', 1, 64))
|
||||
strings[i][3] = fmt.Sprintf("%4s", strconv.FormatFloat(float64((*procs)[i].Mem), 'f', 1, 64))
|
||||
}
|
||||
self.Rows = strings
|
||||
}
|
||||
|
||||
func (self *ProcWidget) ChangeProcSortMethod(method ProcSortMethod) {
|
||||
if self.sortMethod != method {
|
||||
self.sortMethod = method
|
||||
self.ScrollTop()
|
||||
self.sortProcs()
|
||||
self.convertProcsToTableRows()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Proc) Tab() {
|
||||
self.group = !self.group
|
||||
if self.group {
|
||||
func (self *ProcWidget) ToggleShowingGroupedProcs() {
|
||||
self.showGroupedProcs = !self.showGroupedProcs
|
||||
if self.showGroupedProcs {
|
||||
self.UniqueCol = 1
|
||||
} else {
|
||||
self.UniqueCol = 0
|
||||
}
|
||||
self.Sort()
|
||||
self.Top()
|
||||
self.ScrollTop()
|
||||
self.sortProcs()
|
||||
self.convertProcsToTableRows()
|
||||
}
|
||||
|
||||
// Group groupes a []Process based on command name.
|
||||
// The first field changes from PID to count.
|
||||
// CPU and Mem are added together for each Process.
|
||||
func Group(P []Process) []Process {
|
||||
groupedP := make(map[string]Process)
|
||||
for _, process := range P {
|
||||
val, ok := groupedP[process.Command]
|
||||
if ok {
|
||||
groupedP[process.Command] = Process{
|
||||
val.PID + 1,
|
||||
val.Command,
|
||||
val.CPU + process.CPU,
|
||||
val.Mem + process.Mem,
|
||||
"",
|
||||
}
|
||||
} else {
|
||||
groupedP[process.Command] = Process{
|
||||
1,
|
||||
process.Command,
|
||||
process.CPU,
|
||||
process.Mem,
|
||||
"",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groupList := make([]Process, len(groupedP))
|
||||
var i int
|
||||
for _, val := range groupedP {
|
||||
groupList[i] = val
|
||||
i++
|
||||
}
|
||||
|
||||
return groupList
|
||||
}
|
||||
|
||||
// FieldsToStrings converts a []Process to a [][]string
|
||||
func FieldsToStrings(P []Process, grouped bool) [][]string {
|
||||
strings := make([][]string, len(P))
|
||||
for i, p := range P {
|
||||
strings[i] = make([]string, 4)
|
||||
strings[i][0] = strconv.Itoa(int(p.PID))
|
||||
if grouped {
|
||||
strings[i][1] = p.Command
|
||||
} else {
|
||||
strings[i][1] = p.Args
|
||||
}
|
||||
strings[i][2] = fmt.Sprintf("%4s", strconv.FormatFloat(p.CPU, 'f', 1, 64))
|
||||
strings[i][3] = fmt.Sprintf("%4s", strconv.FormatFloat(float64(p.Mem), 'f', 1, 64))
|
||||
}
|
||||
return strings
|
||||
}
|
||||
|
||||
// Kill kills process or group of processes.
|
||||
func (self *Proc) Kill() {
|
||||
// KillProc kills a process or group of processes depending on if we're displaying the processes grouped or not.
|
||||
func (self *ProcWidget) KillProc() {
|
||||
self.SelectedItem = ""
|
||||
command := "kill"
|
||||
if self.UniqueCol == 1 {
|
||||
|
@ -200,57 +194,91 @@ func (self *Proc) Kill() {
|
|||
cmd.Wait()
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// []Process Sorting //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// groupProcs groupes a []Proc based on command name.
|
||||
// The first field changes from PID to count.
|
||||
// Cpu and Mem are added together for each Proc.
|
||||
func groupProcs(procs []Proc) []Proc {
|
||||
groupedProcsMap := make(map[string]Proc)
|
||||
for _, proc := range procs {
|
||||
val, ok := groupedProcsMap[proc.CommandName]
|
||||
if ok {
|
||||
groupedProcsMap[proc.CommandName] = Proc{
|
||||
val.Pid + 1,
|
||||
val.CommandName,
|
||||
"",
|
||||
val.Cpu + proc.Cpu,
|
||||
val.Mem + proc.Mem,
|
||||
}
|
||||
} else {
|
||||
groupedProcsMap[proc.CommandName] = Proc{
|
||||
1,
|
||||
proc.CommandName,
|
||||
"",
|
||||
proc.Cpu,
|
||||
proc.Mem,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ProcessByCPU []Process
|
||||
groupedProcsList := make([]Proc, len(groupedProcsMap))
|
||||
i := 0
|
||||
for _, val := range groupedProcsMap {
|
||||
groupedProcsList[i] = val
|
||||
i++
|
||||
}
|
||||
|
||||
return groupedProcsList
|
||||
}
|
||||
|
||||
// []Proc Sorting //////////////////////////////////////////////////////////////
|
||||
|
||||
type SortProcsByCpu []Proc
|
||||
|
||||
// Len implements Sort interface
|
||||
func (P ProcessByCPU) Len() int {
|
||||
return len(P)
|
||||
func (self SortProcsByCpu) Len() int {
|
||||
return len(self)
|
||||
}
|
||||
|
||||
// Swap implements Sort interface
|
||||
func (P ProcessByCPU) Swap(i, j int) {
|
||||
P[i], P[j] = P[j], P[i]
|
||||
func (self SortProcsByCpu) Swap(i, j int) {
|
||||
self[i], self[j] = self[j], self[i]
|
||||
}
|
||||
|
||||
// Less implements Sort interface
|
||||
func (P ProcessByCPU) Less(i, j int) bool {
|
||||
return P[i].CPU < P[j].CPU
|
||||
func (self SortProcsByCpu) Less(i, j int) bool {
|
||||
return self[i].Cpu < self[j].Cpu
|
||||
}
|
||||
|
||||
type ProcessByPID []Process
|
||||
type SortProcsByPid []Proc
|
||||
|
||||
// Len implements Sort interface
|
||||
func (P ProcessByPID) Len() int {
|
||||
return len(P)
|
||||
func (self SortProcsByPid) Len() int {
|
||||
return len(self)
|
||||
}
|
||||
|
||||
// Swap implements Sort interface
|
||||
func (P ProcessByPID) Swap(i, j int) {
|
||||
P[i], P[j] = P[j], P[i]
|
||||
func (self SortProcsByPid) Swap(i, j int) {
|
||||
self[i], self[j] = self[j], self[i]
|
||||
}
|
||||
|
||||
// Less implements Sort interface
|
||||
func (P ProcessByPID) Less(i, j int) bool {
|
||||
return P[i].PID < P[j].PID
|
||||
func (self SortProcsByPid) Less(i, j int) bool {
|
||||
return self[i].Pid < self[j].Pid
|
||||
}
|
||||
|
||||
type ProcessByMem []Process
|
||||
type SortProcsByMem []Proc
|
||||
|
||||
// Len implements Sort interface
|
||||
func (P ProcessByMem) Len() int {
|
||||
return len(P)
|
||||
func (self SortProcsByMem) Len() int {
|
||||
return len(self)
|
||||
}
|
||||
|
||||
// Swap implements Sort interface
|
||||
func (P ProcessByMem) Swap(i, j int) {
|
||||
P[i], P[j] = P[j], P[i]
|
||||
func (self SortProcsByMem) Swap(i, j int) {
|
||||
self[i], self[j] = self[j], self[i]
|
||||
}
|
||||
|
||||
// Less implements Sort interface
|
||||
func (P ProcessByMem) Less(i, j int) bool {
|
||||
return P[i].Mem < P[j].Mem
|
||||
func (self SortProcsByMem) Less(i, j int) bool {
|
||||
return self[i].Mem < self[j].Mem
|
||||
}
|
||||
|
|
|
@ -8,35 +8,17 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
func (self *Proc) update() {
|
||||
processes, err := Processes()
|
||||
if err != nil {
|
||||
log.Printf("failed to retrieve processes: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// have to iterate like this in order to actually change the value
|
||||
for i := range processes {
|
||||
processes[i].CPU /= self.cpuCount
|
||||
}
|
||||
|
||||
self.ungroupedProcs = processes
|
||||
self.groupedProcs = Group(processes)
|
||||
|
||||
self.Sort()
|
||||
}
|
||||
|
||||
func Processes() ([]Process, error) {
|
||||
func getProcs() ([]Proc, error) {
|
||||
output, err := exec.Command("ps", "-axo", "pid:10,comm:50,pcpu:5,pmem:5,args").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute 'ps' command: %v", err)
|
||||
}
|
||||
|
||||
// converts to []string, removing trailing newline and header
|
||||
processStrArr := strings.Split(strings.TrimSuffix(string(output), "\n"), "\n")[1:]
|
||||
linesOfProcStrings := strings.Split(strings.TrimSuffix(string(output), "\n"), "\n")[1:]
|
||||
|
||||
processes := []Process{}
|
||||
for _, line := range processStrArr {
|
||||
procs := []Proc{}
|
||||
for _, line := range linesOfProcStrings {
|
||||
pid, err := strconv.Atoi(strings.TrimSpace(line[0:10]))
|
||||
if err != nil {
|
||||
log.Printf("failed to convert PID to int: %v. line: %v", err, line)
|
||||
|
@ -49,14 +31,15 @@ func Processes() ([]Process, error) {
|
|||
if err != nil {
|
||||
log.Printf("failed to convert Mem usage to float: %v. line: %v", err, line)
|
||||
}
|
||||
process := Process{
|
||||
PID: pid,
|
||||
Command: strings.TrimSpace(line[11:61]),
|
||||
CPU: cpu,
|
||||
Mem: mem,
|
||||
Args: line[74:],
|
||||
proc := Proc{
|
||||
Pid: pid,
|
||||
CommandName: strings.TrimSpace(line[11:61]),
|
||||
FullCommand: line[74:],
|
||||
Cpu: cpu,
|
||||
Mem: mem,
|
||||
}
|
||||
processes = append(processes, process)
|
||||
procs = append(procs, proc)
|
||||
}
|
||||
return processes, nil
|
||||
|
||||
return procs, nil
|
||||
}
|
||||
|
|
|
@ -11,31 +11,31 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// Define column widths for ps output used in Processes()
|
||||
// Define column widths for ps output used in Procs()
|
||||
five = "12345"
|
||||
ten = five + five
|
||||
fifty = ten + ten + ten + ten + ten
|
||||
)
|
||||
|
||||
func (self *Proc) update() {
|
||||
processes, err := Processes()
|
||||
func (self *ProcWidget) update() {
|
||||
procs, err := GetProcs()
|
||||
if err != nil {
|
||||
log.Printf("failed to retrieve processes: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// have to iterate like this in order to actually change the value
|
||||
for i := range processes {
|
||||
processes[i].CPU /= self.cpuCount
|
||||
for i := range procs {
|
||||
procs[i].CPU /= self.cpuCount
|
||||
}
|
||||
|
||||
self.ungroupedProcs = processes
|
||||
self.groupedProcs = Group(processes)
|
||||
self.ungroupedProcs = procs
|
||||
self.groupedProcs = GroupProcs(procs)
|
||||
|
||||
self.Sort()
|
||||
self.SortProcesses()
|
||||
}
|
||||
|
||||
func Processes() ([]Process, error) {
|
||||
func GetProcs() ([]Process, error) {
|
||||
keywords := fmt.Sprintf("pid=%s,comm=%s,pcpu=%s,pmem=%s,args", ten, fifty, five, five)
|
||||
output, err := exec.Command("ps", "-caxo", keywords).Output()
|
||||
if err != nil {
|
||||
|
@ -43,10 +43,10 @@ func Processes() ([]Process, error) {
|
|||
}
|
||||
|
||||
// converts to []string and removes the header
|
||||
strOutput := strings.Split(strings.TrimSpace(string(output)), "\n")[1:]
|
||||
linesOfProcStrings := strings.Split(strings.TrimSpace(string(output)), "\n")[1:]
|
||||
|
||||
processes := []Process{}
|
||||
for _, line := range strOutput {
|
||||
procs := []Proc{}
|
||||
for _, line := range linesOfProcStrings {
|
||||
pid, err := strconv.Atoi(strings.TrimSpace(line[0:10]))
|
||||
if err != nil {
|
||||
log.Printf("failed to convert first field to int: %v. split: %v", err, line)
|
||||
|
@ -59,14 +59,15 @@ func Processes() ([]Process, error) {
|
|||
if err != nil {
|
||||
log.Printf("failed to convert fourth field to float: %v. split: %v", err, line)
|
||||
}
|
||||
process := Process{
|
||||
proc := Proc{
|
||||
PID: pid,
|
||||
Command: strings.TrimSpace(line[11:61]),
|
||||
CPU: cpu,
|
||||
Mem: mem,
|
||||
Args: line[74:],
|
||||
}
|
||||
processes = append(processes, process)
|
||||
procs = append(procs, proc)
|
||||
}
|
||||
return processes, nil
|
||||
|
||||
return procs, nil
|
||||
}
|
||||
|
|
|
@ -1,34 +1,35 @@
|
|||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
psProc "github.com/shirou/gopsutil/process"
|
||||
)
|
||||
|
||||
func (self *Proc) update() {
|
||||
psProcesses, err := psProc.Processes()
|
||||
func getProcs() ([]Proc, error) {
|
||||
psProcs, err := psProc.Processes()
|
||||
if err != nil {
|
||||
log.Printf("failed to get processes from gopsutil: %v", err)
|
||||
return
|
||||
return nil, fmt.Errorf("failed to get processes from gopsutil: %v", err)
|
||||
}
|
||||
processes := make([]Process, len(psProcesses))
|
||||
for i, psProcess := range psProcesses {
|
||||
pid := psProcess.Pid
|
||||
command, err := psProcess.Name()
|
||||
|
||||
procs := make([]Proc, len(psProcs))
|
||||
for i, psProc := range psProcs {
|
||||
pid := psProc.Pid
|
||||
command, err := psProc.Name()
|
||||
if err != nil {
|
||||
log.Printf("failed to get process command from gopsutil: %v. psProcess: %v. i: %v. pid: %v", err, psProcess, i, pid)
|
||||
log.Printf("failed to get process command from gopsutil: %v. psProc: %v. i: %v. pid: %v", err, psProc, i, pid)
|
||||
}
|
||||
cpu, err := psProcess.CPUPercent()
|
||||
cpu, err := psProc.CPUPercent()
|
||||
if err != nil {
|
||||
log.Printf("failed to get process cpu usage from gopsutil: %v. psProcess: %v. i: %v. pid: %v", err, psProcess, i, pid)
|
||||
log.Printf("failed to get process cpu usage from gopsutil: %v. psProc: %v. i: %v. pid: %v", err, psProc, i, pid)
|
||||
}
|
||||
mem, err := psProcess.MemoryPercent()
|
||||
mem, err := psProc.MemoryPercent()
|
||||
if err != nil {
|
||||
log.Printf("failed to get process memeory usage from gopsutil: %v. psProcess: %v. i: %v. pid: %v", err, psProcess, i, pid)
|
||||
log.Printf("failed to get process memeory usage from gopsutil: %v. psProc: %v. i: %v. pid: %v", err, psProc, i, pid)
|
||||
}
|
||||
|
||||
processes[i] = Process{
|
||||
procs[i] = Proc{
|
||||
int(pid),
|
||||
command,
|
||||
cpu / self.cpuCount,
|
||||
|
@ -39,8 +40,5 @@ func (self *Proc) update() {
|
|||
}
|
||||
}
|
||||
|
||||
self.ungroupedProcs = processes
|
||||
self.groupedProcs = Group(processes)
|
||||
|
||||
self.Sort()
|
||||
return procs, nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package widgets
|
|||
|
||||
import (
|
||||
"image"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
|
@ -21,27 +22,31 @@ func NewStatusBar() *StatusBar {
|
|||
func (self *StatusBar) Draw(buf *ui.Buffer) {
|
||||
self.Block.Draw(buf)
|
||||
|
||||
hostname, _ := os.Hostname()
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
log.Printf("could not get hostname: %v", err)
|
||||
return
|
||||
}
|
||||
buf.SetString(
|
||||
hostname,
|
||||
ui.NewStyle(7),
|
||||
ui.NewStyle(ui.ColorWhite),
|
||||
image.Pt(self.Inner.Min.X, self.Inner.Min.Y+(self.Inner.Dy()/2)),
|
||||
)
|
||||
|
||||
t := time.Now()
|
||||
_time := t.Format("15:04:05")
|
||||
currentTime := time.Now()
|
||||
formattedTime := currentTime.Format("15:04:05")
|
||||
buf.SetString(
|
||||
_time,
|
||||
ui.NewStyle(7),
|
||||
formattedTime,
|
||||
ui.NewStyle(ui.ColorWhite),
|
||||
image.Pt(
|
||||
self.Inner.Min.X+(self.Inner.Dx()/2)-len(_time)/2,
|
||||
self.Inner.Min.X+(self.Inner.Dx()/2)-len(formattedTime)/2,
|
||||
self.Inner.Min.Y+(self.Inner.Dy()/2),
|
||||
),
|
||||
)
|
||||
|
||||
buf.SetString(
|
||||
"gotop",
|
||||
ui.NewStyle(7),
|
||||
ui.NewStyle(ui.ColorWhite),
|
||||
image.Pt(
|
||||
self.Inner.Max.X-6,
|
||||
self.Inner.Min.Y+(self.Inner.Dy()/2),
|
||||
|
|
|
@ -8,36 +8,45 @@ import (
|
|||
"time"
|
||||
|
||||
ui "github.com/gizak/termui"
|
||||
|
||||
"github.com/cjbassi/gotop/src/utils"
|
||||
)
|
||||
|
||||
type Temp struct {
|
||||
*ui.Block // inherits from Block instead of a premade Widget
|
||||
interval time.Duration
|
||||
Data map[string]int
|
||||
Threshold int
|
||||
TempLow ui.Color
|
||||
TempHigh ui.Color
|
||||
Fahrenheit bool
|
||||
type TempScale int
|
||||
|
||||
const (
|
||||
Celcius TempScale = 0
|
||||
Fahrenheit = 1
|
||||
)
|
||||
|
||||
type TempWidget struct {
|
||||
*ui.Block // inherits from Block instead of a premade Widget
|
||||
updateInterval time.Duration
|
||||
Data map[string]int
|
||||
TempThreshold int
|
||||
TempLowColor ui.Color
|
||||
TempHighColor ui.Color
|
||||
TempScale TempScale
|
||||
}
|
||||
|
||||
func NewTemp(renderLock *sync.RWMutex, fahrenheit bool) *Temp {
|
||||
self := &Temp{
|
||||
Block: ui.NewBlock(),
|
||||
interval: time.Second * 5,
|
||||
Data: make(map[string]int),
|
||||
Threshold: 80, // temp at which color should change
|
||||
func NewTempWidget(renderLock *sync.RWMutex, tempScale TempScale) *TempWidget {
|
||||
self := &TempWidget{
|
||||
Block: ui.NewBlock(),
|
||||
updateInterval: time.Second * 5,
|
||||
Data: make(map[string]int),
|
||||
TempThreshold: 80,
|
||||
TempScale: tempScale,
|
||||
}
|
||||
self.Title = " Temperatures "
|
||||
|
||||
if fahrenheit {
|
||||
self.Fahrenheit = true
|
||||
self.Threshold = int(self.Threshold*9/5 + 32)
|
||||
if tempScale == Fahrenheit {
|
||||
self.TempThreshold = utils.CelsiusToFahrenheit(self.TempThreshold)
|
||||
}
|
||||
|
||||
self.update()
|
||||
|
||||
go func() {
|
||||
for range time.NewTicker(self.interval).C {
|
||||
for range time.NewTicker(self.updateInterval).C {
|
||||
renderLock.RLock()
|
||||
self.update()
|
||||
renderLock.RUnlock()
|
||||
|
@ -47,8 +56,8 @@ func NewTemp(renderLock *sync.RWMutex, fahrenheit bool) *Temp {
|
|||
return self
|
||||
}
|
||||
|
||||
// We implement a custom Draw method instead of inheriting from a generic Widget.
|
||||
func (self *Temp) Draw(buf *ui.Buffer) {
|
||||
// Custom Draw method instead of inheriting from a generic Widget.
|
||||
func (self *TempWidget) Draw(buf *ui.Buffer) {
|
||||
self.Block.Draw(buf)
|
||||
|
||||
var keys []string
|
||||
|
@ -62,9 +71,11 @@ func (self *Temp) Draw(buf *ui.Buffer) {
|
|||
break
|
||||
}
|
||||
|
||||
fg := self.TempLow
|
||||
if self.Data[key] >= self.Threshold {
|
||||
fg = self.TempHigh
|
||||
var fg ui.Color
|
||||
if self.Data[key] < self.TempThreshold {
|
||||
fg = self.TempLowColor
|
||||
} else {
|
||||
fg = self.TempHighColor
|
||||
}
|
||||
|
||||
s := ui.TrimString(key, (self.Inner.Dx() - 4))
|
||||
|
@ -72,13 +83,15 @@ func (self *Temp) Draw(buf *ui.Buffer) {
|
|||
ui.Theme.Default,
|
||||
image.Pt(self.Inner.Min.X, self.Inner.Min.Y+y),
|
||||
)
|
||||
if self.Fahrenheit {
|
||||
|
||||
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),
|
||||
)
|
||||
} else {
|
||||
case Celcius:
|
||||
buf.SetString(
|
||||
fmt.Sprintf("%3dC", self.Data[key]),
|
||||
ui.NewStyle(fg),
|
||||
|
|
|
@ -53,7 +53,7 @@ func SensorsTemperatures() ([]TemperatureStat, error) {
|
|||
return temperatures, nil
|
||||
}
|
||||
|
||||
func (self *Temp) update() {
|
||||
func (self *TempWidget) update() {
|
||||
sensors, err := SensorsTemperatures()
|
||||
if err != nil {
|
||||
log.Printf("failed to get sensors from CGO: %v", err)
|
||||
|
@ -61,9 +61,10 @@ func (self *Temp) update() {
|
|||
}
|
||||
for _, sensor := range sensors {
|
||||
if sensor.Temperature != 0 {
|
||||
if self.Fahrenheit {
|
||||
switch self.TempScale {
|
||||
case Fahrenheit:
|
||||
self.Data[sensor.SensorKey] = utils.CelsiusToFahrenheit(int(sensor.Temperature))
|
||||
} else {
|
||||
case Celcius:
|
||||
self.Data[sensor.SensorKey] = int(sensor.Temperature)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package widgets
|
||||
|
||||
// loosely based on https://github.com/openbsd/src/blob/master/sbin/sysctl/sysctl.c#L2517
|
||||
|
||||
// #include <sys/time.h>
|
||||
// #include <sys/sysctl.h>
|
||||
// #include <sys/sensors.h>
|
||||
|
@ -13,14 +15,14 @@ import (
|
|||
"github.com/cjbassi/gotop/src/utils"
|
||||
)
|
||||
|
||||
func getTemp(t *Temp, mib []C.int, mlen int, snsrdev *C.struct_sensordev, index int) {
|
||||
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
|
||||
getTemp(t, mib, mlen+1, snsrdev, int(numt))
|
||||
self.getTemp(mib, mlen+1, snsrdev, int(numt))
|
||||
}
|
||||
case 5:
|
||||
var snsr C.struct_sensor
|
||||
|
@ -34,16 +36,17 @@ func getTemp(t *Temp, mib []C.int, mlen int, snsrdev *C.struct_sensordev, index
|
|||
key := C.GoString(&snsrdev.xname[0]) + ".temp" + strconv.Itoa(index)
|
||||
temp := int((snsr.value - 273150000.0) / 1000000.0)
|
||||
|
||||
if t.Fahrenheit {
|
||||
t.Data[key] = utils.CelsiusToFahrenheit(temp)
|
||||
} else {
|
||||
t.Data[key] = temp
|
||||
switch self.TempScale {
|
||||
case Fahrenheit:
|
||||
self.Data[key] = utils.CelsiusToFahrenheit(temp)
|
||||
case Celcius:
|
||||
self.Data[key] = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Temp) update() {
|
||||
func (self *TempWidget) update() {
|
||||
mib := []C.int{0, 1, 2, 3, 4}
|
||||
|
||||
var snsrdev C.struct_sensordev
|
||||
|
@ -56,17 +59,14 @@ func (self *Temp) update() {
|
|||
var i C.int
|
||||
for i = 0; ; i++ {
|
||||
mib[2] = i
|
||||
|
||||
if v, e := C.sysctl(&mib[0], 3, unsafe.Pointer(&snsrdev), &len, nil, 0); v == -1 {
|
||||
if e == syscall.ENXIO {
|
||||
continue
|
||||
}
|
||||
|
||||
if e == syscall.ENOENT {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
getTemp(self, mib, 4, &snsrdev, 0)
|
||||
self.getTemp(mib, 4, &snsrdev, 0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/cjbassi/gotop/src/utils"
|
||||
)
|
||||
|
||||
func (self *Temp) update() {
|
||||
func (self *TempWidget) update() {
|
||||
sensors, err := psHost.SensorsTemperatures()
|
||||
if err != nil {
|
||||
log.Printf("error recieved from gopsutil: %v", err)
|
||||
|
@ -21,9 +21,10 @@ func (self *Temp) update() {
|
|||
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")]
|
||||
if self.Fahrenheit {
|
||||
switch self.TempScale {
|
||||
case Fahrenheit:
|
||||
self.Data[label] = utils.CelsiusToFahrenheit(int(sensor.Temperature))
|
||||
} else {
|
||||
case Celcius:
|
||||
self.Data[label] = int(sensor.Temperature)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/cjbassi/gotop/src/utils"
|
||||
)
|
||||
|
||||
func (self *Temp) update() {
|
||||
func (self *TempWidget) update() {
|
||||
sensors, err := psHost.SensorsTemperatures()
|
||||
if err != nil {
|
||||
log.Printf("failed to get sensors from gopsutil: %v", err)
|
||||
|
@ -16,9 +16,10 @@ func (self *Temp) update() {
|
|||
}
|
||||
for _, sensor := range sensors {
|
||||
if sensor.Temperature != 0 {
|
||||
if self.Fahrenheit {
|
||||
switch self.TempScale {
|
||||
case Fahrenheit:
|
||||
self.Data[sensor.SensorKey] = utils.CelsiusToFahrenheit(int(sensor.Temperature))
|
||||
} else {
|
||||
case Celcius:
|
||||
self.Data[sensor.SensorKey] = int(sensor.Temperature)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user