added barchart component
This commit is contained in:
parent
e0986b7e31
commit
56fbf0cc0d
24
config.yml
24
config.yml
|
@ -1,12 +1,12 @@
|
|||
runcharts:
|
||||
- title: SEARCH ENGINE RESPONSE TIME (sec)
|
||||
- title: SEARCH ENGINE RESPONSE TIME (sec)
|
||||
position:
|
||||
w: 0
|
||||
h: 0
|
||||
size:
|
||||
w: 50
|
||||
h: 14
|
||||
precision: 3
|
||||
scale: 3
|
||||
items:
|
||||
- label: GOOGLE
|
||||
script: curl -o /dev/null -s -w '%{time_total}' https://www.google.com
|
||||
|
@ -14,7 +14,7 @@ runcharts:
|
|||
script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com
|
||||
- label: BING
|
||||
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
|
||||
position:
|
||||
w: 0
|
||||
|
@ -32,21 +32,27 @@ runcharts:
|
|||
script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com
|
||||
- label: BING
|
||||
script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com
|
||||
- title: MONGO COLLECTIONS COUNT
|
||||
precision: 0
|
||||
- title: MONGO COLLECTIONS COUNT
|
||||
position:
|
||||
w: 17
|
||||
h: 14
|
||||
size:
|
||||
w: 17
|
||||
h: 10
|
||||
scale: 0
|
||||
items:
|
||||
- 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
|
||||
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()"
|
||||
barcharts:
|
||||
- title: ANCHOR CONTEXT EVENTS
|
||||
- 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()"
|
||||
asciiboxes:
|
||||
- title: COUNT
|
||||
- title: COUNT
|
||||
position:
|
||||
w: 34
|
||||
h: 14
|
||||
|
@ -54,7 +60,7 @@ asciiboxes:
|
|||
w: 16
|
||||
h: 4
|
||||
script: date +%r
|
||||
- title: MODE
|
||||
- title: MODE
|
||||
position:
|
||||
w: 34
|
||||
h: 18
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
type Config struct {
|
||||
Theme *console.Theme `yaml:"theme,omitempty"`
|
||||
RunCharts []RunChartConfig `yaml:"runcharts,omitempty"`
|
||||
BarCharts []BarChartConfig `yaml:"barcharts,omitempty"`
|
||||
AsciiBoxes []AsciiBoxConfig `yaml:"asciiboxes,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -27,7 +28,13 @@ type ComponentConfig struct {
|
|||
type RunChartConfig struct {
|
||||
ComponentConfig `yaml:",inline"`
|
||||
Legend *LegendConfig `yaml:"legend,omitempty"`
|
||||
Precision *int `yaml:"precision,omitempty"`
|
||||
Scale *int `yaml:"scale,omitempty"`
|
||||
Items []data.Item `yaml:"items"`
|
||||
}
|
||||
|
||||
type BarChartConfig struct {
|
||||
ComponentConfig `yaml:",inline"`
|
||||
Scale *int `yaml:"scale,omitempty"`
|
||||
Items []data.Item `yaml:"items"`
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
const (
|
||||
defaultRefreshRateMs = 1000
|
||||
defaultPrecision = 1
|
||||
defaultScale = 1
|
||||
defaultTheme = console.ThemeDark
|
||||
)
|
||||
|
||||
|
@ -29,9 +29,9 @@ func (c *Config) setDefaultValues() {
|
|||
r := defaultRefreshRateMs
|
||||
chart.RefreshRateMs = &r
|
||||
}
|
||||
if chart.Precision == nil {
|
||||
p := defaultPrecision
|
||||
chart.Precision = &p
|
||||
if chart.Scale == nil {
|
||||
p := defaultScale
|
||||
chart.Scale = &p
|
||||
}
|
||||
if chart.Legend == nil {
|
||||
chart.Legend = &LegendConfig{true, true}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -3,7 +3,7 @@ module github.com/sqshq/sampler
|
|||
require (
|
||||
github.com/hajimehoshi/go-mp3 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/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
github.com/sqshq/termui v0.0.0-20190125032456-731556c09f2c
|
||||
|
|
14
main.go
14
main.go
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/sqshq/sampler/event"
|
||||
"github.com/sqshq/sampler/widgets"
|
||||
"github.com/sqshq/sampler/widgets/asciibox"
|
||||
"github.com/sqshq/sampler/widgets/barchart"
|
||||
"github.com/sqshq/sampler/widgets/runchart"
|
||||
ui "github.com/sqshq/termui"
|
||||
"time"
|
||||
|
@ -25,7 +26,7 @@ func main() {
|
|||
for _, c := range cfg.RunCharts {
|
||||
|
||||
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)
|
||||
|
||||
for _, item := range c.Items {
|
||||
|
@ -40,6 +41,17 @@ func main() {
|
|||
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{
|
||||
Layout: layout,
|
||||
RenderEvents: time.NewTicker(console.RenderRate).C,
|
||||
|
|
|
@ -1,83 +1,109 @@
|
|||
package barchart
|
||||
|
||||
//
|
||||
//import (
|
||||
// "fmt"
|
||||
// "image"
|
||||
//)
|
||||
//
|
||||
//type BarChart struct {
|
||||
// Block
|
||||
// BarColors []Color
|
||||
// LabelStyles []Style
|
||||
// NumStyles []Style // only Fg and Modifier are used
|
||||
// NumFmt func(float64) string
|
||||
// Data []float64
|
||||
// Labels []string
|
||||
// BarWidth int
|
||||
// BarGap int
|
||||
// MaxVal float64
|
||||
//}
|
||||
//
|
||||
//func NewBarChart() *BarChart {
|
||||
// return &BarChart{
|
||||
// Block: *NewBlock(),
|
||||
// BarColors: Theme.BarChart.Bars,
|
||||
// NumStyles: Theme.BarChart.Nums,
|
||||
// LabelStyles: Theme.BarChart.Labels,
|
||||
// NumFmt: func(n float64) string { return fmt.Sprint(n) },
|
||||
// BarGap: 1,
|
||||
// BarWidth: 3,
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (self *BarChart) Draw(buf *Buffer) {
|
||||
// self.Block.Draw(buf)
|
||||
//
|
||||
// maxVal := self.MaxVal
|
||||
// if maxVal == 0 {
|
||||
// maxVal, _ = GetMaxFloat64FromSlice(self.Data)
|
||||
// }
|
||||
//
|
||||
// barXCoordinate := self.Inner.Min.X
|
||||
//
|
||||
// for i, data := range self.Data {
|
||||
// // draw bar
|
||||
// height := int((data / maxVal) * float64(self.Inner.Dy()-1))
|
||||
// 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-- {
|
||||
// c := NewCell(' ', NewStyle(ColorClear, SelectColor(self.BarColors, i)))
|
||||
// buf.SetCell(c, image.Pt(x, y))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // draw label
|
||||
// if i < len(self.Labels) {
|
||||
// labelXCoordinate := barXCoordinate +
|
||||
// int((float64(self.BarWidth) / 2)) -
|
||||
// int((float64(rw.StringWidth(self.Labels[i])) / 2))
|
||||
// buf.SetString(
|
||||
// self.Labels[i],
|
||||
// SelectStyle(self.LabelStyles, i),
|
||||
// image.Pt(labelXCoordinate, self.Inner.Max.Y-1),
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// // draw number
|
||||
// numberXCoordinate := barXCoordinate + int((float64(self.BarWidth) / 2))
|
||||
// if numberXCoordinate <= self.Inner.Max.X {
|
||||
// buf.SetString(
|
||||
// self.NumFmt(data),
|
||||
// NewStyle(
|
||||
// SelectStyle(self.NumStyles, i+1).Fg,
|
||||
// SelectColor(self.BarColors, i),
|
||||
// SelectStyle(self.NumStyles, i+1).Modifier,
|
||||
// ),
|
||||
// image.Pt(numberXCoordinate, self.Inner.Max.Y-2),
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// barXCoordinate += (self.BarWidth + self.BarGap)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
import (
|
||||
rw "github.com/mattn/go-runewidth"
|
||||
"github.com/sqshq/sampler/data"
|
||||
ui "github.com/sqshq/termui"
|
||||
"image"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
barSymbol rune = '⠿'
|
||||
barIndent int = 1
|
||||
)
|
||||
|
||||
type BarChart struct {
|
||||
ui.Block
|
||||
bars []Bar
|
||||
scale int
|
||||
maxValue float64
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
label string
|
||||
color ui.Color
|
||||
value float64
|
||||
}
|
||||
|
||||
func NewBarChart(title string, scale int) *BarChart {
|
||||
block := *ui.NewBlock()
|
||||
block.Title = title
|
||||
return &BarChart{
|
||||
Block: block,
|
||||
bars: []Bar{},
|
||||
scale: scale,
|
||||
maxValue: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BarChart) AddBar(label string, color ui.Color) {
|
||||
b.bars = append(b.bars, Bar{label: label, color: color, value: 0})
|
||||
}
|
||||
|
||||
func (b *BarChart) ConsumeSample(sample data.Sample) {
|
||||
|
||||
float, err := strconv.ParseFloat(sample.Value, 64)
|
||||
|
||||
if err != nil {
|
||||
// TODO visual notification + check sample.Error
|
||||
}
|
||||
|
||||
index := -1
|
||||
for i, bar := range b.bars {
|
||||
if bar.label == sample.Label {
|
||||
index = i
|
||||
}
|
||||
}
|
||||
|
||||
bar := b.bars[index]
|
||||
bar.value = float
|
||||
b.bars[index] = bar
|
||||
}
|
||||
|
||||
func (b *BarChart) Draw(buf *ui.Buffer) {
|
||||
b.Block.Draw(buf)
|
||||
|
||||
maxVal := b.maxValue
|
||||
|
||||
barXCoordinate := b.Inner.Min.X
|
||||
|
||||
for i, data := range b.Data {
|
||||
// draw bar
|
||||
height := int((data / maxVal) * float64(b.Inner.Dy()-1))
|
||||
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-- {
|
||||
c := ui.NewCell(barSymbol, ui.NewStyle(ui.SelectColor(b.BarColors, i)))
|
||||
buf.SetCell(c, image.Pt(x, y))
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,13 +77,13 @@ func (c *RunChart) renderAxes(buffer *ui.Buffer) {
|
|||
for i := 0; i < int(labelsCount); i++ {
|
||||
value := c.grid.valueExtrema.max - (valuePerY * float64(i) * (yAxisLabelsIndent + yAxisLabelsHeight))
|
||||
buffer.SetString(
|
||||
formatValue(value, c.precision),
|
||||
formatValue(value, c.scale),
|
||||
ui.NewStyle(ui.ColorWhite),
|
||||
image.Pt(c.Inner.Min.X, 1+c.Inner.Min.Y+i*(yAxisLabelsIndent+yAxisLabelsHeight)))
|
||||
}
|
||||
} else {
|
||||
buffer.SetString(
|
||||
formatValue(c.grid.valueExtrema.max, c.precision),
|
||||
formatValue(c.grid.valueExtrema.max, c.scale),
|
||||
ui.NewStyle(ui.ColorWhite),
|
||||
image.Pt(c.Inner.Min.X, c.Inner.Min.Y+c.Inner.Dy()/2))
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func (c *RunChart) renderLegend(buffer *ui.Buffer, rectangle image.Rectangle) {
|
|||
|
||||
rowCount := (c.Dy() - 2*yAxisLegendIndent) / (height + yAxisLegendIndent)
|
||||
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 row := 0; row < rowCount; row++ {
|
||||
|
@ -61,7 +61,7 @@ func (c *RunChart) renderLegend(buffer *ui.Buffer, rectangle image.Rectangle) {
|
|||
|
||||
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("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
|
||||
}
|
||||
|
||||
|
@ -70,10 +70,10 @@ func (c *RunChart) renderLegend(buffer *ui.Buffer, rectangle image.Rectangle) {
|
|||
}
|
||||
|
||||
details := [4]string{
|
||||
fmt.Sprintf("cur %s", formatValue(getCurrentValue(line), c.precision)),
|
||||
fmt.Sprintf("dlt %s", formatValueWithSign(getDiffWithPreviousValue(line), c.precision)),
|
||||
fmt.Sprintf("max %s", formatValue(line.extrema.max, c.precision)),
|
||||
fmt.Sprintf("min %s", formatValue(line.extrema.min, c.precision)),
|
||||
fmt.Sprintf("cur %s", formatValue(getCurrentValue(line), c.scale)),
|
||||
fmt.Sprintf("dlt %s", formatValueWithSign(getDiffWithPreviousValue(line), c.scale)),
|
||||
fmt.Sprintf("max %s", formatValue(line.extrema.max, c.scale)),
|
||||
fmt.Sprintf("min %s", formatValue(line.extrema.min, c.scale)),
|
||||
}
|
||||
|
||||
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 {
|
||||
return len(timeFormat)
|
||||
}
|
||||
|
||||
width := len(formatValue(0, precision))
|
||||
width := len(formatValue(0, scale))
|
||||
for _, line := range lines {
|
||||
if len(line.label) > width {
|
||||
width = len(line.label)
|
||||
|
|
|
@ -42,7 +42,7 @@ type RunChart struct {
|
|||
mutex *sync.Mutex
|
||||
mode Mode
|
||||
selection time.Time
|
||||
precision int
|
||||
scale int
|
||||
legend Legend
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ type ValueExtrema struct {
|
|||
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.Title = title
|
||||
return &RunChart{
|
||||
|
@ -79,7 +79,7 @@ func NewRunChart(title string, precision int, refreshRateMs int, legend Legend)
|
|||
lines: []TimeLine{},
|
||||
timescale: calculateTimescale(refreshRateMs),
|
||||
mutex: &sync.Mutex{},
|
||||
precision: precision,
|
||||
scale: scale,
|
||||
mode: ModeDefault,
|
||||
legend: legend,
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ func (c *RunChart) getMaxValueLength() int {
|
|||
|
||||
for _, line := range c.lines {
|
||||
for _, point := range line.points {
|
||||
l := len(formatValue(point.value, c.precision))
|
||||
l := len(formatValue(point.value, c.scale))
|
||||
if l > maxValueLength {
|
||||
maxValueLength = l
|
||||
}
|
||||
|
@ -327,22 +327,22 @@ func getMidRangeTime(r TimeRange) time.Time {
|
|||
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 {
|
||||
return "Inf"
|
||||
} else {
|
||||
format := "%." + strconv.Itoa(precision) + "f"
|
||||
format := "%." + strconv.Itoa(scale) + "f"
|
||||
return fmt.Sprintf(format, value)
|
||||
}
|
||||
}
|
||||
|
||||
func formatValueWithSign(value float64, precision int) string {
|
||||
func formatValueWithSign(value float64, scale int) string {
|
||||
if value == 0 {
|
||||
return " 0"
|
||||
} else if value > 0 {
|
||||
return "+" + formatValue(value, precision)
|
||||
return "+" + formatValue(value, scale)
|
||||
} else {
|
||||
return formatValue(value, precision)
|
||||
return formatValue(value, scale)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue