mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
more progress
This commit is contained in:
parent
88b46a79fe
commit
8c34a5d9f7
4 changed files with 130 additions and 35 deletions
|
|
@ -1,6 +1,15 @@
|
||||||
package commands
|
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/
|
// ContainerStats autogenerated at https://mholt.github.io/json-to-go/
|
||||||
type ContainerStats struct {
|
type ContainerStats struct {
|
||||||
|
|
@ -125,10 +134,96 @@ func (s *ContainerStats) CalculateContainerCPUPercentage() float64 {
|
||||||
cpuTotalUsageDelta := s.CPUStats.SystemCPUUsage - s.PrecpuStats.SystemCPUUsage
|
cpuTotalUsageDelta := s.CPUStats.SystemCPUUsage - s.PrecpuStats.SystemCPUUsage
|
||||||
numberOfCores := len(s.CPUStats.CPUUsage.PercpuUsage)
|
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
|
// CalculateContainerMemoryUsage calculates the memory usage of the container as a percent of total available memory
|
||||||
func (s *ContainerStats) CalculateContainerMemoryUsage() float64 {
|
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"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/carlosms/asciigraph"
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
"github.com/jesseduffield/lazydocker/pkg/commands"
|
||||||
|
|
@ -118,7 +116,7 @@ func (gui *Gui) renderStats(mainView *gocui.View, container *commands.Container,
|
||||||
mainView.Autoscroll = false
|
mainView.Autoscroll = false
|
||||||
mainView.Title = "Stats"
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +124,7 @@ func (gui *Gui) renderStats(mainView *gocui.View, container *commands.Container,
|
||||||
go func() {
|
go func() {
|
||||||
cpuUsageHistory := []float64{}
|
cpuUsageHistory := []float64{}
|
||||||
memoryUsageHistory := []float64{}
|
memoryUsageHistory := []float64{}
|
||||||
scanner := bufio.NewScanner(stats.Body)
|
scanner := bufio.NewScanner(stream.Body)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
data := scanner.Bytes()
|
data := scanner.Bytes()
|
||||||
var stats commands.ContainerStats
|
var stats commands.ContainerStats
|
||||||
|
|
@ -140,34 +138,22 @@ func (gui *Gui) renderStats(mainView *gocui.View, container *commands.Container,
|
||||||
memoryUsageHistory = memoryUsageHistory[1:]
|
memoryUsageHistory = memoryUsageHistory[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
percentMemory := stats.CalculateContainerMemoryUsage()
|
width, _ := mainView.Size()
|
||||||
|
|
||||||
memoryUsageHistory = append(memoryUsageHistory, percentMemory)
|
contents, err := stats.RenderStats(width, cpuUsageHistory, memoryUsageHistory)
|
||||||
memoryGraph := asciigraph.Plot(
|
if err != nil {
|
||||||
memoryUsageHistory,
|
gui.createErrorPanel(gui.g, err.Error())
|
||||||
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()
|
if gui.State.MainWriterID != writerID {
|
||||||
|
stream.Body.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cpuUsageHistory = append(cpuUsageHistory, percentageCPU)
|
gui.reRenderString(gui.g, "main", contents)
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.Body.Close()
|
stream.Body.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -480,7 +480,7 @@ func (gui *Gui) Run() error {
|
||||||
// TODO: see if this is right
|
// TODO: see if this is right
|
||||||
// gui.goEvery(time.Second*10, gui.refreshContainers)
|
// gui.goEvery(time.Second*10, gui.refreshContainers)
|
||||||
gui.goEvery(time.Millisecond*50, gui.renderAppStatus)
|
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()))
|
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
|
||||||
|
|
@ -493,10 +493,12 @@ func (gui *Gui) Run() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) reRender() error {
|
func (gui *Gui) reRenderMain() error {
|
||||||
gui.g.Update(func(g *gocui.Gui) error {
|
if gui.getMainView().IsTainted() {
|
||||||
return nil
|
gui.g.Update(func(g *gocui.Gui) error {
|
||||||
})
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,18 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
|
||||||
return nil
|
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 {
|
func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
||||||
optionsArray := make([]string, 0)
|
optionsArray := make([]string, 0)
|
||||||
for key, description := range optionsMap {
|
for key, description := range optionsMap {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue