From 8c34a5d9f74177775622f78aba5ae010a3a5132b Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 18 May 2019 19:57:53 +1000 Subject: [PATCH] more progress --- pkg/commands/container_stats.go | 101 +++++++++++++++++++++++++++++++- pkg/gui/containers_panel.go | 40 ++++--------- pkg/gui/gui.go | 12 ++-- pkg/gui/view_helpers.go | 12 ++++ 4 files changed, 130 insertions(+), 35 deletions(-) diff --git a/pkg/commands/container_stats.go b/pkg/commands/container_stats.go index 5b94b275..129c7f81 100644 --- a/pkg/commands/container_stats.go +++ b/pkg/commands/container_stats.go @@ -1,6 +1,15 @@ package commands -import "time" +import ( + "encoding/json" + "fmt" + "math" + "time" + + "github.com/carlosms/asciigraph" + "github.com/fatih/color" + "github.com/jesseduffield/lazydocker/pkg/utils" +) // ContainerStats autogenerated at https://mholt.github.io/json-to-go/ type ContainerStats struct { @@ -125,10 +134,96 @@ func (s *ContainerStats) CalculateContainerCPUPercentage() float64 { cpuTotalUsageDelta := s.CPUStats.SystemCPUUsage - s.PrecpuStats.SystemCPUUsage numberOfCores := len(s.CPUStats.CPUUsage.PercpuUsage) - return float64(cpuUsageDelta*100) * float64(numberOfCores) / float64(cpuTotalUsageDelta) + value := float64(cpuUsageDelta*100) * float64(numberOfCores) / float64(cpuTotalUsageDelta) + if math.IsNaN(value) { + return 0 + } + return value } // CalculateContainerMemoryUsage calculates the memory usage of the container as a percent of total available memory func (s *ContainerStats) CalculateContainerMemoryUsage() float64 { - return float64(s.MemoryStats.Usage*100) / float64(s.MemoryStats.Limit) + value := float64(s.MemoryStats.Usage*100) / float64(s.MemoryStats.Limit) + if math.IsNaN(value) { + return 0 + } + return value +} + +// RenderStats returns a string containing the rendered stats of the container +func (s *ContainerStats) RenderStats(viewWidth int, cpuUsageHistory []float64, memoryUsageHistory []float64) (string, error) { + percentMemory := s.CalculateContainerMemoryUsage() + memoryUsageHistory = append(memoryUsageHistory, percentMemory) + memoryGraph := asciigraph.Plot( + memoryUsageHistory, + asciigraph.Height(10), + asciigraph.Width(viewWidth-10), + asciigraph.Min(0), + asciigraph.Max(100), + asciigraph.Caption( + fmt.Sprintf( + "%.2f%% Memory (%s/%s)", + percentMemory, + formatBinaryBytes(s.MemoryStats.Usage), + formatBinaryBytes(int(s.MemoryStats.Limit)), + ), + ), + ) + + percentageCPU := s.CalculateContainerCPUPercentage() + cpuUsageHistory = append(cpuUsageHistory, percentageCPU) + cpuGraph := asciigraph.Plot( + cpuUsageHistory, + asciigraph.Height(10), + asciigraph.Width(viewWidth-10), + asciigraph.Min(0), + asciigraph.Max(100), + asciigraph.Caption(fmt.Sprintf("%.2f%% CPU", percentageCPU)), + ) + + pidsCount := fmt.Sprintf("PIDs: %d", s.PidsStats.Current) + dataReceived := fmt.Sprintf("Traffic received: %s", formatDecimalBytes(s.Networks.Eth0.RxBytes)) + dataSent := fmt.Sprintf("Traffic sent: %s", formatDecimalBytes(s.Networks.Eth0.TxBytes)) + + originalJSON, err := json.MarshalIndent(s, "", " ") + if err != nil { + return "", err + } + + contents := fmt.Sprintf("\n\n%s\n\n%s\n\n%s\n\n%s\n%s\n\n%s", + utils.ColoredString(cpuGraph, color.FgCyan), + utils.ColoredString(memoryGraph, color.FgGreen), + pidsCount, + dataReceived, + dataSent, + string(originalJSON), + ) + + return contents, nil +} + +func formatBinaryBytes(b int) string { + n := float64(b) + units := []string{"B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} + for _, unit := range units { + if n > math.Pow(2, 10) { + n = n / math.Pow(2, 10) + } else { + return fmt.Sprintf("%.2f%s", n, unit) + } + } + return "a lot" +} + +func formatDecimalBytes(b int) string { + n := float64(b) + units := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} + for _, unit := range units { + if n > math.Pow(10, 3) { + n = n / math.Pow(10, 3) + } else { + return fmt.Sprintf("%.2f%s", n, unit) + } + } + return "a lot" } diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 0b3578fd..24d2e150 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -5,11 +5,9 @@ import ( "context" "encoding/json" "fmt" - "math" "os/exec" "time" - "github.com/carlosms/asciigraph" "github.com/go-errors/errors" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazydocker/pkg/commands" @@ -118,7 +116,7 @@ func (gui *Gui) renderStats(mainView *gocui.View, container *commands.Container, mainView.Autoscroll = false mainView.Title = "Stats" - stats, err := gui.DockerCommand.Client.ContainerStats(context.Background(), container.ID, true) + stream, err := gui.DockerCommand.Client.ContainerStats(context.Background(), container.ID, true) if err != nil { return err } @@ -126,7 +124,7 @@ func (gui *Gui) renderStats(mainView *gocui.View, container *commands.Container, go func() { cpuUsageHistory := []float64{} memoryUsageHistory := []float64{} - scanner := bufio.NewScanner(stats.Body) + scanner := bufio.NewScanner(stream.Body) for scanner.Scan() { data := scanner.Bytes() var stats commands.ContainerStats @@ -140,34 +138,22 @@ func (gui *Gui) renderStats(mainView *gocui.View, container *commands.Container, memoryUsageHistory = memoryUsageHistory[1:] } - percentMemory := stats.CalculateContainerMemoryUsage() + width, _ := mainView.Size() - memoryUsageHistory = append(memoryUsageHistory, percentMemory) - memoryGraph := asciigraph.Plot( - memoryUsageHistory, - asciigraph.Height(10), - asciigraph.Width(30), - asciigraph.Min(0), - asciigraph.Max(100), - asciigraph.Caption(fmt.Sprintf("%.2f%% Memory (%.2f/%.2f)", percentMemory, float64(stats.MemoryStats.Usage)/math.Pow(2, 20), float64(stats.MemoryStats.Limit)/math.Pow(2, 20))), - ) + contents, err := stats.RenderStats(width, cpuUsageHistory, memoryUsageHistory) + if err != nil { + gui.createErrorPanel(gui.g, err.Error()) + } - percentageCPU := stats.CalculateContainerCPUPercentage() + if gui.State.MainWriterID != writerID { + stream.Body.Close() + return + } - cpuUsageHistory = append(cpuUsageHistory, percentageCPU) - cpuGraph := asciigraph.Plot( - cpuUsageHistory, - asciigraph.Height(10), - asciigraph.Width(30), - asciigraph.Min(0), - asciigraph.Max(100), - asciigraph.Caption(fmt.Sprintf("%.2f%% CPU", percentageCPU)), - ) - - gui.renderString(gui.g, "main", fmt.Sprintf("%s\n%s", cpuGraph, memoryGraph)) + gui.reRenderString(gui.g, "main", contents) } - stats.Body.Close() + stream.Body.Close() }() return nil diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 35334ca0..59082f23 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -480,7 +480,7 @@ func (gui *Gui) Run() error { // TODO: see if this is right // gui.goEvery(time.Second*10, gui.refreshContainers) gui.goEvery(time.Millisecond*50, gui.renderAppStatus) - gui.goEvery(time.Millisecond*10, gui.reRender) + gui.goEvery(time.Millisecond*30, gui.reRenderMain) }() g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout())) @@ -493,10 +493,12 @@ func (gui *Gui) Run() error { return err } -func (gui *Gui) reRender() error { - gui.g.Update(func(g *gocui.Gui) error { - return nil - }) +func (gui *Gui) reRenderMain() error { + if gui.getMainView().IsTainted() { + gui.g.Update(func(g *gocui.Gui) error { + return nil + }) + } return nil } diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index ca2849c9..dd098bb5 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -186,6 +186,18 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error { return nil } +// reRenderString sets the view's content, without changing its origin +func (gui *Gui) reRenderString(g *gocui.Gui, viewName, s string) error { + g.Update(func(*gocui.Gui) error { + v, err := g.View(viewName) + if err != nil { + return nil // return gracefully if view has been deleted + } + return gui.setViewContent(gui.g, v, s) + }) + return nil +} + func (gui *Gui) optionsMapToString(optionsMap map[string]string) string { optionsArray := make([]string, 0) for key, description := range optionsMap {