diff --git a/component/asciibox/asciibox.go b/component/asciibox/asciibox.go index 4d30f17..c9c4b5c 100644 --- a/component/asciibox/asciibox.go +++ b/component/asciibox/asciibox.go @@ -3,6 +3,7 @@ package asciibox import ( fl "github.com/mbndr/figlet4go" "github.com/sqshq/sampler/asset" + "github.com/sqshq/sampler/config" "github.com/sqshq/sampler/data" ui "github.com/sqshq/termui" "image" @@ -10,6 +11,7 @@ import ( type AsciiBox struct { ui.Block + data.Consumer text string ascii string style ui.Style @@ -17,22 +19,15 @@ type AsciiBox struct { options *fl.RenderOptions } -type AsciiFont string - -const ( - AsciiFontFlat AsciiFont = "flat" - AsciiFont3D AsciiFont = "3d" -) - const asciiFontExtension = ".flf" -func NewAsciiBox(title string, font AsciiFont, color ui.Color) *AsciiBox { +func NewAsciiBox(c config.AsciiBoxConfig) *AsciiBox { block := *ui.NewBlock() - block.Title = title + block.Title = c.Title options := fl.NewRenderOptions() - options.FontName = string(font) + options.FontName = string(*c.Font) fontStr, err := asset.Asset(options.FontName + asciiFontExtension) if err != nil { @@ -41,17 +36,29 @@ func NewAsciiBox(title string, font AsciiFont, color ui.Color) *AsciiBox { render := fl.NewAsciiRender() _ = render.LoadBindataFont(fontStr, options.FontName) - return &AsciiBox{ - Block: block, - style: ui.NewStyle(color), - render: render, - options: options, + box := AsciiBox{ + Block: block, + Consumer: data.NewConsumer(), + style: ui.NewStyle(*c.Color), + render: render, + options: options, } + + go box.consume() + + return &box } -func (a *AsciiBox) ConsumeSample(sample data.Sample) { - a.text = sample.Value - a.ascii, _ = a.render.RenderOpts(sample.Value, a.options) +func (a *AsciiBox) consume() { + for { + select { + case sample := <-a.SampleChannel: + a.text = sample.Value + a.ascii, _ = a.render.RenderOpts(sample.Value, a.options) + //case alert := <-a.alertChannel: + // TODO base alerting mechanism + } + } } func (a *AsciiBox) Draw(buffer *ui.Buffer) { diff --git a/component/barchart/barchart.go b/component/barchart/barchart.go index 4a7d1a8..c744dff 100644 --- a/component/barchart/barchart.go +++ b/component/barchart/barchart.go @@ -17,6 +17,7 @@ const ( type BarChart struct { ui.Block + data.Consumer bars []Bar scale int maxValue float64 @@ -33,19 +34,31 @@ type Bar struct { func NewBarChart(title string, scale int) *BarChart { block := *ui.NewBlock() block.Title = title - return &BarChart{ + chart := BarChart{ Block: block, + Consumer: data.NewConsumer(), bars: []Bar{}, scale: scale, maxValue: -math.MaxFloat64, } + + go chart.consume() + + return &chart } -func (b *BarChart) AddBar(label string, color ui.Color) { - b.bars = append(b.bars, Bar{label: label, color: color, value: 0}) +func (b *BarChart) consume() { + for { + select { + case sample := <-b.SampleChannel: + b.consumeSample(sample) + //case alert := <-b.alertChannel: + // TODO base alerting mechanism + } + } } -func (b *BarChart) ConsumeSample(sample data.Sample) { +func (b *BarChart) consumeSample(sample data.Sample) { b.count++ @@ -76,6 +89,10 @@ func (b *BarChart) ConsumeSample(sample data.Sample) { } } +func (b *BarChart) AddBar(label string, color ui.Color) { + b.bars = append(b.bars, Bar{label: label, color: color, value: 0}) +} + func (b *BarChart) reselectMaxValue() { maxValue := -math.MaxFloat64 for _, bar := range b.bars { diff --git a/component/gauge/gauge.go b/component/gauge/gauge.go index b7d8c19..0e1cb15 100644 --- a/component/gauge/gauge.go +++ b/component/gauge/gauge.go @@ -18,6 +18,7 @@ const ( type Gauge struct { ui.Block + data.Consumer minValue float64 maxValue float64 curValue float64 @@ -26,12 +27,30 @@ type Gauge struct { } func NewGauge(title string, scale int, color ui.Color) *Gauge { + block := *ui.NewBlock() block.Title = title - return &Gauge{ - Block: block, - scale: scale, - color: color, + + gauge := Gauge{ + Block: block, + Consumer: data.NewConsumer(), + scale: scale, + color: color, + } + + go gauge.consume() + + return &gauge +} + +func (g *Gauge) consume() { + for { + select { + case sample := <-g.SampleChannel: + g.ConsumeSample(sample) + //case alert := <-g.alertChannel: + // TODO base alerting mechanism + } } } diff --git a/component/geometry.go b/component/geometry.go index 39b8a10..9bc7699 100644 --- a/component/geometry.go +++ b/component/geometry.go @@ -5,35 +5,35 @@ import ( "math" ) -func getRectLeftAgeCenter(rect image.Rectangle) image.Point { +func GetRectLeftAgeCenter(rect image.Rectangle) image.Point { return image.Point{ X: rect.Min.X, Y: rect.Min.Y + rect.Dy()/2, } } -func getRectRightAgeCenter(rect image.Rectangle) image.Point { +func GetRectRightAgeCenter(rect image.Rectangle) image.Point { return image.Point{ X: rect.Max.X, Y: rect.Min.Y + rect.Dy()/2, } } -func getRectTopAgeCenter(rect image.Rectangle) image.Point { +func GetRectTopAgeCenter(rect image.Rectangle) image.Point { return image.Point{ X: rect.Min.X + rect.Dx()/2, Y: rect.Min.Y, } } -func getRectBottomAgeCenter(rect image.Rectangle) image.Point { +func GetRectBottomAgeCenter(rect image.Rectangle) image.Point { return image.Point{ X: rect.Min.X + rect.Dx()/2, Y: rect.Max.Y, } } -func getDistance(p1 image.Point, p2 image.Point) float64 { +func GetDistance(p1 image.Point, p2 image.Point) float64 { x := math.Abs(float64(p1.X - p2.X)) y := math.Abs(float64(p1.Y - p2.Y)) return math.Sqrt(x*x + y*y) diff --git a/component/layout.go b/component/layout/layout.go similarity index 59% rename from component/layout.go rename to component/layout/layout.go index 0efa576..535e3bd 100644 --- a/component/layout.go +++ b/component/layout/layout.go @@ -1,6 +1,7 @@ -package component +package layout import ( + "github.com/sqshq/sampler/component" "github.com/sqshq/sampler/component/runchart" "github.com/sqshq/sampler/config" "github.com/sqshq/sampler/console" @@ -11,10 +12,10 @@ import ( type Layout struct { ui.Block - Components []Component + Components []component.Component ChangeModeEvents chan Mode - statusbar *StatusBar - menu *Menu + statusbar *component.StatusBar + menu *component.Menu mode Mode selection int } @@ -38,7 +39,7 @@ const ( statusbarHeight = 1 ) -func NewLayout(width, height int, statusline *StatusBar, menu *Menu) *Layout { +func NewLayout(width, height int, statusline *component.StatusBar, menu *component.Menu) *Layout { block := *ui.NewBlock() block.SetRect(0, 0, width, height) @@ -46,7 +47,7 @@ func NewLayout(width, height int, statusline *StatusBar, menu *Menu) *Layout { return &Layout{ Block: block, - Components: make([]Component, 0), + Components: make([]component.Component, 0), statusbar: statusline, menu: menu, mode: ModeDefault, @@ -56,16 +57,23 @@ func NewLayout(width, height int, statusline *StatusBar, menu *Menu) *Layout { } func (l *Layout) AddComponent(Type config.ComponentType, drawable ui.Drawable, title string, position config.Position, size config.Size, refreshRateMs int) { - l.Components = append(l.Components, Component{Type, drawable, title, position, size, refreshRateMs}) + l.Components = append(l.Components, component.Component{ + Type: Type, + Drawable: drawable, + Title: title, + Position: position, + Size: size, + RefreshRateMs: refreshRateMs, + }) } -func (l *Layout) GetComponents(Type config.ComponentType) []ui.Drawable { +func (l *Layout) GetComponents(Type config.ComponentType) []component.Component { - var components []ui.Drawable + var components []component.Component - for _, component := range l.Components { - if component.Type == Type { - components = append(components, component.Drawable) + for _, c := range l.Components { + if c.Type == Type { + components = append(components, c) } } @@ -87,36 +95,36 @@ func (l *Layout) HandleConsoleEvent(e string) { chart := l.getSelectedComponent().Drawable.(*runchart.RunChart) chart.DisableSelection() } - l.menu.idle() + l.menu.Idle() l.changeMode(ModePause) } case console.KeyEnter: switch l.mode { case ModeComponentSelect: - l.menu.choose() + l.menu.Choose() l.changeMode(ModeMenuOptionSelect) case ModeMenuOptionSelect: - option := l.menu.getSelectedOption() + option := l.menu.GetSelectedOption() switch option { - case MenuOptionMove: + case component.MenuOptionMove: l.changeMode(ModeComponentMove) - l.menu.moveOrResize() - case MenuOptionResize: + l.menu.MoveOrResize() + case component.MenuOptionResize: l.changeMode(ModeComponentResize) - l.menu.moveOrResize() - case MenuOptionPinpoint: + l.menu.MoveOrResize() + case component.MenuOptionPinpoint: l.changeMode(ModeChartPinpoint) - l.menu.idle() + l.menu.Idle() chart := l.getSelectedComponent().Drawable.(*runchart.RunChart) chart.MoveSelection(0) - case MenuOptionResume: + case component.MenuOptionResume: l.changeMode(ModeDefault) - l.menu.idle() + l.menu.Idle() } case ModeComponentMove: fallthrough case ModeComponentResize: - l.menu.idle() + l.menu.Idle() l.changeMode(ModeDefault) } case console.KeyEsc: @@ -128,20 +136,20 @@ func (l *Layout) HandleConsoleEvent(e string) { case ModeComponentSelect: fallthrough case ModeMenuOptionSelect: - l.menu.idle() + l.menu.Idle() l.changeMode(ModeDefault) } case console.KeyLeft: switch l.mode { case ModeDefault: l.changeMode(ModeComponentSelect) - l.menu.highlight(l.getComponent(l.selection)) + l.menu.Highlight(l.getComponent(l.selection)) case ModeChartPinpoint: chart := l.getSelectedComponent().Drawable.(*runchart.RunChart) chart.MoveSelection(-1) case ModeComponentSelect: l.moveSelection(e) - l.menu.highlight(l.getComponent(l.selection)) + l.menu.Highlight(l.getComponent(l.selection)) case ModeComponentMove: l.getSelectedComponent().Move(-1, 0) case ModeComponentResize: @@ -151,13 +159,13 @@ func (l *Layout) HandleConsoleEvent(e string) { switch l.mode { case ModeDefault: l.changeMode(ModeComponentSelect) - l.menu.highlight(l.getComponent(l.selection)) + l.menu.Highlight(l.getComponent(l.selection)) case ModeChartPinpoint: chart := l.getSelectedComponent().Drawable.(*runchart.RunChart) chart.MoveSelection(1) case ModeComponentSelect: l.moveSelection(e) - l.menu.highlight(l.getComponent(l.selection)) + l.menu.Highlight(l.getComponent(l.selection)) case ModeComponentMove: l.getSelectedComponent().Move(1, 0) case ModeComponentResize: @@ -167,12 +175,12 @@ func (l *Layout) HandleConsoleEvent(e string) { switch l.mode { case ModeDefault: l.changeMode(ModeComponentSelect) - l.menu.highlight(l.getComponent(l.selection)) + l.menu.Highlight(l.getComponent(l.selection)) case ModeComponentSelect: l.moveSelection(e) - l.menu.highlight(l.getComponent(l.selection)) + l.menu.Highlight(l.getComponent(l.selection)) case ModeMenuOptionSelect: - l.menu.up() + l.menu.Up() case ModeComponentMove: l.getSelectedComponent().Move(0, -1) case ModeComponentResize: @@ -182,12 +190,12 @@ func (l *Layout) HandleConsoleEvent(e string) { switch l.mode { case ModeDefault: l.changeMode(ModeComponentSelect) - l.menu.highlight(l.getComponent(l.selection)) + l.menu.Highlight(l.getComponent(l.selection)) case ModeComponentSelect: l.moveSelection(e) - l.menu.highlight(l.getComponent(l.selection)) + l.menu.Highlight(l.getComponent(l.selection)) case ModeMenuOptionSelect: - l.menu.down() + l.menu.Down() case ModeComponentMove: l.getSelectedComponent().Move(0, 1) case ModeComponentResize: @@ -200,11 +208,11 @@ func (l *Layout) ChangeDimensions(width, height int) { l.SetRect(0, 0, width, height) } -func (l *Layout) getComponent(i int) Component { - return l.Components[i] +func (l *Layout) getComponent(i int) *component.Component { + return &l.Components[i] } -func (l *Layout) getSelectedComponent() *Component { +func (l *Layout) getSelectedComponent() *component.Component { return &l.Components[l.selection] } @@ -229,24 +237,25 @@ func (l *Layout) moveSelection(direction string) { switch direction { case console.KeyLeft: - previouslySelectedCornerPoint = getRectLeftAgeCenter(previouslySelected.Drawable.GetRect()) - newlySelectedCornerPoint = getRectRightAgeCenter(l.getComponent(newlySelectedIndex).Drawable.GetRect()) - currentCornerPoint = getRectRightAgeCenter(current.Drawable.GetRect()) + previouslySelectedCornerPoint = component.GetRectLeftAgeCenter(previouslySelected.Drawable.GetRect()) + newlySelectedCornerPoint = component.GetRectRightAgeCenter(l.getComponent(newlySelectedIndex).Drawable.GetRect()) + currentCornerPoint = component.GetRectRightAgeCenter(current.Drawable.GetRect()) case console.KeyRight: - previouslySelectedCornerPoint = getRectRightAgeCenter(previouslySelected.Drawable.GetRect()) - newlySelectedCornerPoint = getRectLeftAgeCenter(l.getComponent(newlySelectedIndex).Drawable.GetRect()) - currentCornerPoint = getRectLeftAgeCenter(current.Drawable.GetRect()) + previouslySelectedCornerPoint = component.GetRectRightAgeCenter(previouslySelected.Drawable.GetRect()) + newlySelectedCornerPoint = component.GetRectLeftAgeCenter(l.getComponent(newlySelectedIndex).Drawable.GetRect()) + currentCornerPoint = component.GetRectLeftAgeCenter(current.Drawable.GetRect()) case console.KeyUp: - previouslySelectedCornerPoint = getRectTopAgeCenter(previouslySelected.Drawable.GetRect()) - newlySelectedCornerPoint = getRectBottomAgeCenter(l.getComponent(newlySelectedIndex).Drawable.GetRect()) - currentCornerPoint = getRectBottomAgeCenter(current.Drawable.GetRect()) + previouslySelectedCornerPoint = component.GetRectTopAgeCenter(previouslySelected.Drawable.GetRect()) + newlySelectedCornerPoint = component.GetRectBottomAgeCenter(l.getComponent(newlySelectedIndex).Drawable.GetRect()) + currentCornerPoint = component.GetRectBottomAgeCenter(current.Drawable.GetRect()) case console.KeyDown: - previouslySelectedCornerPoint = getRectBottomAgeCenter(previouslySelected.Drawable.GetRect()) - newlySelectedCornerPoint = getRectTopAgeCenter(l.getComponent(newlySelectedIndex).Drawable.GetRect()) - currentCornerPoint = getRectTopAgeCenter(current.Drawable.GetRect()) + previouslySelectedCornerPoint = component.GetRectBottomAgeCenter(previouslySelected.Drawable.GetRect()) + newlySelectedCornerPoint = component.GetRectTopAgeCenter(l.getComponent(newlySelectedIndex).Drawable.GetRect()) + currentCornerPoint = component.GetRectTopAgeCenter(current.Drawable.GetRect()) } - if getDistance(previouslySelectedCornerPoint, currentCornerPoint) < getDistance(previouslySelectedCornerPoint, newlySelectedCornerPoint) { + if component.GetDistance(previouslySelectedCornerPoint, currentCornerPoint) < + component.GetDistance(previouslySelectedCornerPoint, newlySelectedCornerPoint) { newlySelectedIndex = i } } @@ -259,12 +268,12 @@ func (l *Layout) Draw(buffer *ui.Buffer) { columnWidth := float64(l.GetRect().Dx()) / float64(columnsCount) rowHeight := float64(l.GetRect().Dy()-statusbarHeight) / float64(rowsCount) - for _, component := range l.Components { + for _, c := range l.Components { - x1 := math.Floor(float64(component.Position.X) * columnWidth) - y1 := math.Floor(float64(component.Position.Y) * rowHeight) - x2 := x1 + math.Floor(float64(component.Size.X))*columnWidth - y2 := y1 + math.Floor(float64(component.Size.Y))*rowHeight + x1 := math.Floor(float64(c.Position.X) * columnWidth) + y1 := math.Floor(float64(c.Position.Y) * rowHeight) + x2 := x1 + math.Floor(float64(c.Size.X))*columnWidth + y2 := y1 + math.Floor(float64(c.Size.Y))*rowHeight if x2-x1 < minDimension { x2 = x1 + minDimension @@ -274,8 +283,8 @@ func (l *Layout) Draw(buffer *ui.Buffer) { y2 = y1 + minDimension } - component.Drawable.SetRect(int(x1), int(y1), int(x2), int(y2)) - component.Drawable.Draw(buffer) + c.Drawable.SetRect(int(x1), int(y1), int(x2), int(y2)) + c.Drawable.Draw(buffer) } l.statusbar.SetRect( diff --git a/component/menu.go b/component/menu.go index 4148d30..6c26403 100644 --- a/component/menu.go +++ b/component/menu.go @@ -48,26 +48,26 @@ func NewMenu() *Menu { } } -func (m *Menu) getSelectedOption() MenuOption { +func (m *Menu) GetSelectedOption() MenuOption { return m.option } -func (m *Menu) highlight(component Component) { - m.component = component +func (m *Menu) Highlight(component *Component) { + m.component = *component m.updateDimensions() m.mode = MenuModeHighlight m.Title = component.Title } -func (m *Menu) choose() { +func (m *Menu) Choose() { m.mode = MenuModeOptionSelect } -func (m *Menu) idle() { +func (m *Menu) Idle() { m.mode = MenuModeIdle } -func (m *Menu) up() { +func (m *Menu) Up() { for i := 1; i < len(m.options); i++ { if m.options[i] == m.option { m.option = m.options[i-1] @@ -75,11 +75,11 @@ func (m *Menu) up() { } } if m.option == MenuOptionPinpoint && m.component.Type != config.TypeRunChart { - m.up() + m.Up() } } -func (m *Menu) down() { +func (m *Menu) Down() { for i := 0; i < len(m.options)-1; i++ { if m.options[i] == m.option { m.option = m.options[i+1] @@ -87,11 +87,11 @@ func (m *Menu) down() { } } if m.option == MenuOptionPinpoint && m.component.Type != config.TypeRunChart { - m.down() + m.Down() } } -func (m *Menu) moveOrResize() { +func (m *Menu) MoveOrResize() { m.mode = MenuModeMoveAndResize } diff --git a/component/runchart/grid.go b/component/runchart/grid.go index faa835c..90bd744 100644 --- a/component/runchart/grid.go +++ b/component/runchart/grid.go @@ -35,13 +35,13 @@ func (c *RunChart) newChartGrid() ChartGrid { func (c *RunChart) renderAxes(buffer *ui.Buffer) { // draw origin cell buffer.SetCell( - ui.NewCell(ui.BOTTOM_LEFT, ui.NewStyle(ui.ColorWhite)), + ui.NewCell(ui.BOTTOM_LEFT, ui.NewStyle(console.ColorWhite)), image.Pt(c.Inner.Min.X+c.grid.minTimeWidth, c.Inner.Max.Y-xAxisLabelsHeight-1)) // draw x axis line for i := c.grid.minTimeWidth + 1; i < c.Inner.Dx(); i++ { buffer.SetCell( - ui.NewCell(ui.HORIZONTAL_DASH, ui.NewStyle(ui.ColorWhite)), + ui.NewCell(ui.HORIZONTAL_DASH, ui.NewStyle(console.ColorWhite)), image.Pt(i+c.Inner.Min.X, c.Inner.Max.Y-xAxisLabelsHeight-1)) } @@ -57,7 +57,7 @@ func (c *RunChart) renderAxes(buffer *ui.Buffer) { // draw y axis line for i := 0; i < c.Inner.Dy()-xAxisLabelsHeight-1; i++ { buffer.SetCell( - ui.NewCell(ui.VERTICAL_DASH, ui.NewStyle(ui.ColorWhite)), + ui.NewCell(ui.VERTICAL_DASH, ui.NewStyle(console.ColorWhite)), image.Pt(c.Inner.Min.X+c.grid.minTimeWidth, i+c.Inner.Min.Y)) } @@ -66,7 +66,7 @@ func (c *RunChart) renderAxes(buffer *ui.Buffer) { labelTime := c.grid.timeRange.max.Add(time.Duration(-i) * c.timescale) buffer.SetString( labelTime.Format("15:04:05"), - ui.NewStyle(ui.ColorWhite), + ui.NewStyle(console.ColorWhite), image.Pt(c.grid.maxTimeWidth-xAxisLabelsWidth/2-i*(xAxisGridWidth), c.Inner.Max.Y-1)) } @@ -78,13 +78,13 @@ func (c *RunChart) renderAxes(buffer *ui.Buffer) { value := c.grid.valueExtrema.max - (valuePerY * float64(i) * (yAxisLabelsIndent + yAxisLabelsHeight)) buffer.SetString( formatValue(value, c.scale), - ui.NewStyle(ui.ColorWhite), + ui.NewStyle(console.ColorWhite), image.Pt(c.Inner.Min.X, 1+c.Inner.Min.Y+i*(yAxisLabelsIndent+yAxisLabelsHeight))) } } else { buffer.SetString( formatValue(c.grid.valueExtrema.max, c.scale), - ui.NewStyle(ui.ColorWhite), + ui.NewStyle(console.ColorWhite), image.Pt(c.Inner.Min.X, c.Inner.Min.Y+c.Inner.Dy()/2)) } } diff --git a/component/runchart/legend.go b/component/runchart/legend.go index 9b3a39a..9e7dfd7 100644 --- a/component/runchart/legend.go +++ b/component/runchart/legend.go @@ -2,6 +2,7 @@ package runchart import ( "fmt" + "github.com/sqshq/sampler/console" ui "github.com/sqshq/termui" "image" "math" @@ -54,7 +55,7 @@ func (c *RunChart) renderLegend(buffer *ui.Buffer, rectangle image.Rectangle) { y := c.Inner.Min.Y + yAxisLegendIndent + row*height titleStyle := ui.NewStyle(line.color) - detailsStyle := ui.NewStyle(ui.ColorWhite) + detailsStyle := ui.NewStyle(console.ColorWhite) buffer.SetString(string(ui.DOT), titleStyle, image.Pt(x-2, y)) buffer.SetString(line.label, titleStyle, image.Pt(x, y)) diff --git a/component/runchart/runchart.go b/component/runchart/runchart.go index 1bdcfcb..b6fc0c8 100644 --- a/component/runchart/runchart.go +++ b/component/runchart/runchart.go @@ -2,9 +2,10 @@ package runchart import ( "fmt" - "github.com/sqshq/sampler/component/trigger" + "github.com/sqshq/sampler/config" "github.com/sqshq/sampler/console" "github.com/sqshq/sampler/data" + "github.com/sqshq/sampler/trigger" "image" "math" "strconv" @@ -37,6 +38,7 @@ const ( type RunChart struct { ui.Block + data.Consumer triggers []trigger.Trigger lines []TimeLine grid ChartGrid @@ -73,17 +75,35 @@ type ValueExtrema struct { min float64 } -func NewRunChart(title string, scale int, refreshRateMs int, legend Legend) *RunChart { +func NewRunChart(c config.RunChartConfig, l Legend) *RunChart { + block := *ui.NewBlock() - block.Title = title - return &RunChart{ + block.Title = c.Title + + chart := RunChart{ Block: block, + Consumer: data.NewConsumer(), lines: []TimeLine{}, - timescale: calculateTimescale(refreshRateMs), + timescale: calculateTimescale(*c.RefreshRateMs), mutex: &sync.Mutex{}, - scale: scale, + scale: *c.Scale, mode: ModeDefault, - legend: legend, + legend: l, + } + + go chart.consume() + + return &chart +} + +func (c *RunChart) consume() { + for { + select { + case sample := <-c.SampleChannel: + c.consumeSample(sample) + //case alert := <-c.alertChannel: + // TODO base alerting mechanism + } } } @@ -123,12 +143,12 @@ func (c *RunChart) AddLine(Label string, color ui.Color) { c.lines = append(c.lines, line) } -func (c *RunChart) ConsumeSample(sample data.Sample) { +func (c *RunChart) consumeSample(sample data.Sample) { float, err := strconv.ParseFloat(sample.Value, 64) if err != nil { - // TODO visual notification + check sample.Error + // TODO visual notification } c.mutex.Lock() diff --git a/component/statusbar.go b/component/statusbar.go index 10a38fd..d99b6f8 100644 --- a/component/statusbar.go +++ b/component/statusbar.go @@ -27,7 +27,7 @@ func NewStatusLine(configFileName string) *StatusBar { "(Q) quit", "(P) pause", "(<->) selection", - "(ESC) reset alarm", + "(ESC) reset alerts", }, } } diff --git a/config/component.go b/config/component.go index e6c236a..de99352 100644 --- a/config/component.go +++ b/config/component.go @@ -1,11 +1,20 @@ package config import ( - "github.com/sqshq/sampler/component/asciibox" - "github.com/sqshq/sampler/data" + "github.com/sqshq/sampler/console" ui "github.com/sqshq/termui" ) +type ComponentType rune + +const ( + TypeRunChart ComponentType = 0 + TypeBarChart ComponentType = 1 + TypeTextBox ComponentType = 2 + TypeAsciiBox ComponentType = 3 + TypeGauge ComponentType = 4 +) + type ComponentConfig struct { Title string `yaml:"title"` RefreshRateMs *int `yaml:"refresh-rate-ms,omitempty"` @@ -32,26 +41,26 @@ type GaugeConfig struct { Scale *int `yaml:"scale,omitempty"` Color *ui.Color `yaml:"color,omitempty"` Values map[string]string `yaml:"values"` - Items []data.Item `yaml:",omitempty"` + Items []Item `yaml:",omitempty"` } type BarChartConfig struct { ComponentConfig `yaml:",inline"` - Scale *int `yaml:"scale,omitempty"` - Items []data.Item `yaml:"items"` + Scale *int `yaml:"scale,omitempty"` + Items []Item `yaml:"items"` } type AsciiBoxConfig struct { ComponentConfig `yaml:",inline"` - data.Item `yaml:",inline"` - Font *asciibox.AsciiFont `yaml:"font,omitempty"` + Item `yaml:",inline"` + Font *console.AsciiFont `yaml:"font,omitempty"` } type RunChartConfig struct { ComponentConfig `yaml:",inline"` Legend *LegendConfig `yaml:"legend,omitempty"` Scale *int `yaml:"scale,omitempty"` - Items []data.Item `yaml:"items"` + Items []Item `yaml:"items"` } type LegendConfig struct { @@ -69,15 +78,11 @@ type Size struct { Y int `yaml:"h"` } -type ComponentType rune - -const ( - TypeRunChart ComponentType = 0 - TypeBarChart ComponentType = 1 - TypeTextBox ComponentType = 2 - TypeAsciiBox ComponentType = 3 - TypeGauge ComponentType = 4 -) +type Item struct { + Label *string `yaml:"label,omitempty"` + Script string `yaml:"value"` + Color *ui.Color `yaml:"color,omitempty"` +} type ComponentSettings struct { Type ComponentType diff --git a/config/default.go b/config/default.go index e80c9d6..fb8cbe5 100644 --- a/config/default.go +++ b/config/default.go @@ -1,9 +1,7 @@ package config import ( - "github.com/sqshq/sampler/component/asciibox" "github.com/sqshq/sampler/console" - "github.com/sqshq/sampler/data" ) const ( @@ -71,10 +69,10 @@ func (c *Config) setDefaultValues() { p := defaultScale g.Scale = &p } - var items []data.Item + var items []Item for label, script := range g.Values { l := label - items = append(items, data.Item{Label: &l, Script: script}) + items = append(items, Item{Label: &l, Script: script}) } g.Items = items c.Gauges[i] = g @@ -93,7 +91,7 @@ func (c *Config) setDefaultValues() { box.Label = &label } if box.Font == nil { - font := asciibox.AsciiFontFlat + font := console.AsciiFontFlat box.Font = &font } if box.Color == nil { diff --git a/console/console.go b/console/console.go index ceaccf2..a219179 100644 --- a/console/console.go +++ b/console/console.go @@ -14,6 +14,13 @@ const ( AppVersion = "0.1.0" ) +type AsciiFont string + +const ( + AsciiFontFlat AsciiFont = "flat" + AsciiFont3D AsciiFont = "3d" +) + type Console struct{} func (self *Console) Init() { diff --git a/console/palette.go b/console/palette.go index 026d459..e7821a6 100644 --- a/console/palette.go +++ b/console/palette.go @@ -20,7 +20,7 @@ const ( ColorOrange ui.Color = 166 ColorPurple ui.Color = 129 ColorGreen ui.Color = 64 - ColorDarkGrey ui.Color = 240 + ColorDarkGrey ui.Color = 235 ColorGrey ui.Color = 242 ColorWhite ui.Color = 15 ColorBlack ui.Color = 0 @@ -28,7 +28,7 @@ const ( ) const ( - MenuColorBackground ui.Color = 234 + MenuColorBackground ui.Color = 235 MenuColorText ui.Color = 255 ) diff --git a/data/consumer.go b/data/consumer.go index 92d42fe..f25dcef 100644 --- a/data/consumer.go +++ b/data/consumer.go @@ -1,11 +1,22 @@ package data -type Consumer interface { - ConsumeSample(sample Sample) +type Consumer struct { + SampleChannel chan Sample + AlertChannel chan Alert } type Sample struct { Label string Value string - Error error +} + +type Alert struct { + Text string +} + +func NewConsumer() Consumer { + return Consumer{ + SampleChannel: make(chan Sample), + AlertChannel: make(chan Alert), + } } diff --git a/data/item.go b/data/item.go index beede7c..86aaa8b 100644 --- a/data/item.go +++ b/data/item.go @@ -7,9 +7,9 @@ import ( ) type Item struct { - Label *string `yaml:"label,omitempty"` - Script string `yaml:"value"` - Color *ui.Color `yaml:"color,omitempty"` + Label string + Script string + Color ui.Color } func (i *Item) nextValue() (value string, err error) { diff --git a/data/sampler.go b/data/sampler.go index 3164c3b..21dfedd 100644 --- a/data/sampler.go +++ b/data/sampler.go @@ -1,18 +1,20 @@ package data import ( + "github.com/sqshq/sampler/trigger" "time" ) type Sampler struct { consumer Consumer item Item + triggers []trigger.Trigger } -func NewSampler(consumer Consumer, item Item, rateMs int) Sampler { +func NewSampler(consumer Consumer, item Item, triggers []trigger.Trigger, rateMs int) Sampler { ticker := time.NewTicker(time.Duration(rateMs * int(time.Millisecond))) - sampler := Sampler{consumer, item} + sampler := Sampler{consumer, item, triggers} go func() { sampler.sample() @@ -26,6 +28,10 @@ func NewSampler(consumer Consumer, item Item, rateMs int) Sampler { func (s *Sampler) sample() { value, err := s.item.nextValue() - sample := Sample{Value: value, Error: err, Label: *s.item.Label} - s.consumer.ConsumeSample(sample) + if err == nil { + sample := Sample{Value: value, Label: s.item.Label} + s.consumer.SampleChannel <- sample + } else { + s.consumer.AlertChannel <- Alert{Text: err.Error()} + } } diff --git a/event/handler.go b/event/handler.go index dbded8e..0ca02eb 100644 --- a/event/handler.go +++ b/event/handler.go @@ -1,7 +1,7 @@ package event import ( - "github.com/sqshq/sampler/component" + "github.com/sqshq/sampler/component/layout" "github.com/sqshq/sampler/config" "github.com/sqshq/sampler/console" ui "github.com/sqshq/termui" @@ -13,13 +13,13 @@ const ( ) type Handler struct { - layout *component.Layout + layout *layout.Layout renderTicker *time.Ticker consoleEvents <-chan ui.Event renderRate time.Duration } -func NewHandler(layout *component.Layout) Handler { +func NewHandler(layout *layout.Layout) Handler { renderRate := calcMinRenderRate(layout) return Handler{ layout: layout, @@ -55,16 +55,16 @@ func (h *Handler) HandleEvents() { } } -func (h *Handler) handleModeChange(m component.Mode) { +func (h *Handler) handleModeChange(m layout.Mode) { // render the change before switching the tickers ui.Render(h.layout) h.renderTicker.Stop() switch m { - case component.ModeDefault: + case layout.ModeDefault: h.renderTicker = time.NewTicker(h.renderRate) - case component.ModePause: + case layout.ModePause: // proceed with stopped timer default: h.renderTicker = time.NewTicker(console.MinRenderInterval) @@ -80,7 +80,7 @@ func (h *Handler) handleExit() { config.Update(settings) } -func calcMinRenderRate(layout *component.Layout) time.Duration { +func calcMinRenderRate(layout *layout.Layout) time.Duration { minRefreshRateMs := layout.Components[0].RefreshRateMs for _, c := range layout.Components { diff --git a/main.go b/main.go index 5768d12..be804b4 100644 --- a/main.go +++ b/main.go @@ -5,11 +5,13 @@ import ( "github.com/sqshq/sampler/component/asciibox" "github.com/sqshq/sampler/component/barchart" "github.com/sqshq/sampler/component/gauge" + "github.com/sqshq/sampler/component/layout" "github.com/sqshq/sampler/component/runchart" "github.com/sqshq/sampler/config" "github.com/sqshq/sampler/console" "github.com/sqshq/sampler/data" "github.com/sqshq/sampler/event" + "github.com/sqshq/sampler/trigger" ui "github.com/sqshq/termui" ) @@ -21,47 +23,55 @@ func main() { defer csl.Close() width, height := ui.TerminalDimensions() - layout := component.NewLayout(width, height, component.NewStatusLine(flg.ConfigFileName), component.NewMenu()) + lout := layout.NewLayout(width, height, component.NewStatusLine(flg.ConfigFileName), component.NewMenu()) for _, c := range cfg.RunCharts { legend := runchart.Legend{Enabled: c.Legend.Enabled, Details: c.Legend.Details} - chart := runchart.NewRunChart(c.Title, *c.Scale, *c.RefreshRateMs, legend) - layout.AddComponent(config.TypeRunChart, chart, c.Title, c.Position, c.Size, *c.RefreshRateMs) + chart := runchart.NewRunChart(c, legend) + lout.AddComponent(config.TypeRunChart, chart, c.Title, c.Position, c.Size, *c.RefreshRateMs) + triggers := trigger.NewTriggers(c.Triggers) - for _, item := range c.Items { - chart.AddLine(*item.Label, *item.Color) - data.NewSampler(chart, item, *c.RefreshRateMs) + for _, i := range c.Items { + item := data.Item{Label: *i.Label, Script: i.Script, Color: *i.Color} + chart.AddLine(item.Label, item.Color) + data.NewSampler(chart.Consumer, item, triggers, *c.RefreshRateMs) } } for _, a := range cfg.AsciiBoxes { - box := asciibox.NewAsciiBox(a.Title, *a.Font, *a.Item.Color) - layout.AddComponent(config.TypeAsciiBox, box, a.Title, a.Position, a.Size, *a.RefreshRateMs) - data.NewSampler(box, a.Item, *a.RefreshRateMs) + box := asciibox.NewAsciiBox(a) + item := data.Item{Label: *a.Label, Script: a.Script, Color: *a.Color} + triggers := trigger.NewTriggers(a.Triggers) + lout.AddComponent(config.TypeAsciiBox, box, a.Title, a.Position, a.Size, *a.RefreshRateMs) + data.NewSampler(box.Consumer, item, triggers, *a.RefreshRateMs) } for _, b := range cfg.BarCharts { chart := barchart.NewBarChart(b.Title, *b.Scale) - layout.AddComponent(config.TypeBarChart, chart, b.Title, b.Position, b.Size, *b.RefreshRateMs) + triggers := trigger.NewTriggers(b.Triggers) + lout.AddComponent(config.TypeBarChart, chart, b.Title, b.Position, b.Size, *b.RefreshRateMs) - for _, item := range b.Items { - chart.AddBar(*item.Label, *item.Color) - data.NewSampler(chart, item, *b.RefreshRateMs) + for _, i := range b.Items { + item := data.Item{Label: *i.Label, Script: i.Script, Color: *i.Color} + chart.AddBar(*i.Label, *i.Color) + data.NewSampler(chart.Consumer, item, triggers, *b.RefreshRateMs) } } for _, gc := range cfg.Gauges { g := gauge.NewGauge(gc.Title, *gc.Scale, *gc.Color) - layout.AddComponent(config.TypeGauge, g, gc.Title, gc.Position, gc.Size, *gc.RefreshRateMs) + triggers := trigger.NewTriggers(gc.Triggers) + lout.AddComponent(config.TypeGauge, g, gc.Title, gc.Position, gc.Size, *gc.RefreshRateMs) - for _, item := range gc.Items { - data.NewSampler(g, item, *gc.RefreshRateMs) + for _, i := range gc.Items { + item := data.Item{Label: *i.Label, Script: i.Script} + data.NewSampler(g.Consumer, item, triggers, *gc.RefreshRateMs) } } - handler := event.NewHandler(layout) + handler := event.NewHandler(lout) handler.HandleEvents() } diff --git a/component/trigger/trigger.go b/trigger/trigger.go similarity index 90% rename from component/trigger/trigger.go rename to trigger/trigger.go index 4307d88..c670507 100644 --- a/component/trigger/trigger.go +++ b/trigger/trigger.go @@ -32,6 +32,17 @@ type Data struct { currentValue interface{} } +func NewTriggers(configs []config.TriggerConfig) []Trigger { + + triggers := make([]Trigger, len(configs)) + + for _, c := range configs { + triggers = append(triggers, NewTrigger(c)) + } + + return triggers +} + func NewTrigger(config config.TriggerConfig) Trigger { return Trigger{ title: config.Title,