added barchart component

This commit is contained in:
sqshq 2019-02-18 23:07:32 -05:00
parent e0986b7e31
commit 56fbf0cc0d
9 changed files with 219 additions and 168 deletions

View File

@ -1,65 +1,71 @@
runcharts: runcharts:
- title: SEARCH ENGINE RESPONSE TIME (sec) - title: SEARCH ENGINE RESPONSE TIME (sec)
position: position:
w: 0 w: 0
h: 0 h: 0
size: size:
w: 50 w: 50
h: 14 h: 14
precision: 3 scale: 3
items: items:
- label: GOOGLE - label: GOOGLE
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: YAHOO
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: BING
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
- title: SEARCH ENGINE RESPONSE TIME 2 (sec) - title: SEARCH ENGINE RESPONSE TIME 2 (sec)
refresh-rate-ms: 5000 refresh-rate-ms: 5000
position: position:
w: 0 w: 0
h: 14 h: 14
size: size:
w: 17 w: 17
h: 10 h: 10
legend: legend:
enabled: true enabled: true
details: false details: false
items: items:
- label: GOOGLE - label: GOOGLE
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: YAHOO
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: BING
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
- title: MONGO COLLECTIONS COUNT - title: MONGO COLLECTIONS COUNT
precision: 0 position:
position: w: 17
w: 17 h: 14
h: 14 size:
size: w: 17
w: 17 h: 10
h: 10 scale: 0
items: items:
- label: ACTIVE
script: mongo --quiet --host=localhost blog --eval "db.getCollection('posts').find({status:'ACTIVE'}).itcount()"
- label: INACTIVE
script: mongo --quiet --host=localhost blog --eval "db.getCollection('posts').find({status:'INACTIVE'}).itcount()"
barcharts:
- title: ANCHOR CONTEXT EVENTS
- label: ACTIVE - label: ACTIVE
script: mongo --quiet --host=localhost blog --eval "db.getCollection('posts').find({status:'ACTIVE'}).size()" script: mongo --quiet --host=localhost blog --eval "db.getCollection('posts').find({status:'ACTIVE'}).itcount()"
- label: INACTIVE - label: INACTIVE
script: mongo --quiet --host=localhost blog --eval "db.getCollection('posts').find({status:'INACTIVE'}).size()" script: mongo --quiet --host=localhost blog --eval "db.getCollection('posts').find({status:'INACTIVE'}).itcount()"
asciiboxes: asciiboxes:
- title: COUNT - title: COUNT
position: position:
w: 34 w: 34
h: 14 h: 14
size: size:
w: 16 w: 16
h: 4 h: 4
script: date +%r script: date +%r
- title: MODE - title: MODE
position: position:
w: 34 w: 34
h: 18 h: 18
size: size:
w: 16 w: 16
h: 6 h: 6
script: date +%r script: date +%r
font: 3d font: 3d

View File

@ -14,6 +14,7 @@ import (
type Config struct { type Config struct {
Theme *console.Theme `yaml:"theme,omitempty"` Theme *console.Theme `yaml:"theme,omitempty"`
RunCharts []RunChartConfig `yaml:"runcharts,omitempty"` RunCharts []RunChartConfig `yaml:"runcharts,omitempty"`
BarCharts []BarChartConfig `yaml:"barcharts,omitempty"`
AsciiBoxes []AsciiBoxConfig `yaml:"asciiboxes,omitempty"` AsciiBoxes []AsciiBoxConfig `yaml:"asciiboxes,omitempty"`
} }
@ -27,10 +28,16 @@ type ComponentConfig struct {
type RunChartConfig struct { type RunChartConfig struct {
ComponentConfig `yaml:",inline"` ComponentConfig `yaml:",inline"`
Legend *LegendConfig `yaml:"legend,omitempty"` Legend *LegendConfig `yaml:"legend,omitempty"`
Precision *int `yaml:"precision,omitempty"` Scale *int `yaml:"scale,omitempty"`
Items []data.Item `yaml:"items"` Items []data.Item `yaml:"items"`
} }
type BarChartConfig struct {
ComponentConfig `yaml:",inline"`
Scale *int `yaml:"scale,omitempty"`
Items []data.Item `yaml:"items"`
}
type AsciiBoxConfig struct { type AsciiBoxConfig struct {
ComponentConfig `yaml:",inline"` ComponentConfig `yaml:",inline"`
data.Item `yaml:",inline"` data.Item `yaml:",inline"`

View File

@ -7,7 +7,7 @@ import (
const ( const (
defaultRefreshRateMs = 1000 defaultRefreshRateMs = 1000
defaultPrecision = 1 defaultScale = 1
defaultTheme = console.ThemeDark defaultTheme = console.ThemeDark
) )
@ -29,9 +29,9 @@ func (c *Config) setDefaultValues() {
r := defaultRefreshRateMs r := defaultRefreshRateMs
chart.RefreshRateMs = &r chart.RefreshRateMs = &r
} }
if chart.Precision == nil { if chart.Scale == nil {
p := defaultPrecision p := defaultScale
chart.Precision = &p chart.Scale = &p
} }
if chart.Legend == nil { if chart.Legend == nil {
chart.Legend = &LegendConfig{true, true} chart.Legend = &LegendConfig{true, true}

2
go.mod
View File

@ -3,7 +3,7 @@ module github.com/sqshq/sampler
require ( require (
github.com/hajimehoshi/go-mp3 v0.1.1 github.com/hajimehoshi/go-mp3 v0.1.1
github.com/hajimehoshi/oto v0.1.1 github.com/hajimehoshi/oto v0.1.1
github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mattn/go-runewidth v0.0.4
github.com/mbndr/figlet4go v0.0.0-20170909125910-47ded4d17030 github.com/mbndr/figlet4go v0.0.0-20170909125910-47ded4d17030
github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/sqshq/termui v0.0.0-20190125032456-731556c09f2c github.com/sqshq/termui v0.0.0-20190125032456-731556c09f2c

14
main.go
View File

@ -7,6 +7,7 @@ import (
"github.com/sqshq/sampler/event" "github.com/sqshq/sampler/event"
"github.com/sqshq/sampler/widgets" "github.com/sqshq/sampler/widgets"
"github.com/sqshq/sampler/widgets/asciibox" "github.com/sqshq/sampler/widgets/asciibox"
"github.com/sqshq/sampler/widgets/barchart"
"github.com/sqshq/sampler/widgets/runchart" "github.com/sqshq/sampler/widgets/runchart"
ui "github.com/sqshq/termui" ui "github.com/sqshq/termui"
"time" "time"
@ -25,7 +26,7 @@ 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.Scale, *c.RefreshRateMs, legend)
layout.AddComponent(chart, c.Title, c.Position, c.Size, config.TypeRunChart) layout.AddComponent(chart, c.Title, c.Position, c.Size, config.TypeRunChart)
for _, item := range c.Items { for _, item := range c.Items {
@ -40,6 +41,17 @@ func main() {
data.NewSampler(box, a.Item, *a.RefreshRateMs) data.NewSampler(box, a.Item, *a.RefreshRateMs)
} }
for _, c := range cfg.BarCharts {
chart := barchart.NewBarChart(c.Title, *c.Scale)
layout.AddComponent(chart, c.Title, c.Position, c.Size, config.TypeBarChart)
for _, item := range c.Items {
chart.AddBar(*item.Label, *item.Color)
data.NewSampler(chart, item, *c.RefreshRateMs)
}
}
handler := event.Handler{ handler := event.Handler{
Layout: layout, Layout: layout,
RenderEvents: time.NewTicker(console.RenderRate).C, RenderEvents: time.NewTicker(console.RenderRate).C,

View File

@ -1,83 +1,109 @@
package barchart package barchart
// import (
//import ( rw "github.com/mattn/go-runewidth"
// "fmt" "github.com/sqshq/sampler/data"
// "image" ui "github.com/sqshq/termui"
//) "image"
// "strconv"
//type BarChart struct { )
// Block
// BarColors []Color const (
// LabelStyles []Style barSymbol rune = '⠿'
// NumStyles []Style // only Fg and Modifier are used barIndent int = 1
// NumFmt func(float64) string )
// Data []float64
// Labels []string type BarChart struct {
// BarWidth int ui.Block
// BarGap int bars []Bar
// MaxVal float64 scale int
//} maxValue float64
// }
//func NewBarChart() *BarChart {
// return &BarChart{ type Bar struct {
// Block: *NewBlock(), label string
// BarColors: Theme.BarChart.Bars, color ui.Color
// NumStyles: Theme.BarChart.Nums, value float64
// LabelStyles: Theme.BarChart.Labels, }
// NumFmt: func(n float64) string { return fmt.Sprint(n) },
// BarGap: 1, func NewBarChart(title string, scale int) *BarChart {
// BarWidth: 3, block := *ui.NewBlock()
// } block.Title = title
//} return &BarChart{
// Block: block,
//func (self *BarChart) Draw(buf *Buffer) { bars: []Bar{},
// self.Block.Draw(buf) scale: scale,
// maxValue: 0,
// maxVal := self.MaxVal }
// if maxVal == 0 { }
// maxVal, _ = GetMaxFloat64FromSlice(self.Data)
// } func (b *BarChart) AddBar(label string, color ui.Color) {
// b.bars = append(b.bars, Bar{label: label, color: color, value: 0})
// barXCoordinate := self.Inner.Min.X }
//
// for i, data := range self.Data { func (b *BarChart) ConsumeSample(sample data.Sample) {
// // draw bar
// height := int((data / maxVal) * float64(self.Inner.Dy()-1)) float, err := strconv.ParseFloat(sample.Value, 64)
// for x := barXCoordinate; x < MinInt(barXCoordinate+self.BarWidth, self.Inner.Max.X); x++ {
// for y := self.Inner.Max.Y - 2; y > (self.Inner.Max.Y-2)-height; y-- { if err != nil {
// c := NewCell(' ', NewStyle(ColorClear, SelectColor(self.BarColors, i))) // TODO visual notification + check sample.Error
// buf.SetCell(c, image.Pt(x, y)) }
// }
// } index := -1
// for i, bar := range b.bars {
// // draw label if bar.label == sample.Label {
// if i < len(self.Labels) { index = i
// labelXCoordinate := barXCoordinate + }
// int((float64(self.BarWidth) / 2)) - }
// int((float64(rw.StringWidth(self.Labels[i])) / 2))
// buf.SetString( bar := b.bars[index]
// self.Labels[i], bar.value = float
// SelectStyle(self.LabelStyles, i), b.bars[index] = bar
// image.Pt(labelXCoordinate, self.Inner.Max.Y-1), }
// )
// } func (b *BarChart) Draw(buf *ui.Buffer) {
// b.Block.Draw(buf)
// // draw number
// numberXCoordinate := barXCoordinate + int((float64(self.BarWidth) / 2)) maxVal := b.maxValue
// if numberXCoordinate <= self.Inner.Max.X {
// buf.SetString( barXCoordinate := b.Inner.Min.X
// self.NumFmt(data),
// NewStyle( for i, data := range b.Data {
// SelectStyle(self.NumStyles, i+1).Fg, // draw bar
// SelectColor(self.BarColors, i), height := int((data / maxVal) * float64(b.Inner.Dy()-1))
// SelectStyle(self.NumStyles, i+1).Modifier, for x := barXCoordinate; x < ui.MinInt(barXCoordinate+b.BarWidth, b.Inner.Max.X); x++ {
// ), for y := b.Inner.Max.Y - 2; y > (b.Inner.Max.Y-2)-height; y-- {
// image.Pt(numberXCoordinate, self.Inner.Max.Y-2), c := ui.NewCell(barSymbol, ui.NewStyle(ui.SelectColor(b.BarColors, i)))
// ) buf.SetCell(c, image.Pt(x, y))
// } }
// }
// barXCoordinate += (self.BarWidth + self.BarGap)
// } // draw label
//} if i < len(b.Labels) {
// labelXCoordinate := barXCoordinate +
int((float64(b.BarWidth) / 2)) -
int((float64(rw.StringWidth(b.Labels[i])) / 2))
buf.SetString(
b.Labels[i],
ui.SelectStyle(b.LabelStyles, i),
image.Pt(labelXCoordinate, b.Inner.Max.Y-1),
)
}
// draw value
numberXCoordinate := barXCoordinate + int((float64(b.BarWidth) / 2))
if numberXCoordinate <= b.Inner.Max.X {
buf.SetString(
b.NumFmt(data),
ui.NewStyle(
ui.SelectStyle(b.NumStyles, i+1).Fg,
ui.SelectColor(b.BarColors, i),
ui.SelectStyle(b.NumStyles, i+1).Modifier,
),
image.Pt(numberXCoordinate, b.Inner.Max.Y-2),
)
}
barXCoordinate += (b.BarWidth + b.BarGap)
}
}

View File

@ -77,13 +77,13 @@ func (c *RunChart) renderAxes(buffer *ui.Buffer) {
for i := 0; i < int(labelsCount); i++ { for i := 0; i < int(labelsCount); i++ {
value := c.grid.valueExtrema.max - (valuePerY * float64(i) * (yAxisLabelsIndent + yAxisLabelsHeight)) value := c.grid.valueExtrema.max - (valuePerY * float64(i) * (yAxisLabelsIndent + yAxisLabelsHeight))
buffer.SetString( buffer.SetString(
formatValue(value, c.precision), formatValue(value, c.scale),
ui.NewStyle(ui.ColorWhite), ui.NewStyle(ui.ColorWhite),
image.Pt(c.Inner.Min.X, 1+c.Inner.Min.Y+i*(yAxisLabelsIndent+yAxisLabelsHeight))) image.Pt(c.Inner.Min.X, 1+c.Inner.Min.Y+i*(yAxisLabelsIndent+yAxisLabelsHeight)))
} }
} else { } else {
buffer.SetString( buffer.SetString(
formatValue(c.grid.valueExtrema.max, c.precision), formatValue(c.grid.valueExtrema.max, c.scale),
ui.NewStyle(ui.ColorWhite), ui.NewStyle(ui.ColorWhite),
image.Pt(c.Inner.Min.X, c.Inner.Min.Y+c.Inner.Dy()/2)) image.Pt(c.Inner.Min.X, c.Inner.Min.Y+c.Inner.Dy()/2))
} }

View File

@ -38,7 +38,7 @@ func (c *RunChart) renderLegend(buffer *ui.Buffer, rectangle image.Rectangle) {
rowCount := (c.Dy() - 2*yAxisLegendIndent) / (height + yAxisLegendIndent) rowCount := (c.Dy() - 2*yAxisLegendIndent) / (height + yAxisLegendIndent)
columnCount := int(math.Ceil(float64(len(c.lines)) / float64(rowCount))) columnCount := int(math.Ceil(float64(len(c.lines)) / float64(rowCount)))
columnWidth := getColumnWidth(c.mode, c.lines, c.precision) columnWidth := getColumnWidth(c.mode, c.lines, c.scale)
for col := 0; col < columnCount; col++ { for col := 0; col < columnCount; col++ {
for row := 0; row < rowCount; row++ { for row := 0; row < rowCount; row++ {
@ -61,7 +61,7 @@ func (c *RunChart) renderLegend(buffer *ui.Buffer, rectangle image.Rectangle) {
if c.mode == ModePinpoint { if c.mode == ModePinpoint {
buffer.SetString(fmt.Sprintf("time %s", line.selectionPoint.time.Format("15:04:05.000")), detailsStyle, image.Pt(x, y+1)) buffer.SetString(fmt.Sprintf("time %s", line.selectionPoint.time.Format("15:04:05.000")), detailsStyle, image.Pt(x, y+1))
buffer.SetString(fmt.Sprintf("value %s", formatValue(line.selectionPoint.value, c.precision)), detailsStyle, image.Pt(x, y+2)) buffer.SetString(fmt.Sprintf("value %s", formatValue(line.selectionPoint.value, c.scale)), detailsStyle, image.Pt(x, y+2))
continue continue
} }
@ -70,10 +70,10 @@ func (c *RunChart) renderLegend(buffer *ui.Buffer, rectangle image.Rectangle) {
} }
details := [4]string{ details := [4]string{
fmt.Sprintf("cur %s", formatValue(getCurrentValue(line), c.precision)), fmt.Sprintf("cur %s", formatValue(getCurrentValue(line), c.scale)),
fmt.Sprintf("dlt %s", formatValueWithSign(getDiffWithPreviousValue(line), c.precision)), fmt.Sprintf("dlt %s", formatValueWithSign(getDiffWithPreviousValue(line), c.scale)),
fmt.Sprintf("max %s", formatValue(line.extrema.max, c.precision)), fmt.Sprintf("max %s", formatValue(line.extrema.max, c.scale)),
fmt.Sprintf("min %s", formatValue(line.extrema.min, c.precision)), fmt.Sprintf("min %s", formatValue(line.extrema.min, c.scale)),
} }
for i, detail := range details { for i, detail := range details {
@ -83,13 +83,13 @@ func (c *RunChart) renderLegend(buffer *ui.Buffer, rectangle image.Rectangle) {
} }
} }
func getColumnWidth(mode Mode, lines []TimeLine, precision int) int { func getColumnWidth(mode Mode, lines []TimeLine, scale int) int {
if mode == ModePinpoint { if mode == ModePinpoint {
return len(timeFormat) return len(timeFormat)
} }
width := len(formatValue(0, precision)) width := len(formatValue(0, scale))
for _, line := range lines { for _, line := range lines {
if len(line.label) > width { if len(line.label) > width {
width = len(line.label) width = len(line.label)

View File

@ -42,7 +42,7 @@ type RunChart struct {
mutex *sync.Mutex mutex *sync.Mutex
mode Mode mode Mode
selection time.Time selection time.Time
precision int scale int
legend Legend legend Legend
} }
@ -71,7 +71,7 @@ type ValueExtrema struct {
min float64 min float64
} }
func NewRunChart(title string, precision int, refreshRateMs int, legend Legend) *RunChart { func NewRunChart(title string, scale int, refreshRateMs int, legend Legend) *RunChart {
block := *ui.NewBlock() block := *ui.NewBlock()
block.Title = title block.Title = title
return &RunChart{ return &RunChart{
@ -79,7 +79,7 @@ func NewRunChart(title string, precision int, refreshRateMs int, legend Legend)
lines: []TimeLine{}, lines: []TimeLine{},
timescale: calculateTimescale(refreshRateMs), timescale: calculateTimescale(refreshRateMs),
mutex: &sync.Mutex{}, mutex: &sync.Mutex{},
precision: precision, scale: scale,
mode: ModeDefault, mode: ModeDefault,
legend: legend, legend: legend,
} }
@ -285,7 +285,7 @@ func (c *RunChart) getMaxValueLength() int {
for _, line := range c.lines { for _, line := range c.lines {
for _, point := range line.points { for _, point := range line.points {
l := len(formatValue(point.value, c.precision)) l := len(formatValue(point.value, c.scale))
if l > maxValueLength { if l > maxValueLength {
maxValueLength = l maxValueLength = l
} }
@ -327,22 +327,22 @@ func getMidRangeTime(r TimeRange) time.Time {
return r.max.Add(-delta / 2) return r.max.Add(-delta / 2)
} }
func formatValue(value float64, precision int) string { func formatValue(value float64, scale int) string {
if math.Abs(value) == math.MaxFloat64 { if math.Abs(value) == math.MaxFloat64 {
return "Inf" return "Inf"
} else { } else {
format := "%." + strconv.Itoa(precision) + "f" format := "%." + strconv.Itoa(scale) + "f"
return fmt.Sprintf(format, value) return fmt.Sprintf(format, value)
} }
} }
func formatValueWithSign(value float64, precision int) string { func formatValueWithSign(value float64, scale int) string {
if value == 0 { if value == 0 {
return " 0" return " 0"
} else if value > 0 { } else if value > 0 {
return "+" + formatValue(value, precision) return "+" + formatValue(value, scale)
} else { } else {
return formatValue(value, precision) return formatValue(value, scale)
} }
} }