mirror of
https://github.com/bcicen/ctop.git
synced 2025-12-06 23:26:45 +08:00
Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4741b276e4 | ||
|
|
9dd12103fc | ||
|
|
c38942c7ed | ||
|
|
4b391e900c | ||
|
|
4584cf34f5 | ||
|
|
1ce07448ce | ||
|
|
d8c7dd4c5c | ||
|
|
b7d81485f9 | ||
|
|
8946c4b03b | ||
|
|
331f50f03e | ||
|
|
4c4f041b40 | ||
|
|
c8ac331652 | ||
|
|
0a5a4c9062 | ||
|
|
98fcfe8b6f | ||
|
|
42f095cd85 | ||
|
|
73986d2732 | ||
|
|
c1d4615cc0 | ||
|
|
d187e8c623 | ||
|
|
b8c38d09ef | ||
|
|
d7384db373 | ||
|
|
1b441db189 | ||
|
|
0479d42e31 | ||
|
|
b401e7b17e | ||
|
|
9592de82a0 | ||
|
|
29fa8cf3e7 | ||
|
|
c49939f965 | ||
|
|
2f7bc2a172 | ||
|
|
7b4d4db049 | ||
|
|
70bd2ae3a3 | ||
|
|
665e8fdd06 | ||
|
|
101ddad692 | ||
|
|
ca35ef2aab | ||
|
|
d59c91a461 | ||
|
|
a39b7a3a3e | ||
|
|
77f5e6b735 | ||
|
|
3c83b7576b | ||
|
|
8a0bd3cf8a | ||
|
|
78caad2dbd | ||
|
|
8d8f1e72eb | ||
|
|
93556a1754 | ||
|
|
4d247f5272 | ||
|
|
db3d7e8927 | ||
|
|
efef345665 | ||
|
|
f158fa742f | ||
|
|
4d48245d7d | ||
|
|
6bee1b7f31 | ||
|
|
7118e45f3a | ||
|
|
a26fc9169c | ||
|
|
967a87a65f | ||
|
|
e68f7ba96a | ||
|
|
3405d19be8 | ||
|
|
f27de1c29e | ||
|
|
9a185b2388 | ||
|
|
caf6fc63c1 | ||
|
|
cf352f7c8a | ||
|
|
ac5bed210f | ||
|
|
a72d43526f | ||
|
|
9eb2457aa4 | ||
|
|
b83402b886 | ||
|
|
078564bd38 | ||
|
|
a2c08d312e | ||
|
|
f7a3d38d6b | ||
|
|
a3b8585697 | ||
|
|
2e526e9b86 | ||
|
|
541fe70b78 | ||
|
|
c786b697bf | ||
|
|
aa6c00b083 | ||
|
|
17855e3d8e | ||
|
|
842809bef5 | ||
|
|
4e567ee007 | ||
|
|
56700e120b | ||
|
|
5261444265 | ||
|
|
b3aa291182 | ||
|
|
051b474bf0 | ||
|
|
fac6632459 | ||
|
|
1c7cf98e58 | ||
|
|
44a54e070d | ||
|
|
10b9a6c013 | ||
|
|
a3b67e4607 | ||
|
|
ac1ce18143 | ||
|
|
01a305d326 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
ctop
|
||||
.idea
|
||||
.idea
|
||||
/vendor/
|
||||
11
Dockerfile
11
Dockerfile
@@ -1,16 +1,17 @@
|
||||
FROM quay.io/vektorcloud/go:1.9
|
||||
FROM quay.io/vektorcloud/go:1.12
|
||||
|
||||
RUN apk add --no-cache make
|
||||
|
||||
COPY Gopkg.* /go/src/github.com/bcicen/ctop/
|
||||
WORKDIR /go/src/github.com/bcicen/ctop/
|
||||
RUN dep ensure -vendor-only
|
||||
WORKDIR /app
|
||||
COPY go.mod .
|
||||
RUN go mod download
|
||||
|
||||
COPY . /go/src/github.com/bcicen/ctop
|
||||
COPY . .
|
||||
RUN make build && \
|
||||
mkdir -p /go/bin && \
|
||||
mv -v ctop /go/bin/
|
||||
|
||||
FROM scratch
|
||||
ENV TERM=linux
|
||||
COPY --from=0 /go/bin/ctop /ctop
|
||||
ENTRYPOINT ["/ctop"]
|
||||
|
||||
159
Gopkg.lock
generated
159
Gopkg.lock
generated
@@ -1,159 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/Azure/go-ansiterm"
|
||||
packages = [".","winterm"]
|
||||
revision = "fa152c58bc15761d0200cb75fe958b89a9d4888e"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/Microsoft/go-winio"
|
||||
packages = ["."]
|
||||
revision = "fff283ad5116362ca252298cfc9b95828956d85d"
|
||||
version = "v0.3.8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/Nvveen/Gotty"
|
||||
packages = ["."]
|
||||
revision = "cd527374f1e5bff4938207604a14f2e38a9cf512"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/Sirupsen/logrus"
|
||||
packages = ["."]
|
||||
revision = "26709e2714106fb8ad40b773b711ebce25b78914"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/c9s/goprocinfo"
|
||||
packages = ["linux"]
|
||||
revision = "b34328d6e0cd139894ea7347d2624ccf31fa3c58"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/coreos/go-systemd"
|
||||
packages = ["dbus","util"]
|
||||
revision = "b4a58d95188dd092ae20072bac14cece0e67c388"
|
||||
version = "v4"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/docker"
|
||||
packages = ["api/types","api/types/blkiodev","api/types/container","api/types/filters","api/types/mount","api/types/network","api/types/registry","api/types/strslice","api/types/swarm","api/types/versions","opts","pkg/archive","pkg/fileutils","pkg/homedir","pkg/idtools","pkg/ioutils","pkg/jsonlog","pkg/jsonmessage","pkg/longpath","pkg/mount","pkg/pools","pkg/promise","pkg/stdcopy","pkg/symlink","pkg/system","pkg/term","pkg/term/windows"]
|
||||
revision = "90d35abf7b3535c1c319c872900fbd76374e521c"
|
||||
version = "v17.05.0-ce-rc3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/go-connections"
|
||||
packages = ["nat"]
|
||||
revision = "a2afab9802043837035592f1c24827fb70766de9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/go-units"
|
||||
packages = ["."]
|
||||
revision = "0dadbb0345b35ec7ef35e228dabb8de89a65bf52"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fsouza/go-dockerclient"
|
||||
packages = ["."]
|
||||
revision = "318513eb1ab27495afbc67f671ba1080513d8aa0"
|
||||
|
||||
[[projects]]
|
||||
branch = "barchart-numfmt"
|
||||
name = "github.com/gizak/termui"
|
||||
packages = ["."]
|
||||
revision = "ea10e6ccee219e572ffad0ac1909f1a17f6db7d6"
|
||||
source = "https://github.com/bcicen/termui"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/godbus/dbus"
|
||||
packages = ["."]
|
||||
revision = "c7fdd8b5cd55e87b4e1f4e372cdb1db61dd6c66f"
|
||||
version = "v3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
revision = "0a4f71a498b7c4812f64969510bcb4eca251e33a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/go-cleanhttp"
|
||||
packages = ["."]
|
||||
revision = "3573b8b52aa7b37b9358d966a898feb387f62437"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jgautheron/codename-generator"
|
||||
packages = ["."]
|
||||
revision = "16d037c7cc3c9b552fe4af9828b7338d752dbaf9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/maruel/panicparse"
|
||||
packages = ["stack"]
|
||||
revision = "25bcac0d793cf4109483505a0d66e066a3a90a80"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
revision = "14207d285c6c197daabb5c9793d63e7af9ab2d50"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/go-wordwrap"
|
||||
packages = ["."]
|
||||
revision = "ad45545899c7b13c020ea92b2072220eefad42b8"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/nsf/termbox-go"
|
||||
packages = ["."]
|
||||
revision = "91bae1bb5fa9ee504905ecbe7043fa30e92feaa3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/nu7hatch/gouuid"
|
||||
packages = ["."]
|
||||
revision = "179d4d0c4d8d407a32af483c2354df1d2c91e6c3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/op/go-logging"
|
||||
packages = ["."]
|
||||
revision = "b2cb9fa56473e98db8caba80237377e83fe44db5"
|
||||
version = "v1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/opencontainers/runc"
|
||||
packages = ["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"]
|
||||
revision = "baf6536d6259209c3edfa2b22237af82942d3dfa"
|
||||
version = "v0.1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/seccomp/libseccomp-golang"
|
||||
packages = ["."]
|
||||
revision = "1b506fc7c24eec5a3693cdcbed40d9c226cfc6a1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/syndtr/gocapability"
|
||||
packages = ["capability"]
|
||||
revision = "2c00daeb6c3b45114c80ac44119e7b8801fdd852"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/vishvananda/netlink"
|
||||
packages = [".","nl"]
|
||||
revision = "1e2e08e8a2dcdacaae3f14ac44c5cfa31361f270"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context","context/ctxhttp"]
|
||||
revision = "a6577fac2d73be281a500b310739095313165611"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix","windows"]
|
||||
revision = "99f16d856c9836c42d24e7ab64ea72916925fa97"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "cf4dacc32111b22d72ac23189b826c8316ec265e55bf987338c7a00633af788e"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
47
Gopkg.toml
47
Gopkg.toml
@@ -1,47 +0,0 @@
|
||||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fsouza/go-dockerclient"
|
||||
revision = "318513eb1ab27495afbc67f671ba1080513d8aa0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "barchart-numfmt"
|
||||
name = "github.com/gizak/termui"
|
||||
source = "https://github.com/bcicen/termui"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/jgautheron/codename-generator"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/nu7hatch/gouuid"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/op/go-logging"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/opencontainers/runc"
|
||||
version = "0.1.1"
|
||||
11
Makefile
11
Makefile
@@ -8,7 +8,7 @@ clean:
|
||||
rm -rf _build/ release/
|
||||
|
||||
build:
|
||||
dep ensure
|
||||
go mod download
|
||||
CGO_ENABLED=0 go build -tags release -ldflags $(LD_FLAGS) -o ctop
|
||||
|
||||
build-dev:
|
||||
@@ -16,10 +16,11 @@ build-dev:
|
||||
|
||||
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
|
||||
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
|
||||
GOOS=windows GOARCH=amd64 go build -tags release -ldflags $(LD_FLAGS) -o _build/ctop-$(VERSION)-windows-amd64
|
||||
cd _build; sha256sum * > sha256sums.txt
|
||||
|
||||
image:
|
||||
|
||||
46
README.md
46
README.md
@@ -20,7 +20,7 @@ Fetch the [latest release](https://github.com/bcicen/ctop/releases) for your pla
|
||||
#### Linux
|
||||
|
||||
```bash
|
||||
sudo wget https://github.com/bcicen/ctop/releases/download/v0.7/ctop-0.7-linux-amd64 -O /usr/local/bin/ctop
|
||||
sudo wget https://github.com/bcicen/ctop/releases/download/v0.7.3/ctop-0.7.3-linux-amd64 -O /usr/local/bin/ctop
|
||||
sudo chmod +x /usr/local/bin/ctop
|
||||
```
|
||||
|
||||
@@ -31,7 +31,7 @@ brew install ctop
|
||||
```
|
||||
or
|
||||
```bash
|
||||
sudo curl -Lo /usr/local/bin/ctop https://github.com/bcicen/ctop/releases/download/v0.7/ctop-0.7-darwin-amd64
|
||||
sudo curl -Lo /usr/local/bin/ctop https://github.com/bcicen/ctop/releases/download/v0.7.3/ctop-0.7.3-darwin-amd64
|
||||
sudo chmod +x /usr/local/bin/ctop
|
||||
```
|
||||
|
||||
@@ -40,7 +40,7 @@ sudo chmod +x /usr/local/bin/ctop
|
||||
```bash
|
||||
docker run --rm -ti \
|
||||
--name=ctop \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
quay.io/vektorlab/ctop:latest
|
||||
```
|
||||
|
||||
@@ -62,30 +62,32 @@ While running, use `S` to save the current filters, sort field, and other option
|
||||
|
||||
Option | Description
|
||||
--- | ---
|
||||
-a | show active containers only
|
||||
-f <string> | set an initial filter string
|
||||
-h | display help dialog
|
||||
-i | invert default colors
|
||||
-r | reverse container sort order
|
||||
-s | select initial container sort field
|
||||
-scale-cpu | show cpu as % of system total
|
||||
-v | output version information and exit
|
||||
`-a` | show active containers only
|
||||
`-f <string>` | set an initial filter string
|
||||
`-h` | display help dialog
|
||||
`-i` | invert default colors
|
||||
`-r` | reverse container sort order
|
||||
`-s` | select initial container sort field
|
||||
`-scale-cpu` | show cpu as % of system total
|
||||
`-v` | output version information and exit
|
||||
`-shell` | specify shell (default: sh)
|
||||
|
||||
### Keybindings
|
||||
|
||||
Key | Action
|
||||
--- | ---
|
||||
<enter> | Open container menu
|
||||
a | Toggle display of all (running and non-running) containers
|
||||
f | Filter displayed containers (`esc` to clear when open)
|
||||
H | Toggle ctop header
|
||||
h | Open help dialog
|
||||
s | Select container sort field
|
||||
r | Reverse container sort order
|
||||
o | Open single view
|
||||
l | View container logs (`t` to toggle timestamp when open)
|
||||
S | Save current configuration to file
|
||||
q | Quit ctop
|
||||
`<enter>` | Open container menu
|
||||
`a` | Toggle display of all (running and non-running) containers
|
||||
`f` | Filter displayed containers (`esc` to clear when open)
|
||||
`H` | Toggle ctop header
|
||||
`h` | Open help dialog
|
||||
`s` | Select container sort field
|
||||
`r` | Reverse container sort order
|
||||
`o` | Open single view
|
||||
`l` | View container logs (`t` to toggle timestamp when open)
|
||||
`e` | Exec Shell
|
||||
`S` | Save current configuration to file
|
||||
`q` | Quit ctop
|
||||
|
||||
[build]: _docs/build.md
|
||||
[connectors]: _docs/connectors.md
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# connectors
|
||||
# Connectors
|
||||
|
||||
`ctop` comes with the below native connectors, enabled via the `--connector` option.
|
||||
|
||||
|
||||
BIN
_docs/img/status.png
Normal file
BIN
_docs/img/status.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
30
_docs/status.md
Normal file
30
_docs/status.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Status Indicator
|
||||
|
||||
The `ctop` grid view provides a compact status indicator to convey container state
|
||||
|
||||
<img width="200px" src="img/status.png" alt="ctop"/>
|
||||
|
||||
### Status
|
||||
|
||||
<span align="center">
|
||||
|
||||
Appearance | Description
|
||||
--- | ---
|
||||
red | container is stopped
|
||||
green | container is running
|
||||
▮▮ | container is paused
|
||||
|
||||
</span>
|
||||
|
||||
### Health
|
||||
If the container is configured with a health check, a `+` will appear next to the indicator
|
||||
|
||||
<span align="center">
|
||||
|
||||
Appearance | Description
|
||||
--- | ---
|
||||
red | health check in failed state
|
||||
yellow | health check in starting state
|
||||
green | health check in OK state
|
||||
|
||||
</span>
|
||||
@@ -52,7 +52,7 @@ var ColorMap = map[string]ui.Attribute{
|
||||
|
||||
func InvertColorMap() {
|
||||
re := regexp.MustCompile(".*.fg")
|
||||
for k, _ := range ColorMap {
|
||||
for k := range ColorMap {
|
||||
if re.FindAllString(k, 1) != nil {
|
||||
ColorMap[k] = ui.ColorBlack
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@ var (
|
||||
xdgRe = regexp.MustCompile("^XDG_*")
|
||||
)
|
||||
|
||||
type ConfigFile struct {
|
||||
type File struct {
|
||||
Options map[string]string `toml:"options"`
|
||||
Toggles map[string]bool `toml:"toggles"`
|
||||
}
|
||||
|
||||
func exportConfig() ConfigFile {
|
||||
c := ConfigFile{
|
||||
func exportConfig() File {
|
||||
c := File{
|
||||
Options: make(map[string]string),
|
||||
Toggles: make(map[string]bool),
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func exportConfig() ConfigFile {
|
||||
}
|
||||
|
||||
func Read() error {
|
||||
var config ConfigFile
|
||||
var config File
|
||||
|
||||
path, err := getConfigPath()
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,11 @@ var params = []*Param{
|
||||
Val: "state",
|
||||
Label: "Container Sort Field",
|
||||
},
|
||||
&Param{
|
||||
Key: "shell",
|
||||
Val: "sh",
|
||||
Label: "Shell",
|
||||
},
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
@@ -30,7 +35,7 @@ func Get(k string) *Param {
|
||||
return &Param{} // default
|
||||
}
|
||||
|
||||
// Get Param value by key
|
||||
// GetVal gets Param value by key
|
||||
func GetVal(k string) string {
|
||||
return Get(k).Val
|
||||
}
|
||||
|
||||
@@ -5,17 +5,22 @@ var switches = []*Switch{
|
||||
&Switch{
|
||||
Key: "sortReversed",
|
||||
Val: false,
|
||||
Label: "Reverse Sort Order",
|
||||
Label: "Reverse sort order",
|
||||
},
|
||||
&Switch{
|
||||
Key: "allContainers",
|
||||
Val: true,
|
||||
Label: "Show All Containers",
|
||||
Label: "Show all containers",
|
||||
},
|
||||
&Switch{
|
||||
Key: "fullRowCursor",
|
||||
Val: true,
|
||||
Label: "Highlight entire cursor row (vs. name only)",
|
||||
},
|
||||
&Switch{
|
||||
Key: "enableHeader",
|
||||
Val: true,
|
||||
Label: "Enable Status Header",
|
||||
Label: "Enable status header",
|
||||
},
|
||||
&Switch{
|
||||
Key: "scaleCpu",
|
||||
@@ -30,7 +35,7 @@ type Switch struct {
|
||||
Label string
|
||||
}
|
||||
|
||||
// Return Switch by key
|
||||
// GetSwitch returns Switch by key
|
||||
func GetSwitch(k string) *Switch {
|
||||
for _, sw := range GlobalSwitches {
|
||||
if sw.Key == k {
|
||||
@@ -40,7 +45,7 @@ func GetSwitch(k string) *Switch {
|
||||
return &Switch{} // default
|
||||
}
|
||||
|
||||
// Return Switch value by key
|
||||
// GetSwitchVal returns Switch value by key
|
||||
func GetSwitchVal(k string) bool {
|
||||
return GetSwitch(k).Val
|
||||
}
|
||||
@@ -56,7 +61,7 @@ func UpdateSwitch(k string, val bool) {
|
||||
// Toggle a boolean switch
|
||||
func Toggle(k string) {
|
||||
sw := GetSwitch(k)
|
||||
newVal := sw.Val != true
|
||||
newVal := !sw.Val
|
||||
log.Noticef("config change: %s: %t -> %t", k, sw.Val, newVal)
|
||||
sw.Val = newVal
|
||||
//log.Errorf("ignoring toggle for non-existant switch: %s", k)
|
||||
|
||||
@@ -34,12 +34,13 @@ func (l *DockerLogs) Stream() chan models.Log {
|
||||
Context: ctx,
|
||||
Container: l.id,
|
||||
OutputStream: w,
|
||||
ErrorStream: w,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
Tail: "10",
|
||||
Follow: true,
|
||||
Timestamps: true,
|
||||
//ErrorStream: w,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
Tail: "20",
|
||||
Follow: true,
|
||||
Timestamps: true,
|
||||
RawTerminal: true,
|
||||
}
|
||||
|
||||
// read io pipe into channel
|
||||
@@ -48,7 +49,7 @@ func (l *DockerLogs) Stream() chan models.Log {
|
||||
for scanner.Scan() {
|
||||
parts := strings.Split(scanner.Text(), " ")
|
||||
ts := l.parseTime(parts[0])
|
||||
logCh <- models.Log{ts, strings.Join(parts[1:], " ")}
|
||||
logCh <- models.Log{Timestamp: ts, Message: strings.Join(parts[1:], " ")}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -62,10 +63,8 @@ func (l *DockerLogs) Stream() chan models.Log {
|
||||
}()
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-l.done:
|
||||
cancel()
|
||||
}
|
||||
<-l.done
|
||||
cancel()
|
||||
}()
|
||||
|
||||
log.Infof("log reader started for container: %s", l.id)
|
||||
@@ -76,9 +75,25 @@ func (l *DockerLogs) Stop() { l.done <- true }
|
||||
|
||||
func (l *DockerLogs) parseTime(s string) time.Time {
|
||||
ts, err := time.Parse("2006-01-02T15:04:05.000000000Z", s)
|
||||
if err != nil {
|
||||
log.Errorf("failed to parse container log: %s", err)
|
||||
ts = time.Now()
|
||||
if err == nil {
|
||||
return ts
|
||||
}
|
||||
return ts
|
||||
|
||||
ts, err2 := time.Parse("2006-01-02T15:04:05.000000000Z", l.stripPfx(s))
|
||||
if err2 == nil {
|
||||
return ts
|
||||
}
|
||||
|
||||
log.Errorf("failed to parse container log: %s", err)
|
||||
log.Errorf("failed to parse container log2: %s", err2)
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// attempt to strip message header prefix from a given raw docker log string
|
||||
func (l *DockerLogs) stripPfx(s string) string {
|
||||
b := []byte(s)
|
||||
if len(b) > 8 {
|
||||
return string(b[8:])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func (l *MockLogs) Stream() chan models.Log {
|
||||
case <-l.done:
|
||||
break
|
||||
default:
|
||||
logCh <- models.Log{time.Now(), mockLog}
|
||||
logCh <- models.Log{Timestamp: time.Now(), Message: mockLog}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !darwin
|
||||
// +build linux
|
||||
|
||||
package collector
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !darwin
|
||||
// +build linux
|
||||
|
||||
package collector
|
||||
|
||||
|
||||
@@ -11,31 +11,51 @@ import (
|
||||
api "github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
func init() { enabled["docker"] = NewDocker }
|
||||
|
||||
type Docker struct {
|
||||
client *api.Client
|
||||
containers map[string]*container.Container
|
||||
needsRefresh chan string // container IDs requiring refresh
|
||||
closed chan struct{}
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewDocker() Connector {
|
||||
func NewDocker() (Connector, error) {
|
||||
// init docker client
|
||||
client, err := api.NewClientFromEnv()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
cm := &Docker{
|
||||
client: client,
|
||||
containers: make(map[string]*container.Container),
|
||||
needsRefresh: make(chan string, 60),
|
||||
closed: make(chan struct{}),
|
||||
lock: sync.RWMutex{},
|
||||
}
|
||||
|
||||
// query info as pre-flight healthcheck
|
||||
info, err := client.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("docker-connector ID: %s", info.ID)
|
||||
log.Debugf("docker-connector Driver: %s", info.Driver)
|
||||
log.Debugf("docker-connector Images: %d", info.Images)
|
||||
log.Debugf("docker-connector Name: %s", info.Name)
|
||||
log.Debugf("docker-connector ServerVersion: %s", info.ServerVersion)
|
||||
|
||||
go cm.Loop()
|
||||
cm.refreshAll()
|
||||
go cm.watchEvents()
|
||||
return cm
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
// Docker implements Connector
|
||||
func (cm *Docker) Wait() struct{} { return <-cm.closed }
|
||||
|
||||
// Docker events watcher
|
||||
func (cm *Docker) watchEvents() {
|
||||
log.Info("docker event listener starting")
|
||||
@@ -58,6 +78,8 @@ func (cm *Docker) watchEvents() {
|
||||
cm.delByID(e.ID)
|
||||
}
|
||||
}
|
||||
log.Info("docker event listener exited")
|
||||
close(cm.closed)
|
||||
}
|
||||
|
||||
func portsFormat(ports map[api.Port][]api.PortBinding) string {
|
||||
@@ -78,6 +100,17 @@ func portsFormat(ports map[api.Port][]api.PortBinding) string {
|
||||
return strings.Join(append(exposed, published...), "\n")
|
||||
}
|
||||
|
||||
func ipsFormat(networks map[string]api.ContainerNetwork) string {
|
||||
var ips []string
|
||||
|
||||
for k, v := range networks {
|
||||
s := fmt.Sprintf("%s:%s", k, v.IPAddress)
|
||||
ips = append(ips, s)
|
||||
}
|
||||
|
||||
return strings.Join(ips, "\n")
|
||||
}
|
||||
|
||||
func (cm *Docker) refresh(c *container.Container) {
|
||||
insp := cm.inspect(c.Id)
|
||||
// remove container if no longer exists
|
||||
@@ -87,17 +120,21 @@ func (cm *Docker) refresh(c *container.Container) {
|
||||
}
|
||||
c.SetMeta("name", shortName(insp.Name))
|
||||
c.SetMeta("image", insp.Config.Image)
|
||||
c.SetMeta("IPs", ipsFormat(insp.NetworkSettings.Networks))
|
||||
c.SetMeta("ports", portsFormat(insp.NetworkSettings.Ports))
|
||||
c.SetMeta("created", insp.Created.Format("Mon Jan 2 15:04:05 2006"))
|
||||
c.SetMeta("health", insp.State.Health.Status)
|
||||
for _, env := range insp.Config.Env {
|
||||
c.SetMeta("[ENV-VAR]", env)
|
||||
}
|
||||
c.SetState(insp.State.Status)
|
||||
}
|
||||
|
||||
func (cm *Docker) inspect(id string) *api.Container {
|
||||
c, err := cm.client.InspectContainer(id)
|
||||
if err != nil {
|
||||
if _, ok := err.(*api.NoSuchContainer); ok == false {
|
||||
log.Errorf(err.Error())
|
||||
if _, ok := err.(*api.NoSuchContainer); !ok {
|
||||
log.Errorf("%s (%T)", err.Error(), err)
|
||||
}
|
||||
}
|
||||
return c
|
||||
@@ -108,7 +145,8 @@ func (cm *Docker) refreshAll() {
|
||||
opts := api.ListContainersOptions{All: true}
|
||||
allContainers, err := cm.client.ListContainers(opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Errorf("%s (%T)", err.Error(), err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, i := range allContainers {
|
||||
@@ -120,13 +158,18 @@ func (cm *Docker) refreshAll() {
|
||||
}
|
||||
|
||||
func (cm *Docker) Loop() {
|
||||
for id := range cm.needsRefresh {
|
||||
c := cm.MustGet(id)
|
||||
cm.refresh(c)
|
||||
for {
|
||||
select {
|
||||
case id := <-cm.needsRefresh:
|
||||
c := cm.MustGet(id)
|
||||
cm.refresh(c)
|
||||
case <-cm.closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get a single container, creating one anew if not existing
|
||||
// MustGet gets a single container, creating one anew if not existing
|
||||
func (cm *Docker) MustGet(id string) *container.Container {
|
||||
c, ok := cm.Get(id)
|
||||
// append container struct for new containers
|
||||
@@ -144,7 +187,7 @@ func (cm *Docker) MustGet(id string) *container.Container {
|
||||
return c
|
||||
}
|
||||
|
||||
// Get a single container, by ID
|
||||
// Docker implements Connector
|
||||
func (cm *Docker) Get(id string) (*container.Container, bool) {
|
||||
cm.lock.Lock()
|
||||
c, ok := cm.containers[id]
|
||||
@@ -160,7 +203,7 @@ func (cm *Docker) delByID(id string) {
|
||||
log.Infof("removed dead container: %s", id)
|
||||
}
|
||||
|
||||
// Return array of all containers, sorted by field
|
||||
// Docker implements Connector
|
||||
func (cm *Docker) All() (containers container.Containers) {
|
||||
cm.lock.Lock()
|
||||
for _, c := range cm.containers {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build !linux
|
||||
|
||||
package connector
|
||||
|
||||
var enabled = map[string]func() Connector{
|
||||
"docker": NewDocker,
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// +build !darwin
|
||||
|
||||
package connector
|
||||
|
||||
var enabled = map[string]func() Connector{
|
||||
"docker": NewDocker,
|
||||
"runc": NewRunc,
|
||||
}
|
||||
@@ -2,25 +2,103 @@ package connector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bcicen/ctop/container"
|
||||
"github.com/bcicen/ctop/logging"
|
||||
)
|
||||
|
||||
var log = logging.Init()
|
||||
var (
|
||||
log = logging.Init()
|
||||
enabled = make(map[string]ConnectorFn)
|
||||
)
|
||||
|
||||
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 ConnectorFn func() (Connector, error)
|
||||
|
||||
type Connector interface {
|
||||
// All returns a pre-sorted container.Containers of all discovered containers
|
||||
All() container.Containers
|
||||
// Get returns a single container.Container by ID
|
||||
Get(string) (*container.Container, bool)
|
||||
// Wait waits for the underlying connection to be lost before returning
|
||||
Wait() struct{}
|
||||
}
|
||||
|
||||
// ConnectorSuper provides initial connection and retry on failure for
|
||||
// an undlerying Connector type
|
||||
type ConnectorSuper struct {
|
||||
conn Connector
|
||||
connFn ConnectorFn
|
||||
err error
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewConnectorSuper(connFn ConnectorFn) *ConnectorSuper {
|
||||
cs := &ConnectorSuper{
|
||||
connFn: connFn,
|
||||
err: fmt.Errorf("connecting..."),
|
||||
}
|
||||
go cs.loop()
|
||||
return cs
|
||||
}
|
||||
|
||||
// Get returns the underlying Connector, or nil and an error
|
||||
// if the Connector is not yet initialized or is disconnected.
|
||||
func (cs *ConnectorSuper) Get() (Connector, error) {
|
||||
cs.lock.RLock()
|
||||
defer cs.lock.RUnlock()
|
||||
if cs.err != nil {
|
||||
return nil, cs.err
|
||||
}
|
||||
return cs.conn, nil
|
||||
}
|
||||
|
||||
func (cs *ConnectorSuper) setError(err error) {
|
||||
cs.lock.Lock()
|
||||
defer cs.lock.Unlock()
|
||||
cs.err = err
|
||||
}
|
||||
|
||||
func (cs *ConnectorSuper) loop() {
|
||||
const interval = 3
|
||||
for {
|
||||
log.Infof("initializing connector")
|
||||
|
||||
conn, err := cs.connFn()
|
||||
if err != nil {
|
||||
cs.setError(err)
|
||||
log.Errorf("failed to initialize connector: %s (%T)", err, err)
|
||||
log.Errorf("retrying in %ds", interval)
|
||||
time.Sleep(interval * time.Second)
|
||||
} else {
|
||||
cs.conn = conn
|
||||
cs.setError(nil)
|
||||
log.Infof("successfully initialized connector")
|
||||
|
||||
// wait until connection closed
|
||||
cs.conn.Wait()
|
||||
cs.setError(fmt.Errorf("attempting to reconnect..."))
|
||||
log.Infof("connector closed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled returns names for all enabled connectors on the current platform
|
||||
func Enabled() (a []string) {
|
||||
for k, _ := range enabled {
|
||||
a = append(a, k)
|
||||
}
|
||||
sort.Strings(a)
|
||||
return a
|
||||
}
|
||||
|
||||
// ByName returns a ConnectorSuper for a given name, or error if the connector
|
||||
// does not exists on the current platform
|
||||
func ByName(s string) (*ConnectorSuper, error) {
|
||||
if cfn, ok := enabled[s]; ok {
|
||||
return NewConnectorSuper(cfn), nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid connector type \"%s\"", s)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package manager
|
||||
import (
|
||||
"fmt"
|
||||
api "github.com/fsouza/go-dockerclient"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Docker struct {
|
||||
@@ -17,6 +20,88 @@ func NewDocker(client *api.Client, id string) *Docker {
|
||||
}
|
||||
}
|
||||
|
||||
// Do not allow to close reader (i.e. /dev/stdin which docker client tries to close after command execution)
|
||||
type noClosableReader struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (w *noClosableReader) Read(p []byte) (n int, err error) {
|
||||
return w.Reader.Read(p)
|
||||
}
|
||||
|
||||
const (
|
||||
STDIN = 0
|
||||
STDOUT = 1
|
||||
STDERR = 2
|
||||
)
|
||||
|
||||
var wrongFrameFormat = errors.New("Wrong frame format")
|
||||
|
||||
// A frame has a Header and a Payload
|
||||
// Header: [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
|
||||
// STREAM_TYPE can be:
|
||||
// 0: stdin (is written on stdout)
|
||||
// 1: stdout
|
||||
// 2: stderr
|
||||
// SIZE1, SIZE2, SIZE3, SIZE4 are the four bytes of the uint32 size encoded as big endian.
|
||||
// But we don't use size, because we don't need to find the end of frame.
|
||||
type frameWriter struct {
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
stdin io.Writer
|
||||
}
|
||||
|
||||
func (w *frameWriter) Write(p []byte) (n int, err error) {
|
||||
// drop initial empty frames
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if len(p) > 8 {
|
||||
var targetWriter io.Writer
|
||||
switch p[0] {
|
||||
case STDIN:
|
||||
targetWriter = w.stdin
|
||||
break
|
||||
case STDOUT:
|
||||
targetWriter = w.stdout
|
||||
break
|
||||
case STDERR:
|
||||
targetWriter = w.stderr
|
||||
break
|
||||
default:
|
||||
return 0, wrongFrameFormat
|
||||
}
|
||||
|
||||
n, err := targetWriter.Write(p[8:])
|
||||
return n + 8, err
|
||||
}
|
||||
|
||||
return 0, wrongFrameFormat
|
||||
}
|
||||
|
||||
func (dc *Docker) Exec(cmd []string) error {
|
||||
execCmd, err := dc.client.CreateExec(api.CreateExecOptions{
|
||||
AttachStdin: true,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Cmd: cmd,
|
||||
Container: dc.id,
|
||||
Tty: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dc.client.StartExec(execCmd.ID, api.StartExecOptions{
|
||||
InputStream: &noClosableReader{os.Stdin},
|
||||
OutputStream: &frameWriter{os.Stdout, os.Stderr, os.Stdin},
|
||||
ErrorStream: os.Stderr,
|
||||
RawTerminal: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (dc *Docker) Start() error {
|
||||
c, err := dc.client.InspectContainer(dc.id)
|
||||
if err != nil {
|
||||
@@ -42,3 +127,24 @@ func (dc *Docker) Remove() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *Docker) Pause() error {
|
||||
if err := dc.client.PauseContainer(dc.id); err != nil {
|
||||
return fmt.Errorf("cannot pause container: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *Docker) Unpause() error {
|
||||
if err := dc.client.UnpauseContainer(dc.id); err != nil {
|
||||
return fmt.Errorf("cannot unpause container: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *Docker) Restart() error {
|
||||
if err := dc.client.RestartContainer(dc.id, 3); err != nil {
|
||||
return fmt.Errorf("cannot restart container: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,4 +4,8 @@ type Manager interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
Remove() error
|
||||
Pause() error
|
||||
Unpause() error
|
||||
Restart() error
|
||||
Exec(cmd []string) error
|
||||
}
|
||||
|
||||
@@ -17,3 +17,19 @@ func (m *Mock) Stop() error {
|
||||
func (m *Mock) Remove() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mock) Pause() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mock) Unpause() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mock) Restart() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mock) Exec(cmd []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,3 +17,19 @@ func (rc *Runc) Stop() error {
|
||||
func (rc *Runc) Remove() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *Runc) Pause() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *Runc) Unpause() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *Runc) Restart() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *Runc) Exec(cmd []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,15 +14,17 @@ import (
|
||||
"github.com/nu7hatch/gouuid"
|
||||
)
|
||||
|
||||
func init() { enabled["mock"] = NewMock }
|
||||
|
||||
type Mock struct {
|
||||
containers container.Containers
|
||||
}
|
||||
|
||||
func NewMock() *Mock {
|
||||
func NewMock() (Connector, error) {
|
||||
cs := &Mock{}
|
||||
go cs.Init()
|
||||
go cs.Loop()
|
||||
return cs
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// Create Mock containers
|
||||
@@ -39,6 +41,15 @@ func (cs *Mock) Init() {
|
||||
|
||||
}
|
||||
|
||||
func (cs *Mock) Wait() struct{} {
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
time.Sleep(30 * time.Second)
|
||||
close(ch)
|
||||
}()
|
||||
return <-ch
|
||||
}
|
||||
|
||||
func (cs *Mock) makeContainer(aggression int64) {
|
||||
collector := collector.NewMock(aggression)
|
||||
manager := manager.NewMock()
|
||||
@@ -71,7 +82,7 @@ func (cs *Mock) Get(id string) (*container.Container, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Return array of all containers, sorted by field
|
||||
// All returns array of all containers, sorted by field
|
||||
func (cs *Mock) All() container.Containers {
|
||||
cs.containers.Sort()
|
||||
cs.containers.Filter()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !darwin
|
||||
// +build linux
|
||||
|
||||
package connector
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
|
||||
)
|
||||
|
||||
func init() { enabled["runc"] = NewRunc }
|
||||
|
||||
type RuncOpts struct {
|
||||
root string // runc root path
|
||||
systemdCgroups bool // use systemd cgroups
|
||||
@@ -52,35 +54,44 @@ type Runc struct {
|
||||
factory libcontainer.Factory
|
||||
containers map[string]*container.Container
|
||||
libContainers map[string]libcontainer.Container
|
||||
closed chan struct{}
|
||||
needsRefresh chan string // container IDs requiring refresh
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewRunc() Connector {
|
||||
func NewRunc() (Connector, error) {
|
||||
opts, err := NewRuncOpts()
|
||||
runcFailOnErr(err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
factory, err := getFactory(opts)
|
||||
runcFailOnErr(err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cm := &Runc{
|
||||
opts: opts,
|
||||
factory: factory,
|
||||
containers: make(map[string]*container.Container),
|
||||
libContainers: make(map[string]libcontainer.Container),
|
||||
needsRefresh: make(chan string, 60),
|
||||
closed: make(chan struct{}),
|
||||
lock: sync.RWMutex{},
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
cm.refreshAll()
|
||||
time.Sleep(5 * time.Second)
|
||||
select {
|
||||
case <-cm.closed:
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
cm.refreshAll()
|
||||
}
|
||||
}
|
||||
}()
|
||||
go cm.Loop()
|
||||
|
||||
return cm
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
func (cm *Runc) GetLibc(id string) libcontainer.Container {
|
||||
@@ -139,7 +150,11 @@ func (cm *Runc) refresh(id string) {
|
||||
// Read runc root, creating any new containers
|
||||
func (cm *Runc) refreshAll() {
|
||||
list, err := ioutil.ReadDir(cm.opts.root)
|
||||
runcFailOnErr(err)
|
||||
if err != nil {
|
||||
log.Errorf("%s (%T)", err.Error(), err)
|
||||
close(cm.closed)
|
||||
return
|
||||
}
|
||||
|
||||
for _, i := range list {
|
||||
if i.IsDir() {
|
||||
@@ -166,7 +181,7 @@ func (cm *Runc) Loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// Get a single ctop container in the map matching libc container, creating one anew if not existing
|
||||
// MustGet gets 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 {
|
||||
@@ -197,14 +212,6 @@ func (cm *Runc) MustGet(id string) *container.Container {
|
||||
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()
|
||||
@@ -214,7 +221,18 @@ func (cm *Runc) delByID(id string) {
|
||||
log.Infof("removed dead container: %s", id)
|
||||
}
|
||||
|
||||
// Return array of all containers, sorted by field
|
||||
// Runc implements Connector
|
||||
func (cm *Runc) Wait() struct{} { return <-cm.closed }
|
||||
|
||||
// Runc implements Connector
|
||||
func (cm *Runc) Get(id string) (*container.Container, bool) {
|
||||
cm.lock.Lock()
|
||||
defer cm.lock.Unlock()
|
||||
c, ok := cm.containers[id]
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// Runc implements Connector
|
||||
func (cm *Runc) All() (containers container.Containers) {
|
||||
cm.lock.Lock()
|
||||
for _, c := range cm.containers {
|
||||
@@ -237,9 +255,3 @@ func getFactory(opts RuncOpts) (libcontainer.Factory, error) {
|
||||
}
|
||||
return libcontainer.New(opts.root, cgroupManager)
|
||||
}
|
||||
|
||||
func runcFailOnErr(err error) {
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("fatal runc error: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ var (
|
||||
log = logging.Init()
|
||||
)
|
||||
|
||||
const (
|
||||
running = "running"
|
||||
)
|
||||
|
||||
// Metrics and metadata representing a container
|
||||
type Container struct {
|
||||
models.Metrics
|
||||
@@ -60,17 +64,17 @@ func (c *Container) GetMeta(k string) string {
|
||||
func (c *Container) SetState(s string) {
|
||||
c.SetMeta("state", s)
|
||||
// start collector, if needed
|
||||
if s == "running" && !c.collector.Running() {
|
||||
if s == running && !c.collector.Running() {
|
||||
c.collector.Start()
|
||||
c.Read(c.collector.Stream())
|
||||
}
|
||||
// stop collector, if needed
|
||||
if s != "running" && c.collector.Running() {
|
||||
if s != running && c.collector.Running() {
|
||||
c.collector.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Return container log collector
|
||||
// Logs returns container log collector
|
||||
func (c *Container) Logs() collector.LogCollector {
|
||||
return c.collector.Logs()
|
||||
}
|
||||
@@ -90,18 +94,18 @@ func (c *Container) Read(stream chan models.Metrics) {
|
||||
}
|
||||
|
||||
func (c *Container) Start() {
|
||||
if c.Meta["state"] != "running" {
|
||||
if c.Meta["state"] != running {
|
||||
if err := c.manager.Start(); err != nil {
|
||||
log.Warningf("container %s: %v", c.Id, err)
|
||||
log.StatusErr(err)
|
||||
return
|
||||
}
|
||||
c.SetState("running")
|
||||
c.SetState(running)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) Stop() {
|
||||
if c.Meta["state"] == "running" {
|
||||
if c.Meta["state"] == running {
|
||||
if err := c.manager.Stop(); err != nil {
|
||||
log.Warningf("container %s: %v", c.Id, err)
|
||||
log.StatusErr(err)
|
||||
@@ -117,3 +121,39 @@ func (c *Container) Remove() {
|
||||
log.StatusErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) Pause() {
|
||||
if c.Meta["state"] == running {
|
||||
if err := c.manager.Pause(); err != nil {
|
||||
log.Warningf("container %s: %v", c.Id, err)
|
||||
log.StatusErr(err)
|
||||
return
|
||||
}
|
||||
c.SetState("paused")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) Unpause() {
|
||||
if c.Meta["state"] == "paused" {
|
||||
if err := c.manager.Unpause(); err != nil {
|
||||
log.Warningf("container %s: %v", c.Id, err)
|
||||
log.StatusErr(err)
|
||||
return
|
||||
}
|
||||
c.SetState(running)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) Restart() {
|
||||
if c.Meta["state"] == running {
|
||||
if err := c.manager.Restart(); err != nil {
|
||||
log.Warningf("container %s: %v", c.Id, err)
|
||||
log.StatusErr(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) Exec(cmd []string) error {
|
||||
return c.manager.Exec(cmd)
|
||||
}
|
||||
|
||||
59
cursor.go
59
cursor.go
@@ -11,7 +11,7 @@ import (
|
||||
type GridCursor struct {
|
||||
selectedID string // id of currently selected container
|
||||
filtered container.Containers
|
||||
cSource connector.Connector
|
||||
cSuper *connector.ConnectorSuper
|
||||
isScrolling bool // toggled when actively scrolling
|
||||
}
|
||||
|
||||
@@ -25,14 +25,20 @@ func (gc *GridCursor) Selected() *container.Container {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refresh containers from source
|
||||
func (gc *GridCursor) RefreshContainers() (lenChanged bool) {
|
||||
// Refresh containers from source, returning whether the quantity of
|
||||
// containers has changed and any error
|
||||
func (gc *GridCursor) RefreshContainers() (bool, error) {
|
||||
oldLen := gc.Len()
|
||||
|
||||
// Containers filtered by display bool
|
||||
gc.filtered = container.Containers{}
|
||||
|
||||
cSource, err := gc.cSuper.Get()
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
// filter Containers by display bool
|
||||
var cursorVisible bool
|
||||
for _, c := range gc.cSource.All() {
|
||||
for _, c := range cSource.All() {
|
||||
if c.Display {
|
||||
if c.Id == gc.selectedID {
|
||||
cursorVisible = true
|
||||
@@ -41,31 +47,30 @@ func (gc *GridCursor) RefreshContainers() (lenChanged bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if oldLen != gc.Len() {
|
||||
lenChanged = true
|
||||
if !cursorVisible || gc.selectedID == "" {
|
||||
gc.Reset()
|
||||
}
|
||||
|
||||
if !cursorVisible {
|
||||
gc.Reset()
|
||||
}
|
||||
if gc.selectedID == "" {
|
||||
gc.Reset()
|
||||
}
|
||||
return lenChanged
|
||||
return oldLen != gc.Len(), nil
|
||||
}
|
||||
|
||||
// Set an initial cursor position, if possible
|
||||
func (gc *GridCursor) Reset() {
|
||||
for _, c := range gc.cSource.All() {
|
||||
c.Widgets.Name.UnHighlight()
|
||||
cSource, err := gc.cSuper.Get()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, c := range cSource.All() {
|
||||
c.Widgets.UnHighlight()
|
||||
}
|
||||
if gc.Len() > 0 {
|
||||
gc.selectedID = gc.filtered[0].Id
|
||||
gc.filtered[0].Widgets.Name.Highlight()
|
||||
gc.filtered[0].Widgets.Highlight()
|
||||
}
|
||||
}
|
||||
|
||||
// Return current cursor index
|
||||
// Idx returns current cursor index
|
||||
func (gc *GridCursor) Idx() int {
|
||||
for n, c := range gc.filtered {
|
||||
if c.Id == gc.selectedID {
|
||||
@@ -109,9 +114,9 @@ func (gc *GridCursor) Up() {
|
||||
active := gc.filtered[idx]
|
||||
next := gc.filtered[idx-1]
|
||||
|
||||
active.Widgets.Name.UnHighlight()
|
||||
active.Widgets.UnHighlight()
|
||||
gc.selectedID = next.Id
|
||||
next.Widgets.Name.Highlight()
|
||||
next.Widgets.Highlight()
|
||||
|
||||
gc.ScrollPage()
|
||||
ui.Render(cGrid)
|
||||
@@ -128,9 +133,9 @@ func (gc *GridCursor) Down() {
|
||||
active := gc.filtered[idx]
|
||||
next := gc.filtered[idx+1]
|
||||
|
||||
active.Widgets.Name.UnHighlight()
|
||||
active.Widgets.UnHighlight()
|
||||
gc.selectedID = next.Id
|
||||
next.Widgets.Name.Highlight()
|
||||
next.Widgets.Highlight()
|
||||
|
||||
gc.ScrollPage()
|
||||
ui.Render(cGrid)
|
||||
@@ -151,9 +156,9 @@ func (gc *GridCursor) PgUp() {
|
||||
active := gc.filtered[idx]
|
||||
next := gc.filtered[nextidx]
|
||||
|
||||
active.Widgets.Name.UnHighlight()
|
||||
active.Widgets.UnHighlight()
|
||||
gc.selectedID = next.Id
|
||||
next.Widgets.Name.Highlight()
|
||||
next.Widgets.Highlight()
|
||||
|
||||
cGrid.Align()
|
||||
ui.Render(cGrid)
|
||||
@@ -174,9 +179,9 @@ func (gc *GridCursor) PgDown() {
|
||||
active := gc.filtered[idx]
|
||||
next := gc.filtered[nextidx]
|
||||
|
||||
active.Widgets.Name.UnHighlight()
|
||||
active.Widgets.UnHighlight()
|
||||
gc.selectedID = next.Id
|
||||
next.Widgets.Name.Highlight()
|
||||
next.Widgets.Highlight()
|
||||
|
||||
cGrid.Align()
|
||||
ui.Render(cGrid)
|
||||
|
||||
@@ -23,6 +23,16 @@ func (w *GaugeCol) Reset() {
|
||||
w.Percent = 0
|
||||
}
|
||||
|
||||
func (w *GaugeCol) Highlight() {
|
||||
w.Bg = ui.ThemeAttr("par.text.fg")
|
||||
w.PercentColor = ui.ThemeAttr("par.text.hi")
|
||||
}
|
||||
|
||||
func (w *GaugeCol) UnHighlight() {
|
||||
w.Bg = ui.ThemeAttr("par.text.bg")
|
||||
w.PercentColor = ui.ThemeAttr("par.text.bg")
|
||||
}
|
||||
|
||||
func colorScale(n int) ui.Attribute {
|
||||
if n > 70 {
|
||||
return ui.ThemeAttr("status.danger")
|
||||
|
||||
@@ -23,10 +23,7 @@ func NewCompactGrid() *CompactGrid {
|
||||
func (cg *CompactGrid) Align() {
|
||||
y := cg.Y
|
||||
|
||||
if cg.Offset >= len(cg.Rows) {
|
||||
cg.Offset = 0
|
||||
}
|
||||
if cg.Offset < 0 {
|
||||
if cg.Offset >= len(cg.Rows) || cg.Offset < 0 {
|
||||
cg.Offset = 0
|
||||
}
|
||||
|
||||
@@ -60,7 +57,5 @@ func (cg *CompactGrid) Buffer() ui.Buffer {
|
||||
}
|
||||
|
||||
func (cg *CompactGrid) AddRows(rows ...ui.GridBufferer) {
|
||||
for _, r := range rows {
|
||||
cg.Rows = append(cg.Rows, r)
|
||||
}
|
||||
cg.Rows = append(cg.Rows, rows...)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package compact
|
||||
|
||||
import (
|
||||
"github.com/bcicen/ctop/config"
|
||||
"github.com/bcicen/ctop/logging"
|
||||
"github.com/bcicen/ctop/models"
|
||||
ui "github.com/gizak/termui"
|
||||
@@ -17,6 +18,7 @@ type Compact struct {
|
||||
Net *TextCol
|
||||
IO *TextCol
|
||||
Pids *TextCol
|
||||
Bg *RowBg
|
||||
X, Y int
|
||||
Width int
|
||||
Height int
|
||||
@@ -36,6 +38,7 @@ func NewCompact(id string) *Compact {
|
||||
Net: NewTextCol("-"),
|
||||
IO: NewTextCol("-"),
|
||||
Pids: NewTextCol("-"),
|
||||
Bg: NewRowBg(),
|
||||
X: 1,
|
||||
Height: 1,
|
||||
}
|
||||
@@ -90,6 +93,8 @@ func (row *Compact) SetY(y int) {
|
||||
if y == row.Y {
|
||||
return
|
||||
}
|
||||
|
||||
row.Bg.Y = y
|
||||
for _, col := range row.all() {
|
||||
col.SetY(y)
|
||||
}
|
||||
@@ -101,6 +106,10 @@ func (row *Compact) SetWidth(width int) {
|
||||
return
|
||||
}
|
||||
x := row.X
|
||||
|
||||
row.Bg.SetX(x + colWidths[0] + 1)
|
||||
row.Bg.SetWidth(width)
|
||||
|
||||
autoWidth := calcWidth(width)
|
||||
for n, col := range row.all() {
|
||||
if colWidths[n] != 0 {
|
||||
@@ -119,6 +128,7 @@ func (row *Compact) SetWidth(width int) {
|
||||
func (row *Compact) Buffer() ui.Buffer {
|
||||
buf := ui.NewBuffer()
|
||||
|
||||
buf.Merge(row.Bg.Buffer())
|
||||
buf.Merge(row.Status.Buffer())
|
||||
buf.Merge(row.Name.Buffer())
|
||||
buf.Merge(row.Cid.Buffer())
|
||||
@@ -142,3 +152,44 @@ func (row *Compact) all() []ui.GridBufferer {
|
||||
row.Pids,
|
||||
}
|
||||
}
|
||||
|
||||
func (row *Compact) Highlight() {
|
||||
row.Name.Highlight()
|
||||
if config.GetSwitchVal("fullRowCursor") {
|
||||
row.Bg.Highlight()
|
||||
row.Cid.Highlight()
|
||||
row.Cpu.Highlight()
|
||||
row.Mem.Highlight()
|
||||
row.Net.Highlight()
|
||||
row.IO.Highlight()
|
||||
row.Pids.Highlight()
|
||||
}
|
||||
}
|
||||
|
||||
func (row *Compact) UnHighlight() {
|
||||
row.Name.UnHighlight()
|
||||
if config.GetSwitchVal("fullRowCursor") {
|
||||
row.Bg.UnHighlight()
|
||||
row.Cid.UnHighlight()
|
||||
row.Cpu.UnHighlight()
|
||||
row.Mem.UnHighlight()
|
||||
row.Net.UnHighlight()
|
||||
row.IO.UnHighlight()
|
||||
row.Pids.UnHighlight()
|
||||
}
|
||||
}
|
||||
|
||||
type RowBg struct {
|
||||
*ui.Par
|
||||
}
|
||||
|
||||
func NewRowBg() *RowBg {
|
||||
bg := ui.NewPar("")
|
||||
bg.Height = 1
|
||||
bg.Border = false
|
||||
bg.Bg = ui.ThemeAttr("par.text.bg")
|
||||
return &RowBg{bg}
|
||||
}
|
||||
|
||||
func (w *RowBg) Highlight() { w.Bg = ui.ThemeAttr("par.text.fg") }
|
||||
func (w *RowBg) UnHighlight() { w.Bg = ui.ThemeAttr("par.text.bg") }
|
||||
|
||||
@@ -19,7 +19,7 @@ func (row *Compact) SetIO(read int64, write int64) {
|
||||
}
|
||||
|
||||
func (row *Compact) SetPids(val int) {
|
||||
label := fmt.Sprintf("%s", strconv.Itoa(val))
|
||||
label := strconv.Itoa(val)
|
||||
row.Pids.Set(label)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
mark = string('\u25C9')
|
||||
healthMark = string('\u207A')
|
||||
mark = "◉"
|
||||
healthMark = "✚"
|
||||
vBar = string('\u25AE') + string('\u25AE')
|
||||
)
|
||||
|
||||
@@ -18,7 +18,10 @@ type Status struct {
|
||||
}
|
||||
|
||||
func NewStatus() *Status {
|
||||
s := &Status{Block: ui.NewBlock()}
|
||||
s := &Status{
|
||||
Block: ui.NewBlock(),
|
||||
health: []ui.Cell{{Ch: ' '}},
|
||||
}
|
||||
s.Height = 1
|
||||
s.Border = false
|
||||
s.Set("")
|
||||
@@ -28,11 +31,12 @@ func NewStatus() *Status {
|
||||
func (s *Status) Buffer() ui.Buffer {
|
||||
buf := s.Block.Buffer()
|
||||
x := 0
|
||||
for _, c := range s.status {
|
||||
for _, c := range s.health {
|
||||
buf.Set(s.InnerX()+x, s.InnerY(), c)
|
||||
x += c.Width()
|
||||
}
|
||||
for _, c := range s.health {
|
||||
x += 1
|
||||
for _, c := range s.status {
|
||||
buf.Set(s.InnerX()+x, s.InnerY(), c)
|
||||
x += c.Width()
|
||||
}
|
||||
@@ -53,18 +57,16 @@ func (s *Status) Set(val string) {
|
||||
text = vBar
|
||||
}
|
||||
|
||||
var cells []ui.Cell
|
||||
for _, ch := range text {
|
||||
cells = append(cells, ui.Cell{Ch: ch, Fg: color})
|
||||
}
|
||||
s.status = cells
|
||||
s.status = ui.TextCells(text, color, ui.ColorDefault)
|
||||
}
|
||||
|
||||
func (s *Status) SetHealth(val string) {
|
||||
if val == "" {
|
||||
return
|
||||
}
|
||||
|
||||
color := ui.ColorDefault
|
||||
mark := healthMark
|
||||
|
||||
switch val {
|
||||
case "healthy":
|
||||
@@ -75,9 +77,5 @@ func (s *Status) SetHealth(val string) {
|
||||
color = ui.ThemeAttr("status.warn")
|
||||
}
|
||||
|
||||
var cells []ui.Cell
|
||||
for _, ch := range healthMark {
|
||||
cells = append(cells, ui.Cell{Ch: ch, Fg: color})
|
||||
}
|
||||
s.health = cells
|
||||
s.health = ui.TextCells(mark, color, ui.ColorDefault)
|
||||
}
|
||||
|
||||
@@ -17,11 +17,13 @@ func NewTextCol(s string) *TextCol {
|
||||
}
|
||||
|
||||
func (w *TextCol) Highlight() {
|
||||
w.Bg = ui.ThemeAttr("par.text.fg")
|
||||
w.TextFgColor = ui.ThemeAttr("par.text.hi")
|
||||
w.TextBgColor = ui.ThemeAttr("par.text.fg")
|
||||
}
|
||||
|
||||
func (w *TextCol) UnHighlight() {
|
||||
w.Bg = ui.ThemeAttr("par.text.bg")
|
||||
w.TextFgColor = ui.ThemeAttr("par.text.fg")
|
||||
w.TextBgColor = ui.ThemeAttr("par.text.bg")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package compact
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ui "github.com/gizak/termui"
|
||||
)
|
||||
|
||||
@@ -11,7 +12,7 @@ const colSpacing = 1
|
||||
|
||||
// per-column width. 0 == auto width
|
||||
var colWidths = []int{
|
||||
3, // status
|
||||
5, // status
|
||||
0, // name
|
||||
0, // cid
|
||||
0, // cpu
|
||||
@@ -28,7 +29,7 @@ func calcWidth(width int) int {
|
||||
for _, w := range colWidths {
|
||||
width -= w
|
||||
if w == 0 {
|
||||
staticCols += 1
|
||||
staticCols++
|
||||
}
|
||||
}
|
||||
return (width - spacing) / staticCols
|
||||
|
||||
36
cwidgets/single/env.go
Normal file
36
cwidgets/single/env.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package single
|
||||
|
||||
import (
|
||||
ui "github.com/gizak/termui"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var envPattern = regexp.MustCompile(`(?P<KEY>[^=]+)=(?P<VALUJE>.*)`)
|
||||
|
||||
type Env struct {
|
||||
*ui.Table
|
||||
data map[string]string
|
||||
}
|
||||
|
||||
func NewEnv() *Env {
|
||||
p := ui.NewTable()
|
||||
p.Height = 4
|
||||
p.Width = colWidth[0]
|
||||
p.FgColor = ui.ThemeAttr("par.text.fg")
|
||||
p.Separator = false
|
||||
i := &Env{p, make(map[string]string)}
|
||||
i.BorderLabel = "Env"
|
||||
return i
|
||||
}
|
||||
|
||||
func (w *Env) Set(k, v string) {
|
||||
match := envPattern.FindStringSubmatch(v)
|
||||
key := match[1]
|
||||
value := match[2]
|
||||
w.data[key] = value
|
||||
|
||||
w.Rows = [][]string{}
|
||||
w.Rows = append(w.Rows, mkInfoRows(key, value)...)
|
||||
|
||||
w.Height = len(w.Rows) + 2
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
ui "github.com/gizak/termui"
|
||||
)
|
||||
|
||||
var displayInfo = []string{"id", "name", "image", "ports", "state", "created", "health"}
|
||||
var displayInfo = []string{"id", "name", "image", "ports", "IPs", "state", "created", "health"}
|
||||
|
||||
type Info struct {
|
||||
*ui.Table
|
||||
@@ -45,7 +45,7 @@ func mkInfoRows(k, v string) (rows [][]string) {
|
||||
// initial row with field name
|
||||
rows = append(rows, []string{k, lines[0]})
|
||||
|
||||
// append any additional lines in seperate row
|
||||
// append any additional lines in separate row
|
||||
if len(lines) > 1 {
|
||||
for _, line := range lines[1:] {
|
||||
if line != "" {
|
||||
|
||||
@@ -18,6 +18,7 @@ type Single struct {
|
||||
Cpu *Cpu
|
||||
Mem *Mem
|
||||
IO *IO
|
||||
Env *Env
|
||||
X, Y int
|
||||
Width int
|
||||
}
|
||||
@@ -32,6 +33,7 @@ func NewSingle(id string) *Single {
|
||||
Cpu: NewCpu(),
|
||||
Mem: NewMem(),
|
||||
IO: NewIO(),
|
||||
Env: NewEnv(),
|
||||
Width: ui.TermWidth(),
|
||||
}
|
||||
}
|
||||
@@ -52,8 +54,14 @@ func (e *Single) Down() {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Single) SetWidth(w int) { e.Width = w }
|
||||
func (e *Single) SetMeta(k, v string) { e.Info.Set(k, v) }
|
||||
func (e *Single) SetWidth(w int) { e.Width = w }
|
||||
func (e *Single) SetMeta(k, v string) {
|
||||
if k == "[ENV-VAR]" {
|
||||
e.Env.Set(k, v)
|
||||
} else {
|
||||
e.Info.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Single) SetMetrics(m models.Metrics) {
|
||||
e.Cpu.Update(m.CPUUtil)
|
||||
@@ -62,13 +70,14 @@ func (e *Single) SetMetrics(m models.Metrics) {
|
||||
e.IO.Update(m.IOBytesRead, m.IOBytesWrite)
|
||||
}
|
||||
|
||||
// Return total column height
|
||||
// GetHeight returns total column height
|
||||
func (e *Single) GetHeight() (h int) {
|
||||
h += e.Info.Height
|
||||
h += e.Net.Height
|
||||
h += e.Cpu.Height
|
||||
h += e.Mem.Height
|
||||
h += e.IO.Height
|
||||
h += e.Env.Height
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -103,6 +112,7 @@ func (e *Single) Buffer() ui.Buffer {
|
||||
buf.Merge(e.Mem.Buffer())
|
||||
buf.Merge(e.Net.Buffer())
|
||||
buf.Merge(e.IO.Buffer())
|
||||
buf.Merge(e.Env.Buffer())
|
||||
return buf
|
||||
}
|
||||
|
||||
@@ -113,6 +123,7 @@ func (e *Single) all() []ui.GridBufferer {
|
||||
e.Mem,
|
||||
e.Net,
|
||||
e.IO,
|
||||
e.Env,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
32
go.mod
Normal file
32
go.mod
Normal file
@@ -0,0 +1,32 @@
|
||||
module github.com/bcicen/ctop
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.0
|
||||
github.com/c9s/goprocinfo v0.0.0-20170609001544-b34328d6e0cd
|
||||
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b // indirect
|
||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50 // indirect
|
||||
github.com/coreos/go-systemd v0.0.0-20151104194251-b4a58d95188d // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.2 // indirect
|
||||
github.com/fsouza/go-dockerclient v1.4.1
|
||||
github.com/gizak/termui v2.3.0+incompatible
|
||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55 // indirect
|
||||
github.com/jgautheron/codename-generator v0.0.0-20150829203204-16d037c7cc3c
|
||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c // indirect
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618 // indirect
|
||||
github.com/nsf/termbox-go v0.0.0-20180303152453-e2050e41c884
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d
|
||||
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473
|
||||
github.com/opencontainers/runc v1.0.0-rc8
|
||||
github.com/opencontainers/runtime-spec v1.0.1 // indirect
|
||||
github.com/opencontainers/selinux v1.2.2 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/seccomp/libseccomp-golang v0.0.0-20150813023252-1b506fc7c24e // indirect
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // indirect
|
||||
github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect
|
||||
)
|
||||
|
||||
replace github.com/gizak/termui => github.com/bcicen/termui v0.0.0-20180326052246-4eb80249d3f5
|
||||
|
||||
go 1.13
|
||||
103
go.sum
Normal file
103
go.sum
Normal file
@@ -0,0 +1,103 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
|
||||
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
|
||||
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/bcicen/termui v0.0.0-20180326052246-4eb80249d3f5 h1:2pI3ZsoefWIi++8EqmANoC7Px/v2lRwnleVUcCuFgLg=
|
||||
github.com/bcicen/termui v0.0.0-20180326052246-4eb80249d3f5/go.mod h1:yIA9ITWZD1p4/DvCQ44xvhyVb9XEUlVnY1rzGSHwbiM=
|
||||
github.com/c9s/goprocinfo v0.0.0-20170609001544-b34328d6e0cd h1:xqaBnULC8wEnQpRDXAsDgXkU/STqoluz1REOoegSfNU=
|
||||
github.com/c9s/goprocinfo v0.0.0-20170609001544-b34328d6e0cd/go.mod h1:uEyr4WpAH4hio6LFriaPkL938XnrvLpNPmQHBdrmbIE=
|
||||
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b h1:T4nWG1TXIxeor8mAu5bFguPJgSIGhZqv/f0z55KCrJM=
|
||||
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho=
|
||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50 h1:WMpHmC6AxwWb9hMqhudkqG7A/p14KiMnl6d3r1iUMjU=
|
||||
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/coreos/go-systemd v0.0.0-20151104194251-b4a58d95188d h1:MJ4ge3i0lehw+gE3JcGUUp8TmWjsLAlQlhmdASs/9wk=
|
||||
github.com/coreos/go-systemd v0.0.0-20151104194251-b4a58d95188d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16 h1:dmUn0SuGx7unKFwxyeQ/oLUHhEfZosEDrpmYM+6MTuc=
|
||||
github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/fsouza/go-dockerclient v1.4.1 h1:W7wuJ3IB48WYZv/UBk9dCTIb9oX805+L9KIm65HcUYs=
|
||||
github.com/fsouza/go-dockerclient v1.4.1/go.mod h1:PUNHxbowDqRXfRgZqMz1OeGtbWC6VKyZvJ99hDjB0qs=
|
||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55 h1:oIgNYSrSUbNH5DJh6DMhU1PiOKOYIHNxrV3djLsLpEI=
|
||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
|
||||
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd h1:anPrsicrIi2ColgWTVPk+TrN42hJIWlfPHSBP9S0ZkM=
|
||||
github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd/go.mod h1:3LVOLeyx9XVvwPgrt2be44XgSqndprz1G18rSk8KD84=
|
||||
github.com/jgautheron/codename-generator v0.0.0-20150829203204-16d037c7cc3c h1:/hc+TxW4Q1v6aqNPHE5jiaNF2xEK0CzWTgo25RQhQ+U=
|
||||
github.com/jgautheron/codename-generator v0.0.0-20150829203204-16d037c7cc3c/go.mod h1:FJRkXmPrkHw0WDjB/LXMUhjWJ112Y6JUYnIVBOy8oH8=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c h1:eFzthqtg3W6Pihj3DMTXLAF4f+ge5r5Ie5g6HLIZAF0=
|
||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618 h1:7InQ7/zrOh6SlFjaXFubv0xX0HsuC9qJsdqm7bNQpYM=
|
||||
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
|
||||
github.com/nsf/termbox-go v0.0.0-20180303152453-e2050e41c884 h1:fcs71SMqqDhUD+PbpIv9xf3EH9F9s6HfiLwr6jKm1VA=
|
||||
github.com/nsf/termbox-go v0.0.0-20180303152453-e2050e41c884/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473 h1:J1QZwDXgZ4dJD2s19iqR9+U00OWM2kDzbf1O/fmvCWg=
|
||||
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc8 h1:dDCFes8Hj1r/i5qnypONo5jdOme/8HWZC/aNDyhECt0=
|
||||
github.com/opencontainers/runc v1.0.0-rc8/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runtime-spec v1.0.1 h1:wY4pOY8fBdSIvs9+IDHC55thBuEulhzfSgKeC1yFvzQ=
|
||||
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.2.2 h1:Kx9J6eDG5/24A6DtUquGSpJQ+m2MUTahn4FtGEe8bFg=
|
||||
github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/seccomp/libseccomp-golang v0.0.0-20150813023252-1b506fc7c24e h1:HJbgNpzYMeTLPpkMwbPNTPlhNd9r4xQtqcZG6qoIGgs=
|
||||
github.com/seccomp/libseccomp-golang v0.0.0-20150813023252-1b506fc7c24e/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc h1:0HAHLwEY4k1VqaO1SzBi4XxT0KA06Cv+QW2LXknBk9g=
|
||||
github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190310054646-10058d7d4faa h1:lqti/xP+yD/6zH5TqEwx2MilNIJY5Vbc6Qr8J3qyPIQ=
|
||||
golang.org/x/sys v0.0.0-20190310054646-10058d7d4faa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
74
grid.go
74
grid.go
@@ -6,6 +6,44 @@ import (
|
||||
ui "github.com/gizak/termui"
|
||||
)
|
||||
|
||||
func ShowConnError(err error) (exit bool) {
|
||||
ui.Clear()
|
||||
ui.DefaultEvtStream.ResetHandlers()
|
||||
defer ui.DefaultEvtStream.ResetHandlers()
|
||||
|
||||
setErr := func(err error) {
|
||||
errView.Append(err.Error())
|
||||
errView.Append("attempting to reconnect...")
|
||||
ui.Render(errView)
|
||||
}
|
||||
|
||||
HandleKeys("exit", func() {
|
||||
exit = true
|
||||
ui.StopLoop()
|
||||
})
|
||||
|
||||
ui.Handle("/timer/1s", func(ui.Event) {
|
||||
_, err := cursor.RefreshContainers()
|
||||
if err == nil {
|
||||
ui.StopLoop()
|
||||
return
|
||||
}
|
||||
setErr(err)
|
||||
})
|
||||
|
||||
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
||||
errView.Resize()
|
||||
ui.Clear()
|
||||
ui.Render(errView)
|
||||
log.Infof("RESIZE")
|
||||
})
|
||||
|
||||
errView.Resize()
|
||||
setErr(err)
|
||||
ui.Loop()
|
||||
return exit
|
||||
}
|
||||
|
||||
func RedrawRows(clr bool) {
|
||||
// reinit body rows
|
||||
cGrid.Clear()
|
||||
@@ -33,7 +71,6 @@ func RedrawRows(clr bool) {
|
||||
}
|
||||
cGrid.Align()
|
||||
ui.Render(cGrid)
|
||||
|
||||
}
|
||||
|
||||
func SingleView() MenuFn {
|
||||
@@ -68,16 +105,21 @@ func SingleView() MenuFn {
|
||||
return nil
|
||||
}
|
||||
|
||||
func RefreshDisplay() {
|
||||
func RefreshDisplay() error {
|
||||
// skip display refresh during scroll
|
||||
if !cursor.isScrolling {
|
||||
needsClear := cursor.RefreshContainers()
|
||||
needsClear, err := cursor.RefreshContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
RedrawRows(needsClear)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Display() bool {
|
||||
var menu MenuFn
|
||||
var connErr error
|
||||
|
||||
cGrid.SetWidth(ui.TermWidth())
|
||||
ui.DefaultEvtStream.Hook(logEvent)
|
||||
@@ -104,17 +146,32 @@ func Display() bool {
|
||||
menu = ContainerMenu
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Handle("/sys/kbd/<left>", func(ui.Event) {
|
||||
menu = LogMenu
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Handle("/sys/kbd/<right>", func(ui.Event) {
|
||||
menu = SingleView
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Handle("/sys/kbd/l", func(ui.Event) {
|
||||
menu = LogMenu
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Handle("/sys/kbd/e", func(ui.Event) {
|
||||
menu = ExecShell
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Handle("/sys/kbd/o", func(ui.Event) {
|
||||
menu = SingleView
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Handle("/sys/kbd/a", func(ui.Event) {
|
||||
config.Toggle("allContainers")
|
||||
RefreshDisplay()
|
||||
connErr = RefreshDisplay()
|
||||
if connErr != nil {
|
||||
ui.StopLoop()
|
||||
}
|
||||
})
|
||||
ui.Handle("/sys/kbd/D", func(ui.Event) {
|
||||
dumpContainer(cursor.Selected())
|
||||
@@ -148,7 +205,10 @@ func Display() bool {
|
||||
if log.StatusQueued() {
|
||||
ui.StopLoop()
|
||||
}
|
||||
RefreshDisplay()
|
||||
connErr = RefreshDisplay()
|
||||
if connErr != nil {
|
||||
ui.StopLoop()
|
||||
}
|
||||
})
|
||||
|
||||
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
||||
@@ -162,6 +222,10 @@ func Display() bool {
|
||||
|
||||
ui.Loop()
|
||||
|
||||
if connErr != nil {
|
||||
return ShowConnError(connErr)
|
||||
}
|
||||
|
||||
if log.StatusQueued() {
|
||||
for sm := range log.FlushStatus() {
|
||||
if sm.IsError {
|
||||
|
||||
26
install.sh
26
install.sh
@@ -5,6 +5,10 @@ KERNEL=$(uname -s)
|
||||
|
||||
function output() { echo -e "\033[32mctop-install\033[0m $@"; }
|
||||
|
||||
function command_exists() {
|
||||
command -v "$@" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# extract github download url matching pattern
|
||||
function extract_url() {
|
||||
match=$1; shift
|
||||
@@ -28,6 +32,26 @@ case $KERNEL in
|
||||
;;
|
||||
esac
|
||||
|
||||
for req in curl wget; do
|
||||
command_exists $req || {
|
||||
output "missing required $req binary"
|
||||
req_failed=1
|
||||
}
|
||||
done
|
||||
[ "$req_failed" == 1 ] && exit 1
|
||||
|
||||
sh_c='sh -c'
|
||||
if [ "$CURRENT_USER" != 'root' ]; then
|
||||
if command_exists sudo; then
|
||||
sh_c='sudo -E sh -c'
|
||||
elif command_exists su; then
|
||||
sh_c='su -c'
|
||||
else
|
||||
output "Error: this installer needs the ability to run commands as root. We are unable to find either "sudo" or "su" available to make this happen."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
TMP=$(mktemp -d "${TMPDIR:-/tmp}/ctop.XXXXX")
|
||||
cd ${TMP}
|
||||
|
||||
@@ -55,6 +79,6 @@ wget -q --show-progress $url
|
||||
|
||||
output "installing to /usr/local/bin"
|
||||
chmod +x ctop-*
|
||||
sudo mv ctop-* /usr/local/bin/ctop
|
||||
$sh_c "mv ctop-* /usr/local/bin/ctop"
|
||||
|
||||
output "done!"
|
||||
|
||||
@@ -2,6 +2,7 @@ package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
@@ -56,13 +57,13 @@ func StopServer() {
|
||||
}
|
||||
}
|
||||
|
||||
func handler(conn net.Conn) {
|
||||
func handler(wc io.WriteCloser) {
|
||||
server.wg.Add(1)
|
||||
defer server.wg.Done()
|
||||
defer conn.Close()
|
||||
defer wc.Close()
|
||||
for msg := range Log.tail() {
|
||||
msg = fmt.Sprintf("%s\n", msg)
|
||||
conn.Write([]byte(msg))
|
||||
wc.Write([]byte(msg))
|
||||
}
|
||||
conn.Write([]byte("bye\n"))
|
||||
wc.Write([]byte("bye\n"))
|
||||
}
|
||||
|
||||
28
main.go
28
main.go
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/bcicen/ctop/config"
|
||||
"github.com/bcicen/ctop/connector"
|
||||
@@ -21,11 +22,12 @@ var (
|
||||
version = "dev-build"
|
||||
goVersion = runtime.Version()
|
||||
|
||||
log *logging.CTopLogger
|
||||
cursor *GridCursor
|
||||
cGrid *compact.CompactGrid
|
||||
header *widgets.CTopHeader
|
||||
status *widgets.StatusLine
|
||||
log *logging.CTopLogger
|
||||
cursor *GridCursor
|
||||
cGrid *compact.CompactGrid
|
||||
header *widgets.CTopHeader
|
||||
status *widgets.StatusLine
|
||||
errView *widgets.ErrorView
|
||||
|
||||
versionStr = fmt.Sprintf("ctop version %v, build %v %v", version, build, goVersion)
|
||||
)
|
||||
@@ -44,6 +46,7 @@ func main() {
|
||||
invertFlag = flag.Bool("i", false, "invert default colors")
|
||||
scaleCpu = flag.Bool("scale-cpu", false, "show cpu as % of system total")
|
||||
connectorFlag = flag.String("connector", "docker", "container connector to use")
|
||||
defaultShell = flag.String("shell", "", "default shell")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
@@ -86,6 +89,10 @@ func main() {
|
||||
config.Toggle("scaleCpu")
|
||||
}
|
||||
|
||||
if *defaultShell != "" {
|
||||
config.Update("shell", *defaultShell)
|
||||
}
|
||||
|
||||
// init ui
|
||||
if *invertFlag {
|
||||
InvertColorMap()
|
||||
@@ -94,17 +101,19 @@ func main() {
|
||||
if err := ui.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tm.SetInputMode(tm.InputAlt)
|
||||
|
||||
defer Shutdown()
|
||||
// init grid, cursor, header
|
||||
conn, err := connector.ByName(*connectorFlag)
|
||||
cSuper, err := connector.ByName(*connectorFlag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cursor = &GridCursor{cSource: conn}
|
||||
cursor = &GridCursor{cSuper: cSuper}
|
||||
cGrid = compact.NewCompactGrid()
|
||||
header = widgets.NewCTopHeader()
|
||||
status = widgets.NewStatusLine()
|
||||
errView = widgets.NewErrorView()
|
||||
|
||||
for {
|
||||
exit := Display()
|
||||
@@ -133,12 +142,13 @@ func validSort(s string) {
|
||||
func panicExit() {
|
||||
if r := recover(); r != nil {
|
||||
Shutdown()
|
||||
panic(r)
|
||||
fmt.Printf("error: %s\n", r)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var helpMsg = `ctop - container metric viewer
|
||||
var helpMsg = `ctop - interactive container viewer
|
||||
|
||||
usage: ctop [options]
|
||||
|
||||
@@ -148,4 +158,6 @@ options:
|
||||
func printHelp() {
|
||||
fmt.Println(helpMsg)
|
||||
flag.PrintDefaults()
|
||||
fmt.Printf("\navailable connectors: ")
|
||||
fmt.Println(strings.Join(connector.Enabled(), ", "))
|
||||
}
|
||||
|
||||
131
menus.go
131
menus.go
@@ -25,6 +25,7 @@ var helpDialog = []menu.Item{
|
||||
{"[r] - reverse container sort order", ""},
|
||||
{"[o] - open single view", ""},
|
||||
{"[l] - view container logs ([t] to toggle timestamp when open)", ""},
|
||||
{"[e] - exec shell", ""},
|
||||
{"[S] - save current configuration to file", ""},
|
||||
{"[q] - exit ctop", ""},
|
||||
}
|
||||
@@ -37,7 +38,10 @@ func HelpMenu() MenuFn {
|
||||
m := menu.NewMenu()
|
||||
m.BorderLabel = "Help"
|
||||
m.AddItems(helpDialog...)
|
||||
ui.Render(m)
|
||||
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
||||
ui.Clear()
|
||||
ui.Render(m)
|
||||
})
|
||||
ui.Handle("/sys/kbd/", func(ui.Event) {
|
||||
ui.StopLoop()
|
||||
})
|
||||
@@ -123,44 +127,111 @@ func ContainerMenu() MenuFn {
|
||||
m.BorderLabel = "Menu"
|
||||
|
||||
items := []menu.Item{
|
||||
menu.Item{Val: "single", Label: "single view"},
|
||||
menu.Item{Val: "logs", Label: "log view"},
|
||||
menu.Item{Val: "single", Label: "[o] single view"},
|
||||
menu.Item{Val: "logs", Label: "[l] log view"},
|
||||
}
|
||||
|
||||
if c.Meta["state"] == "running" {
|
||||
items = append(items, menu.Item{Val: "stop", Label: "stop"})
|
||||
items = append(items, menu.Item{Val: "stop", Label: "[s] stop"})
|
||||
items = append(items, menu.Item{Val: "pause", Label: "[p] pause"})
|
||||
items = append(items, menu.Item{Val: "restart", Label: "[r] restart"})
|
||||
items = append(items, menu.Item{Val: "exec", Label: "[e] exec shell"})
|
||||
}
|
||||
if c.Meta["state"] == "exited" || c.Meta["state"] == "created" {
|
||||
items = append(items, menu.Item{Val: "start", Label: "start"})
|
||||
items = append(items, menu.Item{Val: "remove", Label: "remove"})
|
||||
items = append(items, menu.Item{Val: "start", Label: "[s] start"})
|
||||
items = append(items, menu.Item{Val: "remove", Label: "[R] remove"})
|
||||
}
|
||||
items = append(items, menu.Item{Val: "cancel", Label: "cancel"})
|
||||
if c.Meta["state"] == "paused" {
|
||||
items = append(items, menu.Item{Val: "unpause", Label: "[p] unpause"})
|
||||
}
|
||||
items = append(items, menu.Item{Val: "cancel", Label: "[c] cancel"})
|
||||
|
||||
m.AddItems(items...)
|
||||
ui.Render(m)
|
||||
|
||||
var nextMenu MenuFn
|
||||
HandleKeys("up", m.Up)
|
||||
HandleKeys("down", m.Down)
|
||||
|
||||
var selected string
|
||||
|
||||
// shortcuts
|
||||
ui.Handle("/sys/kbd/o", func(ui.Event) {
|
||||
selected = "single"
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Handle("/sys/kbd/l", func(ui.Event) {
|
||||
selected = "logs"
|
||||
ui.StopLoop()
|
||||
})
|
||||
if c.Meta["state"] != "paused" {
|
||||
ui.Handle("/sys/kbd/s", func(ui.Event) {
|
||||
if c.Meta["state"] == "running" {
|
||||
selected = "stop"
|
||||
} else {
|
||||
selected = "start"
|
||||
}
|
||||
ui.StopLoop()
|
||||
})
|
||||
}
|
||||
if c.Meta["state"] != "exited" || c.Meta["state"] != "created" {
|
||||
ui.Handle("/sys/kbd/p", func(ui.Event) {
|
||||
if c.Meta["state"] == "paused" {
|
||||
selected = "unpause"
|
||||
} else {
|
||||
selected = "pause"
|
||||
}
|
||||
ui.StopLoop()
|
||||
})
|
||||
}
|
||||
if c.Meta["state"] == "running" {
|
||||
ui.Handle("/sys/kbd/e", func(ui.Event) {
|
||||
selected = "exec"
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Handle("/sys/kbd/r", func(ui.Event) {
|
||||
selected = "restart"
|
||||
ui.StopLoop()
|
||||
})
|
||||
}
|
||||
ui.Handle("/sys/kbd/R", func(ui.Event) {
|
||||
selected = "remove"
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Handle("/sys/kbd/c", func(ui.Event) {
|
||||
ui.StopLoop()
|
||||
})
|
||||
|
||||
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
||||
switch m.SelectedItem().Val {
|
||||
case "single":
|
||||
nextMenu = SingleView
|
||||
case "logs":
|
||||
nextMenu = LogMenu
|
||||
case "start":
|
||||
nextMenu = Confirm(confirmTxt("start", c.GetMeta("name")), c.Start)
|
||||
case "stop":
|
||||
nextMenu = Confirm(confirmTxt("stop", c.GetMeta("name")), c.Stop)
|
||||
case "remove":
|
||||
nextMenu = Confirm(confirmTxt("remove", c.GetMeta("name")), c.Remove)
|
||||
}
|
||||
selected = m.SelectedItem().Val
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Handle("/sys/kbd/", func(ui.Event) {
|
||||
ui.StopLoop()
|
||||
})
|
||||
ui.Loop()
|
||||
|
||||
var nextMenu MenuFn
|
||||
switch selected {
|
||||
case "single":
|
||||
nextMenu = SingleView
|
||||
case "logs":
|
||||
nextMenu = LogMenu
|
||||
case "exec":
|
||||
nextMenu = ExecShell
|
||||
case "start":
|
||||
nextMenu = Confirm(confirmTxt("start", c.GetMeta("name")), c.Start)
|
||||
case "stop":
|
||||
nextMenu = Confirm(confirmTxt("stop", c.GetMeta("name")), c.Stop)
|
||||
case "remove":
|
||||
nextMenu = Confirm(confirmTxt("remove", c.GetMeta("name")), c.Remove)
|
||||
case "pause":
|
||||
nextMenu = Confirm(confirmTxt("pause", c.GetMeta("name")), c.Pause)
|
||||
case "unpause":
|
||||
nextMenu = Confirm(confirmTxt("unpause", c.GetMeta("name")), c.Unpause)
|
||||
case "restart":
|
||||
nextMenu = Confirm(confirmTxt("restart", c.GetMeta("name")), c.Restart)
|
||||
}
|
||||
|
||||
return nextMenu
|
||||
}
|
||||
|
||||
@@ -176,7 +247,7 @@ func LogMenu() MenuFn {
|
||||
|
||||
logs, quit := logReader(c)
|
||||
m := widgets.NewTextView(logs)
|
||||
m.BorderLabel = "Logs"
|
||||
m.BorderLabel = fmt.Sprintf("Logs [%s]", c.GetMeta("name"))
|
||||
ui.Render(m)
|
||||
|
||||
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
||||
@@ -193,6 +264,24 @@ func LogMenu() MenuFn {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExecShell() MenuFn {
|
||||
c := cursor.Selected()
|
||||
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ui.DefaultEvtStream.ResetHandlers()
|
||||
defer ui.DefaultEvtStream.ResetHandlers()
|
||||
|
||||
shell := config.Get("shell")
|
||||
if err := c.Exec([]string{shell.Val, "-c", "printf '\\e[0m\\e[?25h' && clear && " + shell.Val}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a confirmation dialog with a given description string and
|
||||
// func to perform if confirmed
|
||||
func Confirm(txt string, fn func()) MenuFn {
|
||||
|
||||
60
widgets/error.go
Normal file
60
widgets/error.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ui "github.com/gizak/termui"
|
||||
)
|
||||
|
||||
type ErrorView struct {
|
||||
*ui.Par
|
||||
lines []string
|
||||
}
|
||||
|
||||
func NewErrorView() *ErrorView {
|
||||
const yPad = 1
|
||||
const xPad = 2
|
||||
|
||||
p := ui.NewPar("")
|
||||
p.X = xPad
|
||||
p.Y = yPad
|
||||
p.Border = true
|
||||
p.Height = 10
|
||||
p.Width = 20
|
||||
p.PaddingTop = yPad
|
||||
p.PaddingBottom = yPad
|
||||
p.PaddingLeft = xPad
|
||||
p.PaddingRight = xPad
|
||||
p.BorderLabel = " ctop - error "
|
||||
p.Bg = ui.ThemeAttr("bg")
|
||||
p.TextFgColor = ui.ThemeAttr("status.warn")
|
||||
p.TextBgColor = ui.ThemeAttr("menu.text.bg")
|
||||
p.BorderFg = ui.ThemeAttr("status.warn")
|
||||
p.BorderLabelFg = ui.ThemeAttr("status.warn")
|
||||
return &ErrorView{p, make([]string, 0, 50)}
|
||||
}
|
||||
|
||||
func (w *ErrorView) Append(s string) {
|
||||
if len(w.lines)+2 >= cap(w.lines) {
|
||||
w.lines = append(w.lines[:0], w.lines[2:]...)
|
||||
}
|
||||
ts := time.Now().Local().Format("15:04:05 MST")
|
||||
w.lines = append(w.lines, fmt.Sprintf("[%s] %s", ts, s))
|
||||
w.lines = append(w.lines, "")
|
||||
}
|
||||
|
||||
func (w *ErrorView) Buffer() ui.Buffer {
|
||||
offset := len(w.lines) - w.InnerHeight()
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
w.Text = strings.Join(w.lines[offset:len(w.lines)], "\n")
|
||||
return w.Par.Buffer()
|
||||
}
|
||||
|
||||
func (w *ErrorView) Resize() {
|
||||
w.Height = ui.TermHeight() - (w.PaddingTop + w.PaddingBottom)
|
||||
w.SetWidth(ui.TermWidth() - (w.PaddingLeft + w.PaddingRight))
|
||||
}
|
||||
@@ -16,7 +16,7 @@ type CTopHeader struct {
|
||||
|
||||
func NewCTopHeader() *CTopHeader {
|
||||
return &CTopHeader{
|
||||
Time: headerPar(2, timeStr()),
|
||||
Time: headerPar(2, ""),
|
||||
Count: headerPar(24, "-"),
|
||||
Filter: headerPar(40, ""),
|
||||
bg: headerBg(),
|
||||
|
||||
@@ -77,7 +77,7 @@ func (i *Input) KeyPress(e ui.Event) {
|
||||
if len(i.Data) >= i.MaxLen {
|
||||
return
|
||||
}
|
||||
if strings.Index(input_chars, ch) > -1 {
|
||||
if strings.Contains(input_chars, ch) {
|
||||
i.Data += ch
|
||||
i.stream <- i.Data
|
||||
ui.Render(i)
|
||||
|
||||
@@ -42,7 +42,7 @@ func (m *Menu) AddItems(items ...Item) {
|
||||
m.refresh()
|
||||
}
|
||||
|
||||
// Remove menu item by value or label
|
||||
// DelItem removes menu item by value or label
|
||||
func (m *Menu) DelItem(s string) (success bool) {
|
||||
for n, i := range m.items {
|
||||
if i.Val == s || i.Label == s {
|
||||
|
||||
Reference in New Issue
Block a user