mirror of
https://github.com/bcicen/ctop.git
synced 2025-12-06 15:16:41 +08:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
617b1b2863 | ||
|
|
e59a360b60 | ||
|
|
91cd53a878 | ||
|
|
c6f2c7b617 | ||
|
|
568bfb2513 | ||
|
|
66ff8ad7ec | ||
|
|
400d9471b6 | ||
|
|
288380ca8d | ||
|
|
429d5b9101 | ||
|
|
1be452d7c0 | ||
|
|
671c944272 | ||
|
|
a48a9031cc | ||
|
|
aff6943d07 | ||
|
|
f56ff96b88 | ||
|
|
b5361c2a28 | ||
|
|
e71b6cacce | ||
|
|
8a1297d3c5 | ||
|
|
bdea7d5853 | ||
|
|
389dee0f3c | ||
|
|
b49e174483 | ||
|
|
53b612ab07 | ||
|
|
446708e456 | ||
|
|
1233ff0ead | ||
|
|
af3f1e2a85 | ||
|
|
4dbc5653ff | ||
|
|
e8d9f3327c | ||
|
|
d372043a17 | ||
|
|
eeac65da8c | ||
|
|
c1780ae30a | ||
|
|
fb39d69fa7 | ||
|
|
6392d63ff8 | ||
|
|
b009a260a4 | ||
|
|
44379cd9fd | ||
|
|
b85ca680f0 | ||
|
|
8fb7a8988f | ||
|
|
6d097c2085 | ||
|
|
f9d68d688d | ||
|
|
bc08b85191 | ||
|
|
f3d26e038d | ||
|
|
b4e1fbf290 | ||
|
|
58d5fba945 | ||
|
|
c76036a6f2 | ||
|
|
6a8848d1e2 | ||
|
|
02d1050130 | ||
|
|
ccb44c964c | ||
|
|
b2165b6a29 | ||
|
|
d81d10ec27 | ||
|
|
9529c04680 | ||
|
|
6a89c9af38 | ||
|
|
06a29fc912 | ||
|
|
2cba7253fc | ||
|
|
47d60fe51b | ||
|
|
28f16c9a17 | ||
|
|
6560768e08 | ||
|
|
084c0c4ec8 | ||
|
|
5db90f31dc | ||
|
|
82677d52ef | ||
|
|
2b2338805b | ||
|
|
60213f1551 | ||
|
|
8aa932b29f | ||
|
|
35cc8d095d | ||
|
|
30530bc2a1 | ||
|
|
2c282923c0 | ||
|
|
d0d39749de | ||
|
|
26b88a9790 | ||
|
|
a135a67c06 | ||
|
|
19b212f45d | ||
|
|
34987df010 | ||
|
|
e2bc4d0a08 | ||
|
|
4ac1348fbb | ||
|
|
66d78a7d74 | ||
|
|
e62a8881a2 | ||
|
|
a5b2e7b074 |
11
Dockerfile
11
Dockerfile
@@ -1,10 +1,3 @@
|
||||
FROM quay.io/vektorcloud/glibc:latest
|
||||
|
||||
ARG CTOP_VERSION=0.5
|
||||
ENV CTOP_URL https://github.com/bcicen/ctop/releases/download/v${CTOP_VERSION}/ctop-${CTOP_VERSION}-linux-amd64
|
||||
|
||||
RUN echo $CTOP_URL && \
|
||||
wget -q $CTOP_URL -O /ctop && \
|
||||
chmod +x /ctop
|
||||
|
||||
FROM scratch
|
||||
COPY ./ctop /ctop
|
||||
ENTRYPOINT ["/ctop"]
|
||||
|
||||
12
Dockerfile_build
Normal file
12
Dockerfile_build
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM quay.io/vektorcloud/go:1.8
|
||||
|
||||
RUN apk add --no-cache make
|
||||
|
||||
COPY glide.* /go/src/github.com/bcicen/ctop/
|
||||
WORKDIR /go/src/github.com/bcicen/ctop/
|
||||
RUN glide install
|
||||
|
||||
COPY . /go/src/github.com/bcicen/ctop
|
||||
RUN make build && \
|
||||
mkdir -p /go/bin && \
|
||||
mv -v ctop /go/bin/
|
||||
37
Makefile
Normal file
37
Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
NAME=ctop
|
||||
VERSION=$(shell cat VERSION)
|
||||
BUILD=$(shell git rev-parse --short HEAD)
|
||||
EXT_LD_FLAGS="-Wl,--allow-multiple-definition"
|
||||
LD_FLAGS="-w -X main.version=$(VERSION) -X main.build=$(BUILD) -extldflags=$(EXT_LD_FLAGS)"
|
||||
|
||||
clean:
|
||||
rm -rf build/ release/
|
||||
|
||||
build:
|
||||
glide install
|
||||
CGO_ENABLED=0 go build -tags release -ldflags $(LD_FLAGS) -o ctop
|
||||
|
||||
build-dev:
|
||||
go build -ldflags "-w -X main.version=$(VERSION)-dev -X main.build=$(BUILD) -extldflags=$(EXT_LD_FLAGS)"
|
||||
|
||||
build-all:
|
||||
mkdir -p build
|
||||
GOOS=darwin GOARCH=amd64 go build -tags release -ldflags $(LD_FLAGS) -o build/ctop-$(VERSION)-darwin-amd64
|
||||
GOOS=linux GOARCH=amd64 go build -tags release -ldflags $(LD_FLAGS) -o build/ctop-$(VERSION)-linux-amd64
|
||||
GOOS=linux GOARCH=arm go build -tags release -ldflags $(LD_FLAGS) -o build/ctop-$(VERSION)-linux-arm
|
||||
GOOS=linux GOARCH=arm64 go build -tags release -ldflags $(LD_FLAGS) -o build/ctop-$(VERSION)-linux-arm64
|
||||
|
||||
image:
|
||||
docker build -t ctop_build -f Dockerfile_build .
|
||||
docker create --name=ctop_built ctop_build ctop -v
|
||||
docker cp ctop_built:/go/bin/ctop .
|
||||
docker build -t ctop -f Dockerfile .
|
||||
|
||||
release:
|
||||
mkdir release
|
||||
go get github.com/progrium/gh-release/...
|
||||
cp build/* release
|
||||
gh-release create bcicen/$(NAME) $(VERSION) \
|
||||
$(shell git rev-parse --abbrev-ref HEAD) $(VERSION)
|
||||
|
||||
.PHONY: build
|
||||
44
README.md
44
README.md
@@ -1,6 +1,9 @@
|
||||
<p align="center"><img width="200px" src="/_docs/img/logo.png" alt="ctop"/></p>
|
||||
|
||||
#
|
||||
|
||||
![release][release] ![homebrew][homebrew]
|
||||
|
||||
Top-like interface for container metrics
|
||||
|
||||
`ctop` provides a concise and condensed overview of real-time metrics for multiple containers:
|
||||
@@ -8,7 +11,7 @@ Top-like interface for container metrics
|
||||
|
||||
as well as an [expanded view][expanded_view] for inspecting a specific container.
|
||||
|
||||
`ctop` currently comes with built-in support for Docker; connectors for other container and cluster systems are planned for future releases.
|
||||
`ctop` comes with built-in support for Docker and runC; connectors for other container and cluster systems are planned for future releases.
|
||||
|
||||
## Install
|
||||
|
||||
@@ -17,44 +20,39 @@ Fetch the [latest release](https://github.com/bcicen/ctop/releases) for your pla
|
||||
#### Linux
|
||||
|
||||
```bash
|
||||
wget https://github.com/bcicen/ctop/releases/download/v0.5/ctop-0.5-linux-amd64 -O ctop
|
||||
sudo mv ctop /usr/local/bin/
|
||||
sudo wget https://github.com/bcicen/ctop/releases/download/v0.6/ctop-0.6-linux-amd64 -O /usr/local/bin/ctop
|
||||
sudo chmod +x /usr/local/bin/ctop
|
||||
```
|
||||
|
||||
#### OS X
|
||||
|
||||
```bash
|
||||
curl -Lo ctop https://github.com/bcicen/ctop/releases/download/v0.5/ctop-0.5-darwin-amd64
|
||||
sudo mv ctop /usr/local/bin/
|
||||
brew install ctop
|
||||
```
|
||||
or
|
||||
```bash
|
||||
sudo curl -Lo /usr/local/bin/ctop https://github.com/bcicen/ctop/releases/download/v0.6/ctop-0.6-darwin-amd64
|
||||
sudo chmod +x /usr/local/bin/ctop
|
||||
```
|
||||
|
||||
or run via Docker:
|
||||
#### Docker
|
||||
|
||||
```bash
|
||||
docker run -ti --name ctop --rm -v /var/run/docker.sock:/var/run/docker.sock quay.io/vektorlab/ctop:latest
|
||||
docker run --rm -ti \
|
||||
--name=ctop \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
quay.io/vektorlab/ctop:latest
|
||||
```
|
||||
|
||||
`ctop` is also available for Arch in the [AUR](https://aur.archlinux.org/packages/ctop/)
|
||||
`ctop` is also available for Arch in the [AUR](https://aur.archlinux.org/packages/ctop-bin/)
|
||||
|
||||
## Building
|
||||
|
||||
To build `ctop` from source, ensure you have a recent version of [glide](http://glide.sh/) installed and run:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/bcicen/ctop.git $GOPATH/src/github.com/bcicen/ctop && \
|
||||
cd $GOPATH/src/github.com/bcicen/ctop && \
|
||||
glide install && \
|
||||
go build
|
||||
```
|
||||
Build steps can be found [here][build].
|
||||
|
||||
## Usage
|
||||
|
||||
`ctop` requires no arguments and will configure itself using the `DOCKER_HOST` environment variable
|
||||
```bash
|
||||
export DOCKER_HOST=tcp://127.0.0.1:4243
|
||||
ctop
|
||||
```
|
||||
`ctop` requires no arguments and uses Docker host variables by default. See [connectors][connectors] for further configuration options.
|
||||
|
||||
### Options
|
||||
|
||||
@@ -80,4 +78,8 @@ s | Select container sort field
|
||||
r | Reverse container sort order
|
||||
q | Quit ctop
|
||||
|
||||
[build]: _docs/build.md
|
||||
[connectors]: _docs/connectors.md
|
||||
[expanded_view]: _docs/expanded.md
|
||||
[release]: https://img.shields.io/github/release/bcicen/ctop.svg "ctop"
|
||||
[homebrew]: https://img.shields.io/homebrew/v/ctop.svg "ctop"
|
||||
|
||||
20
_docs/build.md
Normal file
20
_docs/build.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Build
|
||||
|
||||
To build `ctop` from source, ensure you have a recent version of [glide](https://github.com/Masterminds/glide) installed and run:
|
||||
|
||||
```bash
|
||||
go get github.com/bcicen/ctop && \
|
||||
cd $GOPATH/src/github.com/bcicen/ctop && \
|
||||
make build
|
||||
```
|
||||
|
||||
To build a minimal Docker image containing only `ctop`:
|
||||
```bash
|
||||
make image
|
||||
```
|
||||
|
||||
Now you can run your local image:
|
||||
|
||||
```bash
|
||||
docker run -ti --name ctop --rm -v /var/run/docker.sock:/var/run/docker.sock ctop
|
||||
```
|
||||
26
_docs/connectors.md
Normal file
26
_docs/connectors.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# connectors
|
||||
|
||||
`ctop` comes with the below native connectors, enabled via the `--connector` option.
|
||||
|
||||
Default connector behavior can be changed by setting the relevant environment variables.
|
||||
|
||||
## Docker
|
||||
|
||||
Default connector, configurable via standard [Docker commandline varaibles](https://docs.docker.com/engine/reference/commandline/cli/#environment-variables)
|
||||
|
||||
#### Options
|
||||
|
||||
Var | Description
|
||||
--- | ---
|
||||
DOCKER_HOST | Daemon socket to connect to (default: `unix://var/run/docker.sock`)
|
||||
|
||||
## RunC
|
||||
|
||||
Using this connector requires full privileges to the local runC root dir (default: `/run/runc`)
|
||||
|
||||
#### Options
|
||||
|
||||
Var | Description
|
||||
--- | ---
|
||||
RUNC_ROOT | path to runc root (default: `/run/runc`)
|
||||
RUNC_SYSTEMD_CGROUP | if set, enable systemd cgroups
|
||||
56
_docs/debug.md
Normal file
56
_docs/debug.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Debug Mode
|
||||
|
||||
`ctop` comes with a built-in logging facility and local socket server to simplify debugging at run time.
|
||||
|
||||
## Quick Start
|
||||
|
||||
If running `ctop` via Docker, debug logging can be most easily enabled as below:
|
||||
```bash
|
||||
docker run -ti --rm \
|
||||
--name=ctop \
|
||||
-e CTOP_DEBUG=1 \
|
||||
-e CTOP_DEBUG_TCP=1 \
|
||||
-p 9000:9000 \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
quay.io/vektorlab/ctop:latest
|
||||
```
|
||||
|
||||
Log messages can be followed by connecting to the default listen address:
|
||||
```bash
|
||||
curl -s localhost:9000
|
||||
```
|
||||
|
||||
example output:
|
||||
```
|
||||
15:06:43.881 ▶ NOTI 002 logger initialized
|
||||
15:06:43.881 ▶ INFO 003 loaded config param: "filterStr": ""
|
||||
15:06:43.881 ▶ INFO 004 loaded config param: "sortField": "state"
|
||||
15:06:43.881 ▶ INFO 005 loaded config switch: "sortReversed": false
|
||||
15:06:43.881 ▶ INFO 006 loaded config switch: "allContainers": true
|
||||
15:06:43.881 ▶ INFO 007 loaded config switch: "enableHeader": true
|
||||
15:06:43.883 ▶ INFO 008 collector started for container: 7120f83ca...
|
||||
...
|
||||
```
|
||||
|
||||
## Unix Socket
|
||||
|
||||
Debug mode is enabled via the `CTOP_DEBUG` environment variable:
|
||||
|
||||
```bash
|
||||
CTOP_DEBUG=1 ./ctop
|
||||
```
|
||||
|
||||
While `ctop` is running, you can connect to the logging socket via socat or similar tools:
|
||||
```bash
|
||||
socat unix-connect:./ctop.sock stdio
|
||||
```
|
||||
|
||||
## TCP Logging Socket
|
||||
|
||||
In lieu of using a local unix socket, TCP logging can be enabled via the `CTOP_DEBUG_TCP` environment variable:
|
||||
|
||||
```bash
|
||||
CTOP_DEBUG=1 CTOP_DEBUG_TCP=1 ./ctop
|
||||
```
|
||||
|
||||
A TCP listener for streaming log messages will be started on the default listen address(`0.0.0.0:9000`)
|
||||
@@ -7,7 +7,7 @@ machine:
|
||||
dependencies:
|
||||
override:
|
||||
- docker info
|
||||
- docker build --build-arg CTOP_VERSION=$(cat VERSION) -t ctop .
|
||||
- make image
|
||||
|
||||
test:
|
||||
override:
|
||||
|
||||
@@ -18,7 +18,6 @@ func Init() {
|
||||
GlobalParams = append(GlobalParams, p)
|
||||
log.Infof("loaded config param: %s: %s", quote(p.Key), quote(p.Val))
|
||||
}
|
||||
|
||||
for _, s := range switches {
|
||||
GlobalSwitches = append(GlobalSwitches, s)
|
||||
log.Infof("loaded config switch: %s: %t", quote(s.Key), s.Val)
|
||||
|
||||
@@ -17,11 +17,6 @@ var switches = []*Switch{
|
||||
Val: true,
|
||||
Label: "Enable Status Header",
|
||||
},
|
||||
&Switch{
|
||||
Key: "loggingEnabled",
|
||||
Val: false,
|
||||
Label: "Enable Logging Server",
|
||||
},
|
||||
}
|
||||
|
||||
type Switch struct {
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package metrics
|
||||
package collector
|
||||
|
||||
import (
|
||||
"github.com/bcicen/ctop/metrics"
|
||||
api "github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
// Docker collector
|
||||
type Docker struct {
|
||||
Metrics
|
||||
metrics.Metrics
|
||||
id string
|
||||
client *api.Client
|
||||
running bool
|
||||
stream chan Metrics
|
||||
stream chan metrics.Metrics
|
||||
done chan bool
|
||||
lastCpu float64
|
||||
lastSysCpu float64
|
||||
@@ -18,7 +19,7 @@ type Docker struct {
|
||||
|
||||
func NewDocker(client *api.Client, id string) *Docker {
|
||||
return &Docker{
|
||||
Metrics: Metrics{},
|
||||
Metrics: metrics.Metrics{},
|
||||
id: id,
|
||||
client: client,
|
||||
}
|
||||
@@ -26,7 +27,7 @@ func NewDocker(client *api.Client, id string) *Docker {
|
||||
|
||||
func (c *Docker) Start() {
|
||||
c.done = make(chan bool)
|
||||
c.stream = make(chan Metrics)
|
||||
c.stream = make(chan metrics.Metrics)
|
||||
stats := make(chan *api.Stats)
|
||||
|
||||
go func() {
|
||||
@@ -60,7 +61,7 @@ func (c *Docker) Running() bool {
|
||||
return c.running
|
||||
}
|
||||
|
||||
func (c *Docker) Stream() chan Metrics {
|
||||
func (c *Docker) Stream() chan metrics.Metrics {
|
||||
return c.stream
|
||||
}
|
||||
|
||||
@@ -86,7 +87,7 @@ func (c *Docker) ReadCPU(stats *api.Stats) {
|
||||
func (c *Docker) ReadMem(stats *api.Stats) {
|
||||
c.MemUsage = int64(stats.MemoryStats.Usage)
|
||||
c.MemLimit = int64(stats.MemoryStats.Limit)
|
||||
c.MemPercent = round((float64(c.MemUsage) / float64(c.MemLimit)) * 100)
|
||||
c.MemPercent = percent(float64(c.MemUsage), float64(c.MemLimit))
|
||||
}
|
||||
|
||||
func (c *Docker) ReadNet(stats *api.Stats) {
|
||||
21
connector/collector/main.go
Normal file
21
connector/collector/main.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/bcicen/ctop/logging"
|
||||
)
|
||||
|
||||
var log = logging.Init()
|
||||
|
||||
func round(num float64) int {
|
||||
return int(num + math.Copysign(0.5, num))
|
||||
}
|
||||
|
||||
// return rounded percentage
|
||||
func percent(val float64, total float64) int {
|
||||
if total <= 0 {
|
||||
return 0
|
||||
}
|
||||
return round((val / total) * 100)
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
// +build !release
|
||||
|
||||
package metrics
|
||||
package collector
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/bcicen/ctop/metrics"
|
||||
)
|
||||
|
||||
// Mock collector
|
||||
type Mock struct {
|
||||
Metrics
|
||||
stream chan Metrics
|
||||
metrics.Metrics
|
||||
stream chan metrics.Metrics
|
||||
done bool
|
||||
running bool
|
||||
aggression int64
|
||||
@@ -18,7 +20,7 @@ type Mock struct {
|
||||
|
||||
func NewMock(a int64) *Mock {
|
||||
c := &Mock{
|
||||
Metrics: Metrics{},
|
||||
Metrics: metrics.Metrics{},
|
||||
aggression: a,
|
||||
}
|
||||
c.MemLimit = 2147483648
|
||||
@@ -31,7 +33,7 @@ func (c *Mock) Running() bool {
|
||||
|
||||
func (c *Mock) Start() {
|
||||
c.done = false
|
||||
c.stream = make(chan Metrics)
|
||||
c.stream = make(chan metrics.Metrics)
|
||||
go c.run()
|
||||
}
|
||||
|
||||
@@ -39,7 +41,7 @@ func (c *Mock) Stop() {
|
||||
c.done = true
|
||||
}
|
||||
|
||||
func (c *Mock) Stream() chan Metrics {
|
||||
func (c *Mock) Stream() chan metrics.Metrics {
|
||||
return c.stream
|
||||
}
|
||||
|
||||
@@ -48,6 +50,11 @@ func (c *Mock) run() {
|
||||
rand.Seed(int64(time.Now().Nanosecond()))
|
||||
defer close(c.stream)
|
||||
|
||||
// set to random static value, once
|
||||
c.Pids = rand.Intn(12)
|
||||
c.IOBytesRead = rand.Int63n(8098) * c.aggression
|
||||
c.IOBytesWrite = rand.Int63n(8098) * c.aggression
|
||||
|
||||
for {
|
||||
c.CPUUtil += rand.Intn(2) * int(c.aggression)
|
||||
if c.CPUUtil >= 100 {
|
||||
@@ -60,7 +67,7 @@ func (c *Mock) run() {
|
||||
if c.MemUsage > c.MemLimit {
|
||||
c.MemUsage = 0
|
||||
}
|
||||
c.MemPercent = round((float64(c.MemUsage) / float64(c.MemLimit)) * 100)
|
||||
c.MemPercent = percent(float64(c.MemUsage), float64(c.MemLimit))
|
||||
c.stream <- c.Metrics
|
||||
if c.done {
|
||||
break
|
||||
44
connector/collector/proc.go
Normal file
44
connector/collector/proc.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// +build !darwin
|
||||
|
||||
package collector
|
||||
|
||||
import (
|
||||
linuxproc "github.com/c9s/goprocinfo/linux"
|
||||
"github.com/opencontainers/runc/libcontainer/system"
|
||||
)
|
||||
|
||||
var sysMemTotal = getSysMemTotal()
|
||||
var clockTicksPerSecond = uint64(system.GetClockTicks())
|
||||
|
||||
const nanoSecondsPerSecond = 1e9
|
||||
|
||||
func getSysMemTotal() int64 {
|
||||
stat, err := linuxproc.ReadMemInfo("/proc/meminfo")
|
||||
if err != nil {
|
||||
log.Errorf("error reading system stats: %s", err)
|
||||
return 0
|
||||
}
|
||||
return int64(stat.MemTotal * 1024)
|
||||
}
|
||||
|
||||
// return cumulative system cpu usage in nanoseconds
|
||||
func getSysCPUUsage() uint64 {
|
||||
stat, err := linuxproc.ReadStat("/proc/stat")
|
||||
if err != nil {
|
||||
log.Errorf("error reading system stats: %s", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
sum := stat.CPUStatAll.User +
|
||||
stat.CPUStatAll.Nice +
|
||||
stat.CPUStatAll.System +
|
||||
stat.CPUStatAll.Idle +
|
||||
stat.CPUStatAll.IOWait +
|
||||
stat.CPUStatAll.IRQ +
|
||||
stat.CPUStatAll.SoftIRQ +
|
||||
stat.CPUStatAll.Steal +
|
||||
stat.CPUStatAll.Guest +
|
||||
stat.CPUStatAll.GuestNice
|
||||
|
||||
return (sum * nanoSecondsPerSecond) / clockTicksPerSecond
|
||||
}
|
||||
124
connector/collector/runc.go
Normal file
124
connector/collector/runc.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// +build !darwin
|
||||
|
||||
package collector
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bcicen/ctop/metrics"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
)
|
||||
|
||||
// Runc collector
|
||||
type Runc struct {
|
||||
metrics.Metrics
|
||||
id string
|
||||
libc libcontainer.Container
|
||||
stream chan metrics.Metrics
|
||||
done bool
|
||||
running bool
|
||||
interval int // collection interval, in seconds
|
||||
lastCpu float64
|
||||
lastSysCpu float64
|
||||
}
|
||||
|
||||
func NewRunc(libc libcontainer.Container) *Runc {
|
||||
c := &Runc{
|
||||
Metrics: metrics.Metrics{},
|
||||
id: libc.ID(),
|
||||
libc: libc,
|
||||
interval: 1,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Runc) Running() bool {
|
||||
return c.running
|
||||
}
|
||||
|
||||
func (c *Runc) Start() {
|
||||
c.done = false
|
||||
c.stream = make(chan metrics.Metrics)
|
||||
go c.run()
|
||||
}
|
||||
|
||||
func (c *Runc) Stop() {
|
||||
c.done = true
|
||||
}
|
||||
|
||||
func (c *Runc) Stream() chan metrics.Metrics {
|
||||
return c.stream
|
||||
}
|
||||
|
||||
func (c *Runc) run() {
|
||||
c.running = true
|
||||
defer close(c.stream)
|
||||
log.Debugf("collector started for container: %s", c.id)
|
||||
|
||||
for {
|
||||
stats, err := c.libc.Stats()
|
||||
if err != nil {
|
||||
log.Errorf("failed to collect stats for container %s:\n%s", c.id, err)
|
||||
break
|
||||
}
|
||||
|
||||
c.ReadCPU(stats.CgroupStats)
|
||||
c.ReadMem(stats.CgroupStats)
|
||||
c.ReadNet(stats.Interfaces)
|
||||
|
||||
c.stream <- c.Metrics
|
||||
if c.done {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
c.running = false
|
||||
}
|
||||
|
||||
func (c *Runc) ReadCPU(stats *cgroups.Stats) {
|
||||
u := stats.CpuStats.CpuUsage
|
||||
ncpus := float64(len(u.PercpuUsage))
|
||||
total := float64(u.TotalUsage)
|
||||
system := float64(getSysCPUUsage())
|
||||
|
||||
cpudiff := total - c.lastCpu
|
||||
syscpudiff := system - c.lastSysCpu
|
||||
|
||||
c.CPUUtil = round((cpudiff / syscpudiff * 100) * ncpus)
|
||||
c.lastCpu = total
|
||||
c.lastSysCpu = system
|
||||
c.Pids = int(stats.PidsStats.Current)
|
||||
}
|
||||
|
||||
func (c *Runc) ReadMem(stats *cgroups.Stats) {
|
||||
c.MemUsage = int64(stats.MemoryStats.Usage.Usage)
|
||||
c.MemLimit = int64(stats.MemoryStats.Usage.Limit)
|
||||
if c.MemLimit > sysMemTotal && sysMemTotal > 0 {
|
||||
c.MemLimit = sysMemTotal
|
||||
}
|
||||
c.MemPercent = percent(float64(c.MemUsage), float64(c.MemLimit))
|
||||
}
|
||||
|
||||
func (c *Runc) ReadNet(interfaces []*libcontainer.NetworkInterface) {
|
||||
var rx, tx int64
|
||||
for _, network := range interfaces {
|
||||
rx += int64(network.RxBytes)
|
||||
tx += int64(network.TxBytes)
|
||||
}
|
||||
c.NetRx, c.NetTx = rx, tx
|
||||
}
|
||||
|
||||
func (c *Runc) ReadIO(stats *cgroups.Stats) {
|
||||
var read, write int64
|
||||
for _, blk := range stats.BlkioStats.IoServiceBytesRecursive {
|
||||
if blk.Op == "Read" {
|
||||
read = int64(blk.Value)
|
||||
}
|
||||
if blk.Op == "Write" {
|
||||
write = int64(blk.Value)
|
||||
}
|
||||
}
|
||||
c.IOBytesRead, c.IOBytesWrite = read, write
|
||||
}
|
||||
@@ -1,35 +1,31 @@
|
||||
package main
|
||||
package connector
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bcicen/ctop/metrics"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"github.com/bcicen/ctop/connector/collector"
|
||||
"github.com/bcicen/ctop/container"
|
||||
api "github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
type ContainerSource interface {
|
||||
All() Containers
|
||||
Get(string) (*Container, bool)
|
||||
}
|
||||
|
||||
type DockerContainerSource struct {
|
||||
client *docker.Client
|
||||
containers map[string]*Container
|
||||
type Docker struct {
|
||||
client *api.Client
|
||||
containers map[string]*container.Container
|
||||
needsRefresh chan string // container IDs requiring refresh
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewDockerContainerSource() *DockerContainerSource {
|
||||
func NewDocker() Connector {
|
||||
// init docker client
|
||||
client, err := docker.NewClientFromEnv()
|
||||
client, err := api.NewClientFromEnv()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cm := &DockerContainerSource{
|
||||
cm := &Docker{
|
||||
client: client,
|
||||
containers: make(map[string]*Container),
|
||||
containers: make(map[string]*container.Container),
|
||||
needsRefresh: make(chan string, 60),
|
||||
lock: sync.RWMutex{},
|
||||
}
|
||||
@@ -40,9 +36,9 @@ func NewDockerContainerSource() *DockerContainerSource {
|
||||
}
|
||||
|
||||
// Docker events watcher
|
||||
func (cm *DockerContainerSource) watchEvents() {
|
||||
func (cm *Docker) watchEvents() {
|
||||
log.Info("docker event listener starting")
|
||||
events := make(chan *docker.APIEvents)
|
||||
events := make(chan *api.APIEvents)
|
||||
cm.client.AddEventListener(events)
|
||||
|
||||
for e := range events {
|
||||
@@ -60,7 +56,25 @@ func (cm *DockerContainerSource) watchEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *DockerContainerSource) refresh(c *Container) {
|
||||
func portsFormat(ports map[api.Port][]api.PortBinding) string {
|
||||
var exposed []string
|
||||
var published []string
|
||||
|
||||
for k, v := range ports {
|
||||
if len(v) == 0 {
|
||||
exposed = append(exposed, string(k))
|
||||
continue
|
||||
}
|
||||
for _, binding := range v {
|
||||
s := fmt.Sprintf("%s -> %s:%s", k, binding.HostIP, binding.HostPort)
|
||||
published = append(published, s)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(append(exposed, published...), "\n")
|
||||
}
|
||||
|
||||
func (cm *Docker) refresh(c *container.Container) {
|
||||
insp := cm.inspect(c.Id)
|
||||
// remove container if no longer exists
|
||||
if insp == nil {
|
||||
@@ -69,14 +83,15 @@ func (cm *DockerContainerSource) refresh(c *Container) {
|
||||
}
|
||||
c.SetMeta("name", shortName(insp.Name))
|
||||
c.SetMeta("image", insp.Config.Image)
|
||||
c.SetMeta("ports", portsFormat(insp.NetworkSettings.Ports))
|
||||
c.SetMeta("created", insp.Created.Format("Mon Jan 2 15:04:05 2006"))
|
||||
c.SetState(insp.State.Status)
|
||||
}
|
||||
|
||||
func (cm *DockerContainerSource) inspect(id string) *docker.Container {
|
||||
func (cm *Docker) inspect(id string) *api.Container {
|
||||
c, err := cm.client.InspectContainer(id)
|
||||
if err != nil {
|
||||
if _, ok := err.(*docker.NoSuchContainer); ok == false {
|
||||
if _, ok := err.(*api.NoSuchContainer); ok == false {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
@@ -84,8 +99,8 @@ func (cm *DockerContainerSource) inspect(id string) *docker.Container {
|
||||
}
|
||||
|
||||
// Mark all container IDs for refresh
|
||||
func (cm *DockerContainerSource) refreshAll() {
|
||||
opts := docker.ListContainersOptions{All: true}
|
||||
func (cm *Docker) refreshAll() {
|
||||
opts := api.ListContainersOptions{All: true}
|
||||
allContainers, err := cm.client.ListContainers(opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -99,7 +114,7 @@ func (cm *DockerContainerSource) refreshAll() {
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *DockerContainerSource) Loop() {
|
||||
func (cm *Docker) Loop() {
|
||||
for id := range cm.needsRefresh {
|
||||
c := cm.MustGet(id)
|
||||
cm.refresh(c)
|
||||
@@ -107,14 +122,14 @@ func (cm *DockerContainerSource) Loop() {
|
||||
}
|
||||
|
||||
// Get a single container, creating one anew if not existing
|
||||
func (cm *DockerContainerSource) MustGet(id string) *Container {
|
||||
func (cm *Docker) MustGet(id string) *container.Container {
|
||||
c, ok := cm.Get(id)
|
||||
// append container struct for new containers
|
||||
if !ok {
|
||||
// create collector
|
||||
collector := metrics.NewDocker(cm.client, id)
|
||||
collector := collector.NewDocker(cm.client, id)
|
||||
// create container
|
||||
c = NewContainer(id, collector)
|
||||
c = container.New(id, collector)
|
||||
cm.lock.Lock()
|
||||
cm.containers[id] = c
|
||||
cm.lock.Unlock()
|
||||
@@ -123,7 +138,7 @@ func (cm *DockerContainerSource) MustGet(id string) *Container {
|
||||
}
|
||||
|
||||
// Get a single container, by ID
|
||||
func (cm *DockerContainerSource) Get(id string) (*Container, bool) {
|
||||
func (cm *Docker) Get(id string) (*container.Container, bool) {
|
||||
cm.lock.Lock()
|
||||
c, ok := cm.containers[id]
|
||||
cm.lock.Unlock()
|
||||
@@ -131,7 +146,7 @@ func (cm *DockerContainerSource) Get(id string) (*Container, bool) {
|
||||
}
|
||||
|
||||
// Remove containers by ID
|
||||
func (cm *DockerContainerSource) delByID(id string) {
|
||||
func (cm *Docker) delByID(id string) {
|
||||
cm.lock.Lock()
|
||||
delete(cm.containers, id)
|
||||
cm.lock.Unlock()
|
||||
@@ -139,14 +154,14 @@ func (cm *DockerContainerSource) delByID(id string) {
|
||||
}
|
||||
|
||||
// Return array of all containers, sorted by field
|
||||
func (cm *DockerContainerSource) All() (containers Containers) {
|
||||
func (cm *Docker) All() (containers container.Containers) {
|
||||
cm.lock.Lock()
|
||||
for _, c := range cm.containers {
|
||||
containers = append(containers, c)
|
||||
}
|
||||
cm.lock.Unlock()
|
||||
sort.Sort(containers)
|
||||
containers.Sort()
|
||||
containers.Filter()
|
||||
cm.lock.Unlock()
|
||||
return containers
|
||||
}
|
||||
|
||||
7
connector/enabled_darwin.go
Normal file
7
connector/enabled_darwin.go
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build !linux
|
||||
|
||||
package connector
|
||||
|
||||
var enabled = map[string]func() Connector{
|
||||
"docker": NewDocker,
|
||||
}
|
||||
8
connector/enabled_linux.go
Normal file
8
connector/enabled_linux.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build !darwin
|
||||
|
||||
package connector
|
||||
|
||||
var enabled = map[string]func() Connector{
|
||||
"docker": NewDocker,
|
||||
"runc": NewRunc,
|
||||
}
|
||||
26
connector/main.go
Normal file
26
connector/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package connector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bcicen/ctop/container"
|
||||
"github.com/bcicen/ctop/logging"
|
||||
)
|
||||
|
||||
var log = logging.Init()
|
||||
|
||||
func ByName(s string) (Connector, error) {
|
||||
if _, ok := enabled[s]; !ok {
|
||||
msg := fmt.Sprintf("invalid connector type \"%s\"\nconnector must be one of:", s)
|
||||
for k, _ := range enabled {
|
||||
msg += fmt.Sprintf("\n %s", k)
|
||||
}
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
return enabled[s](), nil
|
||||
}
|
||||
|
||||
type Connector interface {
|
||||
All() container.Containers
|
||||
Get(string) (*container.Container, bool)
|
||||
}
|
||||
@@ -1,31 +1,31 @@
|
||||
// +build !release
|
||||
|
||||
package main
|
||||
package connector
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bcicen/ctop/metrics"
|
||||
"github.com/bcicen/ctop/connector/collector"
|
||||
"github.com/bcicen/ctop/container"
|
||||
"github.com/jgautheron/codename-generator"
|
||||
"github.com/nu7hatch/gouuid"
|
||||
)
|
||||
|
||||
type MockContainerSource struct {
|
||||
containers Containers
|
||||
type Mock struct {
|
||||
containers container.Containers
|
||||
}
|
||||
|
||||
func NewMockContainerSource() *MockContainerSource {
|
||||
cs := &MockContainerSource{}
|
||||
func NewMock() *Mock {
|
||||
cs := &Mock{}
|
||||
go cs.Init()
|
||||
go cs.Loop()
|
||||
return cs
|
||||
}
|
||||
|
||||
// Create Mock containers
|
||||
func (cs *MockContainerSource) Init() {
|
||||
func (cs *Mock) Init() {
|
||||
rand.Seed(int64(time.Now().Nanosecond()))
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
@@ -38,15 +38,15 @@ func (cs *MockContainerSource) Init() {
|
||||
|
||||
}
|
||||
|
||||
func (cs *MockContainerSource) makeContainer(aggression int64) {
|
||||
collector := metrics.NewMock(aggression)
|
||||
c := NewContainer(makeID(), collector)
|
||||
func (cs *Mock) makeContainer(aggression int64) {
|
||||
collector := collector.NewMock(aggression)
|
||||
c := container.New(makeID(), collector)
|
||||
c.SetMeta("name", makeName())
|
||||
c.SetState(makeState())
|
||||
cs.containers = append(cs.containers, c)
|
||||
}
|
||||
|
||||
func (cs *MockContainerSource) Loop() {
|
||||
func (cs *Mock) Loop() {
|
||||
iter := 0
|
||||
for {
|
||||
// Change state for random container
|
||||
@@ -60,7 +60,7 @@ func (cs *MockContainerSource) Loop() {
|
||||
}
|
||||
|
||||
// Get a single container, by ID
|
||||
func (cs *MockContainerSource) Get(id string) (*Container, bool) {
|
||||
func (cs *Mock) Get(id string) (*container.Container, bool) {
|
||||
for _, c := range cs.containers {
|
||||
if c.Id == id {
|
||||
return c, true
|
||||
@@ -70,14 +70,14 @@ func (cs *MockContainerSource) Get(id string) (*Container, bool) {
|
||||
}
|
||||
|
||||
// Return array of all containers, sorted by field
|
||||
func (cs *MockContainerSource) All() Containers {
|
||||
sort.Sort(cs.containers)
|
||||
func (cs *Mock) All() container.Containers {
|
||||
cs.containers.Sort()
|
||||
cs.containers.Filter()
|
||||
return cs.containers
|
||||
}
|
||||
|
||||
// Remove containers by ID
|
||||
func (cs *MockContainerSource) delByID(id string) {
|
||||
func (cs *Mock) delByID(id string) {
|
||||
for n, c := range cs.containers {
|
||||
if c.Id == id {
|
||||
cs.del(n)
|
||||
@@ -87,7 +87,7 @@ func (cs *MockContainerSource) delByID(id string) {
|
||||
}
|
||||
|
||||
// Remove one or more containers by index
|
||||
func (cs *MockContainerSource) del(idx ...int) {
|
||||
func (cs *Mock) del(idx ...int) {
|
||||
for _, i := range idx {
|
||||
cs.containers = append(cs.containers[:i], cs.containers[i+1:]...)
|
||||
}
|
||||
243
connector/runc.go
Normal file
243
connector/runc.go
Normal file
@@ -0,0 +1,243 @@
|
||||
// +build !darwin
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bcicen/ctop/connector/collector"
|
||||
"github.com/bcicen/ctop/container"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
|
||||
)
|
||||
|
||||
type RuncOpts struct {
|
||||
root string // runc root path
|
||||
systemdCgroups bool // use systemd cgroups
|
||||
}
|
||||
|
||||
func NewRuncOpts() (RuncOpts, error) {
|
||||
var opts RuncOpts
|
||||
// read runc root path
|
||||
root := os.Getenv("RUNC_ROOT")
|
||||
if root == "" {
|
||||
root = "/run/runc"
|
||||
}
|
||||
abs, err := filepath.Abs(root)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.root = abs
|
||||
|
||||
// ensure runc root path is readable
|
||||
_, err = ioutil.ReadDir(opts.root)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
|
||||
if os.Getenv("RUNC_SYSTEMD_CGROUP") == "1" {
|
||||
opts.systemdCgroups = true
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
type Runc struct {
|
||||
opts RuncOpts
|
||||
factory libcontainer.Factory
|
||||
containers map[string]*container.Container
|
||||
libContainers map[string]libcontainer.Container
|
||||
needsRefresh chan string // container IDs requiring refresh
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewRunc() Connector {
|
||||
opts, err := NewRuncOpts()
|
||||
runcFailOnErr(err)
|
||||
|
||||
factory, err := getFactory(opts)
|
||||
runcFailOnErr(err)
|
||||
|
||||
cm := &Runc{
|
||||
opts: opts,
|
||||
factory: factory,
|
||||
containers: make(map[string]*container.Container),
|
||||
libContainers: make(map[string]libcontainer.Container),
|
||||
needsRefresh: make(chan string, 60),
|
||||
lock: sync.RWMutex{},
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
cm.refreshAll()
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}()
|
||||
go cm.Loop()
|
||||
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *Runc) GetLibc(id string) libcontainer.Container {
|
||||
// return previously loaded container
|
||||
libc, ok := cm.libContainers[id]
|
||||
if ok {
|
||||
return libc
|
||||
}
|
||||
// load container
|
||||
libc, err := cm.factory.Load(id)
|
||||
if err != nil {
|
||||
// remove container if no longer exists
|
||||
if lerr, ok := err.(libcontainer.Error); ok && lerr.Code() == libcontainer.ContainerNotExists {
|
||||
cm.delByID(id)
|
||||
} else {
|
||||
log.Warningf("failed to read container: %s\n", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return libc
|
||||
}
|
||||
|
||||
// update a ctop container from libcontainer
|
||||
func (cm *Runc) refresh(id string) {
|
||||
libc := cm.GetLibc(id)
|
||||
if libc == nil {
|
||||
return
|
||||
}
|
||||
c := cm.MustGet(id)
|
||||
|
||||
// remove container if entered destroyed state on last refresh
|
||||
// this gives adequate time for the collector to be shut down
|
||||
if c.GetMeta("state") == "destroyed" {
|
||||
cm.delByID(id)
|
||||
return
|
||||
}
|
||||
|
||||
status, err := libc.Status()
|
||||
if err != nil {
|
||||
log.Warningf("failed to read status for container: %s\n", err)
|
||||
} else {
|
||||
c.SetState(status.String())
|
||||
}
|
||||
|
||||
state, err := libc.State()
|
||||
if err != nil {
|
||||
log.Warningf("failed to read state for container: %s\n", err)
|
||||
} else {
|
||||
c.SetMeta("created", state.BaseState.Created.Format("Mon Jan 2 15:04:05 2006"))
|
||||
}
|
||||
|
||||
conf := libc.Config()
|
||||
c.SetMeta("rootfs", conf.Rootfs)
|
||||
}
|
||||
|
||||
// Read runc root, creating any new containers
|
||||
func (cm *Runc) refreshAll() {
|
||||
list, err := ioutil.ReadDir(cm.opts.root)
|
||||
runcFailOnErr(err)
|
||||
|
||||
for _, i := range list {
|
||||
if i.IsDir() {
|
||||
name := i.Name()
|
||||
// attempt to load
|
||||
libc := cm.GetLibc(name)
|
||||
if libc == nil {
|
||||
continue
|
||||
}
|
||||
_ = cm.MustGet(i.Name()) // ensure container exists
|
||||
}
|
||||
}
|
||||
|
||||
// queue all existing containers for refresh
|
||||
for id, _ := range cm.containers {
|
||||
cm.needsRefresh <- id
|
||||
}
|
||||
log.Debugf("queued %d containers for refresh", len(cm.containers))
|
||||
}
|
||||
|
||||
func (cm *Runc) Loop() {
|
||||
for id := range cm.needsRefresh {
|
||||
cm.refresh(id)
|
||||
}
|
||||
}
|
||||
|
||||
// Get a single ctop container in the map matching libc container, creating one anew if not existing
|
||||
func (cm *Runc) MustGet(id string) *container.Container {
|
||||
c, ok := cm.Get(id)
|
||||
if !ok {
|
||||
libc := cm.GetLibc(id)
|
||||
|
||||
// create collector
|
||||
collector := collector.NewRunc(libc)
|
||||
|
||||
// create container
|
||||
c = container.New(id, collector)
|
||||
|
||||
name := libc.ID()
|
||||
// set initial metadata
|
||||
if len(name) > 12 {
|
||||
name = name[0:12]
|
||||
}
|
||||
c.SetMeta("name", name)
|
||||
|
||||
// add to map
|
||||
cm.lock.Lock()
|
||||
cm.containers[id] = c
|
||||
cm.libContainers[id] = libc
|
||||
cm.lock.Unlock()
|
||||
log.Debugf("saw new container: %s", id)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Get a single container, by ID
|
||||
func (cm *Runc) Get(id string) (*container.Container, bool) {
|
||||
cm.lock.Lock()
|
||||
defer cm.lock.Unlock()
|
||||
c, ok := cm.containers[id]
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// Remove containers by ID
|
||||
func (cm *Runc) delByID(id string) {
|
||||
cm.lock.Lock()
|
||||
delete(cm.containers, id)
|
||||
delete(cm.libContainers, id)
|
||||
cm.lock.Unlock()
|
||||
log.Infof("removed dead container: %s", id)
|
||||
}
|
||||
|
||||
// Return array of all containers, sorted by field
|
||||
func (cm *Runc) All() (containers container.Containers) {
|
||||
cm.lock.Lock()
|
||||
for _, c := range cm.containers {
|
||||
containers = append(containers, c)
|
||||
}
|
||||
containers.Sort()
|
||||
containers.Filter()
|
||||
cm.lock.Unlock()
|
||||
return containers
|
||||
}
|
||||
|
||||
func getFactory(opts RuncOpts) (libcontainer.Factory, error) {
|
||||
cgroupManager := libcontainer.Cgroupfs
|
||||
if opts.systemdCgroups {
|
||||
if systemd.UseSystemd() {
|
||||
cgroupManager = libcontainer.SystemdCgroups
|
||||
} else {
|
||||
return nil, fmt.Errorf("systemd cgroup enabled, but systemd support for managing cgroups is not available")
|
||||
}
|
||||
}
|
||||
return libcontainer.New(opts.root, cgroupManager)
|
||||
}
|
||||
|
||||
func runcFailOnErr(err error) {
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("fatal runc error: %s", err))
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,28 @@
|
||||
package main
|
||||
package container
|
||||
|
||||
import (
|
||||
"github.com/bcicen/ctop/cwidgets"
|
||||
"github.com/bcicen/ctop/cwidgets/compact"
|
||||
"github.com/bcicen/ctop/logging"
|
||||
"github.com/bcicen/ctop/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logging.Init()
|
||||
)
|
||||
|
||||
// Metrics and metadata representing a container
|
||||
type Container struct {
|
||||
metrics.Metrics
|
||||
Id string
|
||||
Meta map[string]string
|
||||
Widgets *compact.Compact
|
||||
Display bool // display this container in compact view
|
||||
updater cwidgets.WidgetUpdater
|
||||
collector metrics.Collector
|
||||
display bool // display this container in compact view
|
||||
}
|
||||
|
||||
func NewContainer(id string, collector metrics.Collector) *Container {
|
||||
func New(id string, collector metrics.Collector) *Container {
|
||||
widgets := compact.NewCompact(id)
|
||||
return &Container{
|
||||
Metrics: metrics.NewMetrics(),
|
||||
@@ -1,8 +1,9 @@
|
||||
package main
|
||||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
||||
"github.com/bcicen/ctop/config"
|
||||
)
|
||||
@@ -89,6 +90,7 @@ func SortFields() (fields []string) {
|
||||
|
||||
type Containers []*Container
|
||||
|
||||
func (a Containers) Sort() { sort.Sort(a) }
|
||||
func (a Containers) Len() int { return len(a) }
|
||||
func (a Containers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a Containers) Less(i, j int) bool {
|
||||
@@ -104,14 +106,14 @@ func (a Containers) Filter() {
|
||||
re := regexp.MustCompile(fmt.Sprintf(".*%s", filter))
|
||||
|
||||
for _, c := range a {
|
||||
c.display = true
|
||||
c.Display = true
|
||||
// Apply name filter
|
||||
if re.FindAllString(c.GetMeta("name"), 1) == nil {
|
||||
c.display = false
|
||||
c.Display = false
|
||||
}
|
||||
// Apply state filter
|
||||
if !config.GetSwitchVal("allContainers") && c.GetMeta("state") != "running" {
|
||||
c.display = false
|
||||
c.Display = false
|
||||
}
|
||||
}
|
||||
}
|
||||
72
cursor.go
72
cursor.go
@@ -1,24 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/bcicen/ctop/connector"
|
||||
"github.com/bcicen/ctop/container"
|
||||
ui "github.com/gizak/termui"
|
||||
)
|
||||
|
||||
type GridCursor struct {
|
||||
selectedID string // id of currently selected container
|
||||
filtered Containers
|
||||
cSource ContainerSource
|
||||
}
|
||||
|
||||
func NewGridCursor() *GridCursor {
|
||||
return &GridCursor{
|
||||
cSource: NewDockerContainerSource(),
|
||||
}
|
||||
filtered container.Containers
|
||||
cSource connector.Connector
|
||||
isScrolling bool // toggled when actively scrolling
|
||||
}
|
||||
|
||||
func (gc *GridCursor) Len() int { return len(gc.filtered) }
|
||||
|
||||
func (gc *GridCursor) Selected() *Container {
|
||||
func (gc *GridCursor) Selected() *container.Container {
|
||||
idx := gc.Idx()
|
||||
if idx < gc.Len() {
|
||||
return gc.filtered[idx]
|
||||
@@ -31,10 +30,10 @@ func (gc *GridCursor) RefreshContainers() (lenChanged bool) {
|
||||
oldLen := gc.Len()
|
||||
|
||||
// Containers filtered by display bool
|
||||
gc.filtered = Containers{}
|
||||
gc.filtered = container.Containers{}
|
||||
var cursorVisible bool
|
||||
for _, c := range gc.cSource.All() {
|
||||
if c.display {
|
||||
if c.Display {
|
||||
if c.Id == gc.selectedID {
|
||||
cursorVisible = true
|
||||
}
|
||||
@@ -100,6 +99,9 @@ func (gc *GridCursor) ScrollPage() {
|
||||
}
|
||||
|
||||
func (gc *GridCursor) Up() {
|
||||
gc.isScrolling = true
|
||||
defer func() { gc.isScrolling = false }()
|
||||
|
||||
idx := gc.Idx()
|
||||
if idx <= 0 { // already at top
|
||||
return
|
||||
@@ -116,6 +118,9 @@ func (gc *GridCursor) Up() {
|
||||
}
|
||||
|
||||
func (gc *GridCursor) Down() {
|
||||
gc.isScrolling = true
|
||||
defer func() { gc.isScrolling = false }()
|
||||
|
||||
idx := gc.Idx()
|
||||
if idx >= gc.Len()-1 { // already at bottom
|
||||
return
|
||||
@@ -130,3 +135,48 @@ func (gc *GridCursor) Down() {
|
||||
gc.ScrollPage()
|
||||
ui.Render(cGrid)
|
||||
}
|
||||
|
||||
func (gc *GridCursor) PgUp() {
|
||||
idx := gc.Idx()
|
||||
if idx <= 0 { // already at top
|
||||
return
|
||||
}
|
||||
|
||||
var nextidx int
|
||||
nextidx = int(math.Max(0.0, float64(idx-cGrid.MaxRows())))
|
||||
cGrid.Offset = int(math.Max(float64(cGrid.Offset-cGrid.MaxRows()),
|
||||
float64(0)))
|
||||
|
||||
active := gc.filtered[idx]
|
||||
next := gc.filtered[nextidx]
|
||||
|
||||
active.Widgets.Name.UnHighlight()
|
||||
gc.selectedID = next.Id
|
||||
next.Widgets.Name.Highlight()
|
||||
|
||||
cGrid.Align()
|
||||
ui.Render(cGrid)
|
||||
}
|
||||
|
||||
func (gc *GridCursor) PgDown() {
|
||||
idx := gc.Idx()
|
||||
if idx >= gc.Len()-1 { // already at bottom
|
||||
return
|
||||
}
|
||||
|
||||
var nextidx int
|
||||
nextidx = int(math.Min(float64(gc.Len()-1),
|
||||
float64(idx+cGrid.MaxRows())))
|
||||
cGrid.Offset = int(math.Min(float64(cGrid.Offset+cGrid.MaxRows()),
|
||||
float64(gc.Len()-cGrid.MaxRows())))
|
||||
|
||||
active := gc.filtered[idx]
|
||||
next := gc.filtered[nextidx]
|
||||
|
||||
active.Widgets.Name.UnHighlight()
|
||||
gc.selectedID = next.Id
|
||||
next.Widgets.Name.Highlight()
|
||||
|
||||
cGrid.Align()
|
||||
ui.Render(cGrid)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package expanded
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
ui "github.com/gizak/termui"
|
||||
)
|
||||
|
||||
var displayInfo = []string{"id", "name", "image", "state"}
|
||||
var displayInfo = []string{"id", "name", "image", "ports", "state", "created"}
|
||||
|
||||
type Info struct {
|
||||
*ui.Table
|
||||
@@ -24,12 +26,33 @@ func NewInfo(id string) *Info {
|
||||
|
||||
func (w *Info) Set(k, v string) {
|
||||
w.data[k] = v
|
||||
|
||||
// rebuild rows
|
||||
w.Rows = [][]string{}
|
||||
for _, k := range displayInfo {
|
||||
if v, ok := w.data[k]; ok {
|
||||
w.Rows = append(w.Rows, []string{k, v})
|
||||
w.Rows = append(w.Rows, mkInfoRows(k, v)...)
|
||||
}
|
||||
}
|
||||
|
||||
w.Height = len(w.Rows) + 2
|
||||
}
|
||||
|
||||
// Build row(s) from a key and value string
|
||||
func mkInfoRows(k, v string) (rows [][]string) {
|
||||
lines := strings.Split(v, "\n")
|
||||
|
||||
// initial row with field name
|
||||
rows = append(rows, []string{k, lines[0]})
|
||||
|
||||
// append any additional lines in seperate row
|
||||
if len(lines) > 1 {
|
||||
for _, line := range lines[1:] {
|
||||
if line != "" {
|
||||
rows = append(rows, []string{"", line})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ const (
|
||||
kb = 1024
|
||||
mb = kb * 1024
|
||||
gb = mb * 1024
|
||||
tb = gb * 1024
|
||||
)
|
||||
|
||||
// convenience method
|
||||
@@ -28,9 +29,13 @@ func ByteFormat(n int64) string {
|
||||
n = n / mb
|
||||
return fmt.Sprintf("%sM", strconv.FormatInt(n, 10))
|
||||
}
|
||||
if n < tb {
|
||||
nf := float64(n) / gb
|
||||
return fmt.Sprintf("%sG", unpadFloat(nf))
|
||||
}
|
||||
nf := float64(n) / tb
|
||||
return fmt.Sprintf("%sT", unpadFloat(nf))
|
||||
}
|
||||
|
||||
func unpadFloat(f float64) string {
|
||||
return strconv.FormatFloat(f, 'f', getPrecision(f), 64)
|
||||
|
||||
3
debug.go
3
debug.go
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/bcicen/ctop/container"
|
||||
ui "github.com/gizak/termui"
|
||||
)
|
||||
|
||||
@@ -19,7 +20,7 @@ func logEvent(e ui.Event) {
|
||||
}
|
||||
|
||||
// log container, metrics, and widget state
|
||||
func dumpContainer(c *Container) {
|
||||
func dumpContainer(c *container.Container) {
|
||||
msg := fmt.Sprintf("logging state for container: %s\n", c.Id)
|
||||
for k, v := range c.Meta {
|
||||
msg += fmt.Sprintf("Meta.%s = %s\n", k, v)
|
||||
|
||||
54
glide.lock
generated
54
glide.lock
generated
@@ -1,12 +1,22 @@
|
||||
hash: c13011881e895378f374b68596a59a0ea6def372b4f5239b2d8aa342eaa46a4b
|
||||
updated: 2017-03-12T09:53:35.212073637+07:00
|
||||
hash: 0d550b01b3a1c4751a8f5c3fba0c43f62252055e231712729628e514bb494da8
|
||||
updated: 2017-06-09T18:11:10.930196504-03:00
|
||||
imports:
|
||||
- name: github.com/Azure/go-ansiterm
|
||||
version: fa152c58bc15761d0200cb75fe958b89a9d4888e
|
||||
subpackages:
|
||||
- winterm
|
||||
- name: github.com/c9s/goprocinfo
|
||||
version: b34328d6e0cd139894ea7347d2624ccf31fa3c58
|
||||
subpackages:
|
||||
- linux
|
||||
- name: github.com/coreos/go-systemd
|
||||
version: b4a58d95188dd092ae20072bac14cece0e67c388
|
||||
subpackages:
|
||||
- activation
|
||||
- dbus
|
||||
- util
|
||||
- name: github.com/docker/docker
|
||||
version: ce07fb6b0f1b8765b92022e45f96bd4349812e06
|
||||
version: 90d35abf7b3535c1c319c872900fbd76374e521c
|
||||
subpackages:
|
||||
- api/types
|
||||
- api/types/blkiodev
|
||||
@@ -27,9 +37,11 @@ imports:
|
||||
- pkg/jsonlog
|
||||
- pkg/jsonmessage
|
||||
- pkg/longpath
|
||||
- pkg/mount
|
||||
- pkg/pools
|
||||
- pkg/promise
|
||||
- pkg/stdcopy
|
||||
- pkg/symlink
|
||||
- pkg/system
|
||||
- pkg/term
|
||||
- pkg/term/windows
|
||||
@@ -45,6 +57,12 @@ imports:
|
||||
version: ea10e6ccee219e572ffad0ac1909f1a17f6db7d6
|
||||
repo: https://github.com/bcicen/termui
|
||||
vcs: git
|
||||
- name: github.com/godbus/dbus
|
||||
version: c7fdd8b5cd55e87b4e1f4e372cdb1db61dd6c66f
|
||||
- name: github.com/golang/protobuf
|
||||
version: f7137ae6b19afbfd61a94b746fda3b3fe0491874
|
||||
subpackages:
|
||||
- proto
|
||||
- name: github.com/hashicorp/go-cleanhttp
|
||||
version: 3573b8b52aa7b37b9358d966a898feb387f62437
|
||||
- name: github.com/jgautheron/codename-generator
|
||||
@@ -63,15 +81,41 @@ imports:
|
||||
version: 91bae1bb5fa9ee504905ecbe7043fa30e92feaa3
|
||||
- name: github.com/nu7hatch/gouuid
|
||||
version: 179d4d0c4d8d407a32af483c2354df1d2c91e6c3
|
||||
- name: github.com/Nvveen/Gotty
|
||||
version: cd527374f1e5bff4938207604a14f2e38a9cf512
|
||||
- name: github.com/op/go-logging
|
||||
version: b2cb9fa56473e98db8caba80237377e83fe44db5
|
||||
- name: github.com/opencontainers/runc
|
||||
version: 31980a53ae7887b2c8f8715d13c3eb486c27b6cf
|
||||
version: baf6536d6259209c3edfa2b22237af82942d3dfa
|
||||
subpackages:
|
||||
- libcontainer
|
||||
- libcontainer/apparmor
|
||||
- libcontainer/cgroups
|
||||
- libcontainer/cgroups/fs
|
||||
- libcontainer/cgroups/systemd
|
||||
- libcontainer/configs
|
||||
- libcontainer/configs/validate
|
||||
- libcontainer/criurpc
|
||||
- libcontainer/keys
|
||||
- libcontainer/label
|
||||
- libcontainer/seccomp
|
||||
- libcontainer/selinux
|
||||
- libcontainer/stacktrace
|
||||
- libcontainer/system
|
||||
- libcontainer/user
|
||||
- libcontainer/utils
|
||||
- name: github.com/seccomp/libseccomp-golang
|
||||
version: 1b506fc7c24eec5a3693cdcbed40d9c226cfc6a1
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: 1deb2db2a6fff8a35532079061b903c3a25eed52
|
||||
version: 26709e2714106fb8ad40b773b711ebce25b78914
|
||||
- name: github.com/syndtr/gocapability
|
||||
version: 2c00daeb6c3b45114c80ac44119e7b8801fdd852
|
||||
subpackages:
|
||||
- capability
|
||||
- name: github.com/vishvananda/netlink
|
||||
version: 1e2e08e8a2dcdacaae3f14ac44c5cfa31361f270
|
||||
subpackages:
|
||||
- nl
|
||||
- name: golang.org/x/net
|
||||
version: a6577fac2d73be281a500b310739095313165611
|
||||
subpackages:
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package: github.com/bcicen/ctop
|
||||
import:
|
||||
- package: github.com/c9s/goprocinfo/linux
|
||||
- package: github.com/docker/docker
|
||||
version: ^17.5.0-ce-rc3
|
||||
- package: github.com/opencontainers/runc
|
||||
version: 0.1.1
|
||||
- package: github.com/fsouza/go-dockerclient
|
||||
version: 318513eb1ab27495afbc67f671ba1080513d8aa0
|
||||
- package: github.com/gizak/termui
|
||||
version: barchart-numfmt
|
||||
repo: https://github.com/bcicen/termui
|
||||
vcs: git
|
||||
- package: github.com/jgautheron/codename-generator
|
||||
- package: github.com/nu7hatch/gouuid
|
||||
- package: github.com/c9s/goprocinfo/linux
|
||||
- package: github.com/op/go-logging
|
||||
version: ^1.0.0
|
||||
|
||||
10
grid.go
10
grid.go
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/bcicen/ctop/config"
|
||||
"github.com/bcicen/ctop/container"
|
||||
"github.com/bcicen/ctop/cwidgets/expanded"
|
||||
ui "github.com/gizak/termui"
|
||||
)
|
||||
@@ -34,7 +35,7 @@ func RedrawRows(clr bool) {
|
||||
ui.Render(cGrid)
|
||||
}
|
||||
|
||||
func ExpandView(c *Container) {
|
||||
func ExpandView(c *container.Container) {
|
||||
ui.Clear()
|
||||
ui.DefaultEvtStream.ResetHandlers()
|
||||
defer ui.DefaultEvtStream.ResetHandlers()
|
||||
@@ -61,9 +62,12 @@ func ExpandView(c *Container) {
|
||||
}
|
||||
|
||||
func RefreshDisplay() {
|
||||
// skip display refresh during scroll
|
||||
if !cursor.isScrolling {
|
||||
needsClear := cursor.RefreshContainers()
|
||||
RedrawRows(needsClear)
|
||||
}
|
||||
}
|
||||
|
||||
func Display() bool {
|
||||
var menu func()
|
||||
@@ -79,6 +83,10 @@ func Display() bool {
|
||||
|
||||
HandleKeys("up", cursor.Up)
|
||||
HandleKeys("down", cursor.Down)
|
||||
|
||||
HandleKeys("pgup", cursor.PgUp)
|
||||
HandleKeys("pgdown", cursor.PgDown)
|
||||
|
||||
HandleKeys("exit", ui.StopLoop)
|
||||
HandleKeys("help", func() {
|
||||
menu = HelpMenu
|
||||
|
||||
9
keys.go
9
keys.go
@@ -14,9 +14,18 @@ var keyMap = map[string][]string{
|
||||
"/sys/kbd/<down>",
|
||||
"/sys/kbd/j",
|
||||
},
|
||||
"pgup": []string{
|
||||
"/sys/kbd/<previous>",
|
||||
"/sys/kbd/C-<up>",
|
||||
},
|
||||
"pgdown": []string{
|
||||
"/sys/kbd/<next>",
|
||||
"/sys/kbd/C-<down>",
|
||||
},
|
||||
"exit": []string{
|
||||
"/sys/kbd/q",
|
||||
"/sys/kbd/C-c",
|
||||
"/sys/kbd/<escape>",
|
||||
},
|
||||
"help": []string{
|
||||
"/sys/kbd/h",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
@@ -13,7 +14,7 @@ const (
|
||||
var (
|
||||
Log *CTopLogger
|
||||
exited bool
|
||||
level = logging.INFO
|
||||
level = logging.INFO // default level
|
||||
format = logging.MustStringFormatter(
|
||||
`%{color}%{time:15:04:05.000} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`,
|
||||
)
|
||||
@@ -33,6 +34,11 @@ func Init() *CTopLogger {
|
||||
logging.NewMemoryBackend(size),
|
||||
}
|
||||
|
||||
if debugMode() {
|
||||
level = logging.DEBUG
|
||||
StartServer()
|
||||
}
|
||||
|
||||
backendLvl := logging.AddModuleLevel(Log.backend)
|
||||
backendLvl.SetLevel(level, "")
|
||||
|
||||
@@ -71,3 +77,6 @@ func (log *CTopLogger) Exit() {
|
||||
exited = true
|
||||
StopServer()
|
||||
}
|
||||
|
||||
func debugMode() bool { return os.Getenv("CTOP_DEBUG") == "1" }
|
||||
func debugModeTCP() bool { return os.Getenv("CTOP_DEBUG_TCP") == "1" }
|
||||
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
path = "/tmp/ctop.sock"
|
||||
socketPath = "./ctop.sock"
|
||||
socketAddr = "0.0.0.0:9000"
|
||||
)
|
||||
|
||||
var server struct {
|
||||
@@ -16,7 +17,13 @@ var server struct {
|
||||
}
|
||||
|
||||
func getListener() net.Listener {
|
||||
ln, err := net.Listen("unix", path)
|
||||
var ln net.Listener
|
||||
var err error
|
||||
if debugModeTCP() {
|
||||
ln, err = net.Listen("tcp", socketAddr)
|
||||
} else {
|
||||
ln, err = net.Listen("unix", socketPath)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
51
main.go
51
main.go
@@ -6,10 +6,13 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/bcicen/ctop/config"
|
||||
"github.com/bcicen/ctop/connector"
|
||||
"github.com/bcicen/ctop/container"
|
||||
"github.com/bcicen/ctop/cwidgets/compact"
|
||||
"github.com/bcicen/ctop/logging"
|
||||
"github.com/bcicen/ctop/widgets"
|
||||
ui "github.com/gizak/termui"
|
||||
tm "github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -20,14 +23,13 @@ var (
|
||||
cursor *GridCursor
|
||||
cGrid *compact.CompactGrid
|
||||
header *widgets.CTopHeader
|
||||
|
||||
versionStr = fmt.Sprintf("ctop version %v, build %v", version, build)
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer panicExit()
|
||||
|
||||
// init global config
|
||||
config.Init()
|
||||
|
||||
// parse command line arguments
|
||||
var versionFlag = flag.Bool("v", false, "output version information and exit")
|
||||
var helpFlag = flag.Bool("h", false, "display this help dialog")
|
||||
@@ -36,10 +38,11 @@ func main() {
|
||||
var sortFieldFlag = flag.String("s", "", "select container sort field")
|
||||
var reverseSortFlag = flag.Bool("r", false, "reverse container sort order")
|
||||
var invertFlag = flag.Bool("i", false, "invert default colors")
|
||||
var connectorFlag = flag.String("connector", "docker", "container connector to use")
|
||||
flag.Parse()
|
||||
|
||||
if *versionFlag {
|
||||
printVersion()
|
||||
fmt.Println(versionStr)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -48,6 +51,12 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// init logger
|
||||
log = logging.Init()
|
||||
|
||||
// init global config
|
||||
config.Init()
|
||||
|
||||
// override default config values with command line flags
|
||||
if *filterFlag != "" {
|
||||
config.Update("filterStr", *filterFlag)
|
||||
@@ -66,12 +75,6 @@ func main() {
|
||||
config.Toggle("sortReversed")
|
||||
}
|
||||
|
||||
// init logger
|
||||
log = logging.Init()
|
||||
if config.GetSwitchVal("loggingEnabled") {
|
||||
logging.StartServer()
|
||||
}
|
||||
|
||||
// init ui
|
||||
if *invertFlag {
|
||||
InvertColorMap()
|
||||
@@ -80,26 +83,36 @@ func main() {
|
||||
if err := ui.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer ui.Close()
|
||||
|
||||
defer Shutdown()
|
||||
// init grid, cursor, header
|
||||
cursor = NewGridCursor()
|
||||
conn, err := connector.ByName(*connectorFlag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cursor = &GridCursor{cSource: conn}
|
||||
cGrid = compact.NewCompactGrid()
|
||||
header = widgets.NewCTopHeader()
|
||||
|
||||
for {
|
||||
exit := Display()
|
||||
if exit {
|
||||
log.Notice("shutting down")
|
||||
log.Exit()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
log.Notice("shutting down")
|
||||
log.Exit()
|
||||
if tm.IsInit {
|
||||
ui.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// ensure a given sort field is valid
|
||||
func validSort(s string) {
|
||||
if _, ok := Sorters[s]; !ok {
|
||||
if _, ok := container.Sorters[s]; !ok {
|
||||
fmt.Printf("invalid sort field: %s\n", s)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -107,8 +120,8 @@ func validSort(s string) {
|
||||
|
||||
func panicExit() {
|
||||
if r := recover(); r != nil {
|
||||
ui.Clear()
|
||||
fmt.Printf("panic: %s\n", r)
|
||||
Shutdown()
|
||||
fmt.Printf("error: %s\n", r)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -124,7 +137,3 @@ func printHelp() {
|
||||
fmt.Println(helpMsg)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func printVersion() {
|
||||
fmt.Printf("ctop version %v, build %v\n", version, build)
|
||||
}
|
||||
|
||||
3
menus.go
3
menus.go
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/bcicen/ctop/config"
|
||||
"github.com/bcicen/ctop/container"
|
||||
"github.com/bcicen/ctop/widgets"
|
||||
"github.com/bcicen/ctop/widgets/menu"
|
||||
ui "github.com/gizak/termui"
|
||||
@@ -74,7 +75,7 @@ func SortMenu() {
|
||||
m.SortItems = true
|
||||
m.BorderLabel = "Sort Field"
|
||||
|
||||
for _, field := range SortFields() {
|
||||
for _, field := range container.SortFields() {
|
||||
m.AddItems(menu.Item{field, ""})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/bcicen/ctop/logging"
|
||||
)
|
||||
|
||||
var log = logging.Init()
|
||||
|
||||
type Metrics struct {
|
||||
CPUUtil int
|
||||
NetTx int64
|
||||
@@ -39,7 +31,3 @@ type Collector interface {
|
||||
Start()
|
||||
Stop()
|
||||
}
|
||||
|
||||
func round(num float64) int {
|
||||
return int(num + math.Copysign(0.5, num))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user