more progress

pull/1/head
Jesse Duffield 5 years ago
parent 3cfa4a2bda
commit 5bf1a40d2f

@ -30,15 +30,10 @@ type Container struct {
OSCommand *OSCommand
Log *logrus.Entry
Stats ContainerCliStat
StatHistory []ContainerCliStat
Details Details
}
type DetailsCollection struct {
InspectDetails Details
CLIStats ContainerCliStat
ClientStats ContainerStats
}
type Details struct {
ID string `json:"Id"`
Created time.Time `json:"Created"`
@ -251,11 +246,13 @@ func (c *Container) GetDisplayStrings(isFocused bool) []string {
// GetDisplayCPUPerc colors the cpu percentage based on how extreme it is
func (c *Container) GetDisplayCPUPerc() string {
if c.Stats.CPUPerc == "" {
stats := c.GetStats()
if stats.CPUPerc == "" {
return ""
}
percentage, err := strconv.ParseFloat(strings.TrimSuffix(c.Stats.CPUPerc, "%"), 32)
percentage, err := strconv.ParseFloat(strings.TrimSuffix(stats.CPUPerc, "%"), 32)
if err != nil {
c.Log.Error(err)
return ""
@ -270,7 +267,7 @@ func (c *Container) GetDisplayCPUPerc() string {
clr = color.FgWhite
}
return utils.ColoredString(c.Stats.CPUPerc, clr)
return utils.ColoredString(stats.CPUPerc, clr)
}
// GetColor Container color
@ -344,3 +341,11 @@ func (c *Container) Attach() (*exec.Cmd, error) {
func (c *Container) Top() (types.ContainerProcessList, error) {
return c.Client.ContainerTop(context.Background(), c.ID, []string{})
}
// GetStats gets the container's most recent stats
func (c *Container) GetStats() ContainerCliStat {
if len(c.StatHistory) == 0 {
return ContainerCliStat{}
}
return c.StatHistory[len(c.StatHistory)-1]
}

@ -1,12 +1,15 @@
package commands
import (
"bufio"
"context"
"encoding/json"
"fmt"
"sort"
"strings"
"sync"
"github.com/acarl005/stripansi"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
@ -25,6 +28,11 @@ type DockerCommand struct {
Client *client.Client
InDockerComposeProject bool
ErrorChan chan error
ContainerMutex sync.Mutex
ServiceMutex sync.Mutex
Services []*Service
Containers []*Container
Images []*Image
}
// NewDockerCommand it runs git commands
@ -45,50 +53,54 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localize
}, nil
}
// UpdateContainerStats takes a slice of containers and returns the same slice but with new stats added
// TODO: consider using this for everything stats-related
func (c *DockerCommand) UpdateContainerStats(containers []*Container) {
// TODO: consider using a stream rather than polling
command := `docker stats --all --no-trunc --no-stream --format '{{json .}}'`
output, err := c.OSCommand.RunCommandWithOutput(command)
if err != nil {
c.ErrorChan <- err
return
}
jsonStats := "[" + strings.Join(
strings.Split(
strings.Trim(output, "\n"), "\n",
), ",",
) + "]"
// MonitorContainerStats monitors a stream of container stats and updates the containers as each new stats object is received
func (c *DockerCommand) MonitorContainerStats() {
c.Log.Warn(jsonStats)
command := `docker stats --all --no-trunc --format '{{json .}}'`
cmd := c.OSCommand.RunCustomCommand(command)
var stats []ContainerCliStat
if err := json.Unmarshal([]byte(jsonStats), &stats); err != nil {
r, err := cmd.StdoutPipe()
if err != nil {
c.ErrorChan <- err
return
}
for _, stat := range stats {
for _, container := range containers {
if container.ID == stat.ID {
container.Stats = stat
cmd.Start()
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
var stats ContainerCliStat
// need to strip ANSI codes because uses escape sequences to clear the screen with each refresh
cleanString := stripansi.Strip(scanner.Text())
if err := json.Unmarshal([]byte(cleanString), &stats); err != nil {
c.ErrorChan <- err
return
}
c.ContainerMutex.Lock()
for _, container := range c.Containers {
if container.ID == stats.ID {
container.StatHistory = append(container.StatHistory, stats)
}
}
c.ContainerMutex.Unlock()
}
c.Log.Warn("updated containers")
cmd.Wait()
return
}
// GetContainersAndServices returns a slice of docker containers
func (c *DockerCommand) GetContainersAndServices(currentServices []*Service) ([]*Container, []*Service, error) {
func (c *DockerCommand) GetContainersAndServices() error {
c.ServiceMutex.Lock()
defer c.ServiceMutex.Unlock()
currentServices := c.Services
containers, err := c.GetContainers()
if err != nil {
return nil, nil, err
return err
}
var services []*Service
@ -98,7 +110,7 @@ func (c *DockerCommand) GetContainersAndServices(currentServices []*Service) ([]
} else {
services, err = c.GetServices()
if err != nil {
return nil, nil, err
return err
}
}
@ -124,11 +136,19 @@ func (c *DockerCommand) GetContainersAndServices(currentServices []*Service) ([]
return services[i].Name < services[j].Name
})
return containers, services, nil
c.Containers = containers
c.Services = services
return nil
}
// GetContainers gets the docker containers
func (c *DockerCommand) GetContainers() ([]*Container, error) {
c.ContainerMutex.Lock()
defer c.ContainerMutex.Unlock()
currentContainers := c.Containers
containers, err := c.Client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
if err != nil {
return nil, err
@ -149,11 +169,15 @@ func (c *DockerCommand) GetContainers() ([]*Container, error) {
OSCommand: c.OSCommand,
Log: c.Log,
}
}
c.UpdateContainerDetails(ownContainers)
c.UpdateContainerStats(ownContainers)
// bring across old stats information
for _, currentContainer := range currentContainers {
if currentContainer.ID == container.ID {
ownContainers[i].StatHistory = currentContainer.StatHistory // might need to use pointers here in case we're deep copying everything
ownContainers[i].Details = currentContainer.Details
}
}
}
return ownContainers, nil
}
@ -191,7 +215,12 @@ func (c *DockerCommand) GetServices() ([]*Service, error) {
// UpdateContainerDetails attaches the details returned from docker inspect to each of the containers
// this contains a bit more info than what you get from the go-docker client
func (c *DockerCommand) UpdateContainerDetails(containers []*Container) error {
func (c *DockerCommand) UpdateContainerDetails() error {
c.ContainerMutex.Lock()
defer c.ContainerMutex.Unlock()
containers := c.Containers
ids := make([]string, len(containers))
for i, container := range containers {
ids[i] = container.ID

@ -4,7 +4,6 @@ import (
"context"
"strings"
"github.com/davecgh/go-spew/spew"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/fatih/color"
@ -91,8 +90,6 @@ func (i *Image) RenderHistory() (string, error) {
return "", err
}
i.Log.Warn(spew.Sdump(history))
layers := make([]*Layer, len(history))
for i, layer := range history {
layers[i] = &Layer{layer}

@ -1,12 +1,14 @@
package commands
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/go-errors/errors"
@ -58,9 +60,11 @@ func (c *OSCommand) SetCommand(cmd func(string, ...string) *exec.Cmd) {
// RunCommandWithOutput wrapper around commands returning their output and error
func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
c.Log.WithField("command", command).Info("RunCommand")
cmd := c.ExecutableFromString(command)
return sanitisedCommandOutput(cmd.CombinedOutput())
before := time.Now()
output, err := sanitisedCommandOutput(cmd.CombinedOutput())
c.Log.Warn(fmt.Sprintf("'%s': %s", command, time.Since(before)))
return output, err
}
// RunExecutableWithOutput runs an executable file and returns its output
@ -77,7 +81,7 @@ func (c *OSCommand) RunExecutable(cmd *exec.Cmd) error {
// ExecutableFromString takes a string like `git status` and returns an executable command for it
func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd {
splitCmd := str.ToArgv(commandStr)
c.Log.Info(splitCmd)
// c.Log.Info(splitCmd)
return c.command(splitCmd[0], splitCmd[1:]...)
}

@ -28,7 +28,7 @@ func (gui *Gui) getSelectedContainer(g *gocui.Gui) (*commands.Container, error)
return &commands.Container{}, gui.Errors.ErrNoContainers
}
return gui.State.Containers[selectedLine], nil
return gui.DockerCommand.Containers[selectedLine], nil
}
func (gui *Gui) handleContainersFocus(g *gocui.Gui, v *gocui.View) error {
@ -42,7 +42,7 @@ func (gui *Gui) handleContainersFocus(g *gocui.Gui, v *gocui.View) error {
prevSelectedLine := gui.State.Panels.Containers.SelectedLine
newSelectedLine := cy - oy
if newSelectedLine > len(gui.State.Containers)-1 || len(utils.Decolorise(gui.State.Containers[newSelectedLine].Name)) < cx {
if newSelectedLine > len(gui.DockerCommand.Containers)-1 || len(utils.Decolorise(gui.DockerCommand.Containers[newSelectedLine].Name)) < cx {
return gui.handleContainerSelect(gui.g, v)
}
@ -75,7 +75,7 @@ func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error {
gui.State.Panels.Main.ObjectKey = key
}
if err := gui.focusPoint(0, gui.State.Panels.Containers.SelectedLine, len(gui.State.Containers), v); err != nil {
if err := gui.focusPoint(0, gui.State.Panels.Containers.SelectedLine, len(gui.DockerCommand.Containers), v); err != nil {
return err
}
@ -109,7 +109,7 @@ func (gui *Gui) renderContainerConfig(mainView *gocui.View, container *commands.
mainView.Autoscroll = false
mainView.Title = "Config"
data, err := json.MarshalIndent(&container.Container, "", " ")
data, err := json.MarshalIndent(&container.Details, "", " ")
if err != nil {
return err
}
@ -242,25 +242,25 @@ func (gui *Gui) refreshContainersAndServices() error {
return err
}
if len(gui.State.Containers) > 0 && gui.State.Panels.Containers.SelectedLine == -1 {
if len(gui.DockerCommand.Containers) > 0 && gui.State.Panels.Containers.SelectedLine == -1 {
gui.State.Panels.Containers.SelectedLine = 0
}
if len(gui.State.Containers)-1 < gui.State.Panels.Containers.SelectedLine {
gui.State.Panels.Containers.SelectedLine = len(gui.State.Containers) - 1
if len(gui.DockerCommand.Containers)-1 < gui.State.Panels.Containers.SelectedLine {
gui.State.Panels.Containers.SelectedLine = len(gui.DockerCommand.Containers) - 1
}
// doing the exact same thing for services
if len(gui.State.Services) > 0 && gui.State.Panels.Services.SelectedLine == -1 {
if len(gui.DockerCommand.Services) > 0 && gui.State.Panels.Services.SelectedLine == -1 {
gui.State.Panels.Services.SelectedLine = 0
}
if len(gui.State.Services)-1 < gui.State.Panels.Services.SelectedLine {
gui.State.Panels.Services.SelectedLine = len(gui.State.Services) - 1
if len(gui.DockerCommand.Services)-1 < gui.State.Panels.Services.SelectedLine {
gui.State.Panels.Services.SelectedLine = len(gui.DockerCommand.Services) - 1
}
gui.g.Update(func(g *gocui.Gui) error {
containersView.Clear()
isFocused := gui.g.CurrentView().Name() == "containers"
list, err := utils.RenderList(gui.State.Containers, utils.IsFocused(isFocused))
list, err := utils.RenderList(gui.DockerCommand.Containers, utils.IsFocused(isFocused))
if err != nil {
return err
}
@ -279,7 +279,7 @@ func (gui *Gui) refreshContainersAndServices() error {
servicesView := gui.getServicesView()
servicesView.Clear()
isFocused = gui.g.CurrentView().Name() == "services"
list, err = utils.RenderList(gui.State.Services, utils.IsFocused(isFocused))
list, err = utils.RenderList(gui.DockerCommand.Services, utils.IsFocused(isFocused))
if err != nil {
return err
}
@ -295,15 +295,7 @@ func (gui *Gui) refreshContainersAndServices() error {
}
func (gui *Gui) refreshStateContainersAndServices() error {
containers, services, err := gui.DockerCommand.GetContainersAndServices(gui.State.Services)
if err != nil {
return err
}
gui.State.Containers = containers
gui.State.Services = services
return nil
return gui.DockerCommand.GetContainersAndServices()
}
func (gui *Gui) handleContainersNextLine(g *gocui.Gui, v *gocui.View) error {
@ -312,7 +304,7 @@ func (gui *Gui) handleContainersNextLine(g *gocui.Gui, v *gocui.View) error {
}
panelState := gui.State.Panels.Containers
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Containers), false)
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Containers), false)
return gui.handleContainerSelect(gui.g, v)
}
@ -323,7 +315,7 @@ func (gui *Gui) handleContainersPrevLine(g *gocui.Gui, v *gocui.View) error {
}
panelState := gui.State.Panels.Containers
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Containers), true)
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Containers), true)
return gui.handleContainerSelect(gui.g, v)
}

@ -109,9 +109,6 @@ type panelStates struct {
}
type guiState struct {
Services []*commands.Service
Containers []*commands.Container
Images []*commands.Image
MenuItemCount int // can't store the actual list because it's of interface{} type
PreviousView string
Platform commands.Platform
@ -126,7 +123,6 @@ type guiState struct {
func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config *config.AppConfig, errorChan chan error) (*Gui, error) {
initialState := guiState{
Containers: make([]*commands.Container, 0),
PreviousView: "services",
Platform: *oSCommand.Platform,
Panels: &panelStates{
@ -430,8 +426,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
listViews := map[*gocui.View]listViewState{
containersView: {selectedLine: gui.State.Panels.Containers.SelectedLine, lineCount: len(gui.State.Containers)},
imagesView: {selectedLine: gui.State.Panels.Images.SelectedLine, lineCount: len(gui.State.Images)},
containersView: {selectedLine: gui.State.Panels.Containers.SelectedLine, lineCount: len(gui.DockerCommand.Containers)},
imagesView: {selectedLine: gui.State.Panels.Images.SelectedLine, lineCount: len(gui.DockerCommand.Images)},
}
// menu view might not exist so we check to be safe
@ -501,6 +497,7 @@ func (gui *Gui) renderGlobalOptions() error {
}
func (gui *Gui) goEvery(interval time.Duration, function func() error) {
_ = function() // time.Tick doesn't run immediately so we'll do that here // TODO: maybe change
go func() {
for range time.Tick(interval) {
_ = function()
@ -536,9 +533,12 @@ func (gui *Gui) Run() error {
gui.waitForIntro.Wait()
gui.goEvery(time.Millisecond*50, gui.renderAppStatus)
gui.goEvery(time.Millisecond*30, gui.reRenderMain)
gui.goEvery(time.Millisecond*500, gui.refreshContainersAndServices)
gui.goEvery(time.Millisecond*100, gui.refreshContainersAndServices)
gui.goEvery(time.Millisecond*1000, gui.DockerCommand.UpdateContainerDetails)
}()
go gui.DockerCommand.MonitorContainerStats()
go func() {
for err := range gui.ErrorChan {
gui.createErrorPanel(gui.g, err.Error())

@ -25,7 +25,7 @@ func (gui *Gui) getSelectedImage(g *gocui.Gui) (*commands.Image, error) {
return &commands.Image{}, gui.Errors.ErrNoImages
}
return gui.State.Images[selectedLine], nil
return gui.DockerCommand.Images[selectedLine], nil
}
func (gui *Gui) handleImagesFocus(g *gocui.Gui, v *gocui.View) error {
@ -39,7 +39,7 @@ func (gui *Gui) handleImagesFocus(g *gocui.Gui, v *gocui.View) error {
prevSelectedLine := gui.State.Panels.Images.SelectedLine
newSelectedLine := cy - oy
if newSelectedLine > len(gui.State.Images)-1 || len(utils.Decolorise(gui.State.Images[newSelectedLine].Name)) < cx {
if newSelectedLine > len(gui.DockerCommand.Images)-1 || len(utils.Decolorise(gui.DockerCommand.Images[newSelectedLine].Name)) < cx {
return gui.handleImageSelect(gui.g, v)
}
@ -72,7 +72,7 @@ func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error {
gui.State.Panels.Main.ObjectKey = key
}
if err := gui.focusPoint(0, gui.State.Panels.Images.SelectedLine, len(gui.State.Images), v); err != nil {
if err := gui.focusPoint(0, gui.State.Panels.Images.SelectedLine, len(gui.DockerCommand.Images), v); err != nil {
return err
}
@ -129,18 +129,18 @@ func (gui *Gui) refreshImages() error {
return err
}
if len(gui.State.Images) > 0 && gui.State.Panels.Images.SelectedLine == -1 {
if len(gui.DockerCommand.Images) > 0 && gui.State.Panels.Images.SelectedLine == -1 {
gui.State.Panels.Images.SelectedLine = 0
}
if len(gui.State.Images)-1 < gui.State.Panels.Images.SelectedLine {
gui.State.Panels.Images.SelectedLine = len(gui.State.Images) - 1
if len(gui.DockerCommand.Images)-1 < gui.State.Panels.Images.SelectedLine {
gui.State.Panels.Images.SelectedLine = len(gui.DockerCommand.Images) - 1
}
gui.g.Update(func(g *gocui.Gui) error {
ImagesView.Clear()
isFocused := gui.g.CurrentView().Name() == "Images"
list, err := utils.RenderList(gui.State.Images, utils.IsFocused(isFocused))
list, err := utils.RenderList(gui.DockerCommand.Images, utils.IsFocused(isFocused))
if err != nil {
return err
}
@ -161,7 +161,7 @@ func (gui *Gui) refreshStateImages() error {
return err
}
gui.State.Images = Images
gui.DockerCommand.Images = Images
return nil
}
@ -172,7 +172,7 @@ func (gui *Gui) handleImagesNextLine(g *gocui.Gui, v *gocui.View) error {
}
panelState := gui.State.Panels.Images
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Images), false)
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Images), false)
return gui.handleImageSelect(gui.g, v)
}
@ -183,7 +183,7 @@ func (gui *Gui) handleImagesPrevLine(g *gocui.Gui, v *gocui.View) error {
}
panelState := gui.State.Panels.Images
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Images), true)
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Images), true)
return gui.handleImageSelect(gui.g, v)
}

@ -23,7 +23,7 @@ func (gui *Gui) getSelectedService(g *gocui.Gui) (*commands.Service, error) {
return &commands.Service{}, errors.New("no service selected")
}
return gui.State.Services[selectedLine], nil
return gui.DockerCommand.Services[selectedLine], nil
}
func (gui *Gui) handleServicesFocus(g *gocui.Gui, v *gocui.View) error {
@ -37,7 +37,7 @@ func (gui *Gui) handleServicesFocus(g *gocui.Gui, v *gocui.View) error {
prevSelectedLine := gui.State.Panels.Services.SelectedLine
newSelectedLine := cy - oy
if newSelectedLine > len(gui.State.Services)-1 || len(utils.Decolorise(gui.State.Services[newSelectedLine].Name)) < cx {
if newSelectedLine > len(gui.DockerCommand.Services)-1 || len(utils.Decolorise(gui.DockerCommand.Services[newSelectedLine].Name)) < cx {
return gui.handleServiceSelect(gui.g, v)
}
@ -67,7 +67,7 @@ func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error {
gui.State.Panels.Main.ObjectKey = key
}
if err := gui.focusPoint(0, gui.State.Panels.Services.SelectedLine, len(gui.State.Services), v); err != nil {
if err := gui.focusPoint(0, gui.State.Panels.Services.SelectedLine, len(gui.DockerCommand.Services), v); err != nil {
return err
}
@ -137,7 +137,7 @@ func (gui *Gui) handleServicesNextLine(g *gocui.Gui, v *gocui.View) error {
}
panelState := gui.State.Panels.Services
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Services), false)
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Services), false)
return gui.handleServiceSelect(gui.g, v)
}
@ -148,7 +148,7 @@ func (gui *Gui) handleServicesPrevLine(g *gocui.Gui, v *gocui.View) error {
}
panelState := gui.State.Panels.Services
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Services), true)
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Services), true)
return gui.handleServiceSelect(gui.g, v)
}

@ -24,15 +24,10 @@ func NewTaskManager(log *logrus.Entry) *TaskManager {
}
func (t *TaskManager) NewTask(f func(stop chan struct{})) error {
t.Log.Warn("new task")
t.waitingMutex.Lock()
defer t.waitingMutex.Unlock()
t.Log.Warn("locked mutex")
if t.currentTask != nil {
t.Log.Warn("about to ask current task to stop")
t.currentTask.Stop()
}
@ -46,7 +41,6 @@ func (t *TaskManager) NewTask(f func(stop chan struct{})) error {
}
go func() {
t.Log.Warn("running new task")
f(stop)
notifyStopped <- struct{}{}
}()
@ -57,6 +51,5 @@ func (t *TaskManager) NewTask(f func(stop chan struct{})) error {
func (t *Task) Stop() {
t.stop <- struct{}{}
<-t.notifyStopped
t.Log.Warn("task successfully stopped")
return
}

Loading…
Cancel
Save