Compare commits

...

16 Commits

Author SHA1 Message Date
Bradley Cicenas
a2011b8bc7 v0.6.1 2017-06-29 14:02:52 +00:00
Bradley Cicenas
40fd9e935a combine image build steps 2017-06-29 14:02:52 +00:00
Bradley Cicenas
b88c143914 add offset sanity check to CompactGrid Align() 2017-07-07 15:43:03 +03:00
Bradley Cicenas
0a05007c4e skip offset updates in page scroll if no pages 2017-07-07 15:38:02 +03:00
Bradley Cicenas
c47ba3f804 add pgCount() method to GridCursor 2017-07-07 15:28:26 +03:00
Bradley Cicenas
79a3f361a7 add container log struct to models, collectors 2017-07-04 12:32:25 +00:00
Bradley Cicenas
65399a37e5 add log panel to expanded widgets 2017-06-29 14:02:52 +00:00
Bradley Cicenas
25a3fcf731 add runtimestats, stack logging to debug 2017-06-28 09:12:24 -03:00
Bradley Cicenas
17e2c2df8e add LogCollector interface, docker, mock log collectors 2017-06-27 14:18:17 -03:00
Bradley Cicenas
240345d527 add StreamLogs() to collector interface 2017-06-26 15:35:57 +00:00
Bradley Cicenas
2d284d9277 rename metrics subpackage 2017-06-26 15:35:57 +00:00
Bradley Cicenas
bfa5c5944f rename 2017-06-26 15:35:57 +00:00
Bradley Cicenas
e1051cd40f use underscore-prefixed build dir in makefile 2017-06-24 08:04:56 -03:00
Bradley Cicenas
13029cc7fe add go runtime to version output 2017-06-19 12:23:39 +00:00
Bradley Cicenas
58d9e4e194 reverse host and container port in metadata 2017-06-18 17:17:56 -03:00
Bradley Cicenas
4de7036e2f fix release url 2017-06-14 15:04:47 -03:00
22 changed files with 332 additions and 78 deletions

View File

@@ -1,3 +1,16 @@
FROM quay.io/vektorcloud/go:1.8
RUN apk add --no-cache make
COPY glide.* /go/src/github.com/bcicen/ctop/
WORKDIR /go/src/github.com/bcicen/ctop/
RUN glide install
COPY . /go/src/github.com/bcicen/ctop
RUN make build && \
mkdir -p /go/bin && \
mv -v ctop /go/bin/
FROM scratch
COPY ./ctop /ctop
COPY --from=0 /go/bin/ctop /ctop
ENTRYPOINT ["/ctop"]

View File

@@ -1,12 +0,0 @@
FROM quay.io/vektorcloud/go:1.8
RUN apk add --no-cache make
COPY glide.* /go/src/github.com/bcicen/ctop/
WORKDIR /go/src/github.com/bcicen/ctop/
RUN glide install
COPY . /go/src/github.com/bcicen/ctop
RUN make build && \
mkdir -p /go/bin && \
mv -v ctop /go/bin/

View File

@@ -5,7 +5,7 @@ EXT_LD_FLAGS="-Wl,--allow-multiple-definition"
LD_FLAGS="-w -X main.version=$(VERSION) -X main.build=$(BUILD) -extldflags=$(EXT_LD_FLAGS)"
clean:
rm -rf build/ release/
rm -rf _build/ _release/
build:
glide install
@@ -16,21 +16,18 @@ 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
image:
docker build -t ctop_build -f Dockerfile_build .
docker create --name=ctop_built ctop_build ctop -v
docker cp ctop_built:/go/bin/ctop .
docker build -t ctop -f Dockerfile .
release:
mkdir release
mkdir _release
go get github.com/progrium/gh-release/...
cp build/* release
cp _build/* _release
gh-release create bcicen/$(NAME) $(VERSION) \
$(shell git rev-parse --abbrev-ref HEAD) $(VERSION)

View File

@@ -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.6/ctop-0.6-linux-amd64 -O /usr/local/bin/ctop
sudo wget https://github.com/bcicen/ctop/releases/download/v0.6.0/ctop-0.6.0-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.6/ctop-0.6-darwin-amd64
sudo curl -Lo /usr/local/bin/ctop https://github.com/bcicen/ctop/releases/download/v0.6.0/ctop-0.6.0-darwin-amd64
sudo chmod +x /usr/local/bin/ctop
```

View File

@@ -1 +1 @@
0.6.0
0.6.1

View File

@@ -1,17 +1,17 @@
package collector
import (
"github.com/bcicen/ctop/metrics"
"github.com/bcicen/ctop/models"
api "github.com/fsouza/go-dockerclient"
)
// Docker collector
type Docker struct {
metrics.Metrics
models.Metrics
id string
client *api.Client
running bool
stream chan metrics.Metrics
stream chan models.Metrics
done chan bool
lastCpu float64
lastSysCpu float64
@@ -19,7 +19,7 @@ type Docker struct {
func NewDocker(client *api.Client, id string) *Docker {
return &Docker{
Metrics: metrics.Metrics{},
Metrics: models.Metrics{},
id: id,
client: client,
}
@@ -27,7 +27,7 @@ func NewDocker(client *api.Client, id string) *Docker {
func (c *Docker) Start() {
c.done = make(chan bool)
c.stream = make(chan metrics.Metrics)
c.stream = make(chan models.Metrics)
stats := make(chan *api.Stats)
go func() {
@@ -61,10 +61,14 @@ func (c *Docker) Running() bool {
return c.running
}
func (c *Docker) Stream() chan metrics.Metrics {
func (c *Docker) Stream() chan models.Metrics {
return c.stream
}
func (c *Docker) Logs() LogCollector {
return &DockerLogs{c.id, c.client, make(chan bool)}
}
// Stop collector
func (c *Docker) Stop() {
c.done <- true

View File

@@ -0,0 +1,74 @@
package collector
import (
"bufio"
"context"
"io"
"strings"
"time"
"github.com/bcicen/ctop/models"
api "github.com/fsouza/go-dockerclient"
)
type DockerLogs struct {
id string
client *api.Client
done chan bool
}
func (l *DockerLogs) Stream() chan models.Log {
r, w := io.Pipe()
logCh := make(chan models.Log)
ctx, cancel := context.WithCancel(context.Background())
opts := api.LogsOptions{
Context: ctx,
Container: l.id,
OutputStream: w,
ErrorStream: w,
Stdout: true,
Stderr: true,
Tail: "10",
Follow: true,
Timestamps: true,
}
// read io pipe into channel
go func() {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
parts := strings.Split(scanner.Text(), " ")
ts := l.parseTime(parts[0])
logCh <- models.Log{ts, strings.Join(parts[1:], " ")}
}
}()
// connect to container log stream
go func() {
err := l.client.Logs(opts)
if err != nil {
log.Errorf("error reading container logs: %s", err)
}
}()
go func() {
select {
case <-l.done:
cancel()
}
}()
return logCh
}
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()
}
return ts
}

View File

@@ -4,10 +4,24 @@ import (
"math"
"github.com/bcicen/ctop/logging"
"github.com/bcicen/ctop/models"
)
var log = logging.Init()
type LogCollector interface {
Stream() chan models.Log
Stop()
}
type Collector interface {
Stream() chan models.Metrics
Logs() LogCollector
Running() bool
Start()
Stop()
}
func round(num float64) int {
return int(num + math.Copysign(0.5, num))
}

View File

@@ -6,13 +6,13 @@ import (
"math/rand"
"time"
"github.com/bcicen/ctop/metrics"
"github.com/bcicen/ctop/models"
)
// Mock collector
type Mock struct {
metrics.Metrics
stream chan metrics.Metrics
models.Metrics
stream chan models.Metrics
done bool
running bool
aggression int64
@@ -20,7 +20,7 @@ type Mock struct {
func NewMock(a int64) *Mock {
c := &Mock{
Metrics: metrics.Metrics{},
Metrics: models.Metrics{},
aggression: a,
}
c.MemLimit = 2147483648
@@ -33,7 +33,7 @@ func (c *Mock) Running() bool {
func (c *Mock) Start() {
c.done = false
c.stream = make(chan metrics.Metrics)
c.stream = make(chan models.Metrics)
go c.run()
}
@@ -41,10 +41,14 @@ func (c *Mock) Stop() {
c.done = true
}
func (c *Mock) Stream() chan metrics.Metrics {
func (c *Mock) Stream() chan models.Metrics {
return c.stream
}
func (c *Mock) Logs() LogCollector {
return &MockLogs{make(chan bool)}
}
func (c *Mock) run() {
c.running = true
rand.Seed(int64(time.Now().Nanosecond()))

View File

@@ -0,0 +1,31 @@
package collector
import (
"time"
"github.com/bcicen/ctop/models"
)
const mockLog = "Cura ob pro qui tibi inveni dum qua fit donec amare illic mea, regem falli contexo pro peregrinorum heremo absconditi araneae meminerim deliciosas actionibus facere modico dura sonuerunt psalmi contra rerum, tempus mala anima volebant dura quae o modis."
type MockLogs struct {
done chan bool
}
func (l *MockLogs) Stream() chan models.Log {
logCh := make(chan models.Log)
go func() {
for {
select {
case <-l.done:
break
default:
logCh <- models.Log{time.Now(), mockLog}
time.Sleep(250 * time.Millisecond)
}
}
}()
return logCh
}
func (l *MockLogs) Stop() { l.done <- true }

View File

@@ -5,17 +5,17 @@ package collector
import (
"time"
"github.com/bcicen/ctop/metrics"
"github.com/bcicen/ctop/models"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/cgroups"
)
// Runc collector
type Runc struct {
metrics.Metrics
models.Metrics
id string
libc libcontainer.Container
stream chan metrics.Metrics
stream chan models.Metrics
done bool
running bool
interval int // collection interval, in seconds
@@ -25,7 +25,7 @@ type Runc struct {
func NewRunc(libc libcontainer.Container) *Runc {
c := &Runc{
Metrics: metrics.Metrics{},
Metrics: models.Metrics{},
id: libc.ID(),
libc: libc,
interval: 1,
@@ -39,7 +39,7 @@ func (c *Runc) Running() bool {
func (c *Runc) Start() {
c.done = false
c.stream = make(chan metrics.Metrics)
c.stream = make(chan models.Metrics)
go c.run()
}
@@ -47,10 +47,14 @@ func (c *Runc) Stop() {
c.done = true
}
func (c *Runc) Stream() chan metrics.Metrics {
func (c *Runc) Stream() chan models.Metrics {
return c.stream
}
func (c *Runc) Logs() LogCollector {
return nil
}
func (c *Runc) run() {
c.running = true
defer close(c.stream)

View File

@@ -66,7 +66,7 @@ func portsFormat(ports map[api.Port][]api.PortBinding) string {
continue
}
for _, binding := range v {
s := fmt.Sprintf("%s -> %s:%s", k, binding.HostIP, binding.HostPort)
s := fmt.Sprintf("%s:%s -> %s", binding.HostIP, binding.HostPort, k)
published = append(published, s)
}
}

View File

@@ -1,10 +1,11 @@
package container
import (
"github.com/bcicen/ctop/connector/collector"
"github.com/bcicen/ctop/cwidgets"
"github.com/bcicen/ctop/cwidgets/compact"
"github.com/bcicen/ctop/logging"
"github.com/bcicen/ctop/metrics"
"github.com/bcicen/ctop/models"
)
var (
@@ -13,19 +14,19 @@ var (
// Metrics and metadata representing a container
type Container struct {
metrics.Metrics
models.Metrics
Id string
Meta map[string]string
Widgets *compact.Compact
Display bool // display this container in compact view
updater cwidgets.WidgetUpdater
collector metrics.Collector
collector collector.Collector
}
func New(id string, collector metrics.Collector) *Container {
func New(id string, collector collector.Collector) *Container {
widgets := compact.NewCompact(id)
return &Container{
Metrics: metrics.NewMetrics(),
Metrics: models.NewMetrics(),
Id: id,
Meta: make(map[string]string),
Widgets: widgets,
@@ -66,15 +67,20 @@ func (c *Container) SetState(s string) {
}
}
// Return container log collector
func (c *Container) Logs() collector.LogCollector {
return c.collector.Logs()
}
// Read metric stream, updating widgets
func (c *Container) Read(stream chan metrics.Metrics) {
func (c *Container) Read(stream chan models.Metrics) {
go func() {
for metrics := range stream {
c.Metrics = metrics
c.updater.SetMetrics(metrics)
}
log.Infof("reader stopped for container: %s", c.Id)
c.Metrics = metrics.NewMetrics()
c.Metrics = models.NewMetrics()
c.Widgets.Reset()
}()
log.Infof("reader started for container: %s", c.Id)

View File

@@ -142,10 +142,11 @@ func (gc *GridCursor) PgUp() {
return
}
var nextidx int
nextidx = int(math.Max(0.0, float64(idx-cGrid.MaxRows())))
nextidx := int(math.Max(0.0, float64(idx-cGrid.MaxRows())))
if gc.pgCount() > 0 {
cGrid.Offset = int(math.Max(float64(cGrid.Offset-cGrid.MaxRows()),
float64(0)))
}
active := gc.filtered[idx]
next := gc.filtered[nextidx]
@@ -164,11 +165,11 @@ func (gc *GridCursor) PgDown() {
return
}
var nextidx int
nextidx = int(math.Min(float64(gc.Len()-1),
float64(idx+cGrid.MaxRows())))
nextidx := int(math.Min(float64(gc.Len()-1), float64(idx+cGrid.MaxRows())))
if gc.pgCount() > 0 {
cGrid.Offset = int(math.Min(float64(cGrid.Offset+cGrid.MaxRows()),
float64(gc.Len()-cGrid.MaxRows())))
}
active := gc.filtered[idx]
next := gc.filtered[nextidx]
@@ -180,3 +181,12 @@ func (gc *GridCursor) PgDown() {
cGrid.Align()
ui.Render(cGrid)
}
// number of pages at current row count and term height
func (gc *GridCursor) pgCount() int {
pages := gc.Len() / cGrid.MaxRows()
if gc.Len()%cGrid.MaxRows() > 0 {
pages++
}
return pages
}

View File

@@ -22,9 +22,14 @@ func NewCompactGrid() *CompactGrid {
func (cg *CompactGrid) Align() {
y := cg.Y
if cg.Offset >= len(cg.Rows) {
cg.Offset = 0
}
if cg.Offset < 0 {
cg.Offset = 0
}
// update row ypos, width recursively
for _, r := range cg.pageRows() {
r.SetY(y)

View File

@@ -2,7 +2,7 @@ package compact
import (
"github.com/bcicen/ctop/logging"
"github.com/bcicen/ctop/metrics"
"github.com/bcicen/ctop/models"
ui "github.com/gizak/termui"
)
@@ -59,7 +59,7 @@ func (row *Compact) SetMeta(k, v string) {
}
}
func (row *Compact) SetMetrics(m metrics.Metrics) {
func (row *Compact) SetMetrics(m models.Metrics) {
row.SetCPU(m.CPUUtil)
row.SetNet(m.NetRx, m.NetTx)
row.SetMem(m.MemUsage, m.MemLimit, m.MemPercent)

83
cwidgets/expanded/logs.go Normal file
View File

@@ -0,0 +1,83 @@
package expanded
import (
"time"
"github.com/bcicen/ctop/models"
ui "github.com/gizak/termui"
)
type LogLines struct {
ts []time.Time
data []string
}
func NewLogLines(max int) *LogLines {
ll := &LogLines{
ts: make([]time.Time, max),
data: make([]string, max),
}
return ll
}
func (ll *LogLines) tail(n int) []string {
lines := make([]string, n)
for i := 0; i < n; i++ {
lines = append(lines, ll.data[len(ll.data)-i])
}
return lines
}
func (ll *LogLines) getLines(start, end int) []string {
if end < 0 {
return ll.data[start:]
}
return ll.data[start:end]
}
func (ll *LogLines) add(l models.Log) {
if len(ll.data) == cap(ll.data) {
ll.data = append(ll.data[:0], ll.data[1:]...)
ll.ts = append(ll.ts[:0], ll.ts[1:]...)
}
ll.ts = append(ll.ts, l.Timestamp)
ll.data = append(ll.data, l.Message)
log.Debugf("recorded log line: %v", l)
}
type Logs struct {
*ui.List
lines *LogLines
}
func NewLogs(stream chan models.Log) *Logs {
p := ui.NewList()
p.Y = ui.TermHeight() / 2
p.X = 0
p.Height = ui.TermHeight() - p.Y
p.Width = ui.TermWidth()
//p.Overflow = "wrap"
p.ItemFgColor = ui.ThemeAttr("par.text.fg")
i := &Logs{p, NewLogLines(4098)}
go func() {
for line := range stream {
i.lines.add(line)
ui.Render(i)
}
}()
return i
}
func (w *Logs) Align() {
w.X = colWidth[0]
w.List.Align()
}
func (w *Logs) Buffer() ui.Buffer {
maxLines := w.Height - 2
offset := len(w.lines.data) - maxLines
w.Items = w.lines.getLines(offset, -1)
return w.List.Buffer()
}
// number of rows a line will occupy at current panel width
func (w *Logs) lineHeight(s string) int { return (len(s) / w.InnerWidth()) + 1 }

View File

@@ -2,7 +2,7 @@ package expanded
import (
"github.com/bcicen/ctop/logging"
"github.com/bcicen/ctop/metrics"
"github.com/bcicen/ctop/models"
ui "github.com/gizak/termui"
)
@@ -55,7 +55,7 @@ func (e *Expanded) Down() {
func (e *Expanded) SetWidth(w int) { e.Width = w }
func (e *Expanded) SetMeta(k, v string) { e.Info.Set(k, v) }
func (e *Expanded) SetMetrics(m metrics.Metrics) {
func (e *Expanded) SetMetrics(m models.Metrics) {
e.Cpu.Update(m.CPUUtil)
e.Net.Update(m.NetRx, m.NetTx)
e.Mem.Update(int(m.MemUsage), int(m.MemLimit))

View File

@@ -2,12 +2,12 @@ package cwidgets
import (
"github.com/bcicen/ctop/logging"
"github.com/bcicen/ctop/metrics"
"github.com/bcicen/ctop/models"
)
var log = logging.Init()
type WidgetUpdater interface {
SetMeta(string, string)
SetMetrics(metrics.Metrics)
SetMetrics(models.Metrics)
}

View File

@@ -3,11 +3,14 @@ package main
import (
"fmt"
"reflect"
"runtime"
"github.com/bcicen/ctop/container"
ui "github.com/gizak/termui"
)
var mstats = &runtime.MemStats{}
func logEvent(e ui.Event) {
var s string
s += fmt.Sprintf("Type=%s", quote(e.Type))
@@ -19,6 +22,22 @@ func logEvent(e ui.Event) {
log.Debugf("new event: %s", s)
}
func runtimeStats() {
var msg string
msg += fmt.Sprintf("cgo calls=%v", runtime.NumCgoCall())
msg += fmt.Sprintf(" routines=%v", runtime.NumGoroutine())
runtime.ReadMemStats(mstats)
msg += fmt.Sprintf(" numgc=%v", mstats.NumGC)
msg += fmt.Sprintf(" alloc=%v", mstats.Alloc)
log.Debugf("runtime: %v", msg)
}
func runtimeStack() {
buf := make([]byte, 32768)
buf = buf[:runtime.Stack(buf, true)]
log.Infof(fmt.Sprintf("stack:\n%v", string(buf)))
}
// log container, metrics, and widget state
func dumpContainer(c *container.Container) {
msg := fmt.Sprintf("logging state for container: %s\n", c.Id)

View File

@@ -4,6 +4,7 @@ import (
"flag"
"fmt"
"os"
"runtime"
"github.com/bcicen/ctop/config"
"github.com/bcicen/ctop/connector"
@@ -18,13 +19,14 @@ import (
var (
build = "none"
version = "dev-build"
goVersion = runtime.Version()
log *logging.CTopLogger
cursor *GridCursor
cGrid *compact.CompactGrid
header *widgets.CTopHeader
versionStr = fmt.Sprintf("ctop version %v, build %v", version, build)
versionStr = fmt.Sprintf("ctop version %v, build %v %v", version, build, goVersion)
)
func main() {

View File

@@ -1,4 +1,11 @@
package metrics
package models
import "time"
type Log struct {
Timestamp time.Time
Message string
}
type Metrics struct {
CPUUtil int
@@ -24,10 +31,3 @@ func NewMetrics() Metrics {
Pids: -1,
}
}
type Collector interface {
Stream() chan Metrics
Running() bool
Start()
Stop()
}