mirror of
https://github.com/bcicen/ctop.git
synced 2025-12-06 15:16:41 +08:00
Compare commits
42 Commits
v0.3
...
v0.4.1-dep
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47b27a7786 | ||
|
|
b28beed3ee | ||
|
|
2e51406d00 | ||
|
|
c84b52ce40 | ||
|
|
4ee8cf621a | ||
|
|
192298c045 | ||
|
|
258536740d | ||
|
|
ef69744249 | ||
|
|
07f95a04b0 | ||
|
|
b2184bbc6d | ||
|
|
96b01eb3b9 | ||
|
|
03d4869361 | ||
|
|
4b7257908f | ||
|
|
1875013a76 | ||
|
|
dab2f926b9 | ||
|
|
ddce54f991 | ||
|
|
168e8f3aae | ||
|
|
ecc37a2f99 | ||
|
|
2f17a9d689 | ||
|
|
8a6808c804 | ||
|
|
3ca94b50cd | ||
|
|
0e3fe88bb4 | ||
|
|
b9b904626c | ||
|
|
e195828f92 | ||
|
|
1d176d46c4 | ||
|
|
7026193f8e | ||
|
|
d9b4295176 | ||
|
|
92cc7bc849 | ||
|
|
70790e88ae | ||
|
|
bcf05b7f42 | ||
|
|
9df3ff2aa0 | ||
|
|
2b80832a36 | ||
|
|
a6ee6edb1d | ||
|
|
d7f9f715bb | ||
|
|
2d2d58d47f | ||
|
|
bf4d59c251 | ||
|
|
b8eb386360 | ||
|
|
02610c59da | ||
|
|
71768b498c | ||
|
|
57e49ea2c6 | ||
|
|
5b25f931df | ||
|
|
4af33fdf12 |
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FROM quay.io/vektorcloud/glibc:latest
|
||||||
|
|
||||||
|
RUN ctop_url=$(wget -q -O - https://api.github.com/repos/bcicen/ctop/releases/latest | grep 'browser_' | cut -d\" -f4 |grep 'linux-amd64') && \
|
||||||
|
wget -q $ctop_url -O /ctop && \
|
||||||
|
chmod +x /ctop
|
||||||
|
|
||||||
|
ENTRYPOINT ["/ctop"]
|
||||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 VektorLab
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
38
README.md
38
README.md
@@ -1,7 +1,15 @@
|
|||||||
# ctop
|
<p align="center"><img width="200px" src="/_docs/img/logo.png" alt="ctop"/></p>
|
||||||
|
#
|
||||||
|
|
||||||
Top-like interface for container metrics
|
Top-like interface for container metrics
|
||||||
|
|
||||||
|
`ctop` provides a concise and condensed overview of real-time metrics for multiple containers:
|
||||||
|
<p align="center"><img src="_docs/img/grid.gif" alt="ctop"/></p>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Fetch the [latest release](https://github.com/bcicen/ctop/releases) for your platform:
|
Fetch the [latest release](https://github.com/bcicen/ctop/releases) for your platform:
|
||||||
@@ -9,7 +17,7 @@ Fetch the [latest release](https://github.com/bcicen/ctop/releases) for your pla
|
|||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/bcicen/ctop/releases/download/v0.1/ctop-0.1-linux-amd64 -O ctop
|
wget https://github.com/bcicen/ctop/releases/download/v0.4.1/ctop-0.4.1-linux-amd64 -O ctop
|
||||||
sudo mv ctop /usr/local/bin/
|
sudo mv ctop /usr/local/bin/
|
||||||
sudo chmod +x /usr/local/bin/ctop
|
sudo chmod +x /usr/local/bin/ctop
|
||||||
```
|
```
|
||||||
@@ -17,14 +25,30 @@ sudo chmod +x /usr/local/bin/ctop
|
|||||||
#### OS X
|
#### OS X
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -Lo ctop https://github.com/bcicen/ctop/releases/download/v0.1/ctop-0.1-darwin-amd64
|
curl -Lo ctop https://github.com/bcicen/ctop/releases/download/v0.4.1/ctop-0.4.1-darwin-amd64
|
||||||
sudo mv ctop /usr/local/bin/
|
sudo mv ctop /usr/local/bin/
|
||||||
sudo chmod +x /usr/local/bin/ctop
|
sudo chmod +x /usr/local/bin/ctop
|
||||||
```
|
```
|
||||||
|
|
||||||
|
or run via Docker:
|
||||||
|
```bash
|
||||||
|
docker run -ti -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/)
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build `ctop` from source ensure you have a recent version of [glide](http://glide.sh/) installed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd $GOPATH/src/github.com/bcicen/ctop
|
||||||
|
glide install
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
cTop requires no arguments and will configure itself using the `DOCKER_HOST` environment variable
|
`ctop` requires no arguments and will configure itself using the `DOCKER_HOST` environment variable
|
||||||
```bash
|
```bash
|
||||||
export DOCKER_HOST=tcp://127.0.0.1:4243
|
export DOCKER_HOST=tcp://127.0.0.1:4243
|
||||||
ctop
|
ctop
|
||||||
@@ -36,8 +60,10 @@ Key | Action
|
|||||||
--- | ---
|
--- | ---
|
||||||
a | Toggle display of all (running and non-running) containers
|
a | Toggle display of all (running and non-running) containers
|
||||||
f | Filter displayed containers
|
f | Filter displayed containers
|
||||||
H | Toggle cTop header
|
H | Toggle ctop header
|
||||||
h | Open help dialog
|
h | Open help dialog
|
||||||
s | Select container sort field
|
s | Select container sort field
|
||||||
r | Reverse container sort order
|
r | Reverse container sort order
|
||||||
q | Quit cTop
|
q | Quit ctop
|
||||||
|
|
||||||
|
[expanded_view]: _docs/expanded.md
|
||||||
|
|||||||
4
_docs/expanded.md
Normal file
4
_docs/expanded.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Expanded View
|
||||||
|
|
||||||
|
ctop provides an expanded, rolling view for following container metrics
|
||||||
|
<p align="center"><img width="80%" src="img/expanded.gif" alt="ctop"/></p>
|
||||||
BIN
_docs/img/expanded.gif
Normal file
BIN
_docs/img/expanded.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 549 KiB |
BIN
_docs/img/grid.gif
Normal file
BIN
_docs/img/grid.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 676 KiB |
BIN
_docs/img/logo.png
Normal file
BIN
_docs/img/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
24
circle.yml
Normal file
24
circle.yml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
machine:
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
override:
|
||||||
|
- docker info
|
||||||
|
- |
|
||||||
|
if [[ "$CIRCLE_BRANCH" == "master" ]]; then
|
||||||
|
docker build -t quay.io/vektorlab/ctop:latest .
|
||||||
|
else
|
||||||
|
docker build -t quay.io/vektorlab/ctop:${CIRCLE_BRANCH} .
|
||||||
|
fi
|
||||||
|
|
||||||
|
test:
|
||||||
|
override:
|
||||||
|
- docker run -t --entrypoint /bin/sh quay.io/vektorlab/ctop:latest -v
|
||||||
|
|
||||||
|
deployment:
|
||||||
|
hub:
|
||||||
|
branch: master
|
||||||
|
commands:
|
||||||
|
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS quay.io
|
||||||
|
- docker push quay.io/vektorlab/ctop:latest
|
||||||
43
colors.go
Normal file
43
colors.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import ui "github.com/gizak/termui"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Valid colors:
|
||||||
|
ui.ColorDefault
|
||||||
|
ui.ColorBlack
|
||||||
|
ui.ColorRed
|
||||||
|
ui.ColorGreen
|
||||||
|
ui.ColorYellow
|
||||||
|
ui.ColorBlue
|
||||||
|
ui.ColorMagenta
|
||||||
|
ui.ColorCyan
|
||||||
|
ui.ColorWhite
|
||||||
|
*/
|
||||||
|
|
||||||
|
var ColorMap = map[string]ui.Attribute{
|
||||||
|
"fg": ui.ColorWhite,
|
||||||
|
"bg": ui.ColorDefault,
|
||||||
|
"block.bg": ui.ColorDefault,
|
||||||
|
"border.bg": ui.ColorDefault,
|
||||||
|
"border.fg": ui.ColorWhite,
|
||||||
|
"label.bg": ui.ColorDefault,
|
||||||
|
"label.fg": ui.ColorGreen,
|
||||||
|
"menu.text.fg": ui.ColorWhite,
|
||||||
|
"menu.text.bg": ui.ColorDefault,
|
||||||
|
"menu.border.fg": ui.ColorCyan,
|
||||||
|
"menu.label.fg": ui.ColorGreen,
|
||||||
|
"header.fg": ui.ColorBlack,
|
||||||
|
"header.bg": ui.ColorWhite,
|
||||||
|
"gauge.bar.bg": ui.ColorGreen,
|
||||||
|
"gauge.percent.fg": ui.ColorWhite,
|
||||||
|
"linechart.axes.fg": ui.ColorDefault,
|
||||||
|
"linechart.line.fg": ui.ColorGreen,
|
||||||
|
"mbarchart.bar.bg": ui.ColorGreen,
|
||||||
|
"mbarchart.num.fg": ui.ColorWhite,
|
||||||
|
"mbarchart.text.fg": ui.ColorWhite,
|
||||||
|
"par.text.fg": ui.ColorWhite,
|
||||||
|
"par.text.bg": ui.ColorDefault,
|
||||||
|
"sparkline.line.fg": ui.ColorGreen,
|
||||||
|
"sparkline.title.fg": ui.ColorWhite,
|
||||||
|
}
|
||||||
@@ -2,11 +2,6 @@ package config
|
|||||||
|
|
||||||
// defaults
|
// defaults
|
||||||
var params = []*Param{
|
var params = []*Param{
|
||||||
&Param{
|
|
||||||
Key: "dockerHost",
|
|
||||||
Val: getEnv("DOCKER_HOST", "unix:///var/run/docker.sock"),
|
|
||||||
Label: "Docker API URL",
|
|
||||||
},
|
|
||||||
&Param{
|
&Param{
|
||||||
Key: "filterStr",
|
Key: "filterStr",
|
||||||
Val: "",
|
Val: "",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ var switches = []*Switch{
|
|||||||
&Switch{
|
&Switch{
|
||||||
Key: "enableHeader",
|
Key: "enableHeader",
|
||||||
Val: true,
|
Val: true,
|
||||||
Label: "Enable cTop Status Line",
|
Label: "Enable Status Header",
|
||||||
},
|
},
|
||||||
&Switch{
|
&Switch{
|
||||||
Key: "loggingEnabled",
|
Key: "loggingEnabled",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type Container struct {
|
|||||||
Widgets *compact.Compact
|
Widgets *compact.Compact
|
||||||
updater cwidgets.WidgetUpdater
|
updater cwidgets.WidgetUpdater
|
||||||
collector metrics.Collector
|
collector metrics.Collector
|
||||||
|
display bool // display this container in compact view
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContainer(id string, collector metrics.Collector) *Container {
|
func NewContainer(id string, collector metrics.Collector) *Container {
|
||||||
|
|||||||
91
cursor.go
91
cursor.go
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
type GridCursor struct {
|
type GridCursor struct {
|
||||||
selectedID string // id of currently selected container
|
selectedID string // id of currently selected container
|
||||||
containers Containers
|
filtered Containers
|
||||||
cSource ContainerSource
|
cSource ContainerSource
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,64 +16,117 @@ func NewGridCursor() *GridCursor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GridCursor) Len() int { return len(gc.containers) }
|
func (gc *GridCursor) Len() int { return len(gc.filtered) }
|
||||||
func (gc *GridCursor) Selected() *Container { return gc.containers[gc.Idx()] }
|
|
||||||
|
|
||||||
func (gc *GridCursor) RefreshContainers() {
|
func (gc *GridCursor) Selected() *Container {
|
||||||
gc.containers = gc.cSource.All().Filter()
|
idx := gc.Idx()
|
||||||
|
if idx < gc.Len() {
|
||||||
|
return gc.filtered[idx]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh containers from source
|
||||||
|
func (gc *GridCursor) RefreshContainers() (lenChanged bool) {
|
||||||
|
oldLen := gc.Len()
|
||||||
|
|
||||||
|
// Containers filtered by display bool
|
||||||
|
gc.filtered = Containers{}
|
||||||
|
var cursorVisible bool
|
||||||
|
for _, c := range gc.cSource.All() {
|
||||||
|
if c.display {
|
||||||
|
if c.Id == gc.selectedID {
|
||||||
|
cursorVisible = true
|
||||||
|
}
|
||||||
|
gc.filtered = append(gc.filtered, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldLen != gc.Len() {
|
||||||
|
lenChanged = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cursorVisible {
|
||||||
|
gc.Reset()
|
||||||
|
}
|
||||||
if gc.selectedID == "" {
|
if gc.selectedID == "" {
|
||||||
gc.Reset()
|
gc.Reset()
|
||||||
}
|
}
|
||||||
|
return lenChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set an initial cursor position, if possible
|
// Set an initial cursor position, if possible
|
||||||
func (gc *GridCursor) Reset() {
|
func (gc *GridCursor) Reset() {
|
||||||
|
for _, c := range gc.cSource.All() {
|
||||||
|
c.Widgets.Name.UnHighlight()
|
||||||
|
}
|
||||||
if gc.Len() > 0 {
|
if gc.Len() > 0 {
|
||||||
gc.selectedID = gc.containers[0].Id
|
gc.selectedID = gc.filtered[0].Id
|
||||||
gc.containers[0].Widgets.Name.Highlight()
|
gc.filtered[0].Widgets.Name.Highlight()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return current cursor index
|
// Return current cursor index
|
||||||
func (gc *GridCursor) Idx() int {
|
func (gc *GridCursor) Idx() int {
|
||||||
for n, c := range gc.containers {
|
for n, c := range gc.filtered {
|
||||||
if c.Id == gc.selectedID {
|
if c.Id == gc.selectedID {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
gc.Reset()
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gc *GridCursor) ScrollPage() {
|
||||||
|
// skip scroll if no need to page
|
||||||
|
if gc.Len() < cGrid.MaxRows() {
|
||||||
|
cGrid.Offset = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := gc.Idx()
|
||||||
|
|
||||||
|
// page down
|
||||||
|
if idx >= cGrid.Offset+cGrid.MaxRows() {
|
||||||
|
cGrid.Offset++
|
||||||
|
cGrid.Align()
|
||||||
|
}
|
||||||
|
// page up
|
||||||
|
if idx < cGrid.Offset {
|
||||||
|
cGrid.Offset--
|
||||||
|
cGrid.Align()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (gc *GridCursor) Up() {
|
func (gc *GridCursor) Up() {
|
||||||
idx := gc.Idx()
|
idx := gc.Idx()
|
||||||
// decrement if possible
|
if idx <= 0 { // already at top
|
||||||
if idx <= 0 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
active := gc.containers[idx]
|
active := gc.filtered[idx]
|
||||||
next := gc.containers[idx-1]
|
next := gc.filtered[idx-1]
|
||||||
|
|
||||||
active.Widgets.Name.UnHighlight()
|
active.Widgets.Name.UnHighlight()
|
||||||
gc.selectedID = next.Id
|
gc.selectedID = next.Id
|
||||||
next.Widgets.Name.Highlight()
|
next.Widgets.Name.Highlight()
|
||||||
|
|
||||||
|
gc.ScrollPage()
|
||||||
ui.Render(cGrid)
|
ui.Render(cGrid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GridCursor) Down() {
|
func (gc *GridCursor) Down() {
|
||||||
idx := gc.Idx()
|
idx := gc.Idx()
|
||||||
// increment if possible
|
if idx >= gc.Len()-1 { // already at bottom
|
||||||
if idx >= (gc.Len() - 1) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if idx >= maxRows()-1 {
|
active := gc.filtered[idx]
|
||||||
return
|
next := gc.filtered[idx+1]
|
||||||
}
|
|
||||||
active := gc.containers[idx]
|
|
||||||
next := gc.containers[idx+1]
|
|
||||||
|
|
||||||
active.Widgets.Name.UnHighlight()
|
active.Widgets.Name.UnHighlight()
|
||||||
gc.selectedID = next.Id
|
gc.selectedID = next.Id
|
||||||
next.Widgets.Name.Highlight()
|
next.Widgets.Name.Highlight()
|
||||||
|
|
||||||
|
gc.ScrollPage()
|
||||||
ui.Render(cGrid)
|
ui.Render(cGrid)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ func NewGaugeCol() *GaugeCol {
|
|||||||
g.Border = false
|
g.Border = false
|
||||||
g.Percent = 0
|
g.Percent = 0
|
||||||
g.PaddingBottom = 0
|
g.PaddingBottom = 0
|
||||||
g.BarColor = ui.ColorGreen
|
|
||||||
g.Label = "-"
|
g.Label = "-"
|
||||||
return &GaugeCol{g}
|
return &GaugeCol{g}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ var header = NewCompactHeader()
|
|||||||
|
|
||||||
type CompactGrid struct {
|
type CompactGrid struct {
|
||||||
ui.GridBufferer
|
ui.GridBufferer
|
||||||
Rows []ui.GridBufferer
|
Rows []ui.GridBufferer
|
||||||
X, Y int
|
X, Y int
|
||||||
Width int
|
Width int
|
||||||
Height int
|
Height int
|
||||||
cursorID string
|
Offset int // starting row offset
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCompactGrid() *CompactGrid {
|
func NewCompactGrid() *CompactGrid {
|
||||||
@@ -20,28 +20,34 @@ func NewCompactGrid() *CompactGrid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cg *CompactGrid) Align() {
|
func (cg *CompactGrid) Align() {
|
||||||
// update row y pos recursively
|
|
||||||
y := cg.Y
|
y := cg.Y
|
||||||
for _, r := range cg.Rows {
|
if cg.Offset >= len(cg.Rows) {
|
||||||
|
cg.Offset = 0
|
||||||
|
}
|
||||||
|
// update row ypos, width recursively
|
||||||
|
for _, r := range cg.pageRows() {
|
||||||
r.SetY(y)
|
r.SetY(y)
|
||||||
y += r.GetHeight()
|
y += r.GetHeight()
|
||||||
}
|
|
||||||
|
|
||||||
// update row width recursively
|
|
||||||
for _, r := range cg.Rows {
|
|
||||||
r.SetWidth(cg.Width)
|
r.SetWidth(cg.Width)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cg *CompactGrid) Clear() { cg.Rows = []ui.GridBufferer{header} }
|
func (cg *CompactGrid) Clear() { cg.Rows = []ui.GridBufferer{} }
|
||||||
func (cg *CompactGrid) GetHeight() int { return len(cg.Rows) }
|
func (cg *CompactGrid) GetHeight() int { return len(cg.Rows) + header.Height }
|
||||||
func (cg *CompactGrid) SetX(x int) { cg.X = x }
|
func (cg *CompactGrid) SetX(x int) { cg.X = x }
|
||||||
func (cg *CompactGrid) SetY(y int) { cg.Y = y }
|
func (cg *CompactGrid) SetY(y int) { cg.Y = y }
|
||||||
func (cg *CompactGrid) SetWidth(w int) { cg.Width = w }
|
func (cg *CompactGrid) SetWidth(w int) { cg.Width = w }
|
||||||
|
func (cg *CompactGrid) MaxRows() int { return ui.TermHeight() - header.Height - cg.Y }
|
||||||
|
|
||||||
|
func (cg *CompactGrid) pageRows() (rows []ui.GridBufferer) {
|
||||||
|
rows = append(rows, header)
|
||||||
|
rows = append(rows, cg.Rows[cg.Offset:]...)
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
func (cg *CompactGrid) Buffer() ui.Buffer {
|
func (cg *CompactGrid) Buffer() ui.Buffer {
|
||||||
buf := ui.NewBuffer()
|
buf := ui.NewBuffer()
|
||||||
for _, r := range cg.Rows {
|
for _, r := range cg.pageRows() {
|
||||||
buf.Merge(r.Buffer())
|
buf.Merge(r.Buffer())
|
||||||
}
|
}
|
||||||
return buf
|
return buf
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func (row *Compact) SetCPU(val int) {
|
|||||||
row.Cpu.Label = fmt.Sprintf("%s%%", strconv.Itoa(val))
|
row.Cpu.Label = fmt.Sprintf("%s%%", strconv.Itoa(val))
|
||||||
if val < 5 {
|
if val < 5 {
|
||||||
val = 5
|
val = 5
|
||||||
row.Cpu.BarColor = ui.ColorBlack
|
row.Cpu.BarColor = ui.ThemeAttr("gauge.bar.bg")
|
||||||
}
|
}
|
||||||
row.Cpu.Percent = val
|
row.Cpu.Percent = val
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ func (row *Compact) SetMem(val int64, limit int64, percent int) {
|
|||||||
percent = 5
|
percent = 5
|
||||||
row.Memory.BarColor = ui.ColorBlack
|
row.Memory.BarColor = ui.ColorBlack
|
||||||
} else {
|
} else {
|
||||||
row.Memory.BarColor = ui.ColorGreen
|
row.Memory.BarColor = ui.ThemeAttr("gauge.bar.bg")
|
||||||
}
|
}
|
||||||
row.Memory.Percent = percent
|
row.Memory.Percent = percent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func NewTextCol(s string) *TextCol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TextCol) Highlight() {
|
func (w *TextCol) Highlight() {
|
||||||
w.TextFgColor = ui.ThemeAttr("par.text.bg")
|
w.TextFgColor = ui.ColorBlack
|
||||||
w.TextBgColor = ui.ThemeAttr("par.text.fg")
|
w.TextBgColor = ui.ThemeAttr("par.text.fg")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ func NewCpu() *Cpu {
|
|||||||
cpu.Width = colWidth[0]
|
cpu.Width = colWidth[0]
|
||||||
cpu.X = 0
|
cpu.X = 0
|
||||||
cpu.DataLabels = cpu.hist.Labels
|
cpu.DataLabels = cpu.hist.Labels
|
||||||
cpu.AxesColor = ui.ColorDefault
|
|
||||||
cpu.LineColor = ui.ColorGreen
|
|
||||||
|
|
||||||
// hack to force the default minY scale to 0
|
// hack to force the default minY scale to 0
|
||||||
tmpData := []float64{20}
|
tmpData := []float64{20}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ func NewInfo(id string) *Info {
|
|||||||
p := ui.NewTable()
|
p := ui.NewTable()
|
||||||
p.Height = 4
|
p.Height = 4
|
||||||
p.Width = colWidth[0]
|
p.Width = colWidth[0]
|
||||||
p.FgColor = ui.ColorWhite
|
p.FgColor = ui.ThemeAttr("par.text.fg")
|
||||||
p.Seperator = false
|
p.Separator = false
|
||||||
i := &Info{p, make(map[string]string)}
|
i := &Info{p, make(map[string]string)}
|
||||||
i.Set("id", id)
|
i.Set("id", id)
|
||||||
return i
|
return i
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ func newMemLabel() *ui.Par {
|
|||||||
p.Border = false
|
p.Border = false
|
||||||
p.Height = 1
|
p.Height = 1
|
||||||
p.Width = 20
|
p.Width = 20
|
||||||
p.TextFgColor = ui.ColorDefault
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +66,6 @@ func newMemChart() *ui.MBarChart {
|
|||||||
mbar.Border = false
|
mbar.Border = false
|
||||||
mbar.BarGap = 1
|
mbar.BarGap = 1
|
||||||
mbar.BarWidth = 6
|
mbar.BarWidth = 6
|
||||||
mbar.TextColor = ui.ColorDefault
|
|
||||||
|
|
||||||
mbar.BarColor[0] = ui.ColorGreen
|
|
||||||
|
|
||||||
mbar.BarColor[1] = ui.ColorBlack
|
mbar.BarColor[1] = ui.ColorBlack
|
||||||
mbar.NumColor[1] = ui.ColorBlack
|
mbar.NumColor[1] = ui.ColorBlack
|
||||||
|
|||||||
@@ -26,14 +26,12 @@ func NewNet() *Net {
|
|||||||
rx.Title = "RX"
|
rx.Title = "RX"
|
||||||
rx.Height = 1
|
rx.Height = 1
|
||||||
rx.Data = net.rxHist.Data
|
rx.Data = net.rxHist.Data
|
||||||
rx.TitleColor = ui.ColorDefault
|
|
||||||
rx.LineColor = ui.ColorGreen
|
rx.LineColor = ui.ColorGreen
|
||||||
|
|
||||||
tx := ui.NewSparkline()
|
tx := ui.NewSparkline()
|
||||||
tx.Title = "TX"
|
tx.Title = "TX"
|
||||||
tx.Height = 1
|
tx.Height = 1
|
||||||
tx.Data = net.txHist.Data
|
tx.Data = net.txHist.Data
|
||||||
tx.TitleColor = ui.ColorDefault
|
|
||||||
tx.LineColor = ui.ColorYellow
|
tx.LineColor = ui.ColorYellow
|
||||||
|
|
||||||
net.Lines = []ui.Sparkline{rx, tx}
|
net.Lines = []ui.Sparkline{rx, tx}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/bcicen/ctop/config"
|
|
||||||
"github.com/bcicen/ctop/metrics"
|
"github.com/bcicen/ctop/metrics"
|
||||||
"github.com/fsouza/go-dockerclient"
|
"github.com/fsouza/go-dockerclient"
|
||||||
)
|
)
|
||||||
@@ -18,11 +18,12 @@ type DockerContainerSource struct {
|
|||||||
client *docker.Client
|
client *docker.Client
|
||||||
containers map[string]*Container
|
containers map[string]*Container
|
||||||
needsRefresh chan string // container IDs requiring refresh
|
needsRefresh chan string // container IDs requiring refresh
|
||||||
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDockerContainerSource() *DockerContainerSource {
|
func NewDockerContainerSource() *DockerContainerSource {
|
||||||
// init docker client
|
// init docker client
|
||||||
client, err := docker.NewClient(config.GetVal("dockerHost"))
|
client, err := docker.NewClientFromEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -30,9 +31,10 @@ func NewDockerContainerSource() *DockerContainerSource {
|
|||||||
client: client,
|
client: client,
|
||||||
containers: make(map[string]*Container),
|
containers: make(map[string]*Container),
|
||||||
needsRefresh: make(chan string, 60),
|
needsRefresh: make(chan string, 60),
|
||||||
|
lock: sync.RWMutex{},
|
||||||
}
|
}
|
||||||
cm.refreshAll()
|
|
||||||
go cm.Loop()
|
go cm.Loop()
|
||||||
|
cm.refreshAll()
|
||||||
go cm.watchEvents()
|
go cm.watchEvents()
|
||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
@@ -113,29 +115,38 @@ func (cm *DockerContainerSource) MustGet(id string) *Container {
|
|||||||
collector := metrics.NewDocker(cm.client, id)
|
collector := metrics.NewDocker(cm.client, id)
|
||||||
// create container
|
// create container
|
||||||
c = NewContainer(id, collector)
|
c = NewContainer(id, collector)
|
||||||
|
cm.lock.Lock()
|
||||||
cm.containers[id] = c
|
cm.containers[id] = c
|
||||||
|
cm.lock.Unlock()
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a single container, by ID
|
// Get a single container, by ID
|
||||||
func (cm *DockerContainerSource) Get(id string) (*Container, bool) {
|
func (cm *DockerContainerSource) Get(id string) (*Container, bool) {
|
||||||
|
cm.lock.Lock()
|
||||||
c, ok := cm.containers[id]
|
c, ok := cm.containers[id]
|
||||||
|
cm.lock.Unlock()
|
||||||
return c, ok
|
return c, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove containers by ID
|
// Remove containers by ID
|
||||||
func (cm *DockerContainerSource) delByID(id string) {
|
func (cm *DockerContainerSource) delByID(id string) {
|
||||||
|
cm.lock.Lock()
|
||||||
delete(cm.containers, id)
|
delete(cm.containers, id)
|
||||||
|
cm.lock.Unlock()
|
||||||
log.Infof("removed dead container: %s", id)
|
log.Infof("removed dead container: %s", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return array of all containers, sorted by field
|
// Return array of all containers, sorted by field
|
||||||
func (cm *DockerContainerSource) All() (containers Containers) {
|
func (cm *DockerContainerSource) All() (containers Containers) {
|
||||||
|
cm.lock.Lock()
|
||||||
for _, c := range cm.containers {
|
for _, c := range cm.containers {
|
||||||
containers = append(containers, c)
|
containers = append(containers, c)
|
||||||
}
|
}
|
||||||
|
cm.lock.Unlock()
|
||||||
sort.Sort(containers)
|
sort.Sort(containers)
|
||||||
|
containers.Filter()
|
||||||
return containers
|
return containers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
85
glide.lock
generated
Normal file
85
glide.lock
generated
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
hash: c13011881e895378f374b68596a59a0ea6def372b4f5239b2d8aa342eaa46a4b
|
||||||
|
updated: 2017-03-12T09:53:35.212073637+07:00
|
||||||
|
imports:
|
||||||
|
- name: github.com/Azure/go-ansiterm
|
||||||
|
version: fa152c58bc15761d0200cb75fe958b89a9d4888e
|
||||||
|
subpackages:
|
||||||
|
- winterm
|
||||||
|
- name: github.com/docker/docker
|
||||||
|
version: ce07fb6b0f1b8765b92022e45f96bd4349812e06
|
||||||
|
subpackages:
|
||||||
|
- 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/pools
|
||||||
|
- pkg/promise
|
||||||
|
- pkg/stdcopy
|
||||||
|
- pkg/system
|
||||||
|
- pkg/term
|
||||||
|
- pkg/term/windows
|
||||||
|
- name: github.com/docker/go-connections
|
||||||
|
version: a2afab9802043837035592f1c24827fb70766de9
|
||||||
|
subpackages:
|
||||||
|
- nat
|
||||||
|
- name: github.com/docker/go-units
|
||||||
|
version: 0dadbb0345b35ec7ef35e228dabb8de89a65bf52
|
||||||
|
- name: github.com/fsouza/go-dockerclient
|
||||||
|
version: 318513eb1ab27495afbc67f671ba1080513d8aa0
|
||||||
|
- name: github.com/gizak/termui
|
||||||
|
version: ea10e6ccee219e572ffad0ac1909f1a17f6db7d6
|
||||||
|
repo: https://github.com/bcicen/termui
|
||||||
|
vcs: git
|
||||||
|
- name: github.com/hashicorp/go-cleanhttp
|
||||||
|
version: 3573b8b52aa7b37b9358d966a898feb387f62437
|
||||||
|
- name: github.com/jgautheron/codename-generator
|
||||||
|
version: 16d037c7cc3c9b552fe4af9828b7338d752dbaf9
|
||||||
|
- name: github.com/maruel/panicparse
|
||||||
|
version: 25bcac0d793cf4109483505a0d66e066a3a90a80
|
||||||
|
subpackages:
|
||||||
|
- stack
|
||||||
|
- name: github.com/mattn/go-runewidth
|
||||||
|
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
||||||
|
- name: github.com/Microsoft/go-winio
|
||||||
|
version: fff283ad5116362ca252298cfc9b95828956d85d
|
||||||
|
- name: github.com/mitchellh/go-wordwrap
|
||||||
|
version: ad45545899c7b13c020ea92b2072220eefad42b8
|
||||||
|
- name: github.com/nsf/termbox-go
|
||||||
|
version: 91bae1bb5fa9ee504905ecbe7043fa30e92feaa3
|
||||||
|
- name: github.com/nu7hatch/gouuid
|
||||||
|
version: 179d4d0c4d8d407a32af483c2354df1d2c91e6c3
|
||||||
|
- name: github.com/op/go-logging
|
||||||
|
version: b2cb9fa56473e98db8caba80237377e83fe44db5
|
||||||
|
- name: github.com/opencontainers/runc
|
||||||
|
version: 31980a53ae7887b2c8f8715d13c3eb486c27b6cf
|
||||||
|
subpackages:
|
||||||
|
- libcontainer/system
|
||||||
|
- libcontainer/user
|
||||||
|
- name: github.com/Sirupsen/logrus
|
||||||
|
version: 1deb2db2a6fff8a35532079061b903c3a25eed52
|
||||||
|
- name: golang.org/x/net
|
||||||
|
version: a6577fac2d73be281a500b310739095313165611
|
||||||
|
subpackages:
|
||||||
|
- context
|
||||||
|
- context/ctxhttp
|
||||||
|
- name: golang.org/x/sys
|
||||||
|
version: 99f16d856c9836c42d24e7ab64ea72916925fa97
|
||||||
|
subpackages:
|
||||||
|
- unix
|
||||||
|
- windows
|
||||||
|
testImports: []
|
||||||
11
glide.yaml
Normal file
11
glide.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package: github.com/bcicen/ctop
|
||||||
|
import:
|
||||||
|
- package: github.com/fsouza/go-dockerclient
|
||||||
|
- 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/op/go-logging
|
||||||
|
version: ^1.0.0
|
||||||
66
grid.go
66
grid.go
@@ -6,11 +6,7 @@ import (
|
|||||||
ui "github.com/gizak/termui"
|
ui "github.com/gizak/termui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func maxRows() int {
|
func RedrawRows(clr bool) {
|
||||||
return ui.TermHeight() - 2 - cGrid.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
func RedrawRows() {
|
|
||||||
// reinit body rows
|
// reinit body rows
|
||||||
cGrid.Clear()
|
cGrid.Clear()
|
||||||
|
|
||||||
@@ -23,25 +19,16 @@ func RedrawRows() {
|
|||||||
}
|
}
|
||||||
cGrid.SetY(y)
|
cGrid.SetY(y)
|
||||||
|
|
||||||
var cursorVisible bool
|
for _, c := range cursor.filtered {
|
||||||
max := maxRows()
|
|
||||||
for n, c := range cursor.containers {
|
|
||||||
if n >= max {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cGrid.AddRows(c.Widgets)
|
cGrid.AddRows(c.Widgets)
|
||||||
if c.Id == cursor.selectedID {
|
|
||||||
cursorVisible = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cursorVisible {
|
if clr {
|
||||||
cursor.Reset()
|
ui.Clear()
|
||||||
|
log.Debugf("screen cleared")
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.Clear()
|
|
||||||
if config.GetSwitchVal("enableHeader") {
|
if config.GetSwitchVal("enableHeader") {
|
||||||
header.Render()
|
ui.Render(header)
|
||||||
}
|
}
|
||||||
cGrid.Align()
|
cGrid.Align()
|
||||||
ui.Render(cGrid)
|
ui.Render(cGrid)
|
||||||
@@ -63,7 +50,7 @@ func ExpandView(c *Container) {
|
|||||||
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
||||||
ex.SetWidth(ui.TermWidth())
|
ex.SetWidth(ui.TermWidth())
|
||||||
ex.Align()
|
ex.Align()
|
||||||
log.Infof("resize: width=%v max-rows=%v", ex.Width, maxRows())
|
log.Infof("resize: width=%v max-rows=%v", ex.Width, cGrid.MaxRows())
|
||||||
})
|
})
|
||||||
ui.Handle("/sys/kbd/", func(ui.Event) {
|
ui.Handle("/sys/kbd/", func(ui.Event) {
|
||||||
ui.StopLoop()
|
ui.StopLoop()
|
||||||
@@ -73,6 +60,11 @@ func ExpandView(c *Container) {
|
|||||||
c.SetUpdater(c.Widgets)
|
c.SetUpdater(c.Widgets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RefreshDisplay() {
|
||||||
|
needsClear := cursor.RefreshContainers()
|
||||||
|
RedrawRows(needsClear)
|
||||||
|
}
|
||||||
|
|
||||||
func Display() bool {
|
func Display() bool {
|
||||||
var menu func()
|
var menu func()
|
||||||
var expand bool
|
var expand bool
|
||||||
@@ -83,23 +75,21 @@ func Display() bool {
|
|||||||
// initial draw
|
// initial draw
|
||||||
header.Align()
|
header.Align()
|
||||||
cursor.RefreshContainers()
|
cursor.RefreshContainers()
|
||||||
RedrawRows()
|
RedrawRows(true)
|
||||||
|
|
||||||
ui.Handle("/sys/kbd/<up>", func(ui.Event) {
|
ui.Handle("/sys/kbd/<up>", func(ui.Event) { cursor.Up() })
|
||||||
cursor.Up()
|
ui.Handle("/sys/kbd/<down>", func(ui.Event) { cursor.Down() })
|
||||||
})
|
|
||||||
ui.Handle("/sys/kbd/<down>", func(ui.Event) {
|
|
||||||
cursor.Down()
|
|
||||||
})
|
|
||||||
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
||||||
expand = true
|
expand = true
|
||||||
ui.StopLoop()
|
ui.StopLoop()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ui.Handle("/sys/kbd/q", func(ui.Event) { ui.StopLoop() })
|
||||||
|
ui.Handle("/sys/kbd/C-c", func(ui.Event) { ui.StopLoop() })
|
||||||
|
|
||||||
ui.Handle("/sys/kbd/a", func(ui.Event) {
|
ui.Handle("/sys/kbd/a", func(ui.Event) {
|
||||||
config.Toggle("allContainers")
|
config.Toggle("allContainers")
|
||||||
cursor.RefreshContainers()
|
RefreshDisplay()
|
||||||
RedrawRows()
|
|
||||||
})
|
})
|
||||||
ui.Handle("/sys/kbd/D", func(ui.Event) {
|
ui.Handle("/sys/kbd/D", func(ui.Event) {
|
||||||
dumpContainer(cursor.Selected())
|
dumpContainer(cursor.Selected())
|
||||||
@@ -114,10 +104,7 @@ func Display() bool {
|
|||||||
})
|
})
|
||||||
ui.Handle("/sys/kbd/H", func(ui.Event) {
|
ui.Handle("/sys/kbd/H", func(ui.Event) {
|
||||||
config.Toggle("enableHeader")
|
config.Toggle("enableHeader")
|
||||||
RedrawRows()
|
RedrawRows(true)
|
||||||
})
|
|
||||||
ui.Handle("/sys/kbd/q", func(ui.Event) {
|
|
||||||
ui.StopLoop()
|
|
||||||
})
|
})
|
||||||
ui.Handle("/sys/kbd/r", func(e ui.Event) {
|
ui.Handle("/sys/kbd/r", func(e ui.Event) {
|
||||||
config.Toggle("sortReversed")
|
config.Toggle("sortReversed")
|
||||||
@@ -128,15 +115,15 @@ func Display() bool {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ui.Handle("/timer/1s", func(e ui.Event) {
|
ui.Handle("/timer/1s", func(e ui.Event) {
|
||||||
cursor.RefreshContainers()
|
RefreshDisplay()
|
||||||
RedrawRows()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
||||||
header.Align()
|
header.Align()
|
||||||
|
cursor.ScrollPage()
|
||||||
cGrid.SetWidth(ui.TermWidth())
|
cGrid.SetWidth(ui.TermWidth())
|
||||||
log.Infof("resize: width=%v max-rows=%v", cGrid.Width, maxRows())
|
log.Infof("resize: width=%v max-rows=%v", cGrid.Width, cGrid.MaxRows())
|
||||||
RedrawRows()
|
RedrawRows(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
ui.Loop()
|
ui.Loop()
|
||||||
@@ -145,7 +132,10 @@ func Display() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if expand {
|
if expand {
|
||||||
ExpandView(cursor.Selected())
|
c := cursor.Selected()
|
||||||
|
if c != nil {
|
||||||
|
ExpandView(c)
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
5
main.go
5
main.go
@@ -26,6 +26,7 @@ func main() {
|
|||||||
defer panicExit()
|
defer panicExit()
|
||||||
|
|
||||||
// init ui
|
// init ui
|
||||||
|
ui.ColorMap = ColorMap // override default colormap
|
||||||
if err := ui.Init(); err != nil {
|
if err := ui.Init(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -82,7 +83,7 @@ func panicExit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var helpMsg = `cTop - container metric viewer
|
var helpMsg = `ctop - container metric viewer
|
||||||
|
|
||||||
usage: ctop [options]
|
usage: ctop [options]
|
||||||
|
|
||||||
@@ -96,5 +97,5 @@ func printHelp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printVersion() {
|
func printVersion() {
|
||||||
fmt.Printf("cTop version %v, build %v\n", version, build)
|
fmt.Printf("ctop version %v, build %v\n", version, build)
|
||||||
}
|
}
|
||||||
|
|||||||
11
menus.go
11
menus.go
@@ -11,7 +11,7 @@ var helpDialog = []menu.Item{
|
|||||||
menu.Item{"[a] - toggle display of all containers", ""},
|
menu.Item{"[a] - toggle display of all containers", ""},
|
||||||
menu.Item{"[f] - filter displayed containers", ""},
|
menu.Item{"[f] - filter displayed containers", ""},
|
||||||
menu.Item{"[h] - open this help dialog", ""},
|
menu.Item{"[h] - open this help dialog", ""},
|
||||||
menu.Item{"[H] - toggle cTop header", ""},
|
menu.Item{"[H] - toggle ctop header", ""},
|
||||||
menu.Item{"[s] - select container sort field", ""},
|
menu.Item{"[s] - select container sort field", ""},
|
||||||
menu.Item{"[r] - reverse container sort order", ""},
|
menu.Item{"[r] - reverse container sort order", ""},
|
||||||
menu.Item{"[q] - exit ctop", ""},
|
menu.Item{"[q] - exit ctop", ""},
|
||||||
@@ -23,9 +23,7 @@ func HelpMenu() {
|
|||||||
defer ui.DefaultEvtStream.ResetHandlers()
|
defer ui.DefaultEvtStream.ResetHandlers()
|
||||||
|
|
||||||
m := menu.NewMenu()
|
m := menu.NewMenu()
|
||||||
m.TextFgColor = ui.ColorWhite
|
|
||||||
m.BorderLabel = "Help"
|
m.BorderLabel = "Help"
|
||||||
m.BorderFg = ui.ColorCyan
|
|
||||||
m.AddItems(helpDialog...)
|
m.AddItems(helpDialog...)
|
||||||
ui.Render(m)
|
ui.Render(m)
|
||||||
ui.Handle("/sys/kbd/", func(ui.Event) {
|
ui.Handle("/sys/kbd/", func(ui.Event) {
|
||||||
@@ -39,9 +37,7 @@ func FilterMenu() {
|
|||||||
defer ui.DefaultEvtStream.ResetHandlers()
|
defer ui.DefaultEvtStream.ResetHandlers()
|
||||||
|
|
||||||
i := widgets.NewInput()
|
i := widgets.NewInput()
|
||||||
i.TextFgColor = ui.ColorWhite
|
|
||||||
i.BorderLabel = "Filter"
|
i.BorderLabel = "Filter"
|
||||||
i.BorderFg = ui.ColorCyan
|
|
||||||
i.SetY(ui.TermHeight() - i.Height)
|
i.SetY(ui.TermHeight() - i.Height)
|
||||||
ui.Render(i)
|
ui.Render(i)
|
||||||
|
|
||||||
@@ -50,8 +46,7 @@ func FilterMenu() {
|
|||||||
go func() {
|
go func() {
|
||||||
for s := range stream {
|
for s := range stream {
|
||||||
config.Update("filterStr", s)
|
config.Update("filterStr", s)
|
||||||
cursor.RefreshContainers()
|
RefreshDisplay()
|
||||||
RedrawRows()
|
|
||||||
ui.Render(i)
|
ui.Render(i)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -72,9 +67,7 @@ func SortMenu() {
|
|||||||
m := menu.NewMenu()
|
m := menu.NewMenu()
|
||||||
m.Selectable = true
|
m.Selectable = true
|
||||||
m.SortItems = true
|
m.SortItems = true
|
||||||
m.TextFgColor = ui.ColorWhite
|
|
||||||
m.BorderLabel = "Sort Field"
|
m.BorderLabel = "Sort Field"
|
||||||
m.BorderFg = ui.ColorCyan
|
|
||||||
|
|
||||||
for _, field := range SortFields() {
|
for _, field := range SortFields() {
|
||||||
m.AddItems(menu.Item{field, ""})
|
m.AddItems(menu.Item{field, ""})
|
||||||
|
|||||||
@@ -10,14 +10,16 @@ import (
|
|||||||
// Mock collector
|
// Mock collector
|
||||||
type Mock struct {
|
type Mock struct {
|
||||||
Metrics
|
Metrics
|
||||||
stream chan Metrics
|
stream chan Metrics
|
||||||
done bool
|
done bool
|
||||||
running bool
|
running bool
|
||||||
|
aggression int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMock() *Mock {
|
func NewMock(a int64) *Mock {
|
||||||
c := &Mock{
|
c := &Mock{
|
||||||
Metrics: Metrics{},
|
Metrics: Metrics{},
|
||||||
|
aggression: a,
|
||||||
}
|
}
|
||||||
c.MemLimit = 2147483648
|
c.MemLimit = 2147483648
|
||||||
return c
|
return c
|
||||||
@@ -47,13 +49,14 @@ func (c *Mock) run() {
|
|||||||
defer close(c.stream)
|
defer close(c.stream)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
c.CPUUtil += rand.Intn(2)
|
c.CPUUtil += rand.Intn(2) * int(c.aggression)
|
||||||
if c.CPUUtil > 100 {
|
if c.CPUUtil >= 100 {
|
||||||
c.CPUUtil = 0
|
c.CPUUtil = 0
|
||||||
}
|
}
|
||||||
c.NetTx += rand.Int63n(600)
|
|
||||||
c.NetRx += rand.Int63n(600)
|
c.NetTx += rand.Int63n(60) * c.aggression
|
||||||
c.MemUsage += rand.Int63n(c.MemLimit / 32)
|
c.NetRx += rand.Int63n(60) * c.aggression
|
||||||
|
c.MemUsage += rand.Int63n(c.MemLimit/512) * c.aggression
|
||||||
if c.MemUsage > c.MemLimit {
|
if c.MemUsage > c.MemLimit {
|
||||||
c.MemUsage = 0
|
c.MemUsage = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,18 +26,24 @@ func NewMockContainerSource() *MockContainerSource {
|
|||||||
|
|
||||||
// Create Mock containers
|
// Create Mock containers
|
||||||
func (cs *MockContainerSource) Init() {
|
func (cs *MockContainerSource) Init() {
|
||||||
total := 20
|
|
||||||
rand.Seed(int64(time.Now().Nanosecond()))
|
rand.Seed(int64(time.Now().Nanosecond()))
|
||||||
|
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
//time.Sleep(1 * time.Second)
|
cs.makeContainer(3)
|
||||||
collector := metrics.NewMock()
|
|
||||||
c := NewContainer(makeID(), collector)
|
|
||||||
c.SetMeta("name", makeName())
|
|
||||||
c.SetState(makeState())
|
|
||||||
cs.containers = append(cs.containers, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
cs.makeContainer(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *MockContainerSource) makeContainer(aggression int64) {
|
||||||
|
collector := metrics.NewMock(aggression)
|
||||||
|
c := NewContainer(makeID(), collector)
|
||||||
|
c.SetMeta("name", makeName())
|
||||||
|
c.SetState(makeState())
|
||||||
|
cs.containers = append(cs.containers, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *MockContainerSource) Loop() {
|
func (cs *MockContainerSource) Loop() {
|
||||||
@@ -66,6 +72,7 @@ func (cs *MockContainerSource) Get(id string) (*Container, bool) {
|
|||||||
// Return array of all containers, sorted by field
|
// Return array of all containers, sorted by field
|
||||||
func (cs *MockContainerSource) All() Containers {
|
func (cs *MockContainerSource) All() Containers {
|
||||||
sort.Sort(cs.containers)
|
sort.Sort(cs.containers)
|
||||||
|
cs.containers.Filter()
|
||||||
return cs.containers
|
return cs.containers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
sort.go
10
sort.go
@@ -83,23 +83,21 @@ func (a Containers) Less(i, j int) bool {
|
|||||||
return f(a[i], a[j])
|
return f(a[i], a[j])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Containers) Filter() (filtered []*Container) {
|
func (a Containers) Filter() {
|
||||||
filter := config.GetVal("filterStr")
|
filter := config.GetVal("filterStr")
|
||||||
re := regexp.MustCompile(fmt.Sprintf(".*%s", filter))
|
re := regexp.MustCompile(fmt.Sprintf(".*%s", filter))
|
||||||
|
|
||||||
for _, c := range a {
|
for _, c := range a {
|
||||||
|
c.display = true
|
||||||
// Apply name filter
|
// Apply name filter
|
||||||
if re.FindAllString(c.GetMeta("name"), 1) == nil {
|
if re.FindAllString(c.GetMeta("name"), 1) == nil {
|
||||||
continue
|
c.display = false
|
||||||
}
|
}
|
||||||
// Apply state filter
|
// Apply state filter
|
||||||
if !config.GetSwitchVal("allContainers") && c.GetMeta("state") != "running" {
|
if !config.GetSwitchVal("allContainers") && c.GetMeta("state") != "running" {
|
||||||
continue
|
c.display = false
|
||||||
}
|
}
|
||||||
filtered = append(filtered, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return filtered
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sumNet(c *Container) int64 { return c.NetRx + c.NetTx }
|
func sumNet(c *Container) int64 { return c.NetRx + c.NetTx }
|
||||||
|
|||||||
@@ -23,10 +23,14 @@ func NewCTopHeader() *CTopHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CTopHeader) Render() {
|
func (c *CTopHeader) Buffer() ui.Buffer {
|
||||||
|
buf := ui.NewBuffer()
|
||||||
c.Time.Text = timeStr()
|
c.Time.Text = timeStr()
|
||||||
ui.Render(c.bg)
|
buf.Merge(c.bg.Buffer())
|
||||||
ui.Render(c.Time, c.Count, c.Filter)
|
buf.Merge(c.Time.Buffer())
|
||||||
|
buf.Merge(c.Count.Buffer())
|
||||||
|
buf.Merge(c.Filter.Buffer())
|
||||||
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CTopHeader) Align() {
|
func (c *CTopHeader) Align() {
|
||||||
@@ -41,7 +45,7 @@ func headerBgBordered() *ui.Par {
|
|||||||
bg := ui.NewPar("")
|
bg := ui.NewPar("")
|
||||||
bg.X = 1
|
bg.X = 1
|
||||||
bg.Height = 3
|
bg.Height = 3
|
||||||
bg.Bg = ui.ColorWhite
|
bg.Bg = ui.ThemeAttr("header.bg")
|
||||||
return bg
|
return bg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +54,7 @@ func headerBg() *ui.Par {
|
|||||||
bg.X = 1
|
bg.X = 1
|
||||||
bg.Height = 1
|
bg.Height = 1
|
||||||
bg.Border = false
|
bg.Border = false
|
||||||
bg.Bg = ui.ColorWhite
|
bg.Bg = ui.ThemeAttr("header.bg")
|
||||||
return bg
|
return bg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +72,7 @@ func (c *CTopHeader) SetFilter(val string) {
|
|||||||
|
|
||||||
func timeStr() string {
|
func timeStr() string {
|
||||||
ts := time.Now().Local().Format("15:04:05 MST")
|
ts := time.Now().Local().Format("15:04:05 MST")
|
||||||
return fmt.Sprintf("cTop - %s", ts)
|
return fmt.Sprintf("ctop - %s", ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func headerPar(x int, s string) *ui.Par {
|
func headerPar(x int, s string) *ui.Par {
|
||||||
@@ -77,8 +81,8 @@ func headerPar(x int, s string) *ui.Par {
|
|||||||
p.Border = false
|
p.Border = false
|
||||||
p.Height = 1
|
p.Height = 1
|
||||||
p.Width = 20
|
p.Width = 20
|
||||||
p.TextFgColor = ui.ColorDefault
|
p.Bg = ui.ThemeAttr("header.bg")
|
||||||
p.TextBgColor = ui.ColorWhite
|
p.TextFgColor = ui.ThemeAttr("header.fg")
|
||||||
p.Bg = ui.ColorWhite
|
p.TextBgColor = ui.ThemeAttr("header.bg")
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,12 @@ func NewInput() *Input {
|
|||||||
Block: *ui.NewBlock(),
|
Block: *ui.NewBlock(),
|
||||||
Label: "input",
|
Label: "input",
|
||||||
MaxLen: 20,
|
MaxLen: 20,
|
||||||
TextFgColor: ui.ThemeAttr("par.text.fg"),
|
TextFgColor: ui.ThemeAttr("menu.text.fg"),
|
||||||
TextBgColor: ui.ThemeAttr("par.text.bg"),
|
TextBgColor: ui.ThemeAttr("menu.text.bg"),
|
||||||
padding: Padding{4, 2},
|
padding: Padding{4, 2},
|
||||||
}
|
}
|
||||||
|
i.BorderFg = ui.ThemeAttr("menu.border.fg")
|
||||||
|
i.BorderLabelFg = ui.ThemeAttr("menu.label.fg")
|
||||||
i.calcSize()
|
i.calcSize()
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ type Menu struct {
|
|||||||
func NewMenu() *Menu {
|
func NewMenu() *Menu {
|
||||||
m := &Menu{
|
m := &Menu{
|
||||||
Block: *ui.NewBlock(),
|
Block: *ui.NewBlock(),
|
||||||
TextFgColor: ui.ThemeAttr("par.text.fg"),
|
TextFgColor: ui.ThemeAttr("menu.text.fg"),
|
||||||
TextBgColor: ui.ThemeAttr("par.text.bg"),
|
TextBgColor: ui.ThemeAttr("menu.text.bg"),
|
||||||
cursorPos: 0,
|
cursorPos: 0,
|
||||||
padding: Padding{4, 2},
|
padding: Padding{4, 2},
|
||||||
}
|
}
|
||||||
|
m.BorderFg = ui.ThemeAttr("menu.border.fg")
|
||||||
|
m.BorderLabelFg = ui.ThemeAttr("menu.label.fg")
|
||||||
m.X = 1
|
m.X = 1
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@@ -86,7 +88,7 @@ func (m *Menu) Buffer() ui.Buffer {
|
|||||||
for _, ch := range item.Text() {
|
for _, ch := range item.Text() {
|
||||||
// invert bg/fg colors on currently selected row
|
// invert bg/fg colors on currently selected row
|
||||||
if m.Selectable && n == m.cursorPos {
|
if m.Selectable && n == m.cursorPos {
|
||||||
cell = ui.Cell{Ch: ch, Fg: m.TextBgColor, Bg: m.TextFgColor}
|
cell = ui.Cell{Ch: ch, Fg: ui.ColorBlack, Bg: m.TextFgColor}
|
||||||
} else {
|
} else {
|
||||||
cell = ui.Cell{Ch: ch, Fg: m.TextFgColor, Bg: m.TextBgColor}
|
cell = ui.Cell{Ch: ch, Fg: m.TextFgColor, Bg: m.TextBgColor}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user