mirror of
https://github.com/bcicen/ctop.git
synced 2024-08-30 18:23:19 +00:00
refactor widgets, add wrapper structs
This commit is contained in:
parent
9f5cd42b73
commit
56be64367b
76
container.go
76
container.go
@ -1,61 +1,69 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bcicen/ctop/cwidgets"
|
|
||||||
"github.com/bcicen/ctop/cwidgets/compact"
|
"github.com/bcicen/ctop/cwidgets/compact"
|
||||||
"github.com/bcicen/ctop/metrics"
|
"github.com/bcicen/ctop/metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Metrics and metadata representing a container
|
||||||
type Container struct {
|
type Container struct {
|
||||||
id string
|
metrics.Metrics
|
||||||
name string
|
Id string
|
||||||
state string
|
Name string
|
||||||
metrics metrics.Metrics
|
State string
|
||||||
widgets cwidgets.ContainerWidgets
|
Meta map[string]string
|
||||||
|
Updates chan [2]string
|
||||||
|
Widgets *compact.Compact
|
||||||
|
collector metrics.Collector
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContainer(id, name string) *Container {
|
func NewContainer(id, name string, collector metrics.Collector) *Container {
|
||||||
c := &Container{
|
return &Container{
|
||||||
id: id,
|
Metrics: metrics.NewMetrics(),
|
||||||
name: name,
|
Id: id,
|
||||||
metrics: metrics.NewMetrics(),
|
Name: name,
|
||||||
|
Meta: make(map[string]string),
|
||||||
|
Updates: make(chan [2]string),
|
||||||
|
Widgets: compact.NewCompact(id, name),
|
||||||
|
collector: collector,
|
||||||
}
|
}
|
||||||
c.widgets = compact.NewCompact(c.ShortID(), c.ShortName(), c.state)
|
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) ShortID() string {
|
func (c *Container) GetMeta(k string) string {
|
||||||
return c.id[:12]
|
if v, ok := c.Meta[k]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) ShortName() string {
|
func (c *Container) SetMeta(k, v string) {
|
||||||
return strings.Replace(c.name, "/", "", 1) // use primary container name
|
c.Meta[k] = v
|
||||||
|
c.Updates <- [2]string{k, v}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) SetState(s string) {
|
func (c *Container) SetState(s string) {
|
||||||
c.state = s
|
c.State = s
|
||||||
c.widgets.SetStatus(s)
|
c.Widgets.Status.Set(s)
|
||||||
}
|
// start collector, if needed
|
||||||
|
if c.State == "running" && !c.collector.Running() {
|
||||||
// Set metrics to zero state, clear widget gauges
|
c.collector.Start()
|
||||||
func (c *Container) reset() {
|
c.Read(c.collector.Stream())
|
||||||
c.metrics = metrics.Metrics{}
|
}
|
||||||
c.widgets.Reset()
|
// stop collector, if needed
|
||||||
|
if c.State != "running" && c.collector.Running() {
|
||||||
|
c.collector.Stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read metric stream, updating widgets
|
// Read metric stream, updating widgets
|
||||||
func (c *Container) Read(stream chan metrics.Metrics) {
|
func (c *Container) Read(stream chan metrics.Metrics) {
|
||||||
go func() {
|
go func() {
|
||||||
for metrics := range stream {
|
for metrics := range stream {
|
||||||
c.metrics = metrics
|
c.Metrics = metrics
|
||||||
c.widgets.SetCPU(metrics.CPUUtil)
|
c.Widgets.SetMetrics(metrics)
|
||||||
c.widgets.SetMem(metrics.MemUsage, metrics.MemLimit, metrics.MemPercent)
|
|
||||||
c.widgets.SetNet(metrics.NetRx, metrics.NetTx)
|
|
||||||
}
|
}
|
||||||
log.Infof("reader stopped for container: %s", c.id)
|
log.Infof("reader stopped for container: %s", c.Id)
|
||||||
c.reset()
|
c.Widgets.Reset()
|
||||||
}()
|
}()
|
||||||
log.Infof("reader started for container: %s", c.id)
|
log.Infof("reader started for container: %s", c.Id)
|
||||||
}
|
}
|
||||||
|
25
cwidgets/compact/gauge.go
Normal file
25
cwidgets/compact/gauge.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package compact
|
||||||
|
|
||||||
|
import (
|
||||||
|
ui "github.com/gizak/termui"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GaugeCol struct {
|
||||||
|
*ui.Gauge
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGaugeCol() *GaugeCol {
|
||||||
|
g := ui.NewGauge()
|
||||||
|
g.Height = 1
|
||||||
|
g.Border = false
|
||||||
|
g.Percent = 0
|
||||||
|
g.PaddingBottom = 0
|
||||||
|
g.BarColor = ui.ColorGreen
|
||||||
|
g.Label = "-"
|
||||||
|
return &GaugeCol{g}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *GaugeCol) Reset() {
|
||||||
|
w.Label = "-"
|
||||||
|
w.Percent = 0
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
package compact
|
package compact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bcicen/ctop/cwidgets"
|
|
||||||
ui "github.com/gizak/termui"
|
ui "github.com/gizak/termui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CompactGrid struct {
|
type CompactGrid struct {
|
||||||
ui.GridBufferer
|
ui.GridBufferer
|
||||||
Rows []cwidgets.ContainerWidgets
|
Rows []*Compact // rows to render
|
||||||
X, Y int
|
X, Y int
|
||||||
Width int
|
Width int
|
||||||
Height int
|
Height int
|
||||||
@ -21,31 +20,37 @@ func NewCompactGrid() *CompactGrid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CompactGrid) Align() {
|
func (cg *CompactGrid) Align() {
|
||||||
// Update y recursively
|
// Update y recursively
|
||||||
c.header.SetY(c.Y)
|
cg.header.SetY(cg.Y)
|
||||||
y := c.Y + 1
|
y := cg.Y + 1
|
||||||
for n, r := range c.Rows {
|
for n, r := range cg.Rows {
|
||||||
r.SetY(y + n)
|
r.SetY(y + n)
|
||||||
}
|
}
|
||||||
// Update width recursively
|
// Update width recursively
|
||||||
c.header.SetWidth(c.Width)
|
cg.header.SetWidth(cg.Width)
|
||||||
for _, r := range c.Rows {
|
for _, r := range cg.Rows {
|
||||||
r.SetWidth(c.Width)
|
r.SetWidth(cg.Width)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CompactGrid) Clear() { c.Rows = []cwidgets.ContainerWidgets{} }
|
func (cg *CompactGrid) Clear() { cg.Rows = []*Compact{} }
|
||||||
func (c *CompactGrid) GetHeight() int { return len(c.Rows) }
|
func (cg *CompactGrid) GetHeight() int { return len(cg.Rows) }
|
||||||
func (c *CompactGrid) SetX(x int) { c.X = x }
|
func (cg *CompactGrid) SetX(x int) { cg.X = x }
|
||||||
func (c *CompactGrid) SetY(y int) { c.Y = y }
|
func (cg *CompactGrid) SetY(y int) { cg.Y = y }
|
||||||
func (c *CompactGrid) SetWidth(w int) { c.Width = w }
|
func (cg *CompactGrid) SetWidth(w int) { cg.Width = w }
|
||||||
|
|
||||||
func (c *CompactGrid) Buffer() ui.Buffer {
|
func (cg *CompactGrid) Buffer() ui.Buffer {
|
||||||
buf := ui.NewBuffer()
|
buf := ui.NewBuffer()
|
||||||
buf.Merge(c.header.Buffer())
|
buf.Merge(cg.header.Buffer())
|
||||||
for _, r := range c.Rows {
|
for _, r := range cg.Rows {
|
||||||
buf.Merge(r.Buffer())
|
buf.Merge(r.Buffer())
|
||||||
}
|
}
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cg *CompactGrid) AddRows(rows ...*Compact) {
|
||||||
|
for _, r := range rows {
|
||||||
|
cg.Rows = append(cg.Rows, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,50 +5,53 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/bcicen/ctop/cwidgets"
|
"github.com/bcicen/ctop/cwidgets"
|
||||||
|
"github.com/bcicen/ctop/logging"
|
||||||
|
"github.com/bcicen/ctop/metrics"
|
||||||
ui "github.com/gizak/termui"
|
ui "github.com/gizak/termui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var log = logging.Init()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
mark = string('\u25C9')
|
colSpacing = 1
|
||||||
vBar = string('\u25AE')
|
|
||||||
colSpacing = 1
|
|
||||||
statusWidth = 3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Compact struct {
|
type Compact struct {
|
||||||
Status *ui.Par
|
Status *Status
|
||||||
Name *ui.Par
|
Name *TextCol
|
||||||
Cid *ui.Par
|
Cid *TextCol
|
||||||
Cpu *ui.Gauge
|
Cpu *GaugeCol
|
||||||
Memory *ui.Gauge
|
Memory *GaugeCol
|
||||||
Net *ui.Par
|
Net *TextCol
|
||||||
X, Y int
|
X, Y int
|
||||||
Width int
|
Width int
|
||||||
Height int
|
Height int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCompact(id, name, status string) *Compact {
|
func NewCompact(id, name string) *Compact {
|
||||||
row := &Compact{
|
row := &Compact{
|
||||||
Status: slimPar(mark),
|
Status: NewStatus(),
|
||||||
Name: slimPar(name),
|
Name: NewTextCol(name),
|
||||||
Cid: slimPar(id),
|
Cid: NewTextCol(id),
|
||||||
Cpu: slimGauge(),
|
Cpu: NewGaugeCol(),
|
||||||
Memory: slimGauge(),
|
Memory: NewGaugeCol(),
|
||||||
Net: slimPar("-"),
|
Net: NewTextCol("-"),
|
||||||
Height: 1,
|
Height: 1,
|
||||||
}
|
}
|
||||||
row.Reset()
|
|
||||||
row.SetStatus(status)
|
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (row *Compact) SetMetrics(m metrics.Metrics) {
|
||||||
|
row.SetCPU(m.CPUUtil)
|
||||||
|
row.SetNet(m.NetRx, m.NetTx)
|
||||||
|
row.SetMem(m.MemUsage, m.MemLimit, m.MemPercent)
|
||||||
|
}
|
||||||
|
|
||||||
// Set gauges, counters to default unread values
|
// Set gauges, counters to default unread values
|
||||||
func (row *Compact) Reset() {
|
func (row *Compact) Reset() {
|
||||||
row.Cpu.Percent = 0
|
row.Cpu.Reset()
|
||||||
row.Cpu.Label = "-"
|
row.Memory.Reset()
|
||||||
row.Memory.Percent = 0
|
row.Net.Reset()
|
||||||
row.Memory.Label = "-"
|
|
||||||
row.Net.Text = "-"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (row *Compact) all() []ui.GridBufferer {
|
func (row *Compact) all() []ui.GridBufferer {
|
||||||
@ -115,24 +118,9 @@ func (row *Compact) UnHighlight() {
|
|||||||
row.Name.TextBgColor = ui.ColorDefault
|
row.Name.TextBgColor = ui.ColorDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
func (row *Compact) SetStatus(val string) {
|
func (row *Compact) SetNet(rx int64, tx int64) {
|
||||||
switch val {
|
label := fmt.Sprintf("%s / %s", cwidgets.ByteFormat(rx), cwidgets.ByteFormat(tx))
|
||||||
case "running":
|
row.Net.Set(label)
|
||||||
row.Status.Text = mark
|
|
||||||
row.Status.TextFgColor = ui.ColorGreen
|
|
||||||
case "exited":
|
|
||||||
row.Status.Text = mark
|
|
||||||
row.Status.TextFgColor = ui.ColorRed
|
|
||||||
case "paused":
|
|
||||||
row.Status.Text = fmt.Sprintf("%s%s", vBar, vBar)
|
|
||||||
row.Status.TextFgColor = ui.ColorDefault
|
|
||||||
case "created":
|
|
||||||
row.Status.Text = mark
|
|
||||||
row.Status.TextFgColor = ui.ColorDefault
|
|
||||||
default:
|
|
||||||
row.Status.Text = mark
|
|
||||||
row.Status.TextFgColor = ui.ColorRed
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (row *Compact) SetCPU(val int) {
|
func (row *Compact) SetCPU(val int) {
|
||||||
@ -145,10 +133,6 @@ func (row *Compact) SetCPU(val int) {
|
|||||||
row.Cpu.Percent = val
|
row.Cpu.Percent = val
|
||||||
}
|
}
|
||||||
|
|
||||||
func (row *Compact) SetNet(rx int64, tx int64) {
|
|
||||||
row.Net.Text = fmt.Sprintf("%s / %s", cwidgets.ByteFormat(rx), cwidgets.ByteFormat(tx))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (row *Compact) SetMem(val int64, limit int64, percent int) {
|
func (row *Compact) SetMem(val int64, limit int64, percent int) {
|
||||||
row.Memory.Label = fmt.Sprintf("%s / %s", cwidgets.ByteFormat(val), cwidgets.ByteFormat(limit))
|
row.Memory.Label = fmt.Sprintf("%s / %s", cwidgets.ByteFormat(val), cwidgets.ByteFormat(limit))
|
||||||
if percent < 5 {
|
if percent < 5 {
|
||||||
|
63
cwidgets/compact/text.go
Normal file
63
cwidgets/compact/text.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package compact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
ui "github.com/gizak/termui"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mark = string('\u25C9')
|
||||||
|
vBar = string('\u25AE')
|
||||||
|
statusWidth = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type TextCol struct {
|
||||||
|
*ui.Par
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTextCol(s string) *TextCol {
|
||||||
|
p := ui.NewPar(s)
|
||||||
|
p.Border = false
|
||||||
|
p.Height = 1
|
||||||
|
p.Width = 20
|
||||||
|
return &TextCol{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TextCol) Reset() {
|
||||||
|
w.Text = "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TextCol) Set(s string) {
|
||||||
|
w.Text = s
|
||||||
|
}
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
*ui.Par
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStatus() *Status {
|
||||||
|
p := ui.NewPar(mark)
|
||||||
|
p.Border = false
|
||||||
|
p.Height = 1
|
||||||
|
p.Width = statusWidth
|
||||||
|
return &Status{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Status) Set(val string) {
|
||||||
|
// defaults
|
||||||
|
text := mark
|
||||||
|
color := ui.ColorDefault
|
||||||
|
|
||||||
|
switch val {
|
||||||
|
case "running":
|
||||||
|
color = ui.ColorGreen
|
||||||
|
case "exited":
|
||||||
|
color = ui.ColorRed
|
||||||
|
case "paused":
|
||||||
|
text = fmt.Sprintf("%s%s", vBar, vBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Text = text
|
||||||
|
s.TextFgColor = color
|
||||||
|
}
|
@ -14,32 +14,14 @@ func calcWidth(width, items int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func slimHeaderPar(s string) *ui.Par {
|
func slimHeaderPar(s string) *ui.Par {
|
||||||
p := slimPar(s)
|
p := ui.NewPar(s)
|
||||||
p.Y = 2
|
p.Y = 2
|
||||||
p.Height = 2
|
p.Height = 2
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func slimPar(s string) *ui.Par {
|
|
||||||
p := ui.NewPar(s)
|
|
||||||
p.Border = false
|
|
||||||
p.Height = 1
|
|
||||||
p.Width = 20
|
p.Width = 20
|
||||||
p.TextFgColor = ui.ColorWhite
|
p.Border = false
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func slimGauge() *ui.Gauge {
|
|
||||||
g := ui.NewGauge()
|
|
||||||
g.Height = 1
|
|
||||||
g.Border = false
|
|
||||||
g.Percent = 0
|
|
||||||
g.PaddingBottom = 0
|
|
||||||
g.BarColor = ui.ColorGreen
|
|
||||||
g.Label = "-"
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
func centerParText(p *ui.Par) {
|
func centerParText(p *ui.Par) {
|
||||||
var text string
|
var text string
|
||||||
var padding string
|
var padding string
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
var log = logging.Init()
|
var log = logging.Init()
|
||||||
|
|
||||||
type ContainerWidgets interface {
|
type ContainerWidgets interface {
|
||||||
Reset()
|
|
||||||
Buffer() ui.Buffer
|
Buffer() ui.Buffer
|
||||||
Highlight()
|
Highlight()
|
||||||
UnHighlight()
|
UnHighlight()
|
||||||
|
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -20,7 +21,6 @@ type ContainerSource interface {
|
|||||||
type DockerContainerSource struct {
|
type DockerContainerSource struct {
|
||||||
client *docker.Client
|
client *docker.Client
|
||||||
containers Containers
|
containers Containers
|
||||||
collectors map[string]metrics.Collector
|
|
||||||
needsRefresh map[string]int // container IDs requiring refresh
|
needsRefresh map[string]int // container IDs requiring refresh
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +32,6 @@ func NewDockerContainerSource() *DockerContainerSource {
|
|||||||
}
|
}
|
||||||
cm := &DockerContainerSource{
|
cm := &DockerContainerSource{
|
||||||
client: client,
|
client: client,
|
||||||
collectors: make(map[string]metrics.Collector),
|
|
||||||
needsRefresh: make(map[string]int),
|
needsRefresh: make(map[string]int),
|
||||||
}
|
}
|
||||||
cm.refreshAll()
|
cm.refreshAll()
|
||||||
@ -73,27 +72,16 @@ func (cm *DockerContainerSource) refresh(id string) {
|
|||||||
c, ok := cm.Get(id)
|
c, ok := cm.Get(id)
|
||||||
// append container struct for new containers
|
// append container struct for new containers
|
||||||
if !ok {
|
if !ok {
|
||||||
c = NewContainer(id, insp.Name)
|
// create collector
|
||||||
|
collector := metrics.NewDocker(cm.client, id)
|
||||||
|
// create container
|
||||||
|
c = NewContainer(shortID(id), shortName(insp.Name), collector)
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
cm.containers = append(cm.containers, c)
|
cm.containers = append(cm.containers, c)
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
// create collector
|
|
||||||
if _, ok := cm.collectors[id]; ok == false {
|
|
||||||
cm.collectors[id] = metrics.NewDocker(cm.client, id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.SetState(insp.State.Status)
|
c.SetState(insp.State.Status)
|
||||||
|
|
||||||
// start collector if needed
|
|
||||||
if c.state == "running" && !cm.collectors[c.id].Running() {
|
|
||||||
cm.collectors[c.id].Start()
|
|
||||||
c.Read(cm.collectors[c.id].Stream())
|
|
||||||
}
|
|
||||||
// stop collector if needed
|
|
||||||
if c.state != "running" && cm.collectors[c.id].Running() {
|
|
||||||
cm.collectors[c.id].Stop()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *DockerContainerSource) inspect(id string) *docker.Container {
|
func (cm *DockerContainerSource) inspect(id string) *docker.Container {
|
||||||
@ -140,7 +128,7 @@ func (cm *DockerContainerSource) Loop() {
|
|||||||
// Get a single container, by ID
|
// Get a single container, by ID
|
||||||
func (cm *DockerContainerSource) Get(id string) (*Container, bool) {
|
func (cm *DockerContainerSource) Get(id string) (*Container, bool) {
|
||||||
for _, c := range cm.containers {
|
for _, c := range cm.containers {
|
||||||
if c.id == id {
|
if c.Id == id {
|
||||||
return c, true
|
return c, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,7 +138,7 @@ func (cm *DockerContainerSource) Get(id string) (*Container, bool) {
|
|||||||
// Remove containers by ID
|
// Remove containers by ID
|
||||||
func (cm *DockerContainerSource) delByID(id string) {
|
func (cm *DockerContainerSource) delByID(id string) {
|
||||||
for n, c := range cm.containers {
|
for n, c := range cm.containers {
|
||||||
if c.id == id {
|
if c.Id == id {
|
||||||
cm.del(n)
|
cm.del(n)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -172,3 +160,13 @@ func (cm *DockerContainerSource) All() []*Container {
|
|||||||
sort.Sort(cm.containers)
|
sort.Sort(cm.containers)
|
||||||
return cm.containers
|
return cm.containers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// truncate container id
|
||||||
|
func shortID(id string) string {
|
||||||
|
return id[:12]
|
||||||
|
}
|
||||||
|
|
||||||
|
// use primary container name
|
||||||
|
func shortName(name string) string {
|
||||||
|
return strings.Replace(name, "/", "", 1)
|
||||||
|
}
|
||||||
|
29
grid.go
29
grid.go
@ -35,15 +35,15 @@ func NewGrid() *Grid {
|
|||||||
// Set an initial cursor position, if possible
|
// Set an initial cursor position, if possible
|
||||||
func (g *Grid) cursorReset() {
|
func (g *Grid) cursorReset() {
|
||||||
if len(g.containers) > 0 {
|
if len(g.containers) > 0 {
|
||||||
g.cursorID = g.containers[0].id
|
g.cursorID = g.containers[0].Id
|
||||||
g.containers[0].widgets.Highlight()
|
g.containers[0].Widgets.Highlight()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return current cursor index
|
// Return current cursor index
|
||||||
func (g *Grid) cursorIdx() int {
|
func (g *Grid) cursorIdx() int {
|
||||||
for n, c := range g.containers {
|
for n, c := range g.containers {
|
||||||
if c.id == g.cursorID {
|
if c.Id == g.cursorID {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,9 +59,9 @@ func (g *Grid) cursorUp() {
|
|||||||
active := g.containers[idx]
|
active := g.containers[idx]
|
||||||
next := g.containers[idx-1]
|
next := g.containers[idx-1]
|
||||||
|
|
||||||
active.widgets.UnHighlight()
|
active.Widgets.UnHighlight()
|
||||||
g.cursorID = next.id
|
g.cursorID = next.Id
|
||||||
next.widgets.Highlight()
|
next.Widgets.Highlight()
|
||||||
|
|
||||||
ui.Render(cGrid)
|
ui.Render(cGrid)
|
||||||
}
|
}
|
||||||
@ -78,9 +78,9 @@ func (g *Grid) cursorDown() {
|
|||||||
active := g.containers[idx]
|
active := g.containers[idx]
|
||||||
next := g.containers[idx+1]
|
next := g.containers[idx+1]
|
||||||
|
|
||||||
active.widgets.UnHighlight()
|
active.Widgets.UnHighlight()
|
||||||
g.cursorID = next.id
|
g.cursorID = next.Id
|
||||||
next.widgets.Highlight()
|
next.Widgets.Highlight()
|
||||||
ui.Render(cGrid)
|
ui.Render(cGrid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +93,6 @@ func (g *Grid) redrawRows() {
|
|||||||
if config.GetSwitchVal("enableHeader") {
|
if config.GetSwitchVal("enableHeader") {
|
||||||
g.header.SetCount(len(g.containers))
|
g.header.SetCount(len(g.containers))
|
||||||
g.header.SetFilter(config.GetVal("filterStr"))
|
g.header.SetFilter(config.GetVal("filterStr"))
|
||||||
g.header.Render()
|
|
||||||
y += g.header.Height()
|
y += g.header.Height()
|
||||||
}
|
}
|
||||||
cGrid.SetY(y)
|
cGrid.SetY(y)
|
||||||
@ -104,8 +103,8 @@ func (g *Grid) redrawRows() {
|
|||||||
if n >= max {
|
if n >= max {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
cGrid.Rows = append(cGrid.Rows, c.widgets)
|
cGrid.AddRows(c.Widgets)
|
||||||
if c.id == g.cursorID {
|
if c.Id == g.cursorID {
|
||||||
cursorVisible = true
|
cursorVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,9 +124,9 @@ func (g *Grid) redrawRows() {
|
|||||||
// Log current container and widget state
|
// Log current container and widget state
|
||||||
func (g *Grid) dumpContainer() {
|
func (g *Grid) dumpContainer() {
|
||||||
c, _ := g.cSource.Get(g.cursorID)
|
c, _ := g.cSource.Get(g.cursorID)
|
||||||
msg := fmt.Sprintf("logging state for container: %s\n", c.ShortID())
|
msg := fmt.Sprintf("logging state for container: %s\n", c.Id)
|
||||||
msg += fmt.Sprintf("id = %s\nname = %s\nstate = %s\n", c.id, c.name, c.state)
|
msg += fmt.Sprintf("Id = %s\nname = %s\nstate = %s\n", c.Id, c.Name, c.State)
|
||||||
msg += inspect(&c.metrics)
|
msg += inspect(&c.Metrics)
|
||||||
log.Infof(msg)
|
log.Infof(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
main.go
10
main.go
@ -1,6 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/bcicen/ctop/config"
|
"github.com/bcicen/ctop/config"
|
||||||
"github.com/bcicen/ctop/logging"
|
"github.com/bcicen/ctop/logging"
|
||||||
ui "github.com/gizak/termui"
|
ui "github.com/gizak/termui"
|
||||||
@ -9,6 +12,13 @@ import (
|
|||||||
var log *logging.CTopLogger
|
var log *logging.CTopLogger
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ui.Clear()
|
||||||
|
fmt.Printf("panic: %s", r)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
config.Init()
|
config.Init()
|
||||||
log = logging.Init()
|
log = logging.Init()
|
||||||
if config.GetSwitchVal("loggingEnabled") {
|
if config.GetSwitchVal("loggingEnabled") {
|
||||||
|
@ -13,13 +13,10 @@ import (
|
|||||||
|
|
||||||
type MockContainerSource struct {
|
type MockContainerSource struct {
|
||||||
containers Containers
|
containers Containers
|
||||||
collectors map[string]metrics.Collector
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMockContainerSource() *MockContainerSource {
|
func NewMockContainerSource() *MockContainerSource {
|
||||||
cs := &MockContainerSource{
|
cs := &MockContainerSource{}
|
||||||
collectors: make(map[string]metrics.Collector),
|
|
||||||
}
|
|
||||||
cs.Init()
|
cs.Init()
|
||||||
go cs.Loop()
|
go cs.Loop()
|
||||||
return cs
|
return cs
|
||||||
@ -27,15 +24,15 @@ func NewMockContainerSource() *MockContainerSource {
|
|||||||
|
|
||||||
// Create Mock containers
|
// Create Mock containers
|
||||||
func (cs *MockContainerSource) Init() {
|
func (cs *MockContainerSource) Init() {
|
||||||
total := 10
|
total := 40
|
||||||
rand.Seed(int64(time.Now().Nanosecond()))
|
rand.Seed(int64(time.Now().Nanosecond()))
|
||||||
|
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
c := NewContainer(makeID(), makeName())
|
collector := metrics.NewMock()
|
||||||
|
c := NewContainer(makeID(), makeName(), collector)
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
cs.containers = append(cs.containers, c)
|
cs.containers = append(cs.containers, c)
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
cs.collectors[c.id] = metrics.NewMock()
|
|
||||||
|
|
||||||
c.SetState(makeState())
|
c.SetState(makeState())
|
||||||
}
|
}
|
||||||
@ -45,26 +42,10 @@ func (cs *MockContainerSource) Init() {
|
|||||||
func (cs *MockContainerSource) Loop() {
|
func (cs *MockContainerSource) Loop() {
|
||||||
iter := 0
|
iter := 0
|
||||||
for {
|
for {
|
||||||
for _, c := range cs.containers {
|
// Change state for random container
|
||||||
// Change state for random container
|
if iter%5 == 0 {
|
||||||
if iter%5 == 0 {
|
randC := cs.containers[rand.Intn(len(cs.containers))]
|
||||||
randC := cs.containers[rand.Intn(len(cs.containers))]
|
randC.SetState(makeState())
|
||||||
randC.SetState(makeState())
|
|
||||||
}
|
|
||||||
|
|
||||||
isCollecting := cs.collectors[c.id].Running()
|
|
||||||
//log.Infof("id=%s state=%s collector=%t", c.id, c.state, isCollecting)
|
|
||||||
|
|
||||||
// start collector if needed
|
|
||||||
if c.state == "running" && !isCollecting {
|
|
||||||
cs.collectors[c.id].Start()
|
|
||||||
c.Read(cs.collectors[c.id].Stream())
|
|
||||||
}
|
|
||||||
// stop collector if needed
|
|
||||||
if c.state != "running" && isCollecting {
|
|
||||||
cs.collectors[c.id].Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
iter++
|
iter++
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
@ -74,7 +55,7 @@ func (cs *MockContainerSource) Loop() {
|
|||||||
// Get a single container, by ID
|
// Get a single container, by ID
|
||||||
func (cs *MockContainerSource) Get(id string) (*Container, bool) {
|
func (cs *MockContainerSource) Get(id string) (*Container, bool) {
|
||||||
for _, c := range cs.containers {
|
for _, c := range cs.containers {
|
||||||
if c.id == id {
|
if c.Id == id {
|
||||||
return c, true
|
return c, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,7 +65,7 @@ func (cs *MockContainerSource) Get(id string) (*Container, bool) {
|
|||||||
// Remove containers by ID
|
// Remove containers by ID
|
||||||
func (cs *MockContainerSource) delByID(id string) {
|
func (cs *MockContainerSource) delByID(id string) {
|
||||||
for n, c := range cs.containers {
|
for n, c := range cs.containers {
|
||||||
if c.id == id {
|
if c.Id == id {
|
||||||
cs.del(n)
|
cs.del(n)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -112,7 +93,7 @@ func makeID() string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return strings.Replace(u.String(), "-", "", -1)
|
return strings.Replace(u.String(), "-", "", -1)[:12]
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeName() string {
|
func makeName() string {
|
||||||
|
26
sort.go
26
sort.go
@ -16,32 +16,32 @@ var stateMap = map[string]int{
|
|||||||
"created": 0,
|
"created": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
var idSorter = func(c1, c2 *Container) bool { return c1.id < c2.id }
|
var idSorter = func(c1, c2 *Container) bool { return c1.Id < c2.Id }
|
||||||
var nameSorter = func(c1, c2 *Container) bool { return c1.name < c2.name }
|
var nameSorter = func(c1, c2 *Container) bool { return c1.Name < c2.Name }
|
||||||
|
|
||||||
var Sorters = map[string]sortMethod{
|
var Sorters = map[string]sortMethod{
|
||||||
"id": idSorter,
|
"id": idSorter,
|
||||||
"name": nameSorter,
|
"name": nameSorter,
|
||||||
"cpu": func(c1, c2 *Container) bool {
|
"cpu": func(c1, c2 *Container) bool {
|
||||||
// Use secondary sort method if equal values
|
// Use secondary sort method if equal values
|
||||||
if c1.metrics.CPUUtil == c2.metrics.CPUUtil {
|
if c1.CPUUtil == c2.CPUUtil {
|
||||||
return nameSorter(c1, c2)
|
return nameSorter(c1, c2)
|
||||||
}
|
}
|
||||||
return c1.metrics.CPUUtil > c2.metrics.CPUUtil
|
return c1.CPUUtil > c2.CPUUtil
|
||||||
},
|
},
|
||||||
"mem": func(c1, c2 *Container) bool {
|
"mem": func(c1, c2 *Container) bool {
|
||||||
// Use secondary sort method if equal values
|
// Use secondary sort method if equal values
|
||||||
if c1.metrics.MemUsage == c2.metrics.MemUsage {
|
if c1.MemUsage == c2.MemUsage {
|
||||||
return nameSorter(c1, c2)
|
return nameSorter(c1, c2)
|
||||||
}
|
}
|
||||||
return c1.metrics.MemUsage > c2.metrics.MemUsage
|
return c1.MemUsage > c2.MemUsage
|
||||||
},
|
},
|
||||||
"mem %": func(c1, c2 *Container) bool {
|
"mem %": func(c1, c2 *Container) bool {
|
||||||
// Use secondary sort method if equal values
|
// Use secondary sort method if equal values
|
||||||
if c1.metrics.MemPercent == c2.metrics.MemPercent {
|
if c1.MemPercent == c2.MemPercent {
|
||||||
return nameSorter(c1, c2)
|
return nameSorter(c1, c2)
|
||||||
}
|
}
|
||||||
return c1.metrics.MemPercent > c2.metrics.MemPercent
|
return c1.MemPercent > c2.MemPercent
|
||||||
},
|
},
|
||||||
"net": func(c1, c2 *Container) bool {
|
"net": func(c1, c2 *Container) bool {
|
||||||
sum1 := sumNet(c1)
|
sum1 := sumNet(c1)
|
||||||
@ -54,10 +54,10 @@ var Sorters = map[string]sortMethod{
|
|||||||
},
|
},
|
||||||
"state": func(c1, c2 *Container) bool {
|
"state": func(c1, c2 *Container) bool {
|
||||||
// Use secondary sort method if equal values
|
// Use secondary sort method if equal values
|
||||||
if c1.state == c2.state {
|
if c1.State == c2.State {
|
||||||
return nameSorter(c1, c2)
|
return nameSorter(c1, c2)
|
||||||
}
|
}
|
||||||
return stateMap[c1.state] > stateMap[c2.state]
|
return stateMap[c1.State] > stateMap[c2.State]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,11 +86,11 @@ func (a Containers) Filter() (filtered []*Container) {
|
|||||||
|
|
||||||
for _, c := range a {
|
for _, c := range a {
|
||||||
// Apply name filter
|
// Apply name filter
|
||||||
if re.FindAllString(c.name, 1) == nil {
|
if re.FindAllString(c.Name, 1) == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Apply state filter
|
// Apply state filter
|
||||||
if !config.GetSwitchVal("allContainers") && c.state != "running" {
|
if !config.GetSwitchVal("allContainers") && c.State != "running" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filtered = append(filtered, c)
|
filtered = append(filtered, c)
|
||||||
@ -99,4 +99,4 @@ func (a Containers) Filter() (filtered []*Container) {
|
|||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
func sumNet(c *Container) int64 { return c.metrics.NetRx + c.metrics.NetTx }
|
func sumNet(c *Container) int64 { return c.NetRx + c.NetTx }
|
||||||
|
Loading…
Reference in New Issue
Block a user