further containermap refactoring

This commit is contained in:
Bradley Cicenas 2017-02-24 09:10:14 +00:00
parent cb9f38adb3
commit c3bb7c6b96
5 changed files with 89 additions and 62 deletions

View File

@ -20,7 +20,7 @@ func NewContainer(id, name string) *Container {
id: id, id: id,
name: name, name: name,
} }
c.Collapse() c.widgets = widgets.NewCompact(c.ShortID(), c.ShortName(), c.state)
return c return c
} }
@ -33,11 +33,12 @@ func (c *Container) ShortName() string {
} }
func (c *Container) Expand() { func (c *Container) Expand() {
c.widgets = widgets.NewExpanded(c.id, c.name) var curWidgets widgets.ContainerWidgets
}
func (c *Container) Collapse() { curWidgets = c.widgets
c.widgets = widgets.NewCompact(c.ShortID(), c.ShortName()) c.widgets = widgets.NewExpanded(c.ShortID(), c.ShortName())
c.widgets.Render()
c.widgets = curWidgets
} }
func (c *Container) SetState(s string) { func (c *Container) SetState(s string) {
@ -45,6 +46,12 @@ func (c *Container) SetState(s string) {
c.widgets.SetStatus(s) c.widgets.SetStatus(s)
} }
// Set metrics to zero state, clear widget gauges
func (c *Container) reset() {
c.metrics = metrics.Metrics{}
c.widgets.Reset()
}
// 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() {
@ -55,6 +62,7 @@ func (c *Container) Read(stream chan metrics.Metrics) {
c.widgets.SetNet(metrics.NetRx, metrics.NetTx) 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()
}() }()
log.Infof("reader started for container: %s", c.id) log.Infof("reader started for container: %s", c.id)
} }

View File

@ -2,12 +2,16 @@ package main
import ( import (
"sort" "sort"
"sync"
"time"
"github.com/bcicen/ctop/config" "github.com/bcicen/ctop/config"
"github.com/bcicen/ctop/metrics" "github.com/bcicen/ctop/metrics"
"github.com/fsouza/go-dockerclient" "github.com/fsouza/go-dockerclient"
) )
var lock = sync.RWMutex{}
type ContainerMap struct { type ContainerMap struct {
client *docker.Client client *docker.Client
containers Containers containers Containers
@ -27,32 +31,29 @@ func NewContainerMap() *ContainerMap {
needsRefresh: make(map[string]int), needsRefresh: make(map[string]int),
} }
cm.refreshAll() cm.refreshAll()
go cm.watch() go cm.UpdateLoop()
go cm.watchEvents()
return cm return cm
} }
// Docker events watcher // Docker events watcher
func (cm *ContainerMap) watch() { func (cm *ContainerMap) watchEvents() {
log.Info("docker event listener starting") log.Info("docker event listener starting")
events := make(chan *docker.APIEvents) events := make(chan *docker.APIEvents)
cm.client.AddEventListener(events) cm.client.AddEventListener(events)
for e := range events { for e := range events {
cm.handleEvent(e) if e.Type != "container" {
} continue
} }
switch e.Action {
// Docker event handler case "start", "die", "pause", "unpause":
func (cm *ContainerMap) handleEvent(e *docker.APIEvents) { log.Debugf("handling docker event: action=%s id=%s", e.Action, e.ID)
// only process container events cm.needsRefresh[e.ID] = 1
if e.Type != "container" { case "destroy":
return log.Debugf("handling docker event: action=%s id=%s", e.Action, e.ID)
} cm.DelByID(e.ID)
switch e.Action { }
case "start", "die", "pause", "unpause":
cm.needsRefresh[e.ID] = 1
case "destroy":
cm.DelByID(e.ID)
} }
} }
@ -67,12 +68,10 @@ func (cm *ContainerMap) 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 = &Container{ c = NewContainer(id, insp.Name)
id: id, lock.Lock()
name: insp.Name,
}
c.Collapse()
cm.containers = append(cm.containers, c) cm.containers = append(cm.containers, c)
lock.Unlock()
// create collector // create collector
if _, ok := cm.collectors[id]; ok == false { if _, ok := cm.collectors[id]; ok == false {
cm.collectors[id] = metrics.NewDocker(cm.client, id) cm.collectors[id] = metrics.NewDocker(cm.client, id)
@ -86,6 +85,10 @@ func (cm *ContainerMap) refresh(id string) {
cm.collectors[c.id].Start() cm.collectors[c.id].Start()
c.Read(cm.collectors[c.id].Stream()) 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 *ContainerMap) inspect(id string) *docker.Container { func (cm *ContainerMap) inspect(id string) *docker.Container {
@ -108,29 +111,26 @@ func (cm *ContainerMap) refreshAll() {
for _, c := range allContainers { for _, c := range allContainers {
cm.needsRefresh[c.ID] = 1 cm.needsRefresh[c.ID] = 1
} }
cm.Update()
} }
func (cm *ContainerMap) Update() { func (cm *ContainerMap) UpdateLoop() {
var ids []string for {
for id, _ := range cm.needsRefresh { switch {
cm.refresh(id) case len(cm.needsRefresh) > 0:
ids = append(ids, id) processed := []string{}
} for id, _ := range cm.needsRefresh {
for _, id := range ids { cm.refresh(id)
delete(cm.needsRefresh, id) processed = append(processed, id)
}
for _, id := range processed {
delete(cm.needsRefresh, id)
}
default:
time.Sleep(1 * time.Second)
}
} }
} }
// Kill a container by ID
//func (cm *ContainerMap) Kill(id string, sig docker.Signal) error {
//opts := docker.KillContainerOptions{
//ID: id,
//Signal: sig,
//}
//return cm.client.KillContainer(opts)
//}
// Return number of containers/rows // Return number of containers/rows
func (cm *ContainerMap) Len() uint { func (cm *ContainerMap) Len() uint {
return uint(len(cm.containers)) return uint(len(cm.containers))
@ -158,9 +158,12 @@ func (cm *ContainerMap) DelByID(id string) {
// Remove one or more containers by index // Remove one or more containers by index
func (cm *ContainerMap) Del(idx ...int) { func (cm *ContainerMap) Del(idx ...int) {
lock.Lock()
defer lock.Unlock()
for _, i := range idx { for _, i := range idx {
cm.containers = append(cm.containers[:i], cm.containers[i+1:]...) cm.containers = append(cm.containers[:i], cm.containers[i+1:]...)
} }
log.Infof("removed %d dead containers", len(idx))
} }
// Return array of all containers, sorted by field // Return array of all containers, sorted by field

30
grid.go
View File

@ -23,11 +23,6 @@ func NewGrid() *Grid {
containers: cmap.All(), containers: cmap.All(),
header: widgets.NewCTopHeader(), header: widgets.NewCTopHeader(),
} }
// set initial cursor position
if len(g.containers) > 0 {
g.cursorID = g.containers[0].id
g.containers[0].widgets.Highlight()
}
return g return g
} }
@ -35,6 +30,14 @@ func (g *Grid) calcMaxRows() {
g.maxRows = ui.TermHeight() - widgets.CompactHeader.Height - ui.Body.Y g.maxRows = ui.TermHeight() - widgets.CompactHeader.Height - ui.Body.Y
} }
// Set an initial cursor position, if possible
func (g *Grid) cursorReset() {
if len(g.containers) > 0 {
g.cursorID = g.containers[0].id
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 {
@ -94,14 +97,24 @@ func (g *Grid) redrawRows() {
} else { } else {
ui.Body.Y = 0 ui.Body.Y = 0
} }
ui.Body.AddRows(widgets.CompactHeader) ui.Body.AddRows(widgets.CompactHeader)
var cursorVisible bool
for n, c := range g.containers.Filter() { for n, c := range g.containers.Filter() {
if n >= g.maxRows { if n >= g.maxRows {
break break
} }
if c.id == g.cursorID {
cursorVisible = true
}
ui.Body.AddRows(c.widgets.Row()) ui.Body.AddRows(c.widgets.Row())
} }
if !cursorVisible {
g.cursorReset()
}
ui.Body.Align() ui.Body.Align()
resizeIndicator() resizeIndicator()
ui.Render(ui.Body) ui.Render(ui.Body)
@ -137,8 +150,6 @@ func (g *Grid) ExpandView() {
defer ui.DefaultEvtStream.ResetHandlers() defer ui.DefaultEvtStream.ResetHandlers()
container, _ := g.cmap.Get(g.cursorID) container, _ := g.cmap.Get(g.cursorID)
container.Expand() container.Expand()
container.widgets.Render()
container.Collapse()
} }
func logEvent(e ui.Event) { func logEvent(e ui.Event) {
@ -153,7 +164,6 @@ func logEvent(e ui.Event) {
func Display(g *Grid) bool { func Display(g *Grid) bool {
var menu func() var menu func()
var expand bool var expand bool
var loopIter int
ui.DefaultEvtStream.Hook(logEvent) ui.DefaultEvtStream.Hook(logEvent)
@ -199,10 +209,6 @@ func Display(g *Grid) bool {
}) })
ui.Handle("/timer/1s", func(e ui.Event) { ui.Handle("/timer/1s", func(e ui.Event) {
loopIter++
if loopIter%5 == 0 {
g.cmap.Update()
}
g.containers = g.cmap.All() // refresh containers for current sort order g.containers = g.cmap.All() // refresh containers for current sort order
g.redrawRows() g.redrawRows()
}) })

View File

@ -15,6 +15,7 @@ const (
type ContainerWidgets interface { type ContainerWidgets interface {
Row() *ui.Row Row() *ui.Row
Render() Render()
Reset()
Highlight() Highlight()
UnHighlight() UnHighlight()
SetStatus(string) SetStatus(string)
@ -42,15 +43,22 @@ type Compact struct {
row *ui.Row row *ui.Row
} }
func NewCompact(id string, name string) *Compact { func NewCompact(id, name, status string) *Compact {
w := &Compact{ w := &Compact{
Status: slimPar(mark), Status: slimPar(mark),
Cid: slimPar(id), Cid: slimPar(id),
Net: slimPar("-"),
Name: slimPar(name), Name: slimPar(name),
Cpu: slimGauge(),
Memory: slimGauge(),
} }
w.Reset()
w.SetStatus(status)
return w
}
// Set gauges, counters to default unread values
func (w *Compact) Reset() {
w.Net = slimPar("-")
w.Cpu = slimGauge()
w.Memory = slimGauge()
w.row = ui.NewRow( w.row = ui.NewRow(
ui.NewCol(1, 0, w.Status), ui.NewCol(1, 0, w.Status),
ui.NewCol(2, 0, w.Name), ui.NewCol(2, 0, w.Name),
@ -59,7 +67,6 @@ func NewCompact(id string, name string) *Compact {
ui.NewCol(2, 0, w.Memory), ui.NewCol(2, 0, w.Memory),
ui.NewCol(2, 0, w.Net), ui.NewCol(2, 0, w.Net),
) )
return w
} }
func (w *Compact) Render() { func (w *Compact) Render() {

View File

@ -33,6 +33,9 @@ func NewInfo(id, name string) *ui.Table {
return p return p
} }
func (w *Expanded) Reset() {
}
func (w *Expanded) Render() { func (w *Expanded) Render() {
ui.Render(w.Info, w.Cpu, w.Mem, w.Net) ui.Render(w.Info, w.Cpu, w.Mem, w.Net)
ui.Handle("/timer/1s", func(ui.Event) { ui.Handle("/timer/1s", func(ui.Event) {