Skip to content

Commit

Permalink
Stats container (#3)
Browse files Browse the repository at this point in the history
* WIP - add function get container stats

* add view stats container

* fix test

---------

Co-authored-by: ernesto <[email protected]>
  • Loading branch information
ernesto27 and ernestopigma authored Jul 5, 2023
1 parent 3f8aa4d commit f16aaa0
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 8 deletions.
82 changes: 82 additions & 0 deletions docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"context"
"dockerniceui/utils"
"encoding/json"
"fmt"
"strings"
"time"
Expand Down Expand Up @@ -53,6 +54,15 @@ type MyContainer struct {
Mounts []types.MountPoint
}

type MyContainerStats struct {
ID string
CPUPer float64
MemUsage string
MemLimit string
MemPer float64
PID uint64
}

type MyImage struct {
Summary types.ImageSummary
Inspect types.ImageInspect
Expand Down Expand Up @@ -341,6 +351,78 @@ func (d *Docker) ContainerLogs(containerId string) (string, error) {
return logs, nil
}

func (d *Docker) ContainerStats(containerID string) (MyContainerStats, error) {
s, err := d.cli.ContainerStats(d.ctx, containerID, false)
if err != nil {
panic(err)
}
defer s.Body.Close()

var containerStats types.Stats
dec := json.NewDecoder(s.Body)
if err := dec.Decode(&containerStats); err != nil {
panic(err)
}

cpuPercentage := calculateCPUPercentage(&containerStats)
memUsage, memLimit := calculateMemoryUsage(&containerStats)
memPercentage := calculateMemoryPercentage(memUsage, memLimit)

cs := MyContainerStats{
ID: containerID,
MemUsage: formatSizeStats(memUsage),
MemLimit: formatSizeStats(memLimit),
MemPer: memPercentage,
CPUPer: cpuPercentage,
PID: containerStats.PidsStats.Current,
}

return cs, err
}

func formatSizeStats(size float64) string {
units := []string{"B", "KB", "MB", "GB", "TB"}

unitIndex := 0
for size >= 1024 && unitIndex < len(units)-1 {
size /= 1024
unitIndex++
}

return fmt.Sprintf("%.2f%s", size, units[unitIndex])
}

func calculateCPUPercentage(stats *types.Stats) float64 {
cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage)
systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage)

cpuPercentage := 0.0
if systemDelta > 0.0 {
cpuPercentage = (cpuDelta / systemDelta) * 100.0
}

return cpuPercentage
}

func calculateMemoryUsage(stats *types.Stats) (float64, float64) {
memUsage := float64(stats.MemoryStats.Usage)
memLimit := float64(stats.MemoryStats.Limit)
return memUsage, memLimit
}

func calculateMemoryPercentage(memUsage, memLimit float64) float64 {
if memLimit <= 0.0 {
return 0.0
}

return (memUsage / memLimit) * 100.0
}

type NetworkIO struct {
Input float64
Output float64
}

func (d *Docker) NetworkList() ([]MyNetwork, error) {
myNetwork := []MyNetwork{}
networks, err := d.cli.NetworkList(d.ctx, types.NetworkListOptions{})
Expand Down
12 changes: 12 additions & 0 deletions models/container_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ func (cl ContainerList) Update(msg tea.Msg, m *model) (table.Model, tea.Cmd) {

m.networkList = NewNetworkList(networks, "")
m.currentModel = MNetworkList
case "ctrl+s":
stats, err := m.dockerClient.ContainerStats(m.containerList.table.SelectedRow()[0])
if err != nil {
fmt.Println(err)
}

cs, err := NewContainerStats(stats, utils.CreateTable)
if err != nil {
fmt.Println(err)
}
m.containerStats = cs
m.currentModel = MContainerStats
}
}

Expand Down
55 changes: 55 additions & 0 deletions models/container_stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package models

import (
"dockerniceui/docker"
"dockerniceui/utils"
"fmt"

"github.com/charmbracelet/bubbles/viewport"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss"
)

func NewContainerStats(stats docker.MyContainerStats, createTable utils.CreateTableFunc) (viewport.Model, error) {
content := getContentStats(stats)
const width = 120

vp := viewport.New(width, 30)
vp.Style = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("62")).
PaddingRight(2)

renderer, err := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(width),
)
if err != nil {
return viewport.Model{}, err
}

str, err := renderer.Render(content)
if err != nil {
return viewport.Model{}, err
}

vp.SetContent(str)

return vp, nil
}

func getContentStats(stats docker.MyContainerStats) string {
response := ""

response += utils.CreateTable("# Stats", []string{"CPU", "MEM USAGE/LIMIT", "MEM", "PIDS"},
[][]string{
{
fmt.Sprintf("%.2f%%", stats.CPUPer),
fmt.Sprintf("%s / %s", stats.MemUsage, stats.MemLimit),
fmt.Sprintf("%.2f%%", stats.MemPer),
fmt.Sprintf("%d", stats.PID),
},
})

return response
}
6 changes: 5 additions & 1 deletion models/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

const commands = `
GENERAL ↑/↓: Navigate • ctrl+c: Exit • ctrl+r: refresh • esc: Back
CONTAINERS ctrl+f: Search • ctrl+l: Logs • ctrl+o: Options • ctrl+e: Attach cmd
CONTAINERS ctrl+f: Search • ctrl+l: Logs • ctrl+o: Options • ctrl+e: Attach cmd • ctrl+s: Stats
IMAGES ctrl+b: List • ctrl+f: Search • ctrl+o: Options
NETWORKS ctrl+n: List • ctrl+f: Search • ctrl+o: Options
VOLUMES ctrl+v: List • ctrl+f: Search • ctrl+o: Options
Expand All @@ -28,6 +28,7 @@ const (
MContainerSearch
MContainerLogs
MContainerOptions
MContainerStats

MImageList
MImageDetail
Expand All @@ -52,6 +53,7 @@ type model struct {
containerSearch ContainerSearch
containerLogs LogsView
containerOptions ContainerOptions
containerStats viewport.Model
imageList ImageList
imageDetail viewport.Model
imageSearch ImageSearch
Expand Down Expand Up @@ -227,6 +229,8 @@ func (m model) View() string {
return fmt.Sprintf("%s\n%s\n%s", HeaderView(m.containerLogs.pager, m.containerLogs.container+" - "+m.containerLogs.image), m.containerLogs.pager.View(), FooterView(m.containerLogs.pager))
case MContainerOptions:
return m.containerOptions.View()
case MContainerStats:
return m.containerStats.View()

case MImageList:
return m.imageList.View(commands, m.dockerVersion)
Expand Down
7 changes: 2 additions & 5 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@ func CreateTable(title string, columns []string, rows [][]string) string {
}

table += "|\n"

table += "| ------------- | ------------- "

if len(columns) > 2 {
table += "| -------------"
for _, _ = range columns {
table += "| ------------- "
}

table += "|\n"
Expand Down
4 changes: 2 additions & 2 deletions utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ func TestCreateTable(t *testing.T) {
},
},
},
want: "# title\n\n| column1 | column2 | column3 |\n| ------------- | ------------- | -------------|\n| row1 | row2 | row3 |\n| row4 | row5 | row6 |\n",
want: "# title\n\n| column1 | column2 | column3 |\n| ------------- | ------------- | ------------- |\n| row1 | row2 | row3 |\n| row4 | row5 | row6 |\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := CreateTable(tt.args.title, tt.args.columns, tt.args.rows); got != tt.want {
t.Errorf("CreateTable() = %v, want %v", got, tt.want)
t.Errorf("CreateTable() = got %v, want %v", got, tt.want)
}
})
}
Expand Down

0 comments on commit f16aaa0

Please sign in to comment.