diff --git a/Gopkg.lock b/Gopkg.lock index 1486eef..63c504c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -17,7 +17,7 @@ branch = "master" name = "github.com/cjbassi/termui" packages = ["."] - revision = "50e37fc8a80ca08ddca62be4537c7d49261994a3" + revision = "87dbce7f69558bede7c7c7a51324ce4d8cfcb87b" [[projects]] branch = "master" diff --git a/src/utils/utils.go b/src/utils/utils.go index ef38162..2acdb7d 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -19,6 +19,25 @@ func BytesToGB(b uint64) float64 { return float64(b) / math.Pow10(9) } +func ConvertBytes(b uint64) (float64, string) { + if b >= 1000000000 { + return BytesToGB(uint64(b)), "GB" + } else if b >= 1000000 { + return BytesToMB(uint64(b)), "MB" + } else if b >= 1000 { + return BytesToKB(uint64(b)), "KB" + } else { + return float64(b), "B" + } +} + +func Max(a, b int) int { + if a > b { + return a + } + return b +} + func Error(issue, diagnostics string) { ui.Close() fmt.Println("Error caught. Exiting program.") diff --git a/src/widgets/disk.go b/src/widgets/disk.go index 06f2bff..94bbaba 100644 --- a/src/widgets/disk.go +++ b/src/widgets/disk.go @@ -2,6 +2,8 @@ package widgets import ( "fmt" + "sort" + "strings" "time" "github.com/cjbassi/gotop/src/utils" @@ -9,19 +11,33 @@ import ( psDisk "github.com/shirou/gopsutil/disk" ) +type Partition struct { + Device string + Mount string + TotalRead uint64 + TotalWrite uint64 + CurRead string + CurWrite string + UsedPercent int + Free string +} + type Disk struct { - *ui.Gauge - fs string // which filesystem to get the disk usage of - interval time.Duration + *ui.Table + interval time.Duration + Partitions map[string]*Partition } func NewDisk() *Disk { self := &Disk{ - Gauge: ui.NewGauge(), - fs: "/", - interval: time.Second * 5, + Table: ui.NewTable(), + interval: time.Second, + Partitions: make(map[string]*Partition), } self.Label = "Disk Usage" + self.Header = []string{"Disk", "Mount", "Used", "Free", "R/s", "W/s"} + self.Gap = 2 + self.ColResizer = self.ColResize self.update() @@ -36,7 +52,98 @@ func NewDisk() *Disk { } func (self *Disk) update() { - usage, _ := psDisk.Usage(self.fs) - self.Percent = int(usage.UsedPercent) - self.Description = fmt.Sprintf(" (%dGB free)", int(utils.BytesToGB(usage.Free))) + Partitions, _ := psDisk.Partitions(false) + + // add partition if it's new + for _, Part := range Partitions { + device := strings.Replace(Part.Device, "/dev/", "", -1) + if _, ok := self.Partitions[device]; !ok { + self.Partitions[device] = &Partition{ + Device: device, + Mount: Part.Mountpoint, + } + } + } + + // delete a partition if it no longer exists + todelete := []string{} + for key, _ := range self.Partitions { + exists := false + for _, Part := range Partitions { + device := strings.Replace(Part.Device, "/dev/", "", -1) + if key == device { + exists = true + break + } + } + if !exists { + todelete = append(todelete, key) + } + } + for _, val := range todelete { + delete(self.Partitions, val) + } + + // updates partition info + for _, Part := range self.Partitions { + usage, _ := psDisk.Usage(Part.Mount) + Part.UsedPercent = int(usage.UsedPercent) + + Free, Mag := utils.ConvertBytes(usage.Free) + Part.Free = fmt.Sprintf("%3d%s", uint64(Free), Mag) + + ret, _ := psDisk.IOCounters("/dev/" + Part.Device) + data := ret[Part.Device] + 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 + + 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) + } else { + Part.CurRead = fmt.Sprintf("%d%s", 0, "B") + Part.CurWrite = fmt.Sprintf("%d%s", 0, "B") + } + Part.TotalRead, Part.TotalWrite = curRead, curWrite + } + + // converts self.Partitions into self.Rows which is a [][]String + sortedPartitions := []string{} + for seriesName := range self.Partitions { + sortedPartitions = append(sortedPartitions, seriesName) + } + sort.Strings(sortedPartitions) + + self.Rows = make([][]string, len(self.Partitions)) + for i, key := range sortedPartitions { + Part := self.Partitions[key] + self.Rows[i] = make([]string, 6) + self.Rows[i][0] = Part.Device + 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{ + 4, + utils.Max(5, self.X-33), + 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 + } } diff --git a/src/widgets/net.go b/src/widgets/net.go index eba1f88..14b1522 100644 --- a/src/widgets/net.go +++ b/src/widgets/net.go @@ -75,13 +75,11 @@ func (self *Net) update() { self.prevRecvTotal = curRecvTotal self.prevSentTotal = curSentTotal - // net widget titles + // render widget titles for i := 0; i < 2; i++ { var method string // either 'Rx' or 'Tx' var total float64 recent := self.Lines[i].Data[len(self.Lines[i].Data)-1] - unitTotal := "B" - unitRecent := "B" if i == 0 { total = float64(curRecvTotal) @@ -91,21 +89,9 @@ func (self *Net) update() { method = "Tx" } - if recent >= 1000000 { - recent = int(utils.BytesToMB(uint64(recent))) - unitRecent = "MB" - } else if recent >= 1000 { - recent = int(utils.BytesToKB(uint64(recent))) - unitRecent = "kB" - } - - if total >= 1000000000 { - total = utils.BytesToGB(uint64(total)) - unitTotal = "GB" - } else if total >= 1000000 { - total = utils.BytesToMB(uint64(total)) - unitTotal = "MB" - } + recentFloat, unitRecent := utils.ConvertBytes(uint64(recent)) + recent = int(recentFloat) + total, unitTotal := utils.ConvertBytes(uint64(total)) self.Lines[i].Title1 = fmt.Sprintf(" Total %s: %5.1f %s", method, total, unitTotal) self.Lines[i].Title2 = fmt.Sprintf(" %s/s: %9d %2s/s", method, recent, unitRecent) diff --git a/src/widgets/proc.go b/src/widgets/proc.go index 8a47709..566f01a 100644 --- a/src/widgets/proc.go +++ b/src/widgets/proc.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/cjbassi/gotop/src/utils" ui "github.com/cjbassi/termui" psCPU "github.com/shirou/gopsutil/cpu" ) @@ -27,14 +28,13 @@ type Process struct { type Proc struct { *ui.Table - cpuCount float64 - interval time.Duration - sortMethod string - groupedProcs []Process - ungroupedProcs []Process - group bool - KeyPressed chan bool - DefaultColWidths []int + cpuCount float64 + interval time.Duration + sortMethod string + groupedProcs []Process + ungroupedProcs []Process + group bool + KeyPressed chan bool } func NewProc(keyPressed chan bool) *Proc { @@ -49,8 +49,9 @@ func NewProc(keyPressed chan bool) *Proc { } self.Label = "Processes" self.ColResizer = self.ColResize - self.DefaultColWidths = []int{5, 10, 4, 4} - self.ColWidths = make([]int, 4) + self.Cursor = true + self.Gap = 3 + self.PadLeft = 2 self.UniqueCol = 0 if self.group { @@ -106,30 +107,8 @@ func (self *Proc) Sort() { // ColResize overrides the default ColResize in the termui table. func (self *Proc) ColResize() { - copy(self.ColWidths, self.DefaultColWidths) - - self.Gap = 3 - - self.CellXPos = []int{ - self.Gap, - self.Gap + self.ColWidths[0] + self.Gap, - (self.X + 2) - self.Gap - self.ColWidths[3] - self.Gap - self.ColWidths[2], - (self.X + 2) - self.Gap - self.ColWidths[3], - } - - rowWidth := self.Gap + - self.ColWidths[0] + self.Gap + - self.ColWidths[1] + self.Gap + - self.ColWidths[2] + self.Gap + - self.ColWidths[3] + self.Gap - - // only renders a column if it fits - if self.X < (rowWidth - self.Gap - self.ColWidths[3]) { - self.ColWidths[2] = 0 - self.ColWidths[3] = 0 - } else if self.X < rowWidth { - self.CellXPos[2] = self.CellXPos[3] - self.ColWidths[3] = 0 + self.ColWidths = []int{ + 5, utils.Max(self.X-26, 10), 4, 4, } } diff --git a/src/widgets/temp.go b/src/widgets/temp.go index 2759c1a..0c1406a 100644 --- a/src/widgets/temp.go +++ b/src/widgets/temp.go @@ -78,7 +78,6 @@ func (self *Temp) Buffer() *ui.Buffer { s := ui.MaxString(key, (self.X - 4)) buf.SetString(1, y+1, s, self.Fg, self.Bg) buf.SetString(self.X-2, y+1, fmt.Sprintf("%2dC", self.Data[key]), fg, self.Bg) - } return buf diff --git a/vendor/github.com/cjbassi/termui/table.go b/vendor/github.com/cjbassi/termui/table.go index b9b7bfb..5d9879b 100644 --- a/vendor/github.com/cjbassi/termui/table.go +++ b/vendor/github.com/cjbassi/termui/table.go @@ -16,8 +16,10 @@ type Table struct { 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 - Cursor Color + Cursor bool + CursorColor Color UniqueCol int // the column used to identify the selected item SelectedItem string // used to keep the cursor on the correct item if the data changes @@ -29,7 +31,7 @@ type Table struct { func NewTable() *Table { self := &Table{ Block: NewBlock(), - Cursor: Theme.TableCursor, + CursorColor: Theme.TableCursor, SelectedRow: 0, TopRow: 0, UniqueCol: 0, @@ -41,20 +43,6 @@ func NewTable() *Table { // ColResize is the default column resizer, but can be overriden. // ColResize calculates the width of each column. func (self *Table) ColResize() { - // calculate gap size based on total width - self.Gap = 3 - if self.X < 50 { - self.Gap = 1 - } else if self.X < 75 { - self.Gap = 2 - } - - cur := 0 - for _, w := range self.ColWidths { - cur += self.Gap - self.CellXPos = append(self.CellXPos, cur) - cur += w - } } // Buffer implements the Bufferer interface. @@ -63,13 +51,25 @@ func (self *Table) Buffer() *Buffer { self.ColResizer() + // finds exact column starting position + self.CellXPos = []int{} + cur := 1 + self.PadLeft + for _, w := range self.ColWidths { + self.CellXPos = append(self.CellXPos, cur) + cur += w + cur += self.Gap + } + // prints header for i, h := range self.Header { width := self.ColWidths[i] if width == 0 { continue } - h = MaxString(h, self.X-self.CellXPos[i]) + // don't render column if it doesn't fit in widget + if width > (self.X-self.CellXPos[i])+1 { + continue + } buf.SetString(self.CellXPos[i], 1, h, self.Fg|AttrBold, self.Bg) } @@ -89,16 +89,18 @@ func (self *Table) Buffer() *Buffer { // prints cursor bg := self.Bg - if (self.SelectedItem == "" && rowNum == self.SelectedRow) || (self.SelectedItem != "" && self.SelectedItem == row[self.UniqueCol]) { - bg = self.Cursor - for _, width := range self.ColWidths { - if width == 0 { - continue + if self.Cursor { + if (self.SelectedItem == "" && rowNum == self.SelectedRow) || (self.SelectedItem != "" && self.SelectedItem == row[self.UniqueCol]) { + bg = self.CursorColor + for _, width := range self.ColWidths { + if width == 0 { + continue + } + buf.SetString(1, y, strings.Repeat(" ", self.X), self.Fg, bg) } - buf.SetString(1, y, strings.Repeat(" ", self.X), self.Fg, bg) + self.SelectedItem = row[self.UniqueCol] + self.SelectedRow = rowNum } - self.SelectedItem = row[self.UniqueCol] - self.SelectedRow = rowNum } // prints each col of the row @@ -106,9 +108,9 @@ func (self *Table) Buffer() *Buffer { if width == 0 { continue } - width = self.X - self.CellXPos[i] - if len(self.CellXPos) > i+1 { - width -= self.X - self.CellXPos[i+1] + // don't render column if width is greater than distance to end of widget + if width > (self.X-self.CellXPos[i])+1 { + continue } r := MaxString(row[i], width) buf.SetString(self.CellXPos[i], y, r, self.Fg, bg)