From c921159cac31ec73e1a48b197d2e5ac10330cd0b Mon Sep 17 00:00:00 2001 From: Ivan Daniluk Date: Sat, 25 Apr 2015 22:46:16 +0300 Subject: [PATCH] Refactored sparklines --- data.go | 1 + main.go | 11 ++- service.go | 23 +++++- ui.go | 2 +- ui_dummy.go | 6 +- ui_termui.go | 214 ++++++++++++++++++++++++++++++--------------------- 6 files changed, 162 insertions(+), 95 deletions(-) diff --git a/data.go b/data.go index 2bf06d9..07cf833 100644 --- a/data.go +++ b/data.go @@ -5,6 +5,7 @@ import "time" // Data represents data to be passed to UI. type Data struct { Services Services + Total int TotalMemory *Stack LastTimestamp time.Time } diff --git a/main.go b/main.go index f3b9e7b..b0b5d1d 100644 --- a/main.go +++ b/main.go @@ -5,11 +5,11 @@ import ( "log" "time" - "github.com/gizak/termui" + "github.com/divan/termui" ) var ( - interval = flag.Duration("i", 1*time.Second, "Polling interval") + interval = flag.Duration("i", 10*time.Second, "Polling interval") portsArg = flag.String("ports", "40001,40002,40000,40004,1233,1234,1235", "Ports for accessing services expvars") dummy = flag.Bool("dummy", false, "Use dummy (console) output") ) @@ -27,12 +27,13 @@ func main() { service := NewService(port) data.Services = append(data.Services, service) } + data.Total = len(data.Services) var ui UI = &TermUI{} if *dummy { ui = &DummyUI{} } - ui.Init() + ui.Init(data) defer ui.Close() tick := time.NewTicker(*interval) @@ -61,6 +62,10 @@ func main() { if e.Type == termui.EventKey && e.Ch == 'q' { return } + if e.Type == termui.EventResize { + termui.Body.Width = termui.TermWidth() + termui.Body.Align() + } } } } diff --git a/service.go b/service.go index 36fc0d2..fc08ad2 100644 --- a/service.go +++ b/service.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/pyk/byten" "net/http" "runtime" "strings" @@ -60,7 +61,7 @@ func (s *Service) Update() { // Put metrics data mem, ok := s.Values["memory"] if !ok { - s.Values["memory"] = NewStack(40) + s.Values["memory"] = NewStack(1200) mem = s.Values["memory"] } mem.Push(int(s.MemStats.Alloc) / 1024) @@ -72,3 +73,23 @@ func (s *Service) Update() { func (s Service) Addr() string { return fmt.Sprintf("http://localhost:%s%s", s.Port, ExpvarsUrl) } + +// StatusLine returns status line for services with it's name and status. +func (s Service) StatusLine() string { + if s.Err != nil { + return fmt.Sprintf("[ERR] %s failed", s.Name) + } + + return fmt.Sprintf("[R] %s", s.Name) +} + +// Meminfo returns memory info string for the given service. +func (s Service) Meminfo() string { + if s.Err != nil || s.MemStats == nil { + return "N/A" + } + + allocated := byten.Size(int64(s.MemStats.Alloc)) + sys := byten.Size(int64(s.MemStats.Sys)) + return fmt.Sprintf("Alloc/Sys: %s / %s", allocated, sys) +} diff --git a/ui.go b/ui.go index 4a5bd73..9fbccc0 100644 --- a/ui.go +++ b/ui.go @@ -2,7 +2,7 @@ package main // UI represents UI module type UI interface { - Init() + Init(Data) Close() Update(Data) } diff --git a/ui_dummy.go b/ui_dummy.go index f1a7067..092bfc5 100644 --- a/ui_dummy.go +++ b/ui_dummy.go @@ -8,9 +8,9 @@ import ( // DummyUI is an simple console UI mockup, for testing purposes. type DummyUI struct{} -func (u *DummyUI) Init() {} -func (u *DummyUI) Close() {} -func (u *DummyUI) Update(data Data) { +func (*DummyUI) Init(Data) {} +func (*DummyUI) Close() {} +func (*DummyUI) Update(data Data) { if data.Services == nil { return } diff --git a/ui_termui.go b/ui_termui.go index c6ae1a5..eced65c 100644 --- a/ui_termui.go +++ b/ui_termui.go @@ -4,116 +4,156 @@ import ( "fmt" "log" - "github.com/gizak/termui" - "github.com/pyk/byten" + "github.com/divan/termui" ) // TermUI is a termUI implementation of UI interface. type TermUI struct { + Title *termui.Par + Status *termui.Par + Services *termui.List + Meminfo *termui.List + MemSparkline *termui.Sparklines } -func (t *TermUI) Init() { +func (t *TermUI) Init(data Data) { err := termui.Init() if err != nil { log.Fatal(err) } termui.UseTheme("helloworld") + + t.Title = func() *termui.Par { + p := termui.NewPar("") + p.Height = 3 + p.TextFgColor = termui.ColorWhite + p.Border.Label = "Services Monitor" + p.Border.FgColor = termui.ColorCyan + return p + }() + t.Status = func() *termui.Par { + p := termui.NewPar("") + p.Height = 3 + p.TextFgColor = termui.ColorWhite + p.Border.Label = "Status" + p.Border.FgColor = termui.ColorCyan + return p + }() + t.Services = func() *termui.List { + l := termui.NewList() + l.ItemFgColor = termui.ColorGreen + l.Border.Label = "Services" + return l + }() + t.Meminfo = func() *termui.List { + l := termui.NewList() + l.ItemFgColor = termui.ColorBlue | termui.AttrBold + l.Border.Label = "Memory Usage" + return l + }() + t.MemSparkline = func() *termui.Sparklines { + var sparklines []termui.Sparkline + for _, service := range data.Services { + spl := termui.NewSparkline() + spl.Height = 1 + spl.LineColor = termui.ColorGreen + spl.Title = service.Name + sparklines = append(sparklines, spl) + } + + s := termui.NewSparklines(sparklines...) + s.Height = 2*data.Total + 2 + s.HasBorder = true + s.Border.Label = "Memory Track" + return s + }() + + termui.Body.AddRows( + termui.NewRow( + termui.NewCol(6, 0, t.Title), + termui.NewCol(6, 0, t.Status)), + termui.NewRow( + termui.NewCol(3, 0, t.Services), + termui.NewCol(9, 0, t.Meminfo)), + termui.NewRow( + termui.NewCol(12, 0, t.MemSparkline)), + ) + + termui.Body.Align() } func (t *TermUI) Update(data Data) { - total := len(data.Services) - text := fmt.Sprintf("monitoring %d services, press q to quit", total) + t.Title.Text = fmt.Sprintf("monitoring %d services, press q to quit", data.Total) + t.Status.Text = fmt.Sprintf("Last update: %v", data.LastTimestamp.Format("15:04:05 02/Jan/06")) - p := termui.NewPar(text) - p.Height = 3 - p.Width = termui.TermWidth() / 2 - p.TextFgColor = termui.ColorWhite - p.Border.Label = "Services Monitor" - p.Border.FgColor = termui.ColorCyan - - text1 := fmt.Sprintf("Last update: %v", data.LastTimestamp.Format("15:04:05 02/Jan/06")) - p1 := termui.NewPar(text1) - p1.Height = 3 - p1.X = p.X + p.Width - p1.Width = termui.TermWidth() - p1.X - p1.TextFgColor = termui.ColorWhite - p1.Border.Label = "Status" - p1.Border.FgColor = termui.ColorCyan - - names := termui.NewList() - names.Y = 3 - names.ItemFgColor = termui.ColorYellow - names.Border.Label = "Services" - names.Height = total + 2 - names.Width = termui.TermWidth() / 4 - - meminfo := termui.NewList() - meminfo.Y = 3 - meminfo.X = names.X + names.Width - meminfo.Width = meminfo.X + termui.TermWidth()/3 - meminfo.Height = total + 2 - meminfo.ItemFgColor = termui.ColorBlue - meminfo.Border.Label = "Memory Usage (Alloc/HeapAlloc)" - - goroutines := termui.NewList() - goroutines.Y = 3 - goroutines.X = meminfo.X + meminfo.Width - goroutines.Width = termui.TermWidth() - goroutines.X - goroutines.Height = total + 2 - goroutines.ItemFgColor = termui.ColorGreen - goroutines.Border.Label = "Goroutines" - - var totalAlloc int64 + var services []string + var meminfos []string for _, service := range data.Services { - if service.Err != nil { - names.Items = append(names.Items, fmt.Sprintf("[ERR] %s failed", service.Name)) - meminfo.Items = append(meminfo.Items, "N/A") - goroutines.Items = append(goroutines.Items, "N/A") - continue + services = append(services, service.StatusLine()) + meminfos = append(meminfos, service.Meminfo()) + } + t.Services.Items = services + t.Services.Height = data.Total + 2 + + t.Meminfo.Items = meminfos + t.Meminfo.Height = data.Total + 2 + + // Sparklines + for i, service := range data.Services { + t.MemSparkline.Lines[i].Title = service.Name + t.MemSparkline.Lines[i].Data = service.Values["memory"].Values + } + + termui.Body.Width = termui.TermWidth() + termui.Body.Align() + termui.Render(termui.Body) + /* + + goroutines := termui.NewList() + goroutines.Y = 3 + goroutines.X = meminfo.X + meminfo.Width + goroutines.Width = termui.TermWidth() - goroutines.X + goroutines.Height = total + 2 + goroutines.ItemFgColor = termui.ColorGreen + goroutines.Border.Label = "Goroutines" + + var totalAlloc int64 + for _, service := range data.Services { + if service.Err != nil { + names.Items = append(names.Items, fmt.Sprintf("[ERR] %s failed", service.Name)) + meminfo.Items = append(meminfo.Items, "N/A") + goroutines.Items = append(goroutines.Items, "N/A") + continue + } + alloc := byten.Size(int64(service.MemStats.Alloc)) + heap := byten.Size(int64(service.MemStats.HeapAlloc)) + totalAlloc += int64(service.MemStats.Alloc) + + name := fmt.Sprintf("[R] %s", service.Name) + meminfos := fmt.Sprintf("%s/%s", alloc, heap) + + names.Items = append(names.Items, name) + meminfo.Items = append(meminfo.Items, meminfos) } - alloc := byten.Size(int64(service.MemStats.Alloc)) - heap := byten.Size(int64(service.MemStats.HeapAlloc)) - totalAlloc += int64(service.MemStats.Alloc) - name := fmt.Sprintf("[R] %s", service.Name) - meminfos := fmt.Sprintf("%s/%s", alloc, heap) - names.Items = append(names.Items, name) - meminfo.Items = append(meminfo.Items, meminfos) - } + data.TotalMemory.Push(int(totalAlloc / 1024)) - var sparklines []termui.Sparkline - for _, service := range data.Services { - spl := termui.NewSparkline() - spl.Data = service.Values["memory"].Values - spl.Height = 1 - spl.LineColor = termui.ColorGreen - sparklines = append(sparklines, spl) - } + spl3 := termui.NewSparkline() + spl3.Data = data.TotalMemory.Values + spl3.Height = termui.TermHeight() - 3 - (total + 2) - 3 + spl3.LineColor = termui.ColorYellow - spls := termui.NewSparklines(sparklines...) - spls.Height = len(data.Services) + 1 - spls.Width = 40 - spls.Y = 3 - spls.X = meminfo.X + meminfo.Width - spls.Width - 1 - spls.HasBorder = false + spls2 := termui.NewSparklines(spl3) + spls2.Y = 3 + (total + 2) + spls2.Height = termui.TermHeight() - spls2.Y + spls2.Width = termui.TermWidth() + spls2.Border.FgColor = termui.ColorCyan + spls2.Border.Label = fmt.Sprintf("Total Memory Usage: %s", byten.Size(totalAlloc)) - data.TotalMemory.Push(int(totalAlloc / 1024)) - - spl3 := termui.NewSparkline() - spl3.Data = data.TotalMemory.Values - spl3.Height = termui.TermHeight() - 3 - (total + 2) - 3 - spl3.LineColor = termui.ColorYellow - - spls2 := termui.NewSparklines(spl3) - spls2.Y = 3 + (total + 2) - spls2.Height = termui.TermHeight() - spls2.Y - spls2.Width = termui.TermWidth() - spls2.Border.FgColor = termui.ColorCyan - spls2.Border.Label = fmt.Sprintf("Total Memory Usage: %s", byten.Size(totalAlloc)) - - termui.Render(p, p1, names, meminfo, goroutines, spls2, spls) + termui.Render(p, p1, names, meminfo, goroutines, spls2, spls) + */ } func (t *TermUI) Close() {