implemented components size/position save in yaml, defaults for optional settings

This commit is contained in:
sqshq 2019-02-16 20:54:48 -05:00
parent 94c7d5011e
commit 687e6f098e
12 changed files with 206 additions and 157 deletions

View File

@ -1,78 +1,48 @@
theme: dark theme: dark
runcharts: runcharts:
- title: SEARCH ENGINE RESPONSE TIME (sec) - title: SEARCH ENGINE RESPONSE TIME (sec)
items: precision: 3
- label: GOOGLE position:
script: curl -o /dev/null -s -w '%{time_total}' https://www.google.com/ w: 0
- label: YAHOO h: 0
script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/ size:
- label: BING w: 30
script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/ h: 20
refresh-rate-ms: 200 legend:
decimal-places: 3 enabled: true
alert: details: true
value: items:
more-than: 0.231 - script: curl -o /dev/null -s -w '%{time_total}' https://www.google.com/
less-than: 0.123 label: GOOGLE
equal: 0.144 - script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/
indicator: label: YAHOO
terminal: true - script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/
beep: true label: BING
legend: - title: SEARCH ENGINE RESPONSE TIME 2 (sec)
enabled: true refresh-rate-ms: 5000
details: true position:
position: w: 0
x: 0 h: 20
y: 0 size:
size: w: 15
x: 30 h: 10
y: 15 legend:
- title: SEARCH ENGINE RESPONSE TIME 2 (sec) enabled: true
items: details: false
- label: GOOGLE items:
script: curl -o /dev/null -s -w '%{time_total}' https://www.google.com/ - script: curl -o /dev/null -s -w '%{time_total}' https://www.google.com/
- label: YAHOO label: GOOGLE
script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/ - script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/
- label: BING label: YAHOO
script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/ - script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/
refresh-rate-ms: 5000 label: BING
decimal-places: 3 - title: MONGO COLLECTIONS COUNT
alert: position:
value: w: 15
more-than: 0.231 h: 20
less-than: 0.123 size:
equal: 0.144 w: 15
indicator: h: 10
terminal: true items:
beep: true - script: mongo --quiet --host=localhost blog --eval "db.getCollection('post').find({}).size()"
legend: label: POSTS
enabled: true
details: false
position:
x: 0
y: 15
size:
x: 15
y: 15
- title: MONGO COLLECTIONS COUNT
items:
- label: POSTS
script: mongo --quiet --host=localhost blog --eval "db.getCollection('post').find({}).size()"
position:
x: 15
y: 15
size:
x: 15
y: 15
barcharts:
none
sparklines:
none
gauges:
none
lists:
none
textboxes:
none
asciiboxes:
none

View File

@ -1,9 +1,9 @@
package config package config
import ( import (
"fmt"
"github.com/sqshq/sampler/console" "github.com/sqshq/sampler/console"
"github.com/sqshq/sampler/data" "github.com/sqshq/sampler/data"
. "github.com/sqshq/sampler/widgets"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"io/ioutil" "io/ioutil"
"log" "log"
@ -11,18 +11,22 @@ import (
) )
type Config struct { type Config struct {
Theme console.Theme `yaml:"theme"` Theme *console.Theme `yaml:"theme,omitempty"`
RunCharts []RunChartConfig `yaml:"runcharts"` RunCharts []RunChartConfig `yaml:"runcharts,omitempty"`
}
type ComponentConfig struct {
Title string `yaml:"title"`
RefreshRateMs *int `yaml:"refresh-rate-ms,omitempty"`
Precision *int `yaml:"precision,omitempty"`
Position Position `yaml:"position"`
Size Size `yaml:"size"`
} }
type RunChartConfig struct { type RunChartConfig struct {
Title string `yaml:"title"` ComponentConfig `yaml:",inline"`
Items []data.Item `yaml:"items"` Legend *LegendConfig `yaml:"legend,omitempty"`
Position Position `yaml:"position"` Items []data.Item `yaml:"items"`
Size Size `yaml:"size"`
RefreshRateMs int `yaml:"refresh-rate-ms"`
Precision int `yaml:"decimal-places"`
Legend LegendConfig `yaml:"legend"`
} }
type LegendConfig struct { type LegendConfig struct {
@ -30,6 +34,30 @@ type LegendConfig struct {
Details bool `yaml:"details"` Details bool `yaml:"details"`
} }
type Position struct {
X int `yaml:"w"`
Y int `yaml:"h"`
}
type Size struct {
X int `yaml:"w"`
Y int `yaml:"h"`
}
type ComponentType rune
const (
TypeRunChart ComponentType = 0
TypeBarChart ComponentType = 1
)
type ComponentSettings struct {
Type ComponentType
Title string
Size Size
Position Position
}
func Load() *Config { func Load() *Config {
if len(os.Args) < 2 { if len(os.Args) < 2 {
@ -39,13 +67,36 @@ func Load() *Config {
cfg := readFile(os.Args[1]) cfg := readFile(os.Args[1])
cfg.validate() cfg.validate()
cfg.setDefaultValues() cfg.setDefaults()
cfg.setDefaultColors()
cfg.setDefaultLayout()
return cfg return cfg
} }
func Update(settings []ComponentSettings) {
cfg := readFile(os.Args[1])
for _, s := range settings {
componentConfig := cfg.findComponent(s.Type, s.Title)
componentConfig.Size = s.Size
componentConfig.Position = s.Position
}
saveFile(cfg)
}
func (c *Config) findComponent(componentType ComponentType, componentTitle string) *ComponentConfig {
switch componentType {
case TypeRunChart:
for i, component := range c.RunCharts {
if component.Title == componentTitle {
return &c.RunCharts[i].ComponentConfig
}
}
}
panic(fmt.Sprintf(
"Can't find component type %v with title %v", componentType, componentTitle))
}
func readFile(location string) *Config { func readFile(location string) *Config {
yamlFile, err := ioutil.ReadFile(location) yamlFile, err := ioutil.ReadFile(location)
@ -62,3 +113,15 @@ func readFile(location string) *Config {
return cfg return cfg
} }
func saveFile(config *Config) {
file, err := yaml.Marshal(config)
if err != nil {
log.Fatalf("Can't marshal config file: %v", err)
}
err = ioutil.WriteFile("config.yml", file, 0644)
if err != nil {
log.Fatalf("Can't save config file: %v", err)
}
}

View File

@ -10,35 +10,48 @@ const (
defaultTheme = console.ThemeDark defaultTheme = console.ThemeDark
) )
func (self *Config) setDefaultValues() { func (c *Config) setDefaults() {
c.setDefaultValues()
c.setDefaultColors()
c.setDefaultLayout()
}
if len(self.Theme) == 0 { func (c *Config) setDefaultValues() {
self.Theme = defaultTheme
if c.Theme == nil {
t := defaultTheme
c.Theme = &t
} }
for i, chart := range self.RunCharts { for i, chart := range c.RunCharts {
if chart.RefreshRateMs == 0 { if chart.RefreshRateMs == nil {
chart.RefreshRateMs = defaultRefreshRateMs r := defaultRefreshRateMs
chart.RefreshRateMs = &r
} }
if chart.Precision == 0 { if chart.Precision == nil {
chart.Precision = defaultPrecision p := defaultPrecision
chart.Precision = &p
} }
self.RunCharts[i] = chart if chart.Legend == nil {
chart.Legend = &LegendConfig{true, true}
c.RunCharts[i] = chart
}
c.RunCharts[i] = chart
} }
} }
func (config *Config) setDefaultLayout() { func (c *Config) setDefaultLayout() {
} }
func (config *Config) setDefaultColors() { func (c *Config) setDefaultColors() {
palette := console.GetPalette(config.Theme) palette := console.GetPalette(*c.Theme)
for i, chart := range config.RunCharts { for i, chart := range c.RunCharts {
for j, item := range chart.Items { for j, item := range chart.Items {
if item.Color == 0 { if item.Color == nil {
item.Color = palette.Colors[i+j] // TODO handle out of range case item.Color = &palette.Colors[i+j] // TODO handle out of range case
chart.Items[j] = item chart.Items[j] = item
} }
} }

View File

@ -5,6 +5,6 @@ package config
- title uniquness and mandatory within a single type of widget - title uniquness and mandatory within a single type of widget
- label uniqueness and mandatory (if > 1 data bullets) - label uniqueness and mandatory (if > 1 data bullets)
*/ */
func (self *Config) validate() { func (c *Config) validate() {
} }

View File

@ -7,14 +7,14 @@ import (
) )
type Item struct { type Item struct {
Script string `yaml:"script"` Script string `yaml:"script"`
Label string `yaml:"label"` Label string `yaml:"label"`
Color ui.Color `yaml:"color"` Color *ui.Color `yaml:"color,omitempty"`
} }
func (self *Item) nextValue() (value string, err error) { func (i *Item) nextValue() (value string, err error) {
output, err := exec.Command("sh", "-c", self.Script).Output() output, err := exec.Command("sh", "-c", i.Script).Output()
if err != nil { if err != nil {
return "", err return "", err

View File

@ -11,10 +11,12 @@ type Sampler struct {
func NewSampler(consumer Consumer, item Item, rateMs int) Sampler { func NewSampler(consumer Consumer, item Item, rateMs int) Sampler {
ticker := time.NewTicker(time.Duration(rateMs * int(time.Millisecond)))
sampler := Sampler{consumer, item} sampler := Sampler{consumer, item}
go func() { go func() {
for t := time.Tick(time.Duration(rateMs * int(time.Millisecond))); ; <-t { sampler.sample()
for ; true; <-ticker.C {
sampler.sample() sampler.sample()
} }
}() }()

View File

@ -1,6 +1,7 @@
package event package event
import ( import (
"github.com/sqshq/sampler/config"
"github.com/sqshq/sampler/console" "github.com/sqshq/sampler/console"
"github.com/sqshq/sampler/widgets" "github.com/sqshq/sampler/widgets"
ui "github.com/sqshq/termui" ui "github.com/sqshq/termui"
@ -13,28 +14,38 @@ type Handler struct {
ConsoleEvents <-chan ui.Event ConsoleEvents <-chan ui.Event
} }
func (self *Handler) HandleEvents() { func (h *Handler) HandleEvents() {
pause := false pause := false
for { for {
select { select {
case <-self.RenderEvents: case <-h.RenderEvents:
if !pause { if !pause {
ui.Render(self.Layout) ui.Render(h.Layout)
} }
case e := <-self.ConsoleEvents: case e := <-h.ConsoleEvents:
switch e.ID { switch e.ID {
case console.KeyQuit, console.KeyExit: case console.KeyQuit, console.KeyExit:
h.handleExit()
return return
case console.KeyPause: case console.KeyPause:
pause = !pause pause = !pause
case console.SignalResize: case console.SignalResize:
payload := e.Payload.(ui.Resize) payload := e.Payload.(ui.Resize)
self.Layout.ChangeDimensions(payload.Width, payload.Height) h.Layout.ChangeDimensions(payload.Width, payload.Height)
default: default:
self.Layout.HandleConsoleEvent(e.ID) h.Layout.HandleConsoleEvent(e.ID)
} }
} }
} }
} }
func (h *Handler) handleExit() {
var settings []config.ComponentSettings
for _, c := range h.Layout.Components {
settings = append(settings,
config.ComponentSettings{Type: c.Type, Title: c.Title, Size: c.Size, Position: c.Position})
}
config.Update(settings)
}

View File

@ -24,12 +24,12 @@ func main() {
for _, c := range cfg.RunCharts { for _, c := range cfg.RunCharts {
legend := runchart.Legend{Enabled: c.Legend.Enabled, Details: c.Legend.Details} legend := runchart.Legend{Enabled: c.Legend.Enabled, Details: c.Legend.Details}
chart := runchart.NewRunChart(c.Title, c.Precision, c.RefreshRateMs, legend) chart := runchart.NewRunChart(c.Title, *c.Precision, *c.RefreshRateMs, legend)
layout.AddComponent(chart, c.Title, c.Position, c.Size, widgets.TypeRunChart) layout.AddComponent(chart, c.Title, c.Position, c.Size, config.TypeRunChart)
for _, item := range c.Items { for _, item := range c.Items {
chart.AddLine(item.Label, item.Color) chart.AddLine(item.Label, *item.Color)
data.NewSampler(chart, item, c.RefreshRateMs) data.NewSampler(chart, item, *c.RefreshRateMs)
} }
} }

View File

@ -1,32 +1,16 @@
package widgets package widgets
import ( import (
. "github.com/sqshq/termui" "github.com/sqshq/sampler/config"
ui "github.com/sqshq/termui"
) )
type Component struct { type Component struct {
Drawable Drawable Drawable ui.Drawable
Title string Title string
Position Position Position config.Position
Size Size Size config.Size
Type ComponentType Type config.ComponentType
}
type ComponentType rune
const (
TypeRunChart ComponentType = 0
TypeBarChart ComponentType = 1
)
type Position struct {
X int `yaml:"x"`
Y int `yaml:"y"`
}
type Size struct {
X int `yaml:"x"`
Y int `yaml:"y"`
} }
func (c *Component) Move(x, y int) { func (c *Component) Move(x, y int) {

View File

@ -1,6 +1,7 @@
package widgets package widgets
import ( import (
"github.com/sqshq/sampler/config"
"github.com/sqshq/sampler/console" "github.com/sqshq/sampler/console"
"github.com/sqshq/sampler/widgets/runchart" "github.com/sqshq/sampler/widgets/runchart"
ui "github.com/sqshq/termui" ui "github.com/sqshq/termui"
@ -8,7 +9,7 @@ import (
type Layout struct { type Layout struct {
ui.Block ui.Block
components []Component Components []Component
menu *Menu menu *Menu
mode Mode mode Mode
selection int selection int
@ -37,22 +38,22 @@ func NewLayout(width, height int, menu *Menu) *Layout {
return &Layout{ return &Layout{
Block: block, Block: block,
components: make([]Component, 0), Components: make([]Component, 0),
menu: menu, menu: menu,
mode: ModeDefault, mode: ModeDefault,
selection: 0, selection: 0,
} }
} }
func (l *Layout) AddComponent(drawable ui.Drawable, title string, position Position, size Size, Type ComponentType) { func (l *Layout) AddComponent(drawable ui.Drawable, title string, position config.Position, size config.Size, Type config.ComponentType) {
l.components = append(l.components, Component{drawable, title, position, size, Type}) l.Components = append(l.Components, Component{drawable, title, position, size, Type})
} }
func (l *Layout) GetComponents(Type ComponentType) []ui.Drawable { func (l *Layout) GetComponents(Type config.ComponentType) []ui.Drawable {
var components []ui.Drawable var components []ui.Drawable
for _, component := range l.components { for _, component := range l.Components {
if component.Type == Type { if component.Type == Type {
components = append(components, component.Drawable) components = append(components, component.Drawable)
} }
@ -133,7 +134,7 @@ func (l *Layout) HandleConsoleEvent(e string) {
chart := l.getSelectedComponent().Drawable.(*runchart.RunChart) chart := l.getSelectedComponent().Drawable.(*runchart.RunChart)
chart.MoveSelection(1) chart.MoveSelection(1)
case ModeComponentSelect: case ModeComponentSelect:
if l.selection < len(l.components)-1 { if l.selection < len(l.Components)-1 {
l.selection++ l.selection++
} }
l.menu.highlight(l.getComponent(l.selection)) l.menu.highlight(l.getComponent(l.selection))
@ -167,7 +168,7 @@ func (l *Layout) HandleConsoleEvent(e string) {
l.selection = 0 l.selection = 0
l.menu.highlight(l.getComponent(l.selection)) l.menu.highlight(l.getComponent(l.selection))
case ModeComponentSelect: case ModeComponentSelect:
if l.selection < len(l.components)-1 { if l.selection < len(l.Components)-1 {
l.selection++ l.selection++
} }
l.menu.highlight(l.getComponent(l.selection)) l.menu.highlight(l.getComponent(l.selection))
@ -187,11 +188,11 @@ func (l *Layout) ChangeDimensions(width, height int) {
// TODO func to get prev/next component navigating left/right/top/bottom // TODO func to get prev/next component navigating left/right/top/bottom
func (l *Layout) getComponent(i int) Component { func (l *Layout) getComponent(i int) Component {
return l.components[i] return l.Components[i]
} }
func (l *Layout) getSelectedComponent() *Component { func (l *Layout) getSelectedComponent() *Component {
return &l.components[l.selection] return &l.Components[l.selection]
} }
func (l *Layout) Draw(buffer *ui.Buffer) { func (l *Layout) Draw(buffer *ui.Buffer) {
@ -199,7 +200,7 @@ func (l *Layout) Draw(buffer *ui.Buffer) {
columnWidth := float64(l.GetRect().Dx()) / columnsCount columnWidth := float64(l.GetRect().Dx()) / columnsCount
rowHeight := float64(l.GetRect().Dy()) / rowsCount rowHeight := float64(l.GetRect().Dy()) / rowsCount
for _, component := range l.components { for _, component := range l.Components {
x1 := float64(component.Position.X) * columnWidth x1 := float64(component.Position.X) * columnWidth
y1 := float64(component.Position.Y) * rowHeight y1 := float64(component.Position.Y) * rowHeight

View File

@ -1,6 +1,7 @@
package widgets package widgets
import ( import (
"github.com/sqshq/sampler/config"
"github.com/sqshq/sampler/console" "github.com/sqshq/sampler/console"
ui "github.com/sqshq/termui" ui "github.com/sqshq/termui"
"image" "image"
@ -191,7 +192,7 @@ func (m *Menu) renderOptions(buffer *ui.Buffer) {
style = highlightedStyle style = highlightedStyle
} }
if option != MenuOptionPinpoint || m.component.Type == TypeRunChart { if option != MenuOptionPinpoint || m.component.Type == config.TypeRunChart {
offset += 2 offset += 2
point := getMiddlePoint(m.Block, string(option), offset-5) point := getMiddlePoint(m.Block, string(option), offset-5)
if point.In(m.GetRect()) { if point.In(m.GetRect()) {

View File

@ -328,8 +328,12 @@ func getMidRangeTime(r TimeRange) time.Time {
} }
func formatValue(value float64, precision int) string { func formatValue(value float64, precision int) string {
format := "%." + strconv.Itoa(precision) + "f" if math.Abs(value) == math.MaxFloat64 {
return fmt.Sprintf(format, value) return "Inf"
} else {
format := "%." + strconv.Itoa(precision) + "f"
return fmt.Sprintf(format, value)
}
} }
// time duration between grid lines // time duration between grid lines