mirror of
https://github.com/bcicen/ctop.git
synced 2024-08-30 18:23:19 +00:00
refactor container refresh with docker event handler
This commit is contained in:
21
container.go
21
container.go
@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/bcicen/ctop/metrics"
|
"github.com/bcicen/ctop/metrics"
|
||||||
"github.com/bcicen/ctop/widgets"
|
"github.com/bcicen/ctop/widgets"
|
||||||
)
|
)
|
||||||
@ -13,12 +15,29 @@ type Container struct {
|
|||||||
widgets widgets.ContainerWidgets
|
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() {
|
func (c *Container) Expand() {
|
||||||
c.widgets = widgets.NewExpanded(c.id, c.name)
|
c.widgets = widgets.NewExpanded(c.id, c.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) Collapse() {
|
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) {
|
func (c *Container) SetState(s string) {
|
||||||
|
133
containermap.go
133
containermap.go
@ -2,11 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bcicen/ctop/config"
|
"github.com/bcicen/ctop/config"
|
||||||
"github.com/bcicen/ctop/metrics"
|
"github.com/bcicen/ctop/metrics"
|
||||||
"github.com/bcicen/ctop/widgets"
|
|
||||||
"github.com/fsouza/go-dockerclient"
|
"github.com/fsouza/go-dockerclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,6 +12,7 @@ type ContainerMap struct {
|
|||||||
client *docker.Client
|
client *docker.Client
|
||||||
containers Containers
|
containers Containers
|
||||||
collectors map[string]metrics.Collector
|
collectors map[string]metrics.Collector
|
||||||
|
needsRefresh map[string]int // container IDs requiring refresh
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContainerMap() *ContainerMap {
|
func NewContainerMap() *ContainerMap {
|
||||||
@ -25,72 +24,112 @@ func NewContainerMap() *ContainerMap {
|
|||||||
cm := &ContainerMap{
|
cm := &ContainerMap{
|
||||||
client: client,
|
client: client,
|
||||||
collectors: make(map[string]metrics.Collector),
|
collectors: make(map[string]metrics.Collector),
|
||||||
|
needsRefresh: make(map[string]int),
|
||||||
}
|
}
|
||||||
//cm.Refresh()
|
cm.refreshAll()
|
||||||
|
go cm.watch()
|
||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *ContainerMap) Refresh() {
|
// Docker events watcher
|
||||||
var id, name string
|
func (cm *ContainerMap) watch() {
|
||||||
|
log.Info("docker event listener starting")
|
||||||
|
events := make(chan *docker.APIEvents)
|
||||||
|
cm.client.AddEventListener(events)
|
||||||
|
|
||||||
opts := docker.ListContainersOptions{All: true}
|
for e := range events {
|
||||||
allContainers, err := cm.client.ListContainers(opts)
|
cm.handleEvent(e)
|
||||||
if err != nil {
|
}
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add new containers
|
// Docker event handler
|
||||||
states := make(map[string]string)
|
func (cm *ContainerMap) handleEvent(e *docker.APIEvents) {
|
||||||
for _, c := range allContainers {
|
// only process container events
|
||||||
id = c.ID[:12]
|
if e.Type != "container" {
|
||||||
states[id] = c.State
|
return
|
||||||
|
}
|
||||||
|
switch e.Action {
|
||||||
|
case "start", "die", "pause", "unpause":
|
||||||
|
cm.needsRefresh[e.ID] = 1
|
||||||
|
case "destroy":
|
||||||
|
cm.DelByID(e.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := cm.Get(id); ok == false {
|
func (cm *ContainerMap) refresh(id string) {
|
||||||
name = strings.Replace(c.Names[0], "/", "", 1) // use primary container name
|
insp := cm.inspect(id)
|
||||||
newc := &Container{
|
// 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,
|
id: id,
|
||||||
name: name,
|
name: insp.Name,
|
||||||
widgets: widgets.NewCompact(id, name),
|
|
||||||
}
|
}
|
||||||
cm.containers = append(cm.containers, newc)
|
c.Collapse()
|
||||||
}
|
cm.containers = append(cm.containers, c)
|
||||||
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var removeIdxs []int
|
c.SetState(insp.State.Status)
|
||||||
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
|
// start collector if needed
|
||||||
//collector := cm.collectors[id]
|
|
||||||
if c.state == "running" && !cm.collectors[c.id].Running() {
|
if c.state == "running" && !cm.collectors[c.id].Running() {
|
||||||
cm.collectors[c.id].Start()
|
cm.collectors[c.id].Start()
|
||||||
c.Read(cm.collectors[c.id].Stream())
|
c.Read(cm.collectors[c.id].Stream())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete removed containers
|
func (cm *ContainerMap) inspect(id string) *docker.Container {
|
||||||
cm.Del(removeIdxs...)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range allContainers {
|
||||||
|
cm.needsRefresh[c.ID] = 1
|
||||||
|
}
|
||||||
|
cm.Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill a container by ID
|
// Kill a container by ID
|
||||||
func (cm *ContainerMap) Kill(id string, sig docker.Signal) error {
|
//func (cm *ContainerMap) Kill(id string, sig docker.Signal) error {
|
||||||
opts := docker.KillContainerOptions{
|
//opts := docker.KillContainerOptions{
|
||||||
ID: id,
|
//ID: id,
|
||||||
Signal: sig,
|
//Signal: sig,
|
||||||
}
|
//}
|
||||||
return cm.client.KillContainer(opts)
|
//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 {
|
||||||
@ -107,6 +146,16 @@ func (cm *ContainerMap) Get(id string) (*Container, bool) {
|
|||||||
return nil, false
|
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
|
// Remove one or more containers by index
|
||||||
func (cm *ContainerMap) Del(idx ...int) {
|
func (cm *ContainerMap) Del(idx ...int) {
|
||||||
for _, i := range idx {
|
for _, i := range idx {
|
||||||
|
3
grid.go
3
grid.go
@ -158,7 +158,6 @@ func Display(g *Grid) bool {
|
|||||||
ui.DefaultEvtStream.Hook(logEvent)
|
ui.DefaultEvtStream.Hook(logEvent)
|
||||||
|
|
||||||
// initial draw
|
// initial draw
|
||||||
g.cmap.Refresh()
|
|
||||||
g.redrawRows()
|
g.redrawRows()
|
||||||
|
|
||||||
ui.Handle("/sys/kbd/<up>", func(ui.Event) {
|
ui.Handle("/sys/kbd/<up>", func(ui.Event) {
|
||||||
@ -202,7 +201,7 @@ func Display(g *Grid) bool {
|
|||||||
ui.Handle("/timer/1s", func(e ui.Event) {
|
ui.Handle("/timer/1s", func(e ui.Event) {
|
||||||
loopIter++
|
loopIter++
|
||||||
if loopIter%5 == 0 {
|
if loopIter%5 == 0 {
|
||||||
g.cmap.Refresh()
|
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()
|
||||||
|
Reference in New Issue
Block a user