Skip to content

Commit b88299c

Browse files
committedNov 7, 2023
initial commit
Signed-off-by: Mohammed Zaki <zs84907@gmail.com>
0 parents  commit b88299c

18 files changed

+1649
-0
lines changed
 

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
./dock-stats
2+
./vendor

‎Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
build:
2+
go build -o dock-stats main.go
3+
build-win:
4+
go build -o dock-stats.exe main.go

‎README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
### dock-stats
2+
dock-stats renders statistics of docker containers in graph.
3+
4+
### Usage
5+
> To get started, [install dock-stats](#installation) first.
6+
7+
To render container stats graph run command:
8+
```sh
9+
dock-stats show [CONTAINER-NAME]
10+
```
11+
<img src="/demo.gif" height="500px"/>
12+
13+
14+
### Installation
15+
16+
First, clone the repository:
17+
```sh
18+
git clone https://github.com/zakisk/dock-stats.git
19+
```
20+
21+
run make command:
22+
```sh
23+
# for macOS or linux
24+
make build
25+
26+
# for windows
27+
make build-win
28+
```

‎demo.gif

284 KB
Loading

‎demo.tape

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Where should we write the GIF?
2+
Output demo.gif
3+
4+
# Set up a 1200x600 terminal with 46px font.
5+
Set FontSize 14
6+
Set Width 1200
7+
Set Height 800
8+
9+
# Type a command in the terminal.
10+
Type "./dock-stats show rig-control-plane"
11+
12+
# Pause for dramatic effect...
13+
Sleep 500ms
14+
15+
# Run the command by pressing enter.
16+
Enter
17+
18+
Type "y"
19+
20+
# Admire the output for a bit.
21+
Sleep 20s

‎dock-stats

16.3 MB
Binary file not shown.

‎go.mod

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
module github.com/zakisk/dock-stats
2+
3+
go 1.21.0
4+
5+
require (
6+
github.com/docker/docker v24.0.7+incompatible
7+
github.com/spf13/cobra v1.8.0
8+
github.com/zakisk/drawille-go v0.0.0-20231107102624-6cd3699af712
9+
golang.org/x/crypto v0.14.0
10+
)
11+
12+
require (
13+
atomicgo.dev/cursor v0.2.0 // indirect
14+
atomicgo.dev/keyboard v0.2.9 // indirect
15+
atomicgo.dev/schedule v0.1.0 // indirect
16+
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
17+
github.com/Microsoft/hcsshim v0.11.4 // indirect
18+
github.com/beorn7/perks v1.0.1 // indirect
19+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
20+
github.com/containerd/console v1.0.3 // indirect
21+
github.com/containerd/log v0.1.0 // indirect
22+
github.com/docker/docker-credential-helpers v0.8.0 // indirect
23+
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
24+
github.com/docker/go-metrics v0.0.1 // indirect
25+
github.com/fvbommel/sortorder v1.1.0 // indirect
26+
github.com/gdamore/encoding v1.0.0 // indirect
27+
github.com/gdamore/tcell/v2 v2.6.0 // indirect
28+
github.com/golang/protobuf v1.5.3 // indirect
29+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
30+
github.com/gookit/color v1.5.4 // indirect
31+
github.com/gorilla/mux v1.8.1 // indirect
32+
github.com/imdario/mergo v0.3.16 // indirect
33+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
34+
github.com/klauspost/compress v1.17.2 // indirect
35+
github.com/lithammer/fuzzysearch v1.1.8 // indirect
36+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
37+
github.com/mattn/go-colorable v0.1.13 // indirect
38+
github.com/mattn/go-isatty v0.0.20 // indirect
39+
github.com/mattn/go-runewidth v0.0.15 // indirect
40+
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
41+
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
42+
github.com/miekg/pkcs11 v1.1.1 // indirect
43+
github.com/mitchellh/mapstructure v1.5.0 // indirect
44+
github.com/moby/patternmatcher v0.6.0 // indirect
45+
github.com/moby/sys/sequential v0.5.0 // indirect
46+
github.com/moby/sys/signal v0.7.0 // indirect
47+
github.com/moby/sys/symlink v0.2.0 // indirect
48+
github.com/moby/term v0.5.0 // indirect
49+
github.com/morikuni/aec v1.0.0 // indirect
50+
github.com/olekukonko/tablewriter v0.0.5 // indirect
51+
github.com/opencontainers/runc v1.1.10 // indirect
52+
github.com/prometheus/client_golang v1.17.0 // indirect
53+
github.com/prometheus/client_model v0.5.0 // indirect
54+
github.com/prometheus/common v0.45.0 // indirect
55+
github.com/prometheus/procfs v0.12.0 // indirect
56+
github.com/rivo/uniseg v0.4.4 // indirect
57+
github.com/sirupsen/logrus v1.9.3 // indirect
58+
github.com/spf13/pflag v1.0.5 // indirect
59+
github.com/theupdateframework/notary v0.7.0 // indirect
60+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
61+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
62+
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
63+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
64+
golang.org/x/sync v0.5.0 // indirect
65+
golang.org/x/term v0.13.0 // indirect
66+
golang.org/x/text v0.14.0 // indirect
67+
golang.org/x/time v0.4.0 // indirect
68+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
69+
google.golang.org/grpc v1.59.0 // indirect
70+
google.golang.org/protobuf v1.31.0 // indirect
71+
gopkg.in/yaml.v2 v2.4.0 // indirect
72+
gotest.tools/v3 v3.5.1 // indirect
73+
)
74+
75+
require (
76+
github.com/Microsoft/go-winio v0.6.1 // indirect
77+
github.com/containerd/containerd v1.7.8 // indirect
78+
github.com/distribution/reference v0.5.0 // indirect
79+
github.com/docker/cli v24.0.7+incompatible
80+
github.com/docker/distribution v2.8.3+incompatible // indirect
81+
github.com/docker/go-connections v0.4.0 // indirect
82+
github.com/docker/go-units v0.5.0
83+
github.com/fatih/color v1.16.0
84+
github.com/gogo/protobuf v1.3.2 // indirect
85+
github.com/opencontainers/go-digest v1.0.0 // indirect
86+
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
87+
github.com/pkg/errors v0.9.1 // indirect
88+
github.com/pterm/pterm v0.12.70
89+
github.com/rivo/tview v0.0.0-20231102183219-1b91b8131c43
90+
github.com/zakisk/docker-clone v0.0.0-20230921071151-3a8e0ce67348
91+
golang.org/x/mod v0.14.0 // indirect
92+
golang.org/x/net v0.17.0 // indirect
93+
golang.org/x/sys v0.14.0 // indirect
94+
golang.org/x/tools v0.14.0 // indirect
95+
)

‎go.sum

+895
Large diffs are not rendered by default.

‎internal/cli/root.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Copyright © 2023 Mohammed Zaki zs84907@gmail.com
3+
*/
4+
package cmd
5+
6+
import (
7+
"github.com/spf13/cobra"
8+
"github.com/zakisk/dock-stats/internal/cli/show"
9+
"github.com/zakisk/dock-stats/pkg/utils"
10+
)
11+
12+
// rootCmd represents the base command when called without any subcommands
13+
var rootCmd = &cobra.Command{
14+
Use: "dock-stats",
15+
Short: "Docker containers stats rendering tool",
16+
Long: `As an utility tool it renders container real-time stats more visualy
17+
Usage:
18+
19+
`,
20+
PreRunE: func(cmd *cobra.Command, args []string) error {
21+
if len(args) == 0 {
22+
cmd.Help()
23+
}
24+
return nil
25+
},
26+
RunE: func(cmd *cobra.Command, args []string) error {
27+
if err := utils.IsDockerInstalled(); err != nil {
28+
return err
29+
}
30+
return nil
31+
},
32+
}
33+
34+
func Execute() error {
35+
return rootCmd.Execute()
36+
}
37+
38+
func init() {
39+
rootCmd.AddCommand(show.ShowCmd)
40+
}

‎internal/cli/show/formatter.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package show
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/zakisk/dock-stats/pkg/utils"
8+
"github.com/zakisk/drawille-go"
9+
)
10+
11+
var (
12+
winOSType = "windows"
13+
)
14+
15+
type formatter struct {
16+
id string
17+
name string
18+
data [][]float64
19+
daemonOSType string
20+
netIO string
21+
blockIO string
22+
memoryUsage string
23+
pids uint64
24+
privateWorkingSet string
25+
}
26+
27+
func NewFormatter(daemonOSType string) *formatter {
28+
var data [][]float64
29+
if daemonOSType != winOSType {
30+
data = make([][]float64, 2)
31+
} else {
32+
data = make([][]float64, 1)
33+
}
34+
return &formatter{data: data, daemonOSType: daemonOSType}
35+
}
36+
37+
func (f *formatter) Plot(canvas drawille.Canvas) {
38+
utils.ClearScreen()
39+
containerId := drawille.ColorString(fmt.Sprintf("CONTAINER ID: %s", f.id), drawille.Aqua)
40+
name := drawille.ColorString(fmt.Sprintf("NAME: %s\n\n", f.name), drawille.DarkSlateBlue)
41+
space := strings.Repeat(" ", 6)
42+
net := drawille.ColorString(fmt.Sprintf("NETWORK I/O (Received/Transferred): %s%s", f.netIO, space), drawille.Tomato)
43+
block := drawille.ColorString(fmt.Sprintf("BLOCK I/O (Read/Write): %s\n\n", f.blockIO), drawille.Chartreuse)
44+
diff := len(net) - len(containerId)
45+
space = strings.Repeat(" ", diff - 1)
46+
fmt.Printf("%s%s", containerId, space)
47+
fmt.Print(name)
48+
fmt.Print(net)
49+
fmt.Print(block)
50+
if f.daemonOSType != winOSType {
51+
processes := fmt.Sprintf("PROCESSES (PIDs): %d\n\n", f.pids)
52+
fmt.Print(drawille.ColorString(processes, drawille.DeepPink))
53+
} else {
54+
privateWorkingSet := fmt.Sprintf("PRIVATE WORKING SET: %s\n\n", f.privateWorkingSet)
55+
fmt.Print(drawille.ColorString(privateWorkingSet, drawille.DeepPink))
56+
}
57+
// print line
58+
fmt.Printf("%s\n\n", drawille.ColorString(utils.GetLine(), drawille.Goldenrod))
59+
60+
// CPU and Memory (OS != Windows) Stats
61+
fmt.Printf("%s\n\n", canvas.PlotWithMinAndMax(f.data, 0, 100, true))
62+
cpuUsage := fmt.Sprintf("%.2f", f.data[0][len(f.data[0])-1]) + "%\t\t"
63+
fmt.Print(drawille.ColorString("██ CPU USAGE(%): "+cpuUsage, drawille.DarkCyan))
64+
if f.daemonOSType != winOSType {
65+
memUsage := fmt.Sprintf("%.2f", f.data[1][len(f.data[1])-1]) + "% " + fmt.Sprintf("(%s)\n\n", f.memoryUsage)
66+
memString := drawille.ColorString("██ Memory USAGE(%): "+memUsage, drawille.Yellow)
67+
fmt.Print(memString)
68+
}
69+
}
70+
71+
func (f *formatter) setStats(item *StatsItem) {
72+
f.id = item.ID[:12]
73+
f.name = item.Name
74+
f.data[0] = append(f.data[0], item.CPUPercentage)
75+
if f.daemonOSType != winOSType {
76+
f.data[1] = append(f.data[1], item.MemoryPercentage)
77+
}
78+
f.netIO = item.NetIO()
79+
f.blockIO = item.BlockIO()
80+
f.memoryUsage = item.MemUsage(f.daemonOSType)
81+
f.pids = item.PidsCurrent
82+
f.privateWorkingSet = item.MemUsage(f.daemonOSType)
83+
}

‎internal/cli/show/show.go

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package show
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
"io"
8+
"time"
9+
10+
"github.com/spf13/cobra"
11+
12+
"github.com/docker/cli/cli/command/container"
13+
"github.com/docker/docker/api/types"
14+
sdkClient "github.com/docker/docker/client"
15+
"github.com/pterm/pterm"
16+
"github.com/zakisk/dock-stats/pkg/logger"
17+
"github.com/zakisk/dock-stats/pkg/utils"
18+
"github.com/zakisk/drawille-go"
19+
)
20+
21+
type Attribute int
22+
23+
const escape = "\x1b"
24+
25+
// rootCmd represents the base command when called without any subcommands
26+
var ShowCmd = &cobra.Command{
27+
Use: "show",
28+
Short: "Shows containers stats",
29+
Long: `Command to render container real-time stats
30+
Usage: dock-stats show [CONTAINER]
31+
32+
Example: dock-stats show ubuntu
33+
`,
34+
Run: func(cmd *cobra.Command, args []string) {
35+
log := logger.NewLog()
36+
if len(args) == 0 {
37+
log.Fatal("Please specify container name", log.Args())
38+
}
39+
40+
confirm := pterm.DefaultInteractiveConfirm.
41+
WithDefaultText("Before showing stats, It will clear your terminal, Do you agree?")
42+
result, _ := confirm.Show()
43+
if !result {
44+
return
45+
}
46+
47+
client, err := sdkClient.NewClientWithOpts(sdkClient.FromEnv, sdkClient.WithAPIVersionNegotiation())
48+
if err != nil {
49+
log.Fatal("Error while creating docker client", log.Args("error", err))
50+
}
51+
statsResponse, err := client.ContainerStats(context.Background(), args[0], true)
52+
if err != nil {
53+
log.Fatal("Error while getting container stats", log.Args("error", err))
54+
}
55+
defer statsResponse.Body.Close()
56+
57+
statChan, errChan := getStats(log, statsResponse)
58+
59+
canvas := drawille.NewCanvas(103, 30)
60+
canvas.LineColors = []drawille.Color{
61+
drawille.DarkCyan, // cpu
62+
drawille.Yellow, // memory
63+
}
64+
canvas.LabelColor = drawille.Crimson
65+
canvas.AxisColor = drawille.DarkOrchid
66+
canvas.NumDataPoints = 50
67+
canvas.HorizontalLabels = []string{}
68+
formatter := NewFormatter(statsResponse.OSType)
69+
70+
for {
71+
select {
72+
case stats := <-statChan:
73+
{
74+
formatter.setStats(stats)
75+
formatter.Plot(canvas)
76+
}
77+
case err := <-errChan:
78+
{
79+
if err != nil {
80+
log.Error(err.Error(), log.Args())
81+
if err == io.EOF {
82+
break
83+
}
84+
}
85+
}
86+
}
87+
}
88+
},
89+
}
90+
91+
func getStats(log *logger.Log, statsResponse types.ContainerStats) (chan *StatsItem, chan error) {
92+
statChan := make(chan *StatsItem)
93+
errChan := make(chan error)
94+
var (
95+
previousCPU uint64
96+
previousSystem uint64
97+
)
98+
99+
dec := json.NewDecoder(statsResponse.Body)
100+
go func() {
101+
for {
102+
var (
103+
stats *types.StatsJSON
104+
memPercent, cpuPercent float64
105+
blkRead, blkWrite uint64
106+
mem, memLimit float64
107+
pidsStatsCurrent uint64
108+
)
109+
if err := dec.Decode(&stats); err != nil {
110+
dec = json.NewDecoder(io.MultiReader(dec.Buffered(), statsResponse.Body))
111+
errChan <- err
112+
if err == io.EOF {
113+
statsResponse.Body.Close()
114+
close(statChan)
115+
close(errChan)
116+
break
117+
}
118+
time.Sleep(100 * time.Millisecond)
119+
continue
120+
}
121+
122+
daemonOSType := statsResponse.OSType
123+
124+
// all these values are evaluated using copied code from docker's source
125+
// values showed in this app's output are exactly same as docker gives in `docker stats` command.
126+
if daemonOSType != "windows" {
127+
previousCPU = stats.PreCPUStats.CPUUsage.TotalUsage
128+
previousSystem = stats.PreCPUStats.SystemUsage
129+
cpuPercent = utils.CalculateCPUPercentUnix(previousCPU, previousSystem, stats)
130+
blkRead, blkWrite = utils.CalculateBlockIO(stats.BlkioStats)
131+
mem = utils.CalculateMemUsageUnixNoCache(stats.MemoryStats)
132+
memLimit = float64(stats.MemoryStats.Limit)
133+
memPercent = utils.CalculateMemPercentUnixNoCache(memLimit, mem)
134+
pidsStatsCurrent = stats.PidsStats.Current
135+
} else {
136+
cpuPercent = utils.CalculateCPUPercentWindows(stats)
137+
blkRead = stats.StorageStats.ReadSizeBytes
138+
blkWrite = stats.StorageStats.WriteSizeBytes
139+
mem = float64(stats.MemoryStats.PrivateWorkingSet)
140+
}
141+
netRx, netTx := utils.CalculateNetwork(stats.Networks)
142+
143+
statsItem := &StatsItem{
144+
StatsEntry: container.StatsEntry{
145+
Name: stats.Name,
146+
ID: stats.ID,
147+
CPUPercentage: cpuPercent,
148+
Memory: mem,
149+
MemoryPercentage: memPercent,
150+
MemoryLimit: memLimit,
151+
NetworkRx: netRx,
152+
NetworkTx: netTx,
153+
BlockRead: float64(blkRead),
154+
BlockWrite: float64(blkWrite),
155+
PidsCurrent: pidsStatsCurrent,
156+
},
157+
daemonOSType: daemonOSType,
158+
}
159+
statChan <- statsItem
160+
}
161+
}()
162+
return statChan, errChan
163+
}
164+
165+
func addElement(arr []float64, el float64) []float64 {
166+
arr = append([]float64{el}, arr...)
167+
if len(arr) > 10 {
168+
arr = arr[:10]
169+
}
170+
return arr
171+
}

‎internal/cli/show/stats.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package show
2+
3+
import (
4+
"github.com/docker/cli/cli/command/container"
5+
"github.com/docker/go-units"
6+
)
7+
8+
type StatsItem struct {
9+
container.StatsEntry
10+
daemonOSType string
11+
}
12+
13+
func (s *StatsItem) NetIO() string {
14+
if s.IsInvalid {
15+
return "--"
16+
}
17+
return units.HumanSizeWithPrecision(s.NetworkRx, 3) + " / " + units.HumanSizeWithPrecision(s.NetworkTx, 3)
18+
}
19+
20+
func (s *StatsItem) BlockIO() string {
21+
if s.IsInvalid {
22+
return "--"
23+
}
24+
return units.HumanSizeWithPrecision(s.BlockRead, 3) + " / " + units.HumanSizeWithPrecision(s.BlockWrite, 3)
25+
}
26+
27+
func (s *StatsItem) MemUsage(os string) string {
28+
if s.IsInvalid {
29+
return "-- / --"
30+
}
31+
if os == "windows" {
32+
return units.BytesSize(s.Memory)
33+
}
34+
return units.BytesSize(s.Memory) + " / " + units.BytesSize(s.MemoryLimit)
35+
}

‎main.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package main
2+
3+
import (
4+
5+
// "github.com/guptarohit/asciigraph"
6+
cmd "github.com/zakisk/dock-stats/internal/cli"
7+
"github.com/zakisk/dock-stats/pkg/logger"
8+
)
9+
10+
func main() {
11+
log := logger.NewLog()
12+
err := cmd.Execute()
13+
if err != nil {
14+
log.Logger.Fatal(err.Error())
15+
}
16+
}

‎pkg/errors/errors.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package errors
2+
3+
import (
4+
"github.com/zakisk/dock-stats/pkg/logger"
5+
)
6+
7+
func ErrDockerNotInstalled() *logger.Message {
8+
return &logger.Message{
9+
Message: "Unable to access `docker`",
10+
KeyValues: map[string]any{
11+
logger.KeyError: "Encountered an error while accessing `docker`",
12+
logger.KeyProbableCause: "Docker may not be installed on your machine or not accessible from terminal.",
13+
logger.KeyRemedy: "If `docker` is installed but not accessible from terminal then add its location in `PATH` environment variable.",
14+
},
15+
}
16+
}

‎pkg/logger/constants.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package logger
2+
3+
const (
4+
KeyError = "error"
5+
KeyProbableCause = "Probable Cause"
6+
KeyRemedy = "Remedy"
7+
)

‎pkg/logger/logger.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package logger
2+
3+
import (
4+
"github.com/pterm/pterm"
5+
)
6+
7+
func NewLog() *Log {
8+
logger := pterm.DefaultLogger.WithKeyStyles(map[string]pterm.Style{
9+
KeyError: *pterm.NewStyle(pterm.FgRed),
10+
KeyProbableCause: *pterm.NewStyle(pterm.FgLightMagenta),
11+
KeyRemedy: *pterm.NewStyle(pterm.FgLightYellow),
12+
})
13+
return &Log{Logger: logger}
14+
}
15+
16+
func (l *Log) Trace(message string, args []pterm.LoggerArgument) {
17+
l.Logger.Trace(message, args)
18+
}
19+
20+
func (l *Log) Debug(message string, args []pterm.LoggerArgument) {
21+
l.Logger.Debug(message, args)
22+
}
23+
24+
func (l *Log) Info(message string, args []pterm.LoggerArgument) {
25+
l.Logger.Info(message, args)
26+
}
27+
28+
func (l *Log) Warn(message string, args []pterm.LoggerArgument) {
29+
l.Logger.Warn(message, args)
30+
}
31+
32+
func (l *Log) Error(message string, args []pterm.LoggerArgument) {
33+
l.Logger.Error(message, args)
34+
}
35+
36+
func (l *Log) Fatal(message string, args []pterm.LoggerArgument) {
37+
l.Logger.Fatal(message, args)
38+
}
39+
40+
func (l *Log) Print(message string, args []pterm.LoggerArgument) {
41+
l.Logger.Print(message, args)
42+
}
43+
44+
func (l *Log) Args(args ...any) []pterm.LoggerArgument {
45+
return l.Logger.Args(args)
46+
}
47+
48+
type Log struct {
49+
Logger *pterm.Logger
50+
}
51+
52+
type Logger interface {
53+
Print(message string, args ...pterm.LoggerArgument)
54+
}

‎pkg/logger/message.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package logger
2+
3+
import (
4+
"github.com/pterm/pterm"
5+
)
6+
7+
type Message struct {
8+
Message string
9+
KeyValues map[string]any
10+
}
11+
12+
func (m *Message) Args() []pterm.LoggerArgument {
13+
return pterm.DefaultLogger.ArgsFromMap(m.KeyValues)
14+
}

‎pkg/utils/helpers.go

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"runtime"
8+
9+
"github.com/docker/docker/api/types"
10+
"github.com/fatih/color"
11+
"golang.org/x/crypto/ssh/terminal"
12+
)
13+
14+
func IsDockerInstalled() error {
15+
return exec.Command("docker", "version").Run()
16+
}
17+
18+
func ClearScreen() {
19+
var cmd *exec.Cmd
20+
if runtime.GOOS == "windows" {
21+
cmd = exec.Command("cmd", "/c", "cls")
22+
} else {
23+
cmd = exec.Command("clear")
24+
}
25+
cmd.Stdout = os.Stdout
26+
_ = cmd.Run()
27+
}
28+
29+
// code copied from docker's source code.
30+
func CalculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
31+
var (
32+
cpuPercent = 0.0
33+
// calculate the change for the cpu usage of the container in between readings
34+
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
35+
// calculate the change for the entire system between readings
36+
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
37+
onlineCPUs = float64(v.CPUStats.OnlineCPUs)
38+
)
39+
40+
if onlineCPUs == 0.0 {
41+
onlineCPUs = float64(len(v.CPUStats.CPUUsage.PercpuUsage))
42+
}
43+
if systemDelta > 0.0 && cpuDelta > 0.0 {
44+
cpuPercent = (cpuDelta / systemDelta) * onlineCPUs * 100.0
45+
}
46+
return cpuPercent
47+
}
48+
49+
func CalculateCPUPercentWindows(v *types.StatsJSON) float64 {
50+
// Max number of 100ns intervals between the previous time read and now
51+
possIntervals := uint64(v.Read.Sub(v.PreRead).Nanoseconds()) // Start with number of ns intervals
52+
possIntervals /= 100 // Convert to number of 100ns intervals
53+
possIntervals *= uint64(v.NumProcs) // Multiple by the number of processors
54+
55+
// Intervals used
56+
intervalsUsed := v.CPUStats.CPUUsage.TotalUsage - v.PreCPUStats.CPUUsage.TotalUsage
57+
58+
// Percentage avoiding divide-by-zero
59+
if possIntervals > 0 {
60+
return float64(intervalsUsed) / float64(possIntervals) * 100.0
61+
}
62+
return 0.00
63+
}
64+
65+
func CalculateBlockIO(blkio types.BlkioStats) (uint64, uint64) {
66+
var blkRead, blkWrite uint64
67+
for _, bioEntry := range blkio.IoServiceBytesRecursive {
68+
if len(bioEntry.Op) == 0 {
69+
continue
70+
}
71+
switch bioEntry.Op[0] {
72+
case 'r', 'R':
73+
blkRead = blkRead + bioEntry.Value
74+
case 'w', 'W':
75+
blkWrite = blkWrite + bioEntry.Value
76+
}
77+
}
78+
return blkRead, blkWrite
79+
}
80+
81+
func CalculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
82+
var rx, tx float64
83+
84+
for _, v := range network {
85+
rx += float64(v.RxBytes)
86+
tx += float64(v.TxBytes)
87+
}
88+
return rx, tx
89+
}
90+
91+
// calculateMemUsageUnixNoCache calculate memory usage of the container.
92+
// Cache is intentionally excluded to avoid misinterpretation of the output.
93+
//
94+
// On cgroup v1 host, the result is `mem.Usage - mem.Stats["total_inactive_file"]` .
95+
// On cgroup v2 host, the result is `mem.Usage - mem.Stats["inactive_file"] `.
96+
//
97+
// This definition is consistent with cadvisor and containerd/CRI.
98+
// * https://github.com/google/cadvisor/commit/307d1b1cb320fef66fab02db749f07a459245451
99+
// * https://github.com/containerd/cri/commit/6b8846cdf8b8c98c1d965313d66bc8489166059a
100+
//
101+
// On Docker 19.03 and older, the result was `mem.Usage - mem.Stats["cache"]`.
102+
// See https://github.com/moby/moby/issues/40727 for the background.
103+
func CalculateMemUsageUnixNoCache(mem types.MemoryStats) float64 {
104+
// cgroup v1
105+
if v, isCgroup1 := mem.Stats["total_inactive_file"]; isCgroup1 && v < mem.Usage {
106+
return float64(mem.Usage - v)
107+
}
108+
// cgroup v2
109+
if v := mem.Stats["inactive_file"]; v < mem.Usage {
110+
return float64(mem.Usage - v)
111+
}
112+
return float64(mem.Usage)
113+
}
114+
115+
func CalculateMemPercentUnixNoCache(limit float64, usedNoCache float64) float64 {
116+
// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
117+
// got any data from cgroup
118+
if limit != 0 {
119+
return usedNoCache / limit * 100.0
120+
}
121+
return 0
122+
}
123+
124+
func Box() {
125+
// Function to print colored text
126+
red := color.New(color.FgRed).SprintFunc()
127+
blue := color.New(color.FgBlue).SprintFunc()
128+
129+
// Define the size of the box
130+
width, height := 10, 5
131+
132+
// Print the top of the box
133+
fmt.Print(red("┌"))
134+
for i := 0; i < width-2; i++ {
135+
fmt.Print(red("─"))
136+
}
137+
fmt.Println(red("┐"))
138+
139+
// Print the middle of the box
140+
for i := 0; i < height-2; i++ {
141+
fmt.Print(red("│"))
142+
for j := 0; j < width-2; j++ {
143+
fmt.Print(blue("█")) // Use any color to fill the inside of the box
144+
}
145+
fmt.Println(red("│"))
146+
}
147+
148+
// Print the bottom of the box
149+
fmt.Print(red("└"))
150+
for i := 0; i < width-2; i++ {
151+
fmt.Print(red("─"))
152+
}
153+
fmt.Println(red("┘"))
154+
}
155+
156+
func GetLine() string {
157+
width, _, err := terminal.GetSize(int(os.Stdout.Fd()))
158+
if err != nil {
159+
return ""
160+
}
161+
162+
dashes := ""
163+
for i := 0; i < width; i++ {
164+
dashes += "-"
165+
}
166+
167+
return dashes
168+
}

0 commit comments

Comments
 (0)
Please sign in to comment.