From 159a1883e56c6a6c1053d7d24723b2fa50f3c2f1 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 2 Jun 2019 13:34:26 +1000 Subject: [PATCH] fix this insane CPU usage issue --- pkg/commands/container.go | 10 ++++++++-- pkg/commands/docker.go | 20 ++++++++++++++------ pkg/commands/service.go | 13 ++++++------- pkg/config/app_config.go | 2 ++ pkg/gui/containers_panel.go | 19 ++++++++++--------- pkg/gui/gui.go | 4 ++-- pkg/gui/services_panel.go | 16 ++++++++++++++-- pkg/gui/status_panel.go | 13 ++++--------- pkg/gui/view_helpers.go | 9 +++++++++ pkg/tasks/tasks.go | 20 +++++++++++++++++--- 10 files changed, 86 insertions(+), 40 deletions(-) diff --git a/pkg/commands/container.go b/pkg/commands/container.go index f178c0cd..6cf112ef 100644 --- a/pkg/commands/container.go +++ b/pkg/commands/container.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/fatih/color" + "github.com/go-errors/errors" "github.com/jesseduffield/lazydocker/pkg/utils" "github.com/sirupsen/logrus" "golang.org/x/xerrors" @@ -298,9 +299,14 @@ func (c *Container) RestartService() error { } // Attach attaches the container -func (c *Container) Attach() *exec.Cmd { +func (c *Container) Attach() (*exec.Cmd, error) { + // verify that we can in fact attach to this container + if !c.Details.Config.AttachStdin { + return nil, errors.New("Container does not support attaching. You must either run the service with the '-it' flag or use `stdin_open: true, tty: true` in the docker-compose.yml file") + } + cmd := c.OSCommand.PrepareSubProcess("docker", "attach", "--sig-proxy=false", c.ID) - return cmd + return cmd, nil } // Top returns process information diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index d7d68417..cca91d49 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -78,16 +78,22 @@ func (c *DockerCommand) UpdateContainerStats(containers []*Container) ([]*Contai } // GetContainersAndServices returns a slice of docker containers -func (c *DockerCommand) GetContainersAndServices() ([]*Container, []*Service, error) { +func (c *DockerCommand) GetContainersAndServices(currentServices []*Service) ([]*Container, []*Service, error) { containers, err := c.GetContainers() if err != nil { return nil, nil, err } - services, err := c.GetServices() - if err != nil { - return nil, nil, err + var services []*Service + // we only need to get these services once because they won't change in the runtime of the program + if currentServices != nil { + services = currentServices + } else { + services, err = c.GetServices() + if err != nil { + return nil, nil, err + } } // find out which services have corresponding containers and assign them @@ -170,8 +176,10 @@ func (c *DockerCommand) GetServices() ([]*Service, error) { for i, str := range lines { arr := strings.Split(str, " ") services[i] = &Service{ - Name: arr[0], - ID: arr[1], + Name: arr[0], + ID: arr[1], + OSCommand: c.OSCommand, + Log: c.Log, } } diff --git a/pkg/commands/service.go b/pkg/commands/service.go index d1e22847..4e65c533 100644 --- a/pkg/commands/service.go +++ b/pkg/commands/service.go @@ -11,12 +11,11 @@ import ( // Service : A docker Service type Service struct { - Name string - ID string - DisplayString string - OSCommand *OSCommand - Log *logrus.Entry - Container *Container + Name string + ID string + OSCommand *OSCommand + Log *logrus.Entry + Container *Container } // GetDisplayStrings returns the dispaly string of Container @@ -50,7 +49,7 @@ func (s *Service) Restart() error { } // Attach attaches to the service -func (s *Service) Attach() *exec.Cmd { +func (s *Service) Attach() (*exec.Cmd, error) { // TODO: if you have a custom command for attaching to a service here is the place to use it return s.Container.Attach() diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 9dfaac59..6d2a294c 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -102,6 +102,7 @@ type CommandTemplatesConfig struct { RestartService string `yaml:"restartService,omitempty"` DockerCompose string `yaml:"dockerCompose,omitempty"` StopService string `yaml:"stopService,omitempty"` + ServiceLogs string `yaml:"serviceLogs,omitempty"` } type OSConfig struct { @@ -132,6 +133,7 @@ func GetDefaultConfig() UserConfig { RestartService: "docker-compose restart {{ .Name }}", DockerCompose: "apdev compose", StopService: "apdev stop {{ .Name }}", + ServiceLogs: "apdev logs {{ .Name }}", }, OS: GetPlatformDefaultConfig(), Update: UpdateConfig{ diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 60ee591b..5190c5ca 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -220,13 +220,11 @@ func (gui *Gui) renderLogsForTTYContainer(mainView *gocui.View, container *comma } go func() { - for { - s := bufio.NewScanner(r) - s.Split(bufio.ScanLines) - for s.Scan() { - // I might put a check on the stopped channel here. Would mean more code duplication though - mainView.Write(append(s.Bytes(), '\n')) - } + s := bufio.NewScanner(r) + s.Split(bufio.ScanLines) + for s.Scan() { + // I might put a check on the stopped channel here. Would mean more code duplication though + mainView.Write(append(s.Bytes(), '\n')) } }() @@ -297,7 +295,7 @@ func (gui *Gui) refreshContainersAndServices() error { } func (gui *Gui) refreshStateContainersAndServices() error { - containers, services, err := gui.DockerCommand.GetContainersAndServices() + containers, services, err := gui.DockerCommand.GetContainersAndServices(gui.State.Services) if err != nil { return err } @@ -457,7 +455,10 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error { return nil } - c := container.Attach() + c, err := container.Attach() + if err != nil { + return gui.createErrorPanel(gui.g, err.Error()) + } gui.SubProcess = c return gui.Errors.ErrSubProcess diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 2d6203f0..361c0364 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -155,7 +155,7 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand Config: config, Tr: tr, statusManager: &statusManager{}, - T: tasks.NewTaskManager(), + T: tasks.NewTaskManager(log), } gui.GenerateSentinelErrors() @@ -415,7 +415,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } - if err := gui.switchFocus(gui.g, nil, gui.getContainersView()); err != nil { + if err := gui.switchFocus(gui.g, nil, gui.getServicesView()); err != nil { return err } } diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index c05d164f..ac89ec71 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -119,7 +119,16 @@ func (gui *Gui) renderServiceStats(mainView *gocui.View, service *commands.Servi } func (gui *Gui) renderServiceLogs(mainView *gocui.View, service *commands.Service) error { - return nil + service, err := gui.getSelectedService(gui.g) + if err != nil { + return nil + } + + if service.Container == nil { + return nil + } + + return gui.renderContainerLogs(gui.getMainView(), service.Container) } func (gui *Gui) handleServicesNextLine(g *gocui.Gui, v *gocui.View) error { @@ -255,7 +264,10 @@ func (gui *Gui) handleServiceAttach(g *gocui.Gui, v *gocui.View) error { return nil } - c := service.Attach() + c, err := service.Attach() + if err != nil { + return gui.createErrorPanel(gui.g, err.Error()) + } gui.SubProcess = c return gui.Errors.ErrSubProcess diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index 1d606b0b..3cb37f61 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -7,15 +7,10 @@ import ( "github.com/jesseduffield/gocui" ) -func (gui *Gui) refreshStatus(g *gocui.Gui) error { - v, err := g.View("status") - if err != nil { - panic(err) - } - // for some reason if this isn't wrapped in an update the clear seems to - // be applied after the other things or something like that; the panel's - // contents end up cleared - g.Update(func(*gocui.Gui) error { +func (gui *Gui) refreshStatus() error { + v := gui.getStatusView() + + gui.g.Update(func(*gocui.Gui) error { v.Clear() fmt.Fprint(v, "lazydocker") return nil diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index bc8fbbcc..a238bd0c 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -19,6 +19,10 @@ func (gui *Gui) refreshSidePanels(g *gocui.Gui) error { if err := gui.refreshImages(); err != nil { return err } + if err := gui.refreshStatus(); err != nil { + return err + } + return nil } @@ -245,6 +249,11 @@ func (gui *Gui) getMainView() *gocui.View { return v } +func (gui *Gui) getStatusView() *gocui.View { + v, _ := gui.g.View("status") + return v +} + func (gui *Gui) trimmedContent(v *gocui.View) string { return strings.TrimSpace(v.Buffer()) } diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go index 666f9077..639724eb 100644 --- a/pkg/tasks/tasks.go +++ b/pkg/tasks/tasks.go @@ -1,27 +1,38 @@ package tasks -import "sync" +import ( + "sync" + + "github.com/sirupsen/logrus" +) type TaskManager struct { waitingTasks []*Task currentTask *Task waitingMutex sync.Mutex + Log *logrus.Entry } type Task struct { stop chan struct{} notifyStopped chan struct{} + Log *logrus.Entry } -func NewTaskManager() *TaskManager { - return &TaskManager{} +func NewTaskManager(log *logrus.Entry) *TaskManager { + return &TaskManager{Log: log} } 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() } @@ -31,9 +42,11 @@ func (t *TaskManager) NewTask(f func(stop chan struct{})) error { t.currentTask = &Task{ stop: stop, notifyStopped: notifyStopped, + Log: t.Log, } go func() { + t.Log.Warn("running new task") f(stop) notifyStopped <- struct{}{} }() @@ -44,5 +57,6 @@ 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 }