diff --git a/data.go b/data.go index 368edb7..d3560f3 100644 --- a/data.go +++ b/data.go @@ -7,11 +7,21 @@ type UIData struct { Services []*Service Vars []VarName LastTimestamp time.Time + Stacks map[VarName]*Stack + Stats map[VarName]*Stat } // NewUIData inits and return new data object. func NewUIData(vars []VarName) *UIData { + stacks := make(map[VarName]*Stack) + stats := make(map[VarName]*Stat) + for _, v := range vars { + stacks[v] = NewStack() + stats[v] = NewStat() + } return &UIData{ - Vars: vars, + Vars: vars, + Stacks: stacks, + Stats: stats, } } diff --git a/stack.go b/stack.go index 527cf3d..1b471de 100644 --- a/stack.go +++ b/stack.go @@ -8,9 +8,8 @@ const DefaultSize = 1200 // Stack is a limited FIFO for holding sparkline values. type Stack struct { - Values []Var + Values []int Len int - Max Var } // NewStack inits new Stack with default size limit. @@ -21,88 +20,26 @@ func NewStack() *Stack { // NewStackWithSize inits new Stack with size limit. func NewStackWithSize(size int) *Stack { return &Stack{ - Values: make([]Var, size), + Values: make([]int, size), Len: size, } } -/* -TODO: FIXME: review or remove this code // Push inserts data to stack, preserving constant length. -func (s *Stack) Push(val Var) { +func (s *Stack) Push(v IntVar) { + val := v.Value() s.Values = append(s.Values, val) if len(s.Values) > s.Len { + // TODO: check if underlying array is growing constantly s.Values = s.Values[1:] } - - if s.Max == nil { - s.Max = val - return - } - - switch val.(type) { - case int64: - switch s.Max.(type) { - case int64: - if val.(int64) > s.Max.(int64) { - s.Max = val - } - case float64: - if float64(val.(int64)) > s.Max.(float64) { - s.Max = val - } - } - case float64: - switch s.Max.(type) { - case int64: - if val.(float64) > float64(s.Max.(int64)) { - s.Max = val - } - case float64: - if val.(float64) > s.Max.(float64) { - s.Max = val - } - } - } -} - -// Front returns front value. -func (s *Stack) Front() Var { - if len(s.Values) == 0 { - return nil - } - return s.Values[len(s.Values)-1] } // IntValues returns stack values explicitly casted to int. // // Main case is to use with termui.Sparklines. func (s *Stack) IntValues() []int { - ret := make([]int, s.Len) - for i, v := range s.Values { - n, ok := v.(int64) - if ok { - ret[i] = int(n) - continue - } - - f, ok := v.(float64) - if ok { - // 12.34 (float) -> 1234 (int) - ret[i] = int(f * 100) - continue - } - - b, ok := v.(bool) - if ok { - // false => 0, true = 1 - if b { - ret[i] = 1 - } else { - ret[i] = 0 - } - } - } - return ret + return s.Values } -*/ + +// TODO: implement trim and resize diff --git a/stat.go b/stat.go new file mode 100644 index 0000000..ce0d714 --- /dev/null +++ b/stat.go @@ -0,0 +1,27 @@ +package main + +// Stat holds basic statistics data for +// integer data used for sparklines. +type Stat struct { + max IntVar + // TODO: implement running median +} + +// NewStat inits new Stat object. +func NewStat() *Stat { + return &Stat{ + max: &Number{}, + } +} + +// Update updates stats on each push. +func (s *Stat) Update(v IntVar) { + if v.Value() > s.max.Value() { + s.max = v + } +} + +// Max returns maximum recorded value. +func (s *Stat) Max() IntVar { + return s.max +} diff --git a/ui_multi.go b/ui_multi.go index d92cb06..81b8230 100644 --- a/ui_multi.go +++ b/ui_multi.go @@ -68,7 +68,7 @@ func (t *TermUI) Init(data UIData) error { var sparklines []termui.Sparkline for _, service := range data.Services { spl := termui.NewSparkline() - spl.Height = 1 + spl.Height = 1 // TODO: set height to th/len(services) spl.LineColor = termui.ColorGreen spl.Title = service.Name sparklines = append(sparklines, spl) @@ -113,14 +113,28 @@ func (t *TermUI) Update(data UIData) { // Sparklines for i, service := range data.Services { - max := "MAX" // TODO: FIXME: formatMax(service.Max(data.Vars[0])) - t.Sparkline1.Lines[i].Title = fmt.Sprintf("%s%s", service.Name, max) - t.Sparkline1.Lines[i].Data = []int{} // TODO: service.Values(data.Vars[0]) + name := data.Vars[0] + v, ok := service.Vars[name].(IntVar) + if ok { + data.Stacks[name].Push(v) + data.Stats[name].Update(v) + max := data.Stats[name].Max().String() + t.Sparkline1.Lines[i].Title = fmt.Sprintf("%s (max: %s)", service.Name, max) + t.Sparkline1.Lines[i].Data = data.Stacks[name].IntValues() + } - if len(data.Vars) > 1 { - max = "MAX" // TODO: FIXME: formatMax(service.Max(data.Vars[1])) - t.Sparkline2.Lines[i].Title = fmt.Sprintf("%s%s", service.Name, max) - t.Sparkline2.Lines[i].Data = []int{} // TODO: service.Values(data.Vars[1]) + if len(data.Vars) == 1 { + continue + } + + name = data.Vars[1] + v, ok = service.Vars[name].(IntVar) + if ok { + data.Stacks[name].Push(v) + data.Stats[name].Update(v) + max := data.Stats[name].Max().String() + t.Sparkline2.Lines[i].Title = fmt.Sprintf("%s (max: %s)", service.Name, max) + t.Sparkline2.Lines[i].Data = data.Stacks[name].IntValues() } } diff --git a/ui_single.go b/ui_single.go index 6c37e3c..1eb8c93 100644 --- a/ui_single.go +++ b/ui_single.go @@ -92,17 +92,21 @@ func (t *TermUISingle) Update(data UIData) { // Sparklines for i, name := range data.Vars { + v, ok := service.Vars[name].(IntVar) + if !ok { + continue + } + data.Stacks[name].Push(v) + data.Stats[name].Update(v) + spl := &t.Sparkline.Lines[i] - max := "MAX" // FIXME: formatMax(service.Max(name)) - spl.Title = fmt.Sprintf("%s: %v%s", name.Long(), service.Value(name), max) + max := data.Stats[name].Max().String() + spl.Title = fmt.Sprintf("%s: %v (max: %v)", name.Long(), service.Value(name), max) spl.TitleColor = colorByKind(name.Kind()) spl.LineColor = colorByKind(name.Kind()) - if name.Kind() == KindString { - continue - } - spl.Data = []int{} // FIXME: service.Values(name) + spl.Data = data.Stacks[name].IntValues() } t.Relayout() diff --git a/var.go b/var.go index 02e3591..cf8c788 100644 --- a/var.go +++ b/var.go @@ -27,6 +27,13 @@ type Var interface { Set(*jason.Value) } +// IntVar represents variable which value can be represented as integer, +// and suitable for displaying with sparklines. +type IntVar interface { + Var + Value() int +} + type Number struct { // TODO: add mutex here or level above, in service? val float64 @@ -46,6 +53,11 @@ func (v *Number) Set(j *jason.Value) { } } +// Value implements IntVar for Number type. +func (v *Number) Value() int { + return int(v.val) +} + type Memory struct { bytes int64 } @@ -62,6 +74,12 @@ func (v *Memory) Set(j *jason.Value) { } } +// Value implements IntVar for Memory type. +func (v *Memory) Value() int { + // TODO: check for possible overflows + return int(v.bytes) +} + type Duration struct { dur time.Duration } @@ -81,6 +99,12 @@ func (v *Duration) Set(j *jason.Value) { } } +// Value implements IntVar for Duration type. +func (v *Duration) Value() int { + // TODO: check for possible overflows + return int(v.dur) +} + type String struct { str string }