more progress

pull/1/head
Jesse Duffield 5 years ago
parent 88b46a79fe
commit 8c34a5d9f7

@ -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"
}

@ -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()
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))),
)
percentageCPU := stats.CalculateContainerCPUPercentage()
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))
width, _ := mainView.Size()
contents, err := stats.RenderStats(width, cpuUsageHistory, memoryUsageHistory)
if err != nil {
gui.createErrorPanel(gui.g, err.Error())
}
if gui.State.MainWriterID != writerID {
stream.Body.Close()
return
}
gui.reRenderString(gui.g, "main", contents)
}
stats.Body.Close()
stream.Body.Close()
}()
return nil

@ -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
}

@ -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 {

Loading…
Cancel
Save