diff --git a/container.go b/container.go index f069aa5..a6cf7e9 100644 --- a/container.go +++ b/container.go @@ -1,6 +1,8 @@ package main import ( + "strings" + "github.com/bcicen/ctop/metrics" "github.com/bcicen/ctop/widgets" ) @@ -13,12 +15,29 @@ type Container struct { widgets widgets.ContainerWidgets } +func NewContainer(id, name string) *Container { + c := &Container{ + id: id, + name: name, + } + c.Collapse() + return c +} + +func (c *Container) ShortID() string { + return c.id[:12] +} + +func (c *Container) ShortName() string { + return strings.Replace(c.name, "/", "", 1) // use primary container name +} + func (c *Container) Expand() { c.widgets = widgets.NewExpanded(c.id, c.name) } func (c *Container) Collapse() { - c.widgets = widgets.NewCompact(c.id, c.name) + c.widgets = widgets.NewCompact(c.ShortID(), c.ShortName()) } func (c *Container) SetState(s string) { diff --git a/containermap.go b/containermap.go index a4c8413..20920de 100644 --- a/containermap.go +++ b/containermap.go @@ -2,18 +2,17 @@ package main import ( "sort" - "strings" "github.com/bcicen/ctop/config" "github.com/bcicen/ctop/metrics" - "github.com/bcicen/ctop/widgets" "github.com/fsouza/go-dockerclient" ) type ContainerMap struct { - client *docker.Client - containers Containers - collectors map[string]metrics.Collector + client *docker.Client + containers Containers + collectors map[string]metrics.Collector + needsRefresh map[string]int // container IDs requiring refresh } func NewContainerMap() *ContainerMap { @@ -23,74 +22,114 @@ func NewContainerMap() *ContainerMap { panic(err) } cm := &ContainerMap{ - client: client, - collectors: make(map[string]metrics.Collector), + client: client, + collectors: make(map[string]metrics.Collector), + needsRefresh: make(map[string]int), } - //cm.Refresh() + cm.refreshAll() + go cm.watch() return cm } -func (cm *ContainerMap) Refresh() { - var id, name string +// Docker events watcher +func (cm *ContainerMap) watch() { + log.Info("docker event listener starting") + events := make(chan *docker.APIEvents) + cm.client.AddEventListener(events) + for e := range events { + cm.handleEvent(e) + } +} + +// Docker event handler +func (cm *ContainerMap) handleEvent(e *docker.APIEvents) { + // only process container events + if e.Type != "container" { + return + } + switch e.Action { + case "start", "die", "pause", "unpause": + cm.needsRefresh[e.ID] = 1 + case "destroy": + cm.DelByID(e.ID) + } +} + +func (cm *ContainerMap) refresh(id string) { + insp := cm.inspect(id) + // remove container if no longer exists + if insp == nil { + cm.DelByID(id) + return + } + + c, ok := cm.Get(id) + // append container struct for new containers + if !ok { + c = &Container{ + id: id, + name: insp.Name, + } + c.Collapse() + cm.containers = append(cm.containers, c) + // create collector + if _, ok := cm.collectors[id]; ok == false { + cm.collectors[id] = metrics.NewDocker(cm.client, id) + } + } + + 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()) + } +} + +func (cm *ContainerMap) inspect(id string) *docker.Container { + c, err := cm.client.InspectContainer(id) + if err != nil { + if _, ok := err.(*docker.NoSuchContainer); ok == false { + log.Errorf(err.Error()) + } + } + return c +} + +func (cm *ContainerMap) refreshAll() { opts := docker.ListContainersOptions{All: true} allContainers, err := cm.client.ListContainers(opts) if err != nil { panic(err) } - // add new containers - states := make(map[string]string) for _, c := range allContainers { - id = c.ID[:12] - states[id] = c.State - - if _, ok := cm.Get(id); ok == false { - name = strings.Replace(c.Names[0], "/", "", 1) // use primary container name - newc := &Container{ - id: id, - name: name, - widgets: widgets.NewCompact(id, name), - } - cm.containers = append(cm.containers, newc) - } - - if _, ok := cm.collectors[id]; ok == false { - cm.collectors[id] = metrics.NewDocker(cm.client, id) - } - + cm.needsRefresh[c.ID] = 1 } + cm.Update() +} - var removeIdxs []int - for n, c := range cm.containers { - - // mark stale internal containers - if _, ok := states[c.id]; ok == false { - removeIdxs = append(removeIdxs, n) - continue - } - - c.SetState(states[c.id]) - // start collector if needed - //collector := cm.collectors[id] - if c.state == "running" && !cm.collectors[c.id].Running() { - cm.collectors[c.id].Start() - c.Read(cm.collectors[c.id].Stream()) - } +func (cm *ContainerMap) Update() { + var ids []string + for id, _ := range cm.needsRefresh { + cm.refresh(id) + ids = append(ids, id) + } + for _, id := range ids { + delete(cm.needsRefresh, id) } - - // delete removed containers - cm.Del(removeIdxs...) } // 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) -} +//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 func (cm *ContainerMap) Len() uint { @@ -107,6 +146,16 @@ func (cm *ContainerMap) Get(id string) (*Container, bool) { return nil, false } +// Remove containers by ID +func (cm *ContainerMap) DelByID(id string) { + for n, c := range cm.containers { + if c.id == id { + cm.Del(n) + return + } + } +} + // Remove one or more containers by index func (cm *ContainerMap) Del(idx ...int) { for _, i := range idx { diff --git a/grid.go b/grid.go index 26ac2bc..c203c85 100644 --- a/grid.go +++ b/grid.go @@ -158,7 +158,6 @@ func Display(g *Grid) bool { ui.DefaultEvtStream.Hook(logEvent) // initial draw - g.cmap.Refresh() g.redrawRows() ui.Handle("/sys/kbd/", func(ui.Event) { @@ -202,7 +201,7 @@ func Display(g *Grid) bool { ui.Handle("/timer/1s", func(e ui.Event) { loopIter++ if loopIter%5 == 0 { - g.cmap.Refresh() + g.cmap.Update() } g.containers = g.cmap.All() // refresh containers for current sort order g.redrawRows()