This commit is contained in:
Caleb Bassi 2019-02-28 16:29:52 -08:00
parent 3a804eb666
commit 71e10bb6be
24 changed files with 597 additions and 559 deletions

4
go.sum
View File

@ -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/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 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-ole/go-ole v1.2.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/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/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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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/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 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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/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/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 h1:g/Jesu8+QLnA0CPzF3E1pURg0Byr7i6jLoX5sqjcAh0=
golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

73
main.go
View File

@ -44,19 +44,19 @@ var (
minimalMode = false minimalMode = false
averageLoad = false averageLoad = false
percpuLoad = false percpuLoad = false
fahrenheit = false tempScale = w.Celcius
battery = false battery = false
statusbar = false statusbar = false
renderLock sync.RWMutex renderLock sync.RWMutex
cpu *w.CPU cpu *w.CpuWidget
batt *w.Batt batt *w.BatteryWidget
mem *w.Mem mem *w.MemWidget
proc *w.Proc proc *w.ProcWidget
net *w.Net net *w.NetWidget
disk *w.Disk disk *w.DiskWidget
temp *w.Temp temp *w.TempWidget
help *w.HelpMenu help *w.HelpMenu
grid *ui.Grid grid *ui.Grid
bar *w.StatusBar bar *w.StatusBar
@ -134,7 +134,10 @@ Colorschemes:
} else { } else {
updateInterval = time.Second / time.Duration(rate) updateInterval = time.Second / time.Duration(rate)
} }
fahrenheit, _ = args["--fahrenheit"].(bool) fahrenheit, _ := args["--fahrenheit"].(bool)
if fahrenheit {
tempScale = w.Fahrenheit
}
return nil return nil
} }
@ -221,8 +224,8 @@ func setDefaultTermuiColors() {
} }
func setWidgetColors() { func setWidgetColors() {
mem.LineColor["Main"] = ui.Color(colorscheme.MainMem) mem.LineColors["Main"] = ui.Color(colorscheme.MainMem)
mem.LineColor["Swap"] = ui.Color(colorscheme.SwapMem) mem.LineColors["Swap"] = ui.Color(colorscheme.SwapMem)
proc.CursorColor = ui.Color(colorscheme.ProcCursor) proc.CursorColor = ui.Color(colorscheme.ProcCursor)
@ -238,7 +241,7 @@ func setWidgetColors() {
i = 0 i = 0
} }
c := colorscheme.CPULines[i] c := colorscheme.CPULines[i]
cpu.LineColor[v] = ui.Color(c) cpu.LineColors[v] = ui.Color(c)
i++ i++
} }
@ -256,13 +259,13 @@ func setWidgetColors() {
i = 0 i = 0
} }
c := colorscheme.BattLines[i] c := colorscheme.BattLines[i]
batt.LineColor[v] = ui.Color(c) batt.LineColors[v] = ui.Color(c)
i++ i++
} }
} }
temp.TempLow = ui.Color(colorscheme.TempLow) temp.TempLowColor = ui.Color(colorscheme.TempLow)
temp.TempHigh = ui.Color(colorscheme.TempHigh) temp.TempHighColor = ui.Color(colorscheme.TempHigh)
net.Lines[0].LineColor = ui.Color(colorscheme.Sparkline) net.Lines[0].LineColor = ui.Color(colorscheme.Sparkline)
net.Lines[0].TitleColor = ui.Color(colorscheme.BorderLabel) net.Lines[0].TitleColor = ui.Color(colorscheme.BorderLabel)
@ -272,17 +275,17 @@ func setWidgetColors() {
} }
func initWidgets() { func initWidgets() {
cpu = w.NewCPU(&renderLock, updateInterval, graphHorizontalScale, averageLoad, percpuLoad) cpu = w.NewCpuWidget(&renderLock, updateInterval, graphHorizontalScale, averageLoad, percpuLoad)
mem = w.NewMem(&renderLock, updateInterval, graphHorizontalScale) mem = w.NewMemWidget(&renderLock, updateInterval, graphHorizontalScale)
proc = w.NewProc(&renderLock) proc = w.NewProcWidget(&renderLock)
help = w.NewHelpMenu() help = w.NewHelpMenu()
if !minimalMode { if !minimalMode {
if battery { if battery {
batt = w.NewBatt(&renderLock, graphHorizontalScale) batt = w.NewBatteryWidget(&renderLock, graphHorizontalScale)
} }
net = w.NewNet(&renderLock) net = w.NewNetWidget(&renderLock)
disk = w.NewDisk(&renderLock) disk = w.NewDiskWidget(&renderLock)
temp = w.NewTemp(&renderLock, fahrenheit) temp = w.NewTempWidget(&renderLock, tempScale)
} }
if statusbar { if statusbar {
bar = w.NewStatusBar() bar = w.NewStatusBar()
@ -367,46 +370,46 @@ func eventLoop() {
} }
case "<MouseLeft>": case "<MouseLeft>":
payload := e.Payload.(ui.Mouse) payload := e.Payload.(ui.Mouse)
proc.Click(payload.X, payload.Y) proc.HandleClick(payload.X, payload.Y)
render(proc) render(proc)
case "k", "<Up>", "<MouseWheelUp>": case "k", "<Up>", "<MouseWheelUp>":
proc.Up() proc.ScrollUp()
render(proc) render(proc)
case "j", "<Down>", "<MouseWheelDown>": case "j", "<Down>", "<MouseWheelDown>":
proc.Down() proc.ScrollDown()
render(proc) render(proc)
case "<Home>": case "<Home>":
proc.Top() proc.ScrollTop()
render(proc) render(proc)
case "g": case "g":
if previousKey == "g" { if previousKey == "g" {
proc.Top() proc.ScrollTop()
render(proc) render(proc)
} }
case "G", "<End>": case "G", "<End>":
proc.Bottom() proc.ScrollBottom()
render(proc) render(proc)
case "<C-d>": case "<C-d>":
proc.HalfPageDown() proc.ScrollHalfPageDown()
render(proc) render(proc)
case "<C-u>": case "<C-u>":
proc.HalfPageUp() proc.ScrollHalfPageUp()
render(proc) render(proc)
case "<C-f>": case "<C-f>":
proc.PageDown() proc.ScrollPageDown()
render(proc) render(proc)
case "<C-b>": case "<C-b>":
proc.PageUp() proc.ScrollPageUp()
render(proc) render(proc)
case "d": case "d":
if previousKey == "d" { if previousKey == "d" {
proc.Kill() proc.KillProc()
} }
case "<Tab>": case "<Tab>":
proc.Tab() proc.ToggleShowingGroupedProcs()
render(proc) render(proc)
case "m", "c", "p": case "m", "c", "p":
proc.ChangeSort(e) proc.ChangeProcSortMethod(w.ProcSortMethod(e.ID))
render(proc) render(proc)
} }

View File

@ -5,6 +5,6 @@ import (
"syscall" "syscall"
) )
func StderrToLogfile(lf *os.File) { func StderrToLogfile(logfile *os.File) {
syscall.Dup3(int(lf.Fd()), 2, 0) syscall.Dup3(int(logfile.Fd()), 2, 0)
} }

View File

@ -7,6 +7,6 @@ import (
"syscall" "syscall"
) )
func StderrToLogfile(lf *os.File) { func StderrToLogfile(logfile *os.File) {
syscall.Dup2(int(lf.Fd()), 2) syscall.Dup2(int(logfile.Fd()), 2)
} }

View File

@ -11,21 +11,26 @@ import (
// LineGraph implements a line graph of data points. // LineGraph implements a line graph of data points.
type LineGraph struct { type LineGraph struct {
*Block *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 DefaultLineColor Color
} }
func NewLineGraph() *LineGraph { func NewLineGraph() *LineGraph {
return &LineGraph{ return &LineGraph{
Block: NewBlock(), Block: NewBlock(),
Data: make(map[string][]float64),
LineColor: make(map[string]Color), Data: make(map[string][]float64),
Labels: make(map[string]string), Labels: make(map[string]string),
HorizontalScale: 5, 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-- { for i := len(seriesList) - 1; i >= 0; i-- {
seriesName := seriesList[i] seriesName := seriesList[i]
seriesData := self.Data[seriesName] seriesData := self.Data[seriesName]
seriesLineColor, ok := self.LineColor[seriesName] seriesLineColor, ok := self.LineColors[seriesName]
if !ok { if !ok {
seriesLineColor = self.DefaultLineColor seriesLineColor = self.DefaultLineColor
} }
@ -110,7 +115,7 @@ func (self *LineGraph) Draw(buf *Buffer) {
if i+2 > self.Inner.Dy() { if i+2 > self.Inner.Dy() {
continue continue
} }
seriesLineColor, ok := self.LineColor[seriesName] seriesLineColor, ok := self.LineColors[seriesName]
if !ok { if !ok {
seriesLineColor = self.DefaultLineColor seriesLineColor = self.DefaultLineColor
} }

View File

@ -16,31 +16,31 @@ type Sparkline struct {
LineColor Color LineColor Color
} }
// Sparklines is a renderable widget which groups together the given sparklines. // SparklineGroup is a renderable widget which groups together the given sparklines.
type Sparklines struct { type SparklineGroup struct {
*Block *Block
Lines []*Sparkline Lines []*Sparkline
} }
// Add appends a given Sparkline to the *Sparklines. // Add appends a given Sparkline to the *SparklineGroup.
func (self *Sparklines) Add(sl Sparkline) { func (self *SparklineGroup) Add(sl Sparkline) {
self.Lines = append(self.Lines, &sl) 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 { func NewSparkline() *Sparkline {
return &Sparkline{} return &Sparkline{}
} }
// NewSparklines return a new *Sparklines with given Sparklines, you can always add a new Sparkline later. // NewSparklineGroup return a new *SparklineGroup with given Sparklines, you can always add a new Sparkline later.
func NewSparklines(ss ...*Sparkline) *Sparklines { func NewSparklineGroup(ss ...*Sparkline) *SparklineGroup {
return &Sparklines{ return &SparklineGroup{
Block: NewBlock(), Block: NewBlock(),
Lines: ss, Lines: ss,
} }
} }
func (self *Sparklines) Draw(buf *Buffer) { func (self *SparklineGroup) Draw(buf *Buffer) {
self.Block.Draw(buf) self.Block.Draw(buf)
lc := len(self.Lines) // lineCount lc := len(self.Lines) // lineCount

View File

@ -14,37 +14,30 @@ type Table struct {
Header []string Header []string
Rows [][]string Rows [][]string
ColWidths []int ColWidths []int
CellXPos []int // column position ColGap int
ColResizer func() // for widgets that inherit a Table and want to overload the ColResize method PadLeft int
Gap int // gap between columns
PadLeft int
Cursor bool ShowCursor bool
CursorColor Color 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 SelectedItem string // used to keep the cursor on the correct item if the data changes
SelectedRow int SelectedRow int
TopRow int // used to indicate where in the table we are scrolled at TopRow int // used to indicate where in the table we are scrolled at
ColResizer func()
} }
// NewTable returns a new Table instance // NewTable returns a new Table instance
func NewTable() *Table { func NewTable() *Table {
self := &Table{ return &Table{
Block: NewBlock(), Block: NewBlock(),
// CursorColor: Theme.TableCursor,
SelectedRow: 0, SelectedRow: 0,
TopRow: 0, TopRow: 0,
UniqueCol: 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) { func (self *Table) Draw(buf *Buffer) {
@ -53,12 +46,12 @@ func (self *Table) Draw(buf *Buffer) {
self.ColResizer() self.ColResizer()
// finds exact column starting position // finds exact column starting position
self.CellXPos = []int{} colXPos := []int{}
cur := 1 + self.PadLeft cur := 1 + self.PadLeft
for _, w := range self.ColWidths { for _, w := range self.ColWidths {
self.CellXPos = append(self.CellXPos, cur) colXPos = append(colXPos, cur)
cur += w cur += w
cur += self.Gap cur += self.ColGap
} }
// prints header // prints header
@ -68,13 +61,13 @@ func (self *Table) Draw(buf *Buffer) {
continue continue
} }
// don't render column if it doesn't fit in widget // 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 continue
} }
buf.SetString( buf.SetString(
h, h,
NewStyle(Theme.Default.Fg, ColorClear, ModifierBold), 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 // prints cursor
style := NewStyle(Theme.Default.Fg) style := NewStyle(Theme.Default.Fg)
if self.Cursor { if self.ShowCursor {
if (self.SelectedItem == "" && rowNum == self.SelectedRow) || (self.SelectedItem != "" && self.SelectedItem == row[self.UniqueCol]) { if (self.SelectedItem == "" && rowNum == self.SelectedRow) || (self.SelectedItem != "" && self.SelectedItem == row[self.UniqueCol]) {
style.Fg = self.CursorColor style.Fg = self.CursorColor
style.Modifier = ModifierReverse style.Modifier = ModifierReverse
@ -115,24 +108,22 @@ func (self *Table) Draw(buf *Buffer) {
continue continue
} }
// don't render column if width is greater than distance to end of widget // 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 continue
} }
r := TrimString(row[i], width) r := TrimString(row[i], width)
buf.SetString( buf.SetString(
r, r,
style, 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),
) )
} }
} }
} }
///////////////////////////////////////////////////////////////////////////////// // Scrolling ///////////////////////////////////////////////////////////////////
// Cursor Movement //
/////////////////////////////////////////////////////////////////////////////////
// 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() { func (self *Table) calcPos() {
self.SelectedItem = "" self.SelectedItem = ""
@ -151,49 +142,47 @@ func (self *Table) calcPos() {
} }
} }
func (self *Table) Up() { func (self *Table) ScrollUp() {
self.SelectedRow-- self.SelectedRow--
self.calcPos() self.calcPos()
} }
func (self *Table) Down() { func (self *Table) ScrollDown() {
self.SelectedRow++ self.SelectedRow++
self.calcPos() self.calcPos()
} }
func (self *Table) Top() { func (self *Table) ScrollTop() {
self.SelectedRow = 0 self.SelectedRow = 0
self.calcPos() self.calcPos()
} }
func (self *Table) Bottom() { func (self *Table) ScrollBottom() {
self.SelectedRow = len(self.Rows) - 1 self.SelectedRow = len(self.Rows) - 1
self.calcPos() self.calcPos()
} }
// The number of lines in a page is equal to the height of the widgeself. func (self *Table) ScrollHalfPageUp() {
func (self *Table) HalfPageUp() {
self.SelectedRow = self.SelectedRow - (self.Inner.Dy()-2)/2 self.SelectedRow = self.SelectedRow - (self.Inner.Dy()-2)/2
self.calcPos() self.calcPos()
} }
func (self *Table) HalfPageDown() { func (self *Table) ScrollHalfPageDown() {
self.SelectedRow = self.SelectedRow + (self.Inner.Dy()-2)/2 self.SelectedRow = self.SelectedRow + (self.Inner.Dy()-2)/2
self.calcPos() self.calcPos()
} }
func (self *Table) PageUp() { func (self *Table) ScrollPageUp() {
self.SelectedRow -= (self.Inner.Dy() - 2) self.SelectedRow -= (self.Inner.Dy() - 2)
self.calcPos() self.calcPos()
} }
func (self *Table) PageDown() { func (self *Table) ScrollPageDown() {
self.SelectedRow += (self.Inner.Dy() - 2) self.SelectedRow += (self.Inner.Dy() - 2)
self.calcPos() self.calcPos()
} }
func (self *Table) Click(x, y int) { func (self *Table) HandleClick(x, y int) {
x = x - self.Min.X x = x - self.Min.X
y = y - self.Min.Y y = y - self.Min.Y
if (x > 0 && x <= self.Inner.Dx()) && (y > 0 && y <= self.Inner.Dy()) { if (x > 0 && x <= self.Inner.Dx()) && (y > 0 && y <= self.Inner.Dy()) {

View File

@ -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 { if a > b {
return a return a
} }

View File

@ -13,25 +13,26 @@ import (
ui "github.com/cjbassi/gotop/src/termui" ui "github.com/cjbassi/gotop/src/termui"
) )
type Batt struct { type BatteryWidget struct {
*ui.LineGraph *ui.LineGraph
interval time.Duration updateInterval time.Duration
} }
func NewBatt(renderLock *sync.RWMutex, horizontalScale int) *Batt { func NewBatteryWidget(renderLock *sync.RWMutex, horizontalScale int) *BatteryWidget {
self := &Batt{ self := &BatteryWidget{
LineGraph: ui.NewLineGraph(), LineGraph: ui.NewLineGraph(),
interval: time.Minute, updateInterval: time.Minute,
} }
self.Title = " Battery Status " self.Title = " Battery Status "
self.HorizontalScale = horizontalScale self.HorizontalScale = horizontalScale
// intentional duplicate // intentional duplicate
// adds 2 datapoints to the graph, otherwise the dot is difficult to see
self.update() self.update()
self.update() self.update()
go func() { go func() {
for range time.NewTicker(self.interval).C { for range time.NewTicker(self.updateInterval).C {
renderLock.RLock() renderLock.RLock()
self.update() self.update()
renderLock.RUnlock() renderLock.RUnlock()
@ -41,20 +42,20 @@ func NewBatt(renderLock *sync.RWMutex, horizontalScale int) *Batt {
return self return self
} }
func mkId(i int) string { func makeId(i int) string {
return "Batt" + strconv.Itoa(i) return "Batt" + strconv.Itoa(i)
} }
func (self *Batt) update() { func (self *BatteryWidget) update() {
batts, err := battery.GetAll() batteries, err := battery.GetAll()
if err != nil { if err != nil {
log.Printf("failed to get battery info from system: %v", err) log.Printf("failed to get battery info: %v", err)
return return
} }
for i, b := range batts { for i, battery := range batteries {
n := mkId(i) id := makeId(i)
pc := math.Abs(b.Current/b.Full) * 100.0 percentFull := math.Abs(battery.Current/battery.Full) * 100.0
self.Data[n] = append(self.Data[n], pc) self.Data[id] = append(self.Data[id], percentFull)
self.Labels[n] = fmt.Sprintf("%3.0f%% %.0f/%.0f", pc, math.Abs(b.Current), math.Abs(b.Full)) self.Labels[id] = fmt.Sprintf("%3.0f%% %.0f/%.0f", percentFull, math.Abs(battery.Current), math.Abs(battery.Full))
} }
} }

View File

@ -6,66 +6,66 @@ import (
"sync" "sync"
"time" "time"
psCPU "github.com/shirou/gopsutil/cpu" psCpu "github.com/shirou/gopsutil/cpu"
ui "github.com/cjbassi/gotop/src/termui" ui "github.com/cjbassi/gotop/src/termui"
) )
type CPU struct { type CpuWidget struct {
*ui.LineGraph *ui.LineGraph
Count int // number of cores CpuCount int
Average bool // show average load ShowAverageLoad bool
PerCPU bool // show per-core load ShowPerCpuLoad bool
interval time.Duration updateInterval time.Duration
formatString string formatString string
renderLock *sync.RWMutex renderLock *sync.RWMutex
updateLock sync.Mutex updateLock sync.Mutex
} }
func NewCPU(renderLock *sync.RWMutex, interval time.Duration, horizontalScale int, average bool, percpu bool) *CPU { func NewCpuWidget(renderLock *sync.RWMutex, updateInterval time.Duration, horizontalScale int, showAverageLoad bool, showPerCpuLoad bool) *CpuWidget {
count, err := psCPU.Counts(false) cpuCount, err := psCpu.Counts(false)
if err != nil { if err != nil {
log.Printf("failed to get CPU count from gopsutil: %v", err) log.Printf("failed to get CPU count from gopsutil: %v", err)
} }
formatString := "CPU%1d" formatString := "CPU%1d"
if count > 10 { if cpuCount > 10 {
formatString = "CPU%02d" formatString = "CPU%02d"
} }
self := &CPU{ self := &CpuWidget{
LineGraph: ui.NewLineGraph(), LineGraph: ui.NewLineGraph(),
Count: count, CpuCount: cpuCount,
interval: interval, updateInterval: updateInterval,
Average: average, ShowAverageLoad: showAverageLoad,
PerCPU: percpu, ShowPerCpuLoad: showPerCpuLoad,
formatString: formatString, formatString: formatString,
renderLock: renderLock, renderLock: renderLock,
} }
self.Title = " CPU Usage " self.Title = " CPU Usage "
self.HorizontalScale = horizontalScale self.HorizontalScale = horizontalScale
if !(self.Average || self.PerCPU) { if !(self.ShowAverageLoad || self.ShowPerCpuLoad) {
if self.Count <= 8 { if self.CpuCount <= 8 {
self.PerCPU = true self.ShowPerCpuLoad = true
} else { } else {
self.Average = true self.ShowAverageLoad = true
} }
} }
if self.Average { if self.ShowAverageLoad {
self.Data["AVRG"] = []float64{0} self.Data["AVRG"] = []float64{0}
} }
if self.PerCPU { if self.ShowPerCpuLoad {
for i := 0; i < self.Count; i++ { for i := 0; i < int(self.CpuCount); i++ {
k := fmt.Sprintf(formatString, i) key := fmt.Sprintf(formatString, i)
self.Data[k] = []float64{0} self.Data[key] = []float64{0}
} }
} }
self.update() self.update()
go func() { go func() {
for range time.NewTicker(self.interval).C { for range time.NewTicker(self.updateInterval).C {
self.update() self.update()
} }
}() }()
@ -73,12 +73,12 @@ func NewCPU(renderLock *sync.RWMutex, interval time.Duration, horizontalScale in
return self return self
} }
func (self *CPU) update() { func (self *CpuWidget) update() {
if self.Average { if self.ShowAverageLoad {
go func() { go func() {
percent, err := psCPU.Percent(self.interval, false) percent, err := psCpu.Percent(self.updateInterval, false)
if err != nil { 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 { } else {
self.renderLock.RLock() self.renderLock.RLock()
defer self.renderLock.RUnlock() defer self.renderLock.RUnlock()
@ -90,23 +90,23 @@ func (self *CPU) update() {
}() }()
} }
if self.PerCPU { if self.ShowPerCpuLoad {
go func() { go func() {
percents, err := psCPU.Percent(self.interval, true) percents, err := psCpu.Percent(self.updateInterval, true)
if err != nil { 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 { } else {
if len(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.Count) log.Printf("error: number of CPU usage percents from gopsutil doesn't match CPU count. percents: %v. self.Count: %v", percents, self.CpuCount)
} else { } else {
self.renderLock.RLock() self.renderLock.RLock()
defer self.renderLock.RUnlock() defer self.renderLock.RUnlock()
self.updateLock.Lock() self.updateLock.Lock()
defer self.updateLock.Unlock() defer self.updateLock.Unlock()
for i, percent := range percents { for i, percent := range percents {
k := fmt.Sprintf(self.formatString, i) key := fmt.Sprintf(self.formatString, i)
self.Data[k] = append(self.Data[k], percent) self.Data[key] = append(self.Data[key], percent)
self.Labels[k] = fmt.Sprintf("%3.0f%%", percent) self.Labels[key] = fmt.Sprintf("%3.0f%%", percent)
} }
} }
} }

View File

@ -15,37 +15,43 @@ import (
) )
type Partition struct { type Partition struct {
Device string Device string
Mount string MountPoint string
TotalRead uint64 BytesRead uint64
TotalWrite uint64 BytesWritten uint64
CurRead string BytesReadRecently string
CurWrite string BytesWrittenRecently string
UsedPercent int UsedPercent uint32
Free string Free string
} }
type Disk struct { type DiskWidget struct {
*ui.Table *ui.Table
interval time.Duration updateInterval time.Duration
Partitions map[string]*Partition Partitions map[string]*Partition
} }
func NewDisk(renderLock *sync.RWMutex) *Disk { func NewDiskWidget(renderLock *sync.RWMutex) *DiskWidget {
self := &Disk{ self := &DiskWidget{
Table: ui.NewTable(), Table: ui.NewTable(),
interval: time.Second, updateInterval: time.Second,
Partitions: make(map[string]*Partition), Partitions: make(map[string]*Partition),
} }
self.Title = " Disk Usage " self.Title = " Disk Usage "
self.Header = []string{"Disk", "Mount", "Used", "Free", "R/s", "W/s"} self.Header = []string{"Disk", "Mount", "Used", "Free", "R/s", "W/s"}
self.Gap = 2 self.ColGap = 2
self.ColResizer = self.ColResize 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() self.update()
go func() { go func() {
for range time.NewTicker(self.interval).C { for range time.NewTicker(self.updateInterval).C {
renderLock.RLock() renderLock.RLock()
self.update() self.update()
renderLock.RUnlock() renderLock.RUnlock()
@ -55,86 +61,87 @@ func NewDisk(renderLock *sync.RWMutex) *Disk {
return self return self
} }
func (self *Disk) update() { func (self *DiskWidget) update() {
Partitions, err := psDisk.Partitions(false) partitions, err := psDisk.Partitions(false)
if err != nil { if err != nil {
log.Printf("failed to get disk partitions from gopsutil: %v", err) log.Printf("failed to get disk partitions from gopsutil: %v", err)
return return
} }
// add partition if it's new // add partition if it's new
for _, Part := range Partitions { for _, partition := range partitions {
// don't show loop devices // don't show loop devices
if strings.HasPrefix(Part.Device, "/dev/loop") { if strings.HasPrefix(partition.Device, "/dev/loop") {
continue continue
} }
// don't show docker container filesystems // don't show docker container filesystems
if strings.HasPrefix(Part.Mountpoint, "/var/lib/docker/") { if strings.HasPrefix(partition.Mountpoint, "/var/lib/docker/") {
continue continue
} }
// check if partition doesn't already exist in our list // check if partition doesn't already exist in our list
if _, ok := self.Partitions[Part.Device]; !ok { if _, ok := self.Partitions[partition.Device]; !ok {
self.Partitions[Part.Device] = &Partition{ self.Partitions[partition.Device] = &Partition{
Device: Part.Device, Device: partition.Device,
Mount: Part.Mountpoint, MountPoint: partition.Mountpoint,
} }
} }
} }
// delete a partition if it no longer exists // delete a partition if it no longer exists
todelete := []string{} toDelete := []string{}
for key := range self.Partitions { for device := range self.Partitions {
exists := false exists := false
for _, Part := range Partitions { for _, partition := range partitions {
if key == Part.Device { if device == partition.Device {
exists = true exists = true
break break
} }
} }
if !exists { if !exists {
todelete = append(todelete, key) toDelete = append(toDelete, device)
} }
} }
for _, val := range todelete { for _, device := range toDelete {
delete(self.Partitions, val) delete(self.Partitions, device)
} }
// updates partition info // updates partition info
for _, Part := range self.Partitions { for _, partition := range self.Partitions {
usage, err := psDisk.Usage(Part.Mount) usage, err := psDisk.Usage(partition.MountPoint)
if err != nil { 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 continue
} }
Part.UsedPercent = int(usage.UsedPercent) partition.UsedPercent = uint32(usage.UsedPercent)
Free, Mag := utils.ConvertBytes(usage.Free) bytesFree, magnitudeFree := utils.ConvertBytes(usage.Free)
Part.Free = fmt.Sprintf("%3d%s", uint64(Free), Mag) partition.Free = fmt.Sprintf("%3d%s", uint64(bytesFree), magnitudeFree)
ret, err := psDisk.IOCounters(Part.Device) ioCounters, err := psDisk.IOCounters(partition.Device)
if err != nil { 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 continue
} }
data := ret[strings.Replace(Part.Device, "/dev/", "", -1)] ioCounter := ioCounters[strings.Replace(partition.Device, "/dev/", "", -1)]
curRead, curWrite := data.ReadBytes, data.WriteBytes bytesRead, bytesWritten := ioCounter.ReadBytes, ioCounter.WriteBytes
if Part.TotalRead != 0 { // if this isn't the first update if partition.BytesRead != 0 { // if this isn't the first update
readRecent := curRead - Part.TotalRead bytesReadRecently := bytesRead - partition.BytesRead
writeRecent := curWrite - Part.TotalWrite bytesWrittenRecently := bytesWritten - partition.BytesWritten
readFloat, unitRead := utils.ConvertBytes(readRecent) readFloat, readMagnitude := utils.ConvertBytes(bytesReadRecently)
writeFloat, unitWrite := utils.ConvertBytes(writeRecent) writeFloat, writeMagnitude := utils.ConvertBytes(bytesWrittenRecently)
readRecent, writeRecent = uint64(readFloat), uint64(writeFloat) bytesReadRecently, bytesWrittenRecently = uint64(readFloat), uint64(writeFloat)
Part.CurRead = fmt.Sprintf("%d%s", readRecent, unitRead) partition.BytesReadRecently = fmt.Sprintf("%d%s", bytesReadRecently, readMagnitude)
Part.CurWrite = fmt.Sprintf("%d%s", writeRecent, unitWrite) partition.BytesWrittenRecently = fmt.Sprintf("%d%s", bytesWrittenRecently, writeMagnitude)
} else { } else {
Part.CurRead = fmt.Sprintf("%d%s", 0, "B") partition.BytesReadRecently = fmt.Sprintf("%d%s", 0, "B")
Part.CurWrite = 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 // converts self.Partitions into self.Rows which is a [][]String
sortedPartitions := []string{} sortedPartitions := []string{}
for seriesName := range self.Partitions { for seriesName := range self.Partitions {
sortedPartitions = append(sortedPartitions, seriesName) sortedPartitions = append(sortedPartitions, seriesName)
@ -142,31 +149,15 @@ func (self *Disk) update() {
sort.Strings(sortedPartitions) sort.Strings(sortedPartitions)
self.Rows = make([][]string, len(self.Partitions)) self.Rows = make([][]string, len(self.Partitions))
for i, key := range sortedPartitions { for i, key := range sortedPartitions {
Part := self.Partitions[key] partition := self.Partitions[key]
self.Rows[i] = make([]string, 6) self.Rows[i] = make([]string, 6)
self.Rows[i][0] = strings.Replace(strings.Replace(Part.Device, "/dev/", "", -1), "mapper/", "", -1) self.Rows[i][0] = strings.Replace(strings.Replace(partition.Device, "/dev/", "", -1), "mapper/", "", -1)
self.Rows[i][1] = Part.Mount self.Rows[i][1] = partition.MountPoint
self.Rows[i][2] = fmt.Sprintf("%d%%", Part.UsedPercent) self.Rows[i][2] = fmt.Sprintf("%d%%", partition.UsedPercent)
self.Rows[i][3] = Part.Free self.Rows[i][3] = partition.Free
self.Rows[i][4] = Part.CurRead self.Rows[i][4] = partition.BytesReadRecently
self.Rows[i][5] = Part.CurWrite self.Rows[i][5] = partition.BytesWrittenRecently
}
}
// 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
} }
} }

View File

@ -57,9 +57,9 @@ func (self *HelpMenu) Draw(buf *ui.Buffer) {
self.Block.Draw(buf) self.Block.Draw(buf)
for y, line := range strings.Split(KEYBINDS, "\n") { for y, line := range strings.Split(KEYBINDS, "\n") {
for x, char := range line { for x, rune := range line {
buf.SetCell( 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), image.Pt(self.Inner.Min.X+x, self.Inner.Min.Y+y-1),
) )
} }

View File

@ -12,15 +12,15 @@ import (
"github.com/cjbassi/gotop/src/utils" "github.com/cjbassi/gotop/src/utils"
) )
type Mem struct { type MemWidget struct {
*ui.LineGraph *ui.LineGraph
interval time.Duration updateInterval time.Duration
} }
func NewMem(renderLock *sync.RWMutex, interval time.Duration, horizontalScale int) *Mem { func NewMemWidget(renderLock *sync.RWMutex, updateInterval time.Duration, horizontalScale int) *MemWidget {
self := &Mem{ self := &MemWidget{
LineGraph: ui.NewLineGraph(), LineGraph: ui.NewLineGraph(),
interval: interval, updateInterval: updateInterval,
} }
self.Title = " Memory Usage " self.Title = " Memory Usage "
self.HorizontalScale = horizontalScale self.HorizontalScale = horizontalScale
@ -30,7 +30,7 @@ func NewMem(renderLock *sync.RWMutex, interval time.Duration, horizontalScale in
self.update() self.update()
go func() { go func() {
for range time.NewTicker(self.interval).C { for range time.NewTicker(self.updateInterval).C {
renderLock.RLock() renderLock.RLock()
self.update() self.update()
renderLock.RUnlock() renderLock.RUnlock()
@ -40,24 +40,36 @@ func NewMem(renderLock *sync.RWMutex, interval time.Duration, horizontalScale in
return self return self
} }
func (self *Mem) update() { func (self *MemWidget) update() {
main, err := psMem.VirtualMemory() mainMemory, err := psMem.VirtualMemory()
if err != nil { if err != nil {
log.Printf("failed to get main memory info from gopsutil: %v", err) log.Printf("failed to get main memory info from gopsutil: %v", err)
} else { } else {
self.Data["Main"] = append(self.Data["Main"], main.UsedPercent) self.Data["Main"] = append(self.Data["Main"], mainMemory.UsedPercent)
mainTotalBytes, mainTotalMagnitude := utils.ConvertBytes(main.Total) mainMemoryTotalBytes, mainMemoryTotalMagnitude := utils.ConvertBytes(mainMemory.Total)
mainUsedBytes, mainUsedMagnitude := utils.ConvertBytes(main.Used) mainMemoryUsedBytes, mainMemoryUsedMagnitude := utils.ConvertBytes(mainMemory.Used)
self.Labels["Main"] = fmt.Sprintf("%3.0f%% %5.1f%s/%.0f%s", main.UsedPercent, mainUsedBytes, mainUsedMagnitude, mainTotalBytes, mainTotalMagnitude) 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 { if err != nil {
log.Printf("failed to get swap memory info from gopsutil: %v", err) log.Printf("failed to get swap memory info from gopsutil: %v", err)
} else { } else {
self.Data["Swap"] = append(self.Data["Swap"], swap.UsedPercent) self.Data["Swap"] = append(self.Data["Swap"], swapMemory.UsedPercent)
swapTotalBytes, swapTotalMagnitude := utils.ConvertBytes(swap.Total) swapMemoryTotalBytes, swapMemoryTotalMagnitude := utils.ConvertBytes(swapMemory.Total)
swapUsedBytes, swapUsedMagnitude := utils.ConvertBytes(swap.Used) swapMemoryUsedBytes, swapMemoryUsedMagnitude := utils.ConvertBytes(swapMemory.Used)
self.Labels["Swap"] = fmt.Sprintf("%3.0f%% %5.1f%s/%.0f%s", swap.UsedPercent, swapUsedBytes, swapUsedMagnitude, swapTotalBytes, swapTotalMagnitude) self.Labels["Swap"] = fmt.Sprintf("%3.0f%% %5.1f%s/%.0f%s",
swapMemory.UsedPercent,
swapMemoryUsedBytes,
swapMemoryUsedMagnitude,
swapMemoryTotalBytes,
swapMemoryTotalMagnitude,
)
} }
} }

View File

@ -12,33 +12,33 @@ import (
"github.com/cjbassi/gotop/src/utils" "github.com/cjbassi/gotop/src/utils"
) )
type Net struct { type NetWidget struct {
*ui.Sparklines *ui.SparklineGroup
interval time.Duration updateInterval time.Duration
// used to calculate recent network activity // used to calculate recent network activity
prevRecvTotal uint64 totalBytesRecv uint64
prevSentTotal uint64 totalBytesSent uint64
} }
func NewNet(renderLock *sync.RWMutex) *Net { func NewNetWidget(renderLock *sync.RWMutex) *NetWidget {
recv := ui.NewSparkline() recvSparkline := ui.NewSparkline()
recv.Data = []int{} recvSparkline.Data = []int{}
sent := ui.NewSparkline() sentSparkline := ui.NewSparkline()
sent.Data = []int{} sentSparkline.Data = []int{}
spark := ui.NewSparklines(recv, sent) spark := ui.NewSparklineGroup(recvSparkline, sentSparkline)
self := &Net{ self := &NetWidget{
Sparklines: spark, SparklineGroup: spark,
interval: time.Second, updateInterval: time.Second,
} }
self.Title = " Network Usage " self.Title = " Network Usage "
self.update() self.update()
go func() { go func() {
for range time.NewTicker(self.interval).C { for range time.NewTicker(self.updateInterval).C {
renderLock.RLock() renderLock.RLock()
self.update() self.update()
renderLock.RUnlock() renderLock.RUnlock()
@ -48,60 +48,62 @@ func NewNet(renderLock *sync.RWMutex) *Net {
return self return self
} }
func (self *Net) update() { func (self *NetWidget) update() {
interfaces, err := psNet.IOCounters(true) interfaces, err := psNet.IOCounters(true)
if err != nil { if err != nil {
log.Printf("failed to get network activity from gopsutil: %v", err) log.Printf("failed to get network activity from gopsutil: %v", err)
return return
} }
var curRecvTotal uint64
var curSentTotal uint64 var totalBytesRecv uint64
var totalBytesSent uint64
for _, _interface := range interfaces { for _, _interface := range interfaces {
// ignore VPN interface // ignore VPN interface
if _interface.Name != "tun0" { if _interface.Name != "tun0" {
curRecvTotal += _interface.BytesRecv totalBytesRecv += _interface.BytesRecv
curSentTotal += _interface.BytesSent totalBytesSent += _interface.BytesSent
} }
} }
var recvRecent uint64
var sentRecent uint64
if self.prevRecvTotal != 0 { // if this isn't the first update var recentBytesRecv uint64
recvRecent = curRecvTotal - self.prevRecvTotal var recentBytesSent uint64
sentRecent = curSentTotal - self.prevSentTotal
if int(recvRecent) < 0 { if self.totalBytesRecv != 0 { // if this isn't the first update
log.Printf("error: negative value for recently received network data from gopsutil. recvRecent: %v", recvRecent) 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 // recover from error
recvRecent = 0 recentBytesRecv = 0
} }
if int(sentRecent) < 0 { if int(recentBytesSent) < 0 {
log.Printf("error: negative value for recently sent network data from gopsutil. sentRecent: %v", sentRecent) log.Printf("error: negative value for recently sent network data from gopsutil. recentBytesSent: %v", recentBytesSent)
// recover from error // recover from error
sentRecent = 0 recentBytesSent = 0
} }
self.Lines[0].Data = append(self.Lines[0].Data, int(recvRecent)) self.Lines[0].Data = append(self.Lines[0].Data, int(recentBytesRecv))
self.Lines[1].Data = append(self.Lines[1].Data, int(sentRecent)) self.Lines[1].Data = append(self.Lines[1].Data, int(recentBytesSent))
} }
// used in later calls to update // used in later calls to update
self.prevRecvTotal = curRecvTotal self.totalBytesRecv = totalBytesRecv
self.prevSentTotal = curSentTotal self.totalBytesSent = totalBytesSent
// render widget titles // render widget titles
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
total, label, recent := func() (uint64, string, uint64) { total, label, recent := func() (uint64, string, uint64) {
if i == 0 { 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)) recentConverted, unitRecent := utils.ConvertBytes(uint64(recent))
totalConv, unitTotal := utils.ConvertBytes(uint64(total)) totalConverted, unitTotal := utils.ConvertBytes(uint64(total))
self.Lines[i].Title1 = fmt.Sprintf(" Total %s: %5.1f %s", label, totalConv, unitTotal) 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, recentConv, unitRecent) self.Lines[i].Title2 = fmt.Sprintf(" %s/s: %9.1f %2s/s", label, recentConverted, unitRecent)
} }
} }

View File

@ -9,7 +9,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/gizak/termui"
psCPU "github.com/shirou/gopsutil/cpu" psCPU "github.com/shirou/gopsutil/cpu"
ui "github.com/cjbassi/gotop/src/termui" ui "github.com/cjbassi/gotop/src/termui"
@ -17,56 +16,67 @@ import (
) )
const ( const (
UP = "▲" UP_ARROW = "▲"
DOWN = "▼" DOWN_ARROW = "▼"
) )
// Process represents each process. type ProcSortMethod string
type Process struct {
PID int const (
Command string ProcSortCpu ProcSortMethod = "c"
CPU float64 ProcSortMem = "m"
Mem float64 ProcSortPid = "p"
Args string )
}
type Proc struct { type Proc struct {
*ui.Table Pid int
cpuCount float64 CommandName string
interval time.Duration FullCommand string
sortMethod string Cpu float64
groupedProcs []Process Mem float64
ungroupedProcs []Process
group bool
} }
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) cpuCount, err := psCPU.Counts(false)
if err != nil { if err != nil {
log.Printf("failed to get CPU count from gopsutil: %v", err) log.Printf("failed to get CPU count from gopsutil: %v", err)
} }
self := &Proc{ self := &ProcWidget{
Table: ui.NewTable(), Table: ui.NewTable(),
interval: time.Second, updateInterval: time.Second,
cpuCount: float64(cpuCount), cpuCount: float64(cpuCount),
sortMethod: "c", sortMethod: ProcSortCpu,
group: true, showGroupedProcs: true,
} }
self.Title = " Processes " self.Title = " Processes "
self.ColResizer = self.ColResize self.ShowCursor = true
self.Cursor = true self.ColGap = 3
self.Gap = 3
self.PadLeft = 2 self.PadLeft = 2
self.ColResizer = func() {
self.ColWidths = []int{
5, utils.MaxInt(self.Inner.Dx()-26, 10), 4, 4,
}
}
self.UniqueCol = 0 self.UniqueCol = 0
if self.group { if self.showGroupedProcs {
self.UniqueCol = 1 self.UniqueCol = 1
} }
self.update() self.update()
go func() { go func() {
for range time.NewTicker(self.interval).C { for range time.NewTicker(self.updateInterval).C {
renderLock.RLock() renderLock.RLock()
self.update() self.update()
renderLock.RUnlock() renderLock.RUnlock()
@ -76,120 +86,104 @@ func NewProc(renderLock *sync.RWMutex) *Proc {
return self 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. // 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%"} self.Header = []string{"Count", "Command", "CPU%", "Mem%"}
if !self.group { if !self.showGroupedProcs {
self.Header[0] = "PID" self.Header[0] = "PID"
} }
processes := &self.ungroupedProcs var procs *[]Proc
if self.group { if self.showGroupedProcs {
processes = &self.groupedProcs procs = &self.groupedProcs
} else {
procs = &self.ungroupedProcs
} }
switch self.sortMethod { switch self.sortMethod {
case "c": case ProcSortCpu:
sort.Sort(sort.Reverse(ProcessByCPU(*processes))) sort.Sort(sort.Reverse(SortProcsByCpu(*procs)))
self.Header[2] += DOWN self.Header[2] += DOWN_ARROW
case "p": case ProcSortPid:
if self.group { if self.showGroupedProcs {
sort.Sort(sort.Reverse(ProcessByPID(*processes))) sort.Sort(sort.Reverse(SortProcsByPid(*procs)))
} else { } else {
sort.Sort(ProcessByPID(*processes)) sort.Sort(SortProcsByPid(*procs))
} }
self.Header[0] += DOWN self.Header[0] += DOWN_ARROW
case "m": case ProcSortMem:
sort.Sort(sort.Reverse(ProcessByMem(*processes))) sort.Sort(sort.Reverse(SortProcsByMem(*procs)))
self.Header[3] += DOWN self.Header[3] += DOWN_ARROW
}
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,
} }
} }
func (self *Proc) ChangeSort(e termui.Event) { // convertProcsToTableRows converts a []Proc to a [][]string and sets it to the table Rows
if self.sortMethod != e.ID { func (self *ProcWidget) convertProcsToTableRows() {
self.sortMethod = e.ID var procs *[]Proc
self.Top() if self.showGroupedProcs {
self.Sort() 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() { func (self *ProcWidget) ToggleShowingGroupedProcs() {
self.group = !self.group self.showGroupedProcs = !self.showGroupedProcs
if self.group { if self.showGroupedProcs {
self.UniqueCol = 1 self.UniqueCol = 1
} else { } else {
self.UniqueCol = 0 self.UniqueCol = 0
} }
self.Sort() self.ScrollTop()
self.Top() self.sortProcs()
self.convertProcsToTableRows()
} }
// Group groupes a []Process based on command name. // KillProc kills a process or group of processes depending on if we're displaying the processes grouped or not.
// The first field changes from PID to count. func (self *ProcWidget) KillProc() {
// 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() {
self.SelectedItem = "" self.SelectedItem = ""
command := "kill" command := "kill"
if self.UniqueCol == 1 { if self.UniqueCol == 1 {
@ -200,57 +194,91 @@ func (self *Proc) Kill() {
cmd.Wait() cmd.Wait()
} }
///////////////////////////////////////////////////////////////////////////////// // groupProcs groupes a []Proc based on command name.
// []Process Sorting // // 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 // Len implements Sort interface
func (P ProcessByCPU) Len() int { func (self SortProcsByCpu) Len() int {
return len(P) return len(self)
} }
// Swap implements Sort interface // Swap implements Sort interface
func (P ProcessByCPU) Swap(i, j int) { func (self SortProcsByCpu) Swap(i, j int) {
P[i], P[j] = P[j], P[i] self[i], self[j] = self[j], self[i]
} }
// Less implements Sort interface // Less implements Sort interface
func (P ProcessByCPU) Less(i, j int) bool { func (self SortProcsByCpu) Less(i, j int) bool {
return P[i].CPU < P[j].CPU return self[i].Cpu < self[j].Cpu
} }
type ProcessByPID []Process type SortProcsByPid []Proc
// Len implements Sort interface // Len implements Sort interface
func (P ProcessByPID) Len() int { func (self SortProcsByPid) Len() int {
return len(P) return len(self)
} }
// Swap implements Sort interface // Swap implements Sort interface
func (P ProcessByPID) Swap(i, j int) { func (self SortProcsByPid) Swap(i, j int) {
P[i], P[j] = P[j], P[i] self[i], self[j] = self[j], self[i]
} }
// Less implements Sort interface // Less implements Sort interface
func (P ProcessByPID) Less(i, j int) bool { func (self SortProcsByPid) Less(i, j int) bool {
return P[i].PID < P[j].PID return self[i].Pid < self[j].Pid
} }
type ProcessByMem []Process type SortProcsByMem []Proc
// Len implements Sort interface // Len implements Sort interface
func (P ProcessByMem) Len() int { func (self SortProcsByMem) Len() int {
return len(P) return len(self)
} }
// Swap implements Sort interface // Swap implements Sort interface
func (P ProcessByMem) Swap(i, j int) { func (self SortProcsByMem) Swap(i, j int) {
P[i], P[j] = P[j], P[i] self[i], self[j] = self[j], self[i]
} }
// Less implements Sort interface // Less implements Sort interface
func (P ProcessByMem) Less(i, j int) bool { func (self SortProcsByMem) Less(i, j int) bool {
return P[i].Mem < P[j].Mem return self[i].Mem < self[j].Mem
} }

View File

@ -8,35 +8,17 @@ import (
"strings" "strings"
) )
func (self *Proc) update() { func getProcs() ([]Proc, error) {
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) {
output, err := exec.Command("ps", "-axo", "pid:10,comm:50,pcpu:5,pmem:5,args").Output() output, err := exec.Command("ps", "-axo", "pid:10,comm:50,pcpu:5,pmem:5,args").Output()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to execute 'ps' command: %v", err) return nil, fmt.Errorf("failed to execute 'ps' command: %v", err)
} }
// converts to []string, removing trailing newline and header // 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{} procs := []Proc{}
for _, line := range processStrArr { for _, line := range linesOfProcStrings {
pid, err := strconv.Atoi(strings.TrimSpace(line[0:10])) pid, err := strconv.Atoi(strings.TrimSpace(line[0:10]))
if err != nil { if err != nil {
log.Printf("failed to convert PID to int: %v. line: %v", err, line) log.Printf("failed to convert PID to int: %v. line: %v", err, line)
@ -49,14 +31,15 @@ func Processes() ([]Process, error) {
if err != nil { if err != nil {
log.Printf("failed to convert Mem usage to float: %v. line: %v", err, line) log.Printf("failed to convert Mem usage to float: %v. line: %v", err, line)
} }
process := Process{ proc := Proc{
PID: pid, Pid: pid,
Command: strings.TrimSpace(line[11:61]), CommandName: strings.TrimSpace(line[11:61]),
CPU: cpu, FullCommand: line[74:],
Mem: mem, Cpu: cpu,
Args: line[74:], Mem: mem,
} }
processes = append(processes, process) procs = append(procs, proc)
} }
return processes, nil
return procs, nil
} }

View File

@ -11,31 +11,31 @@ import (
) )
const ( const (
// Define column widths for ps output used in Processes() // Define column widths for ps output used in Procs()
five = "12345" five = "12345"
ten = five + five ten = five + five
fifty = ten + ten + ten + ten + ten fifty = ten + ten + ten + ten + ten
) )
func (self *Proc) update() { func (self *ProcWidget) update() {
processes, err := Processes() procs, err := GetProcs()
if err != nil { if err != nil {
log.Printf("failed to retrieve processes: %v", err) log.Printf("failed to retrieve processes: %v", err)
return return
} }
// have to iterate like this in order to actually change the value // have to iterate like this in order to actually change the value
for i := range processes { for i := range procs {
processes[i].CPU /= self.cpuCount procs[i].CPU /= self.cpuCount
} }
self.ungroupedProcs = processes self.ungroupedProcs = procs
self.groupedProcs = Group(processes) 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) keywords := fmt.Sprintf("pid=%s,comm=%s,pcpu=%s,pmem=%s,args", ten, fifty, five, five)
output, err := exec.Command("ps", "-caxo", keywords).Output() output, err := exec.Command("ps", "-caxo", keywords).Output()
if err != nil { if err != nil {
@ -43,10 +43,10 @@ func Processes() ([]Process, error) {
} }
// converts to []string and removes the header // 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{} procs := []Proc{}
for _, line := range strOutput { for _, line := range linesOfProcStrings {
pid, err := strconv.Atoi(strings.TrimSpace(line[0:10])) pid, err := strconv.Atoi(strings.TrimSpace(line[0:10]))
if err != nil { if err != nil {
log.Printf("failed to convert first field to int: %v. split: %v", err, line) 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 { if err != nil {
log.Printf("failed to convert fourth field to float: %v. split: %v", err, line) log.Printf("failed to convert fourth field to float: %v. split: %v", err, line)
} }
process := Process{ proc := Proc{
PID: pid, PID: pid,
Command: strings.TrimSpace(line[11:61]), Command: strings.TrimSpace(line[11:61]),
CPU: cpu, CPU: cpu,
Mem: mem, Mem: mem,
Args: line[74:], Args: line[74:],
} }
processes = append(processes, process) procs = append(procs, proc)
} }
return processes, nil
return procs, nil
} }

View File

@ -1,34 +1,35 @@
package widgets package widgets
import ( import (
"fmt"
"log" "log"
psProc "github.com/shirou/gopsutil/process" psProc "github.com/shirou/gopsutil/process"
) )
func (self *Proc) update() { func getProcs() ([]Proc, error) {
psProcesses, err := psProc.Processes() psProcs, err := psProc.Processes()
if err != nil { if err != nil {
log.Printf("failed to get processes from gopsutil: %v", err) return nil, fmt.Errorf("failed to get processes from gopsutil: %v", err)
return
} }
processes := make([]Process, len(psProcesses))
for i, psProcess := range psProcesses { procs := make([]Proc, len(psProcs))
pid := psProcess.Pid for i, psProc := range psProcs {
command, err := psProcess.Name() pid := psProc.Pid
command, err := psProc.Name()
if err != nil { 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 { 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 { 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), int(pid),
command, command,
cpu / self.cpuCount, cpu / self.cpuCount,
@ -39,8 +40,5 @@ func (self *Proc) update() {
} }
} }
self.ungroupedProcs = processes return procs, nil
self.groupedProcs = Group(processes)
self.Sort()
} }

View File

@ -2,6 +2,7 @@ package widgets
import ( import (
"image" "image"
"log"
"os" "os"
"time" "time"
@ -21,27 +22,31 @@ func NewStatusBar() *StatusBar {
func (self *StatusBar) Draw(buf *ui.Buffer) { func (self *StatusBar) Draw(buf *ui.Buffer) {
self.Block.Draw(buf) 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( buf.SetString(
hostname, hostname,
ui.NewStyle(7), ui.NewStyle(ui.ColorWhite),
image.Pt(self.Inner.Min.X, self.Inner.Min.Y+(self.Inner.Dy()/2)), image.Pt(self.Inner.Min.X, self.Inner.Min.Y+(self.Inner.Dy()/2)),
) )
t := time.Now() currentTime := time.Now()
_time := t.Format("15:04:05") formattedTime := currentTime.Format("15:04:05")
buf.SetString( buf.SetString(
_time, formattedTime,
ui.NewStyle(7), ui.NewStyle(ui.ColorWhite),
image.Pt( 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), self.Inner.Min.Y+(self.Inner.Dy()/2),
), ),
) )
buf.SetString( buf.SetString(
"gotop", "gotop",
ui.NewStyle(7), ui.NewStyle(ui.ColorWhite),
image.Pt( image.Pt(
self.Inner.Max.X-6, self.Inner.Max.X-6,
self.Inner.Min.Y+(self.Inner.Dy()/2), self.Inner.Min.Y+(self.Inner.Dy()/2),

View File

@ -8,36 +8,45 @@ import (
"time" "time"
ui "github.com/gizak/termui" ui "github.com/gizak/termui"
"github.com/cjbassi/gotop/src/utils"
) )
type Temp struct { type TempScale int
*ui.Block // inherits from Block instead of a premade Widget
interval time.Duration const (
Data map[string]int Celcius TempScale = 0
Threshold int Fahrenheit = 1
TempLow ui.Color )
TempHigh ui.Color
Fahrenheit bool 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 { func NewTempWidget(renderLock *sync.RWMutex, tempScale TempScale) *TempWidget {
self := &Temp{ self := &TempWidget{
Block: ui.NewBlock(), Block: ui.NewBlock(),
interval: time.Second * 5, updateInterval: time.Second * 5,
Data: make(map[string]int), Data: make(map[string]int),
Threshold: 80, // temp at which color should change TempThreshold: 80,
TempScale: tempScale,
} }
self.Title = " Temperatures " self.Title = " Temperatures "
if fahrenheit { if tempScale == Fahrenheit {
self.Fahrenheit = true self.TempThreshold = utils.CelsiusToFahrenheit(self.TempThreshold)
self.Threshold = int(self.Threshold*9/5 + 32)
} }
self.update() self.update()
go func() { go func() {
for range time.NewTicker(self.interval).C { for range time.NewTicker(self.updateInterval).C {
renderLock.RLock() renderLock.RLock()
self.update() self.update()
renderLock.RUnlock() renderLock.RUnlock()
@ -47,8 +56,8 @@ func NewTemp(renderLock *sync.RWMutex, fahrenheit bool) *Temp {
return self return self
} }
// We implement a custom Draw method instead of inheriting from a generic Widget. // Custom Draw method instead of inheriting from a generic Widget.
func (self *Temp) Draw(buf *ui.Buffer) { func (self *TempWidget) Draw(buf *ui.Buffer) {
self.Block.Draw(buf) self.Block.Draw(buf)
var keys []string var keys []string
@ -62,9 +71,11 @@ func (self *Temp) Draw(buf *ui.Buffer) {
break break
} }
fg := self.TempLow var fg ui.Color
if self.Data[key] >= self.Threshold { if self.Data[key] < self.TempThreshold {
fg = self.TempHigh fg = self.TempLowColor
} else {
fg = self.TempHighColor
} }
s := ui.TrimString(key, (self.Inner.Dx() - 4)) s := ui.TrimString(key, (self.Inner.Dx() - 4))
@ -72,13 +83,15 @@ func (self *Temp) Draw(buf *ui.Buffer) {
ui.Theme.Default, ui.Theme.Default,
image.Pt(self.Inner.Min.X, self.Inner.Min.Y+y), image.Pt(self.Inner.Min.X, self.Inner.Min.Y+y),
) )
if self.Fahrenheit {
switch self.TempScale {
case Fahrenheit:
buf.SetString( buf.SetString(
fmt.Sprintf("%3dF", self.Data[key]), fmt.Sprintf("%3dF", self.Data[key]),
ui.NewStyle(fg), ui.NewStyle(fg),
image.Pt(self.Inner.Max.X-4, self.Inner.Min.Y+y), image.Pt(self.Inner.Max.X-4, self.Inner.Min.Y+y),
) )
} else { case Celcius:
buf.SetString( buf.SetString(
fmt.Sprintf("%3dC", self.Data[key]), fmt.Sprintf("%3dC", self.Data[key]),
ui.NewStyle(fg), ui.NewStyle(fg),

View File

@ -53,7 +53,7 @@ func SensorsTemperatures() ([]TemperatureStat, error) {
return temperatures, nil return temperatures, nil
} }
func (self *Temp) update() { func (self *TempWidget) update() {
sensors, err := SensorsTemperatures() sensors, err := SensorsTemperatures()
if err != nil { if err != nil {
log.Printf("failed to get sensors from CGO: %v", err) log.Printf("failed to get sensors from CGO: %v", err)
@ -61,9 +61,10 @@ func (self *Temp) update() {
} }
for _, sensor := range sensors { for _, sensor := range sensors {
if sensor.Temperature != 0 { if sensor.Temperature != 0 {
if self.Fahrenheit { switch self.TempScale {
case Fahrenheit:
self.Data[sensor.SensorKey] = utils.CelsiusToFahrenheit(int(sensor.Temperature)) self.Data[sensor.SensorKey] = utils.CelsiusToFahrenheit(int(sensor.Temperature))
} else { case Celcius:
self.Data[sensor.SensorKey] = int(sensor.Temperature) self.Data[sensor.SensorKey] = int(sensor.Temperature)
} }
} }

View File

@ -1,5 +1,7 @@
package widgets package widgets
// loosely based on https://github.com/openbsd/src/blob/master/sbin/sysctl/sysctl.c#L2517
// #include <sys/time.h> // #include <sys/time.h>
// #include <sys/sysctl.h> // #include <sys/sysctl.h>
// #include <sys/sensors.h> // #include <sys/sensors.h>
@ -13,14 +15,14 @@ import (
"github.com/cjbassi/gotop/src/utils" "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 { switch mlen {
case 4: case 4:
k := mib[3] k := mib[3]
var numt C.int var numt C.int
for numt = 0; numt < snsrdev.maxnumt[k]; numt++ { for numt = 0; numt < snsrdev.maxnumt[k]; numt++ {
mib[4] = numt mib[4] = numt
getTemp(t, mib, mlen+1, snsrdev, int(numt)) self.getTemp(mib, mlen+1, snsrdev, int(numt))
} }
case 5: case 5:
var snsr C.struct_sensor 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) key := C.GoString(&snsrdev.xname[0]) + ".temp" + strconv.Itoa(index)
temp := int((snsr.value - 273150000.0) / 1000000.0) temp := int((snsr.value - 273150000.0) / 1000000.0)
if t.Fahrenheit { switch self.TempScale {
t.Data[key] = utils.CelsiusToFahrenheit(temp) case Fahrenheit:
} else { self.Data[key] = utils.CelsiusToFahrenheit(temp)
t.Data[key] = temp case Celcius:
self.Data[key] = temp
} }
} }
} }
} }
func (self *Temp) update() { func (self *TempWidget) update() {
mib := []C.int{0, 1, 2, 3, 4} mib := []C.int{0, 1, 2, 3, 4}
var snsrdev C.struct_sensordev var snsrdev C.struct_sensordev
@ -56,17 +59,14 @@ func (self *Temp) update() {
var i C.int var i C.int
for i = 0; ; i++ { for i = 0; ; i++ {
mib[2] = i mib[2] = i
if v, e := C.sysctl(&mib[0], 3, unsafe.Pointer(&snsrdev), &len, nil, 0); v == -1 { if v, e := C.sysctl(&mib[0], 3, unsafe.Pointer(&snsrdev), &len, nil, 0); v == -1 {
if e == syscall.ENXIO { if e == syscall.ENXIO {
continue continue
} }
if e == syscall.ENOENT { if e == syscall.ENOENT {
break break
} }
} }
self.getTemp(mib, 4, &snsrdev, 0)
getTemp(self, mib, 4, &snsrdev, 0)
} }
} }

View File

@ -11,7 +11,7 @@ import (
"github.com/cjbassi/gotop/src/utils" "github.com/cjbassi/gotop/src/utils"
) )
func (self *Temp) update() { func (self *TempWidget) update() {
sensors, err := psHost.SensorsTemperatures() sensors, err := psHost.SensorsTemperatures()
if err != nil { if err != nil {
log.Printf("error recieved from gopsutil: %v", err) 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 { if strings.Contains(sensor.SensorKey, "input") && sensor.Temperature != 0 {
// removes '_input' from the end of the sensor name // removes '_input' from the end of the sensor name
label := sensor.SensorKey[:strings.Index(sensor.SensorKey, "_input")] label := sensor.SensorKey[:strings.Index(sensor.SensorKey, "_input")]
if self.Fahrenheit { switch self.TempScale {
case Fahrenheit:
self.Data[label] = utils.CelsiusToFahrenheit(int(sensor.Temperature)) self.Data[label] = utils.CelsiusToFahrenheit(int(sensor.Temperature))
} else { case Celcius:
self.Data[label] = int(sensor.Temperature) self.Data[label] = int(sensor.Temperature)
} }
} }

View File

@ -8,7 +8,7 @@ import (
"github.com/cjbassi/gotop/src/utils" "github.com/cjbassi/gotop/src/utils"
) )
func (self *Temp) update() { func (self *TempWidget) update() {
sensors, err := psHost.SensorsTemperatures() sensors, err := psHost.SensorsTemperatures()
if err != nil { if err != nil {
log.Printf("failed to get sensors from gopsutil: %v", err) log.Printf("failed to get sensors from gopsutil: %v", err)
@ -16,9 +16,10 @@ func (self *Temp) update() {
} }
for _, sensor := range sensors { for _, sensor := range sensors {
if sensor.Temperature != 0 { if sensor.Temperature != 0 {
if self.Fahrenheit { switch self.TempScale {
case Fahrenheit:
self.Data[sensor.SensorKey] = utils.CelsiusToFahrenheit(int(sensor.Temperature)) self.Data[sensor.SensorKey] = utils.CelsiusToFahrenheit(int(sensor.Temperature))
} else { case Celcius:
self.Data[sensor.SensorKey] = int(sensor.Temperature) self.Data[sensor.SensorKey] = int(sensor.Temperature)
} }
} }