mirror of
https://github.com/bingohuang/docker-labs.git
synced 2025-07-14 01:57:32 +08:00
Show cpu and memory stats of every node
This commit is contained in:
parent
bc7dbead33
commit
8b0749a9ba
@ -1,6 +1,7 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
@ -28,6 +29,12 @@ func init() {
|
||||
|
||||
}
|
||||
|
||||
func GetContainerStats(id string) (io.ReadCloser, error) {
|
||||
stats, err := c.ContainerStats(context.Background(), id, true)
|
||||
|
||||
return stats.Body, err
|
||||
}
|
||||
|
||||
func GetContainerInfo(id string) (types.ContainerJSON, error) {
|
||||
return c.ContainerInspect(context.Background(), id)
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
@ -10,17 +12,19 @@ import (
|
||||
"golang.org/x/text/encoding"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
var rw sync.Mutex
|
||||
|
||||
type Instance struct {
|
||||
session *Session `json:"-"`
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
conn *types.HijackedResponse `json:"-"`
|
||||
ctx context.Context `json:"-"`
|
||||
session *Session `json:"-"`
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
conn *types.HijackedResponse `json:"-"`
|
||||
ctx context.Context `json:"-"`
|
||||
statsReader io.ReadCloser `json:"-"`
|
||||
}
|
||||
|
||||
func (i *Instance) IsConnected() bool {
|
||||
@ -70,6 +74,9 @@ func NewInstance(session *Session) (*Instance, error) {
|
||||
|
||||
wsServer.BroadcastTo(session.Id, "new instance", instance.Name, instance.IP, instance.Hostname)
|
||||
|
||||
// Start collecting stats
|
||||
go instance.CollectStats()
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
@ -82,6 +89,67 @@ func (s *sessionWriter) Write(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (o *Instance) CollectStats() {
|
||||
reader, err := GetContainerStats(o.Hostname)
|
||||
if err != nil {
|
||||
log.Println("Error while trying to collect instance stats", err)
|
||||
return
|
||||
}
|
||||
o.statsReader = reader
|
||||
dec := json.NewDecoder(o.statsReader)
|
||||
var (
|
||||
mem = 0.0
|
||||
memLimit = 0.0
|
||||
memPercent = 0.0
|
||||
v *types.StatsJSON
|
||||
memFormatted = ""
|
||||
|
||||
cpuPercent = 0.0
|
||||
previousCPU uint64
|
||||
previousSystem uint64
|
||||
cpuFormatted = ""
|
||||
)
|
||||
for {
|
||||
e := dec.Decode(&v)
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Memory
|
||||
if v.MemoryStats.Limit != 0 {
|
||||
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
||||
}
|
||||
mem = float64(v.MemoryStats.Usage)
|
||||
memLimit = float64(v.MemoryStats.Limit)
|
||||
|
||||
memFormatted = fmt.Sprintf("%.2f%% (%s / %s)", memPercent, units.BytesSize(mem), units.BytesSize(memLimit))
|
||||
|
||||
// cpu
|
||||
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
|
||||
previousSystem = v.PreCPUStats.SystemUsage
|
||||
cpuPercent = calculateCPUPercentUnix(previousCPU, previousSystem, v)
|
||||
cpuFormatted = fmt.Sprintf("%.2f%%", cpuPercent)
|
||||
|
||||
wsServer.BroadcastTo(o.session.Id, "instance stats", o.Name, memFormatted, cpuFormatted)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
||||
var (
|
||||
cpuPercent = 0.0
|
||||
// calculate the change for the cpu usage of the container in between readings
|
||||
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
|
||||
// calculate the change for the entire system between readings
|
||||
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
|
||||
)
|
||||
|
||||
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
||||
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
||||
}
|
||||
return cpuPercent
|
||||
}
|
||||
|
||||
func (i *Instance) ResizeTerminal(cols, rows uint) error {
|
||||
return ResizeConnection(i.Name, cols, rows)
|
||||
}
|
||||
@ -111,6 +179,11 @@ func GetInstance(session *Session, name string) *Instance {
|
||||
return session.Instances[name]
|
||||
}
|
||||
func DeleteInstance(session *Session, instance *Instance) error {
|
||||
// stop collecting stats
|
||||
if instance.statsReader != nil {
|
||||
instance.statsReader.Close()
|
||||
}
|
||||
|
||||
//TODO: Use redis
|
||||
delete(session.Instances, instance.Name)
|
||||
err := DeleteContainer(instance.Name)
|
||||
|
@ -144,6 +144,13 @@ func LoadSessionsFromDisk() error {
|
||||
for _, s := range sessions {
|
||||
timeLeft := s.ExpiresAt.Sub(time.Now())
|
||||
CloseSessionAfter(s, timeLeft)
|
||||
|
||||
// start collecting stats for every instance
|
||||
for _, i := range s.Instances {
|
||||
// wire the session back to the instance
|
||||
i.session = s
|
||||
go i.CollectStats()
|
||||
}
|
||||
}
|
||||
}
|
||||
file.Close()
|
||||
|
@ -132,6 +132,12 @@
|
||||
$scope.connected = true;
|
||||
});
|
||||
|
||||
socket.on('instance stats', function(name, mem, cpu) {
|
||||
$scope.idx[name].mem = mem;
|
||||
$scope.idx[name].cpu = cpu;
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
$scope.socket = socket;
|
||||
|
||||
var i = response.data;
|
||||
|
@ -46,3 +46,10 @@ md-card-content.terminal {
|
||||
.disconnected {
|
||||
background-color: #FDF4B6;
|
||||
}
|
||||
md-input-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
md-input-container .md-errors-spacer {
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html ng-app="DockerPlay" ng-controller="PlayController">
|
||||
<head>
|
||||
<title>Docker Playground</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic" />
|
||||
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.css">
|
||||
<link rel="stylesheet" href="/assets/xterm.css" />
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
@ -64,7 +65,20 @@
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<span class="md-headline">{{instance.name}}</span>
|
||||
<span class="md-subhead">{{instance.ip}}</span>
|
||||
<md-input-container class="md-icon-float md-block">
|
||||
<label>IP</label>
|
||||
<input ng-model="instance.ip" type="text" readonly="readonly">
|
||||
</md-input-container>
|
||||
<div layout-gt-sm="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>Memory</label>
|
||||
<input ng-model="instance.mem" type="text" readonly="readonly">
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>CPU</label>
|
||||
<input ng-model="instance.cpu" type="text" readonly="readonly">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-actions>
|
||||
|
Loading…
x
Reference in New Issue
Block a user