refactor dockersource container refresh into channel

This commit is contained in:
Bradley Cicenas 2017-03-05 06:46:41 +00:00
parent 8fb5c5de59
commit 3172f141f9
9 changed files with 85 additions and 100 deletions

View File

@ -17,14 +17,13 @@ type Container struct {
collector metrics.Collector collector metrics.Collector
} }
func NewContainer(id, name string, collector metrics.Collector) *Container { func NewContainer(id string, collector metrics.Collector) *Container {
return &Container{ return &Container{
Metrics: metrics.NewMetrics(), Metrics: metrics.NewMetrics(),
Id: id, Id: id,
Name: name,
Meta: make(map[string]string), Meta: make(map[string]string),
Updates: make(chan [2]string), Updates: make(chan [2]string),
Widgets: compact.NewCompact(id, name), Widgets: compact.NewCompact(id),
collector: collector, collector: collector,
} }
} }
@ -41,6 +40,11 @@ func (c *Container) SetMeta(k, v string) {
c.Updates <- [2]string{k, v} c.Updates <- [2]string{k, v}
} }
func (c *Container) SetName(n string) {
c.Name = n
c.Widgets.Name.Set(n)
}
func (c *Container) SetState(s string) { func (c *Container) SetState(s string) {
c.State = s c.State = s
c.Widgets.Status.Set(s) c.Widgets.Status.Set(s)

View File

@ -23,3 +23,13 @@ func (w *GaugeCol) Reset() {
w.Label = "-" w.Label = "-"
w.Percent = 0 w.Percent = 0
} }
func colorScale(n int) ui.Attribute {
if n > 70 {
return ui.ColorRed
}
if n > 30 {
return ui.ColorYellow
}
return ui.ColorGreen
}

View File

@ -24,8 +24,9 @@ func (cg *CompactGrid) Align() {
// Update y recursively // Update y recursively
cg.header.SetY(cg.Y) cg.header.SetY(cg.Y)
y := cg.Y + 1 y := cg.Y + 1
for n, r := range cg.Rows { for _, r := range cg.Rows {
r.SetY(y + n) r.SetY(y)
y += r.Height
} }
// Update width recursively // Update width recursively
cg.header.SetWidth(cg.Width) cg.header.SetWidth(cg.Width)

View File

@ -28,10 +28,14 @@ type Compact struct {
Height int Height int
} }
func NewCompact(id, name string) *Compact { func NewCompact(id string) *Compact {
// truncate container id
if len(id) > 12 {
id = id[:12]
}
row := &Compact{ row := &Compact{
Status: NewStatus(), Status: NewStatus(),
Name: NewTextCol(name), Name: NewTextCol("-"),
Cid: NewTextCol(id), Cid: NewTextCol(id),
Cpu: NewGaugeCol(), Cpu: NewGaugeCol(),
Memory: NewGaugeCol(), Memory: NewGaugeCol(),
@ -41,6 +45,14 @@ func NewCompact(id, name string) *Compact {
return row return row
} }
//func (row *Compact) ToggleExpand() {
//if row.Height == 1 {
//row.Height = 4
//} else {
//row.Height = 1
//}
//}
func (row *Compact) SetMetrics(m metrics.Metrics) { func (row *Compact) SetMetrics(m metrics.Metrics) {
row.SetCPU(m.CPUUtil) row.SetCPU(m.CPUUtil)
row.SetNet(m.NetRx, m.NetTx) row.SetNet(m.NetRx, m.NetTx)
@ -114,7 +126,7 @@ func (row *Compact) SetNet(rx int64, tx int64) {
} }
func (row *Compact) SetCPU(val int) { func (row *Compact) SetCPU(val int) {
row.Cpu.BarColor = cwidgets.ColorScale(val) row.Cpu.BarColor = colorScale(val)
row.Cpu.Label = fmt.Sprintf("%s%%", strconv.Itoa(val)) row.Cpu.Label = fmt.Sprintf("%s%%", strconv.Itoa(val))
if val < 5 { if val < 5 {
val = 5 val = 5

View File

@ -3,8 +3,6 @@ package cwidgets
import ( import (
"fmt" "fmt"
"strconv" "strconv"
ui "github.com/gizak/termui"
) )
const ( const (
@ -48,13 +46,3 @@ func getPrecision(f float64) int {
} }
return 2 // default precision return 2 // default precision
} }
func ColorScale(n int) ui.Attribute {
if n > 70 {
return ui.ColorRed
}
if n > 30 {
return ui.ColorYellow
}
return ui.ColorGreen
}

View File

@ -3,16 +3,12 @@ package main
import ( import (
"sort" "sort"
"strings" "strings"
"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 ContainerSource interface { type ContainerSource interface {
All() []*Container All() []*Container
Get(string) (*Container, bool) Get(string) (*Container, bool)
@ -20,8 +16,8 @@ type ContainerSource interface {
type DockerContainerSource struct { type DockerContainerSource struct {
client *docker.Client client *docker.Client
containers Containers containers map[string]*Container
needsRefresh map[string]int // container IDs requiring refresh needsRefresh chan string // container IDs requiring refresh
} }
func NewDockerContainerSource() *DockerContainerSource { func NewDockerContainerSource() *DockerContainerSource {
@ -32,7 +28,8 @@ func NewDockerContainerSource() *DockerContainerSource {
} }
cm := &DockerContainerSource{ cm := &DockerContainerSource{
client: client, client: client,
needsRefresh: make(map[string]int), containers: make(map[string]*Container),
needsRefresh: make(chan string, 60),
} }
cm.refreshAll() cm.refreshAll()
go cm.Loop() go cm.Loop()
@ -53,7 +50,7 @@ func (cm *DockerContainerSource) watchEvents() {
switch e.Action { switch e.Action {
case "start", "die", "pause", "unpause": case "start", "die", "pause", "unpause":
log.Debugf("handling docker event: action=%s id=%s", e.Action, e.ID) log.Debugf("handling docker event: action=%s id=%s", e.Action, e.ID)
cm.needsRefresh[e.ID] = 1 cm.needsRefresh <- e.ID
case "destroy": case "destroy":
log.Debugf("handling docker event: action=%s id=%s", e.Action, e.ID) log.Debugf("handling docker event: action=%s id=%s", e.Action, e.ID)
cm.delByID(e.ID) cm.delByID(e.ID)
@ -61,26 +58,14 @@ func (cm *DockerContainerSource) watchEvents() {
} }
} }
func (cm *DockerContainerSource) refresh(id string) { func (cm *DockerContainerSource) refresh(c *Container) {
insp := cm.inspect(id) insp := cm.inspect(c.Id)
// remove container if no longer exists // remove container if no longer exists
if insp == nil { if insp == nil {
cm.delByID(id) cm.delByID(c.Id)
return return
} }
c.SetName(shortName(insp.Name))
c, ok := cm.Get(id)
// append container struct for new containers
if !ok {
// create collector
collector := metrics.NewDocker(cm.client, id)
// create container
c = NewContainer(shortID(id), shortName(insp.Name), collector)
lock.Lock()
cm.containers = append(cm.containers, c)
lock.Unlock()
}
c.SetState(insp.State.Status) c.SetState(insp.State.Status)
} }
@ -102,68 +87,55 @@ func (cm *DockerContainerSource) refreshAll() {
panic(err) panic(err)
} }
for _, c := range allContainers { for _, i := range allContainers {
cm.needsRefresh[c.ID] = 1 c := cm.MustGet(i.ID)
c.SetName(shortName(i.Names[0]))
c.SetState(i.State)
cm.needsRefresh <- c.Id
} }
} }
func (cm *DockerContainerSource) Loop() { func (cm *DockerContainerSource) Loop() {
for { for id := range cm.needsRefresh {
switch { c := cm.MustGet(id)
case len(cm.needsRefresh) > 0: cm.refresh(c)
processed := []string{}
for id, _ := range cm.needsRefresh {
cm.refresh(id)
processed = append(processed, id)
}
for _, id := range processed {
delete(cm.needsRefresh, id)
}
default:
time.Sleep(3 * time.Second)
}
} }
} }
// Get a single container, creating one anew if not existing
func (cm *DockerContainerSource) MustGet(id string) *Container {
c, ok := cm.Get(id)
// append container struct for new containers
if !ok {
// create collector
collector := metrics.NewDocker(cm.client, id)
// create container
c = NewContainer(id, collector)
cm.containers[id] = c
}
return c
}
// 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 { c, ok := cm.containers[id]
if c.Id == id { return c, ok
return c, true
}
}
return nil, false
} }
// 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 { delete(cm.containers, id)
if c.Id == id { log.Infof("removed dead container: %s", id)
cm.del(n)
return
}
}
}
// Remove one or more containers by index
func (cm *DockerContainerSource) del(idx ...int) {
lock.Lock()
defer lock.Unlock()
for _, i := range idx {
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
func (cm *DockerContainerSource) All() []*Container { func (cm *DockerContainerSource) All() []*Container {
sort.Sort(cm.containers) var containers Containers
return cm.containers for _, c := range cm.containers {
} containers = append(containers, c)
}
// truncate container id sort.Sort(containers)
func shortID(id string) string { return containers
return id[:12]
} }
// use primary container name // use primary container name

11
grid.go
View File

@ -23,11 +23,9 @@ type Grid struct {
} }
func NewGrid() *Grid { func NewGrid() *Grid {
cs := NewDockerContainerSource()
g := &Grid{ g := &Grid{
cSource: cs, cSource: NewDockerContainerSource(),
containers: cs.All(), header: widgets.NewCTopHeader(),
header: widgets.NewCTopHeader(),
} }
return g return g
} }
@ -169,6 +167,7 @@ func Display(g *Grid) bool {
ui.DefaultEvtStream.Hook(logEvent) ui.DefaultEvtStream.Hook(logEvent)
// initial draw // initial draw
g.containers = g.cSource.All()
g.redrawRows() g.redrawRows()
ui.Handle("/sys/kbd/<up>", func(ui.Event) { ui.Handle("/sys/kbd/<up>", func(ui.Event) {
@ -178,7 +177,9 @@ func Display(g *Grid) bool {
g.cursorDown() g.cursorDown()
}) })
ui.Handle("/sys/kbd/<enter>", func(ui.Event) { ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
ui.StopLoop() //c := g.containers[g.cursorIdx()]
//c.Widgets.ToggleExpand()
g.redrawRows()
}) })
ui.Handle("/sys/kbd/a", func(ui.Event) { ui.Handle("/sys/kbd/a", func(ui.Event) {

View File

@ -13,7 +13,7 @@ const (
var ( var (
Log *CTopLogger Log *CTopLogger
exited bool exited bool
level = logging.INFO level = logging.DEBUG
format = logging.MustStringFormatter( format = logging.MustStringFormatter(
`%{color}%{time:15:04:05.000} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, `%{color}%{time:15:04:05.000} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`,
) )

View File

@ -29,10 +29,9 @@ func (cs *MockContainerSource) Init() {
for i := 0; i < total; i++ { for i := 0; i < total; i++ {
collector := metrics.NewMock() collector := metrics.NewMock()
c := NewContainer(makeID(), makeName(), collector) c := NewContainer(makeID(), collector)
lock.Lock() c.SetName(makeName())
cs.containers = append(cs.containers, c) cs.containers = append(cs.containers, c)
lock.Unlock()
c.SetState(makeState()) c.SetState(makeState())
} }
@ -74,8 +73,6 @@ func (cs *MockContainerSource) delByID(id string) {
// Remove one or more containers by index // Remove one or more containers by index
func (cs *MockContainerSource) del(idx ...int) { func (cs *MockContainerSource) del(idx ...int) {
lock.Lock()
defer lock.Unlock()
for _, i := range idx { for _, i := range idx {
cs.containers = append(cs.containers[:i], cs.containers[i+1:]...) cs.containers = append(cs.containers[:i], cs.containers[i+1:]...)
} }