diff --git a/.gitignore b/.gitignore index 50f1f89..12507d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ termstats +*.swp diff --git a/cmdline.go b/cmdline.go new file mode 100644 index 0000000..06d8ce1 --- /dev/null +++ b/cmdline.go @@ -0,0 +1,15 @@ +package main + +import ( + "path/filepath" +) + +// BaseCommand returns cleaned command name from Cmdline array. +// +// I.e. "./some.service/binary.name -arg 1 -arg" will be "binary.name". +func BaseCommand(cmdline []string) string { + if len(cmdline) == 0 { + return "" + } + return filepath.Base(cmdline[0]) +} diff --git a/data.go b/data.go new file mode 100644 index 0000000..2bf06d9 --- /dev/null +++ b/data.go @@ -0,0 +1,31 @@ +package main + +import "time" + +// Data represents data to be passed to UI. +type Data struct { + Services Services + TotalMemory *Stack + LastTimestamp time.Time +} + +// NewData inits and return new data object. +func NewData() *Data { + return &Data{ + TotalMemory: NewStack(140), + } +} + +// FindService returns existing service by port. +func (d *Data) FindService(port string) *Service { + if d.Services == nil { + return nil + } + for _, service := range d.Services { + if service.Port == port { + return service + } + } + + return nil +} diff --git a/expvars.go b/expvars.go index 12c7fbc..9712e81 100644 --- a/expvars.go +++ b/expvars.go @@ -2,16 +2,13 @@ package main import ( "encoding/json" - "fmt" "io" - "net/http" "runtime" ) -// Expvars holds all vars we support via expvars. It implements Source interface. - const ExpvarsUrl = "/debug/vars" +// ExpvarsSource implements Source interface for retrieving Expvars. type ExpvarsSource struct { Ports []string } @@ -19,9 +16,8 @@ type ExpvarsSource struct { type Expvars map[string]Expvar type Expvar struct { - MemStats *runtime.MemStats `json:"memstats"` - Cmdline []string `json:"cmdline"` - Goroutines int64 `json:"goroutines,omitempty"` + MemStats *runtime.MemStats `json:"memstats"` + Cmdline []string `json:"cmdline"` Err error `json:"-,omitempty"` } @@ -32,37 +28,6 @@ func NewExpvarsSource(ports []string) *ExpvarsSource { } } -func (e *ExpvarsSource) Update() (interface{}, error) { - vars := make(Expvars) - for _, port := range e.Ports { - addr := fmt.Sprintf("http://localhost:%s%s", port, ExpvarsUrl) - resp, err := http.Get(addr) - if err != nil { - expvar := &Expvar{} - expvar.Err = err - vars[port] = *expvar - continue - } - if resp.StatusCode == http.StatusNotFound { - expvar := &Expvar{} - expvar.Err = fmt.Errorf("Page not found. Did you import expvars?") - vars[port] = *expvar - continue - } - defer resp.Body.Close() - - expvar, err := ParseExpvar(resp.Body) - if err != nil { - expvar = &Expvar{} - expvar.Err = err - } - - vars[port] = *expvar - } - - return vars, nil -} - // ParseExpvar unmarshals data to Expvar variable. func ParseExpvar(r io.Reader) (*Expvar, error) { var vars Expvar diff --git a/expvars_advanced.json b/expvars_advanced.json new file mode 100644 index 0000000..417eeef --- /dev/null +++ b/expvars_advanced.json @@ -0,0 +1,6 @@ +{ +"cmdline": ["./geo.service/geo.service","-p=:40004","-dsn=root:@tcp(localhost:3306)/geo"], +"memstats": {"Alloc":306192,"TotalAlloc":18134040,"Sys":3999992,"Lookups":961,"Mallocs":219206,"Frees":218107,"HeapAlloc":306192,"HeapSys":1851392,"HeapIdle":1163264,"HeapInuse":688128,"HeapReleased":1114112,"HeapObjects":1099,"StackInuse":245760,"StackSys":245760,"MSpanInuse":6968,"MSpanSys":16384,"MCacheInuse":1200,"MCacheSys":16384,"BuckHashSys":1445672,"GCSys":137579,"OtherSys":286821,"NextGC":499776,"LastGC":1429554826339587426,"PauseTotalNs":58953963,"PauseNs":[149673,116970,116282,152960,241678,269277,2345682,2813202,395563,896996,413271,524246,411490,438143,465269,410017,407874,429678,407380,400152,462808,416069,396655,411999,2235261,483709,532587,423111,402061,410275,527945,377815,454049,398089,439974,428984,454590,448865,438237,408540,432028,476609,461090,459348,407046,447736,442569,501791,390718,451293,411698,554251,612890,454626,539950,475728,481920,436132,537898,407788,366017,528018,445895,475310,426996,510830,563059,562691,632919,605119,580397,469276,593978,815423,426771,575456,872826,929380,699880,1099353,727790,825090,815515,1143818,801543,2286288,826334,791492,817455,440831,376237,2237337,519402,694540,782088,948074,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1429551836329548506,1429551836329992823,1429551836330364410,1429551836330972293,1429551836332086156,1429551836332787858,1429551896183637429,1429552016336417703,1429552038892146378,1429552038905749325,1429552038920018001,1429552038931843671,1429552038945437201,1429552038960316731,1429552038974053317,1429552038989797695,1429552039002185699,1429552039014183958,1429552039026587369,1429552039038164871,1429552039051065585,1429552039062402317,1429552039074574600,1429552039087577539,1429552039099006520,1429552039113868847,1429552039126842855,1429552039138210121,1429552039150669078,1429552045073734820,1429552045084533164,1429552045097820035,1429552045111401775,1429552045124221035,1429552045136039350,1429552045146856774,1429552045158451390,1429552045172432052,1429552045183862574,1429552045195450754,1429552045206890854,1429552045217811105,1429552045229223666,1429552045239924164,1429552045252202806,1429552045263999145,1429552045275883429,1429552045286550637,1429552045299642824,1429552045310732720,1429552045322523365,1429552049787455566,1429552049801233968,1429552049813748109,1429552049826657828,1429552049838428456,1429552049849704476,1429552049860731436,1429552049872770863,1429552049887348193,1429552049899354726,1429552049910091446,1429552049922791888,1429552049935592566,1429552049949048309,1429552049961817616,1429552049975874584,1429552049992176815,1429552050006202883,1429552050021524454,1429552050035892415,1429552050051383684,1429552050063614314,1429552170337189191,1429552291337252450,1429552412338543040,1429552533335058879,1429552653337824128,1429552774336205167,1429552895338586147,1429553016337759124,1429553136339470606,1429553257339468848,1429553378336184002,1429553499338033468,1429553619340990287,1429553740339511139,1429553861334820854,1429553981339514309,1429554102337221147,1429554223338016039,1429554344338826682,1429554465336311605,1429554585339334313,1429554706335732334,1429554826339586952,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":96,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":39541,"Frees":39497},{"Size":16,"Mallocs":47557,"Frees":47188},{"Size":32,"Mallocs":25990,"Frees":25864},{"Size":48,"Mallocs":36196,"Frees":36005},{"Size":64,"Mallocs":9930,"Frees":9822},{"Size":80,"Mallocs":585,"Frees":542},{"Size":96,"Mallocs":5530,"Frees":5518},{"Size":112,"Mallocs":8345,"Frees":8336},{"Size":128,"Mallocs":921,"Frees":916},{"Size":144,"Mallocs":23,"Frees":21},{"Size":160,"Mallocs":1451,"Frees":1432},{"Size":176,"Mallocs":13320,"Frees":13295},{"Size":192,"Mallocs":10,"Frees":2},{"Size":208,"Mallocs":3866,"Frees":3841},{"Size":224,"Mallocs":1,"Frees":0},{"Size":240,"Mallocs":1,"Frees":0},{"Size":256,"Mallocs":3,"Frees":0},{"Size":288,"Mallocs":62,"Frees":31},{"Size":320,"Mallocs":4,"Frees":3},{"Size":352,"Mallocs":69,"Frees":62},{"Size":384,"Mallocs":2,"Frees":0},{"Size":416,"Mallocs":932,"Frees":925},{"Size":448,"Mallocs":0,"Frees":0},{"Size":480,"Mallocs":2,"Frees":0},{"Size":512,"Mallocs":0,"Frees":0},{"Size":576,"Mallocs":30,"Frees":19},{"Size":640,"Mallocs":5,"Frees":2},{"Size":704,"Mallocs":4,"Frees":3},{"Size":768,"Mallocs":0,"Frees":0},{"Size":896,"Mallocs":13,"Frees":9},{"Size":1024,"Mallocs":1,"Frees":0},{"Size":1152,"Mallocs":21,"Frees":18},{"Size":1280,"Mallocs":2,"Frees":2},{"Size":1408,"Mallocs":3,"Frees":1},{"Size":1536,"Mallocs":0,"Frees":0},{"Size":1664,"Mallocs":8,"Frees":4},{"Size":2048,"Mallocs":70,"Frees":68},{"Size":2304,"Mallocs":20,"Frees":14},{"Size":2560,"Mallocs":2,"Frees":1},{"Size":2816,"Mallocs":1,"Frees":1},{"Size":3072,"Mallocs":0,"Frees":0},{"Size":3328,"Mallocs":4,"Frees":1},{"Size":4096,"Mallocs":1856,"Frees":1852},{"Size":4608,"Mallocs":19,"Frees":18},{"Size":5376,"Mallocs":7,"Frees":4},{"Size":6144,"Mallocs":22,"Frees":17},{"Size":6400,"Mallocs":0,"Frees":0},{"Size":6656,"Mallocs":1,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":0,"Frees":0},{"Size":8448,"Mallocs":0,"Frees":0},{"Size":8704,"Mallocs":2,"Frees":2},{"Size":9472,"Mallocs":4,"Frees":3},{"Size":10496,"Mallocs":1,"Frees":0},{"Size":12288,"Mallocs":1,"Frees":1},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14080,"Mallocs":0,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":16640,"Mallocs":0,"Frees":0},{"Size":17664,"Mallocs":0,"Frees":0}]}, +"goroutines": 10, +"counters": { "A": 123.12, "B": 245342 } +} diff --git a/expvars_test.go b/expvars_test.go index d039adf..4ef93c6 100644 --- a/expvars_test.go +++ b/expvars_test.go @@ -5,7 +5,10 @@ import ( "testing" ) -const expvarsTestFile = "./expvars.json" +const ( + expvarsTestFile = "./expvars.json" + expvarsAdvTestFile = "./expvars_advanced.json" +) func TestExpvars(t *testing.T) { file, err := os.Open(expvarsTestFile) @@ -23,3 +26,36 @@ func TestExpvars(t *testing.T) { t.Fatalf("Cmdline should have 3 items, but has %d", len(vars.Cmdline)) } } + +/* +func TestExpvarsAdvanced(t *testing.T) { + file, err := os.Open(expvarsAdvTestFile) + if err != nil { + t.Fatalf("cannot open test file %v", err) + } + defer file.Close() + + vars, err := ParseExpvar(file) + if err != nil { + t.Fatal(err) + } + + if len(vars.Vars) != 4 { + t.Fatalf("vars should have 2 items, but has %d", len(vars.Vars)) + } + + if int(vars.Vars["goroutines"].(float64)) != 10 { + t.Logf("Expecting 'goroutines' to be %d, but got %d", 10, vars.Vars["goroutines"]) + } + + counters := vars.Vars["counters"].(map[string]interface{}) + counterA := counters["A"].(float64) + counterB := counters["B"].(float64) + if counterA != 123.12 { + t.Logf("Expecting 'counter.A' to be %f, but got %f", 123.12, counterA) + } + if int(counterB) != 245342 { + t.Logf("Expecting 'counter.B' to be %d, but got %d", 245342, counterB) + } +} +*/ diff --git a/main.go b/main.go index c884fe2..f3b9e7b 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,6 @@ package main import ( "flag" "log" - "path/filepath" - "strings" "time" "github.com/gizak/termui" @@ -24,7 +22,7 @@ func main() { } data := *NewData() - var source Source = NewExpvarsSource(ports) + var source = NewExpvarsSource(ports) for _, port := range ports { service := NewService(port) data.Services = append(data.Services, service) @@ -41,31 +39,17 @@ func main() { evtCh := termui.EventCh() update := func() { - d, err := source.Update() - if err != nil { - log.Println("[ERROR] Cannot update data from source:", err) - return - } - switch source.(type) { - case *ExpvarsSource: - dat := d.(Expvars) - - for _, port := range source.(*ExpvarsSource).Ports { - service := data.FindService(port) - if service == nil { - continue - } - - service.Err = dat[port].Err - service.Memstats = dat[port].MemStats - service.Goroutines = dat[port].Goroutines - service.Cmdline = strings.Join(dat[port].Cmdline, " ") - if dat[port].Cmdline != nil { - service.Name = filepath.Base(dat[port].Cmdline[0]) - } + for _, port := range source.Ports { + service := data.FindService(port) + if service == nil { + continue } + + service.Update() } + data.LastTimestamp = time.Now() + ui.Update(data) } update() diff --git a/service.go b/service.go index dbe2ac1..a4c6110 100644 --- a/service.go +++ b/service.go @@ -1,33 +1,21 @@ package main import ( + "fmt" + "net/http" "runtime" - "time" + "strings" ) -// Data represents data to be passed to UI. -type Data struct { - Services Services - TotalMemory *Stack - LastTimestamp time.Time -} - -func NewData() *Data { - return &Data{ - TotalMemory: NewStack(140), - } -} - type Services []*Service // Service represents constantly updating info about single service. type Service struct { - Name string - Port string - IsAlive bool - Cmdline string - Memstats *runtime.MemStats - Goroutines int64 + Port string + Name string + + Cmdline string + Memstats *runtime.MemStats Err error } @@ -40,15 +28,35 @@ func NewService(port string) *Service { } } -func (d *Data) FindService(port string) *Service { - if d.Services == nil { - return nil - } - for _, service := range d.Services { - if service.Port == port { - return service +// Update updates Service info from Expvar variable. +func (s *Service) Update() { + expvar := &Expvar{} + resp, err := http.Get(s.Addr()) + defer resp.Body.Close() + if err != nil { + expvar.Err = err + } else if resp.StatusCode == http.StatusNotFound { + expvar.Err = fmt.Errorf("Vars not found. Did you import expvars?") + } else { + expvar, err = ParseExpvar(resp.Body) + if err != nil { + expvar = &Expvar{Err: err} } } - return nil + s.Err = expvar.Err + s.Memstats = expvar.MemStats + + // Update name and cmdline only if empty + if len(s.Cmdline) == 0 { + s.Cmdline = strings.Join(expvar.Cmdline, " ") + s.Name = BaseCommand(expvar.Cmdline) + } +} + +// Addr returns fully qualified host:port pair for service. +// +// If host is not specified, 'localhost' is used. +func (s Service) Addr() string { + return fmt.Sprintf("http://localhost:%s%s", s.Port, ExpvarsUrl) } diff --git a/ui_dummy.go b/ui_dummy.go index 0b117c3..56eca02 100644 --- a/ui_dummy.go +++ b/ui_dummy.go @@ -27,9 +27,11 @@ func (u *DummyUI) Update(data Data) { fmt.Printf("%s/%s ", alloc, sys) } - if service.Goroutines != 0 { - fmt.Printf("goroutines: %d", service.Goroutines) - } + /* + if service.Goroutines != 0 { + fmt.Printf("goroutines: %d", service.Goroutines) + } + */ fmt.Printf("\n") } } diff --git a/ui_termui.go b/ui_termui.go index bc084ef..e452124 100644 --- a/ui_termui.go +++ b/ui_termui.go @@ -78,11 +78,11 @@ func (t *TermUI) Update(data Data) { name := fmt.Sprintf("[R] %s", service.Name) meminfos := fmt.Sprintf("%s/%s", alloc, heap) - goroutine := fmt.Sprintf("%d", service.Goroutines) + //goroutine := fmt.Sprintf("%d", service.Goroutines) names.Items = append(names.Items, name) meminfo.Items = append(meminfo.Items, meminfos) - goroutines.Items = append(goroutines.Items, goroutine) + //goroutines.Items = append(goroutines.Items, goroutine) } data.TotalMemory.Push(int(totalAlloc / 1024))