mirror of
https://github.com/bingohuang/docker-labs.git
synced 2025-07-14 10:17:26 +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
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"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) {
|
func GetContainerInfo(id string) (types.ContainerJSON, error) {
|
||||||
return c.ContainerInspect(context.Background(), id)
|
return c.ContainerInspect(context.Background(), id)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@ -10,17 +12,19 @@ import (
|
|||||||
"golang.org/x/text/encoding"
|
"golang.org/x/text/encoding"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
units "github.com/docker/go-units"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rw sync.Mutex
|
var rw sync.Mutex
|
||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
session *Session `json:"-"`
|
session *Session `json:"-"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
conn *types.HijackedResponse `json:"-"`
|
conn *types.HijackedResponse `json:"-"`
|
||||||
ctx context.Context `json:"-"`
|
ctx context.Context `json:"-"`
|
||||||
|
statsReader io.ReadCloser `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) IsConnected() bool {
|
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)
|
wsServer.BroadcastTo(session.Id, "new instance", instance.Name, instance.IP, instance.Hostname)
|
||||||
|
|
||||||
|
// Start collecting stats
|
||||||
|
go instance.CollectStats()
|
||||||
|
|
||||||
return instance, nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +89,67 @@ func (s *sessionWriter) Write(p []byte) (n int, err error) {
|
|||||||
return len(p), nil
|
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 {
|
func (i *Instance) ResizeTerminal(cols, rows uint) error {
|
||||||
return ResizeConnection(i.Name, cols, rows)
|
return ResizeConnection(i.Name, cols, rows)
|
||||||
}
|
}
|
||||||
@ -111,6 +179,11 @@ func GetInstance(session *Session, name string) *Instance {
|
|||||||
return session.Instances[name]
|
return session.Instances[name]
|
||||||
}
|
}
|
||||||
func DeleteInstance(session *Session, instance *Instance) error {
|
func DeleteInstance(session *Session, instance *Instance) error {
|
||||||
|
// stop collecting stats
|
||||||
|
if instance.statsReader != nil {
|
||||||
|
instance.statsReader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Use redis
|
//TODO: Use redis
|
||||||
delete(session.Instances, instance.Name)
|
delete(session.Instances, instance.Name)
|
||||||
err := DeleteContainer(instance.Name)
|
err := DeleteContainer(instance.Name)
|
||||||
|
@ -144,6 +144,13 @@ func LoadSessionsFromDisk() error {
|
|||||||
for _, s := range sessions {
|
for _, s := range sessions {
|
||||||
timeLeft := s.ExpiresAt.Sub(time.Now())
|
timeLeft := s.ExpiresAt.Sub(time.Now())
|
||||||
CloseSessionAfter(s, timeLeft)
|
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()
|
file.Close()
|
||||||
|
@ -132,6 +132,12 @@
|
|||||||
$scope.connected = true;
|
$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;
|
$scope.socket = socket;
|
||||||
|
|
||||||
var i = response.data;
|
var i = response.data;
|
||||||
|
@ -46,3 +46,10 @@ md-card-content.terminal {
|
|||||||
.disconnected {
|
.disconnected {
|
||||||
background-color: #FDF4B6;
|
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">
|
<html ng-app="DockerPlay" ng-controller="PlayController">
|
||||||
<head>
|
<head>
|
||||||
<title>Docker Playground</title>
|
<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="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/xterm.css" />
|
||||||
<link rel="stylesheet" href="/assets/style.css" />
|
<link rel="stylesheet" href="/assets/style.css" />
|
||||||
@ -64,7 +65,20 @@
|
|||||||
<md-card-title>
|
<md-card-title>
|
||||||
<md-card-title-text>
|
<md-card-title-text>
|
||||||
<span class="md-headline">{{instance.name}}</span>
|
<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-text>
|
||||||
</md-card-title>
|
</md-card-title>
|
||||||
<md-card-actions>
|
<md-card-actions>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user