added time axis autoscale

This commit is contained in:
sqshq 2019-02-03 23:03:59 -05:00
parent 8caa058b42
commit 18c2085fd9
5 changed files with 61 additions and 42 deletions

View File

@ -9,7 +9,7 @@ runcharts:
- 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/
refresh-rate-ms: 200 refresh-rate-ms: 200
decimal-places: 1 decimal-places: 3
alert: alert:
value: value:
more-than: 0.231 more-than: 0.231

View File

@ -20,6 +20,7 @@ type RunChartConfig struct {
Position Position `yaml:"position"` Position Position `yaml:"position"`
Size Size `yaml:"size"` Size Size `yaml:"size"`
RefreshRateMs int `yaml:"refresh-rate-ms"` RefreshRateMs int `yaml:"refresh-rate-ms"`
Precision int `yaml:"decimal-places"`
} }
func Load(location string) *Config { func Load(location string) *Config {

View File

@ -6,6 +6,7 @@ import (
const ( const (
defaultRefreshRateMs = 300 defaultRefreshRateMs = 300
defaultPrecision = 1
defaultTheme = console.ThemeDark defaultTheme = console.ThemeDark
) )
@ -19,6 +20,9 @@ func (self *Config) setDefaultValues() {
if chart.RefreshRateMs == 0 { if chart.RefreshRateMs == 0 {
chart.RefreshRateMs = defaultRefreshRateMs chart.RefreshRateMs = defaultRefreshRateMs
} }
if chart.Precision == 0 {
chart.Precision = defaultPrecision
}
self.RunCharts[i] = chart self.RunCharts[i] = chart
} }
} }

View File

@ -21,7 +21,7 @@ func main() {
for _, chartConfig := range cfg.RunCharts { for _, chartConfig := range cfg.RunCharts {
chart := widgets.NewRunChart(chartConfig.Title) chart := widgets.NewRunChart(chartConfig.Title, chartConfig.Precision, chartConfig.RefreshRateMs)
layout.AddComponent(chart, chartConfig.Position, chartConfig.Size, widgets.TypeRunChart) layout.AddComponent(chart, chartConfig.Position, chartConfig.Size, widgets.TypeRunChart)
for _, item := range chartConfig.Items { for _, item := range chartConfig.Items {

View File

@ -15,13 +15,16 @@ import (
) )
const ( const (
chartHistoryReserve = 10 xAxisLegendWidth = 20
xAxisLabelsHeight = 1 xAxisLabelsHeight = 1
xAxisLabelsWidth = 8 xAxisLabelsWidth = 8
xAxisLabelsGap = 2 xAxisLabelsGap = 2
xAxisGridWidth = xAxisLabelsGap + xAxisLabelsWidth
yAxisLabelsHeight = 1 yAxisLabelsHeight = 1
yAxisLabelsGap = 1 yAxisLabelsGap = 1
xAxisLegendWidth = 20
chartHistoryReserve = 5
) )
type RunChart struct { type RunChart struct {
@ -29,6 +32,7 @@ type RunChart struct {
lines []TimeLine lines []TimeLine
grid ChartGrid grid ChartGrid
precision int precision int
timescale time.Duration
selection *time.Time selection *time.Time
mutex *sync.Mutex mutex *sync.Mutex
} }
@ -47,7 +51,6 @@ type TimeLine struct {
type ChartGrid struct { type ChartGrid struct {
linesCount int linesCount int
paddingDuration time.Duration
paddingWidth int paddingWidth int
maxTimeWidth int maxTimeWidth int
minTimeWidth int minTimeWidth int
@ -65,30 +68,29 @@ type ValueExtremum struct {
min float64 min float64
} }
func NewRunChart(title string) *RunChart { func NewRunChart(title string, precision int, refreshRateMs int) *RunChart {
block := *NewBlock() block := *NewBlock()
block.Title = title block.Title = title
return &RunChart{ return &RunChart{
Block: block, Block: block,
lines: []TimeLine{}, lines: []TimeLine{},
mutex: &sync.Mutex{}, mutex: &sync.Mutex{},
precision: 2, // TODO move to config precision: precision,
timescale: calculateTimescale(refreshRateMs),
} }
} }
func (self *RunChart) newChartGrid() ChartGrid { func (self *RunChart) newChartGrid() ChartGrid {
linesCount := (self.Inner.Max.X - self.Inner.Min.X - self.grid.minTimeWidth) / (xAxisLabelsGap + xAxisLabelsWidth) linesCount := (self.Inner.Max.X - self.Inner.Min.X - self.grid.minTimeWidth) / xAxisGridWidth
paddingDuration := time.Duration(time.Second) // TODO support others and/or adjust automatically depending on refresh rate
return ChartGrid{ return ChartGrid{
linesCount: linesCount, linesCount: linesCount,
paddingDuration: paddingDuration, paddingWidth: xAxisGridWidth,
paddingWidth: xAxisLabelsGap + xAxisLabelsWidth,
maxTimeWidth: self.Inner.Max.X, maxTimeWidth: self.Inner.Max.X,
minTimeWidth: self.getMaxValueLength(), minTimeWidth: self.getMaxValueLength(),
timeExtremum: GetTimeExtremum(linesCount, paddingDuration), timeExtremum: getTimeExtremum(linesCount, self.timescale),
valueExtremum: GetChartValueExtremum(self.lines), valueExtremum: getChartValueExtremum(self.lines),
} }
} }
@ -159,8 +161,8 @@ func (self *RunChart) SelectPoint(x int, y int) {
return return
} }
timeDeltaToPaddingRelation := (self.grid.maxTimeWidth - x) / self.grid.paddingWidth timeDeltaToPaddingRelation := (self.grid.maxTimeWidth - x) / xAxisGridWidth
timeDeltaWithGridMaxTime := timeDeltaToPaddingRelation * int(self.grid.paddingDuration.Nanoseconds()) timeDeltaWithGridMaxTime := timeDeltaToPaddingRelation * int(self.timescale.Nanoseconds())
selection := self.grid.timeExtremum.max.Add(-time.Duration(timeDeltaWithGridMaxTime) * time.Nanosecond) selection := self.grid.timeExtremum.max.Add(-time.Duration(timeDeltaWithGridMaxTime) * time.Nanosecond)
self.selection = &selection self.selection = &selection
@ -200,7 +202,7 @@ func (self *RunChart) getSelectedTimePoints() []TimePoint {
func (self *RunChart) trimOutOfRangeValues() { func (self *RunChart) trimOutOfRangeValues() {
historyReserve := self.grid.paddingDuration * time.Duration(self.grid.linesCount) * chartHistoryReserve historyReserve := self.timescale * time.Duration(self.grid.linesCount) * chartHistoryReserve
minRangeTime := self.grid.timeExtremum.min.Add(-historyReserve) minRangeTime := self.grid.timeExtremum.min.Add(-historyReserve)
for i, item := range self.lines { for i, item := range self.lines {
@ -232,8 +234,8 @@ func (self *RunChart) renderItems(buffer *Buffer, drawArea image.Rectangle) {
for _, timePoint := range line.points { for _, timePoint := range line.points {
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.time).Nanoseconds() timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.time).Nanoseconds()
timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.grid.paddingDuration.Nanoseconds()) timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.timescale.Nanoseconds())
x := self.grid.maxTimeWidth - (int(float64(self.grid.paddingWidth) * timeDeltaToPaddingRelation)) x := self.grid.maxTimeWidth - (int(float64(xAxisGridWidth) * timeDeltaToPaddingRelation))
var y int var y int
if self.grid.valueExtremum.max-self.grid.valueExtremum.min == 0 { if self.grid.valueExtremum.max-self.grid.valueExtremum.min == 0 {
@ -299,7 +301,7 @@ func (self *RunChart) renderAxes(buffer *Buffer) {
for x := 1; x <= self.grid.linesCount; x++ { for x := 1; x <= self.grid.linesCount; x++ {
buffer.SetCell( buffer.SetCell(
NewCell(VERTICAL_DASH, NewStyle(console.ColorDarkGrey)), NewCell(VERTICAL_DASH, NewStyle(console.ColorDarkGrey)),
image.Pt(self.grid.maxTimeWidth-x*self.grid.paddingWidth, y+self.Inner.Min.Y+1), image.Pt(self.grid.maxTimeWidth-x*xAxisGridWidth, y+self.Inner.Min.Y+1),
) )
} }
} }
@ -314,11 +316,11 @@ func (self *RunChart) renderAxes(buffer *Buffer) {
// draw x axis time labels // draw x axis time labels
for i := 1; i <= self.grid.linesCount; i++ { for i := 1; i <= self.grid.linesCount; i++ {
labelTime := self.grid.timeExtremum.max.Add(time.Duration(-i) * self.grid.paddingDuration) labelTime := self.grid.timeExtremum.max.Add(time.Duration(-i) * self.timescale)
buffer.SetString( buffer.SetString(
labelTime.Format("15:04:05"), labelTime.Format("15:04:05"),
NewStyle(ColorWhite), NewStyle(ColorWhite),
image.Pt(self.grid.maxTimeWidth-xAxisLabelsWidth/2-i*(self.grid.paddingWidth), self.Inner.Max.Y-1), image.Pt(self.grid.maxTimeWidth-xAxisLabelsWidth/2-i*(xAxisGridWidth), self.Inner.Max.Y-1),
) )
} }
@ -380,7 +382,7 @@ func (self *RunChart) renderLegend(buffer *Buffer, rectangle image.Rectangle, se
) )
} }
} else { } else {
extremum := GetLineValueExtremum(line.points) extremum := getLineValueExtremum(line.points)
buffer.SetString( buffer.SetString(
fmt.Sprintf("cur %s", formatValue(line.points[len(line.points)-1].value, self.precision)), fmt.Sprintf("cur %s", formatValue(line.points[len(line.points)-1].value, self.precision)),
@ -406,8 +408,8 @@ func (self *RunChart) renderSelection(buffer *Buffer, drawArea image.Rectangle,
for _, timePoint := range selectedPoints { for _, timePoint := range selectedPoints {
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.time).Nanoseconds() timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.time).Nanoseconds()
timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.grid.paddingDuration.Nanoseconds()) timeDeltaToPaddingRelation := float64(timeDeltaWithGridMaxTime) / float64(self.timescale.Nanoseconds())
x := self.grid.maxTimeWidth - (int(float64(self.grid.paddingWidth) * timeDeltaToPaddingRelation)) x := self.grid.maxTimeWidth - (int(float64(xAxisGridWidth) * timeDeltaToPaddingRelation))
var y int var y int
if self.grid.valueExtremum.max-self.grid.valueExtremum.min == 0 { if self.grid.valueExtremum.max-self.grid.valueExtremum.min == 0 {
@ -446,7 +448,7 @@ func formatValue(value float64, precision int) string {
return fmt.Sprintf(format, value) return fmt.Sprintf(format, value)
} }
func GetChartValueExtremum(items []TimeLine) ValueExtremum { func getChartValueExtremum(items []TimeLine) ValueExtremum {
if len(items) == 0 { if len(items) == 0 {
return ValueExtremum{0, 0} return ValueExtremum{0, 0}
@ -468,7 +470,7 @@ func GetChartValueExtremum(items []TimeLine) ValueExtremum {
return ValueExtremum{max: max, min: min} return ValueExtremum{max: max, min: min}
} }
func GetLineValueExtremum(points []TimePoint) ValueExtremum { func getLineValueExtremum(points []TimePoint) ValueExtremum {
if len(points) == 0 { if len(points) == 0 {
return ValueExtremum{0, 0} return ValueExtremum{0, 0}
@ -488,10 +490,22 @@ func GetLineValueExtremum(points []TimePoint) ValueExtremum {
return ValueExtremum{max: max, min: min} return ValueExtremum{max: max, min: min}
} }
func GetTimeExtremum(linesCount int, paddingDuration time.Duration) TimeExtremum { func getTimeExtremum(linesCount int, scale time.Duration) TimeExtremum {
maxTime := time.Now() maxTime := time.Now()
return TimeExtremum{ return TimeExtremum{
max: maxTime, max: maxTime,
min: maxTime.Add(-time.Duration(paddingDuration.Nanoseconds() * int64(linesCount))), min: maxTime.Add(-time.Duration(scale.Nanoseconds() * int64(linesCount))),
}
}
func calculateTimescale(refreshRateMs int) time.Duration {
multiplier := refreshRateMs * xAxisGridWidth / 2
timescale := time.Duration(time.Millisecond * time.Duration(multiplier)).Round(time.Second)
if timescale.Seconds() == 0 {
return time.Second
} else {
return timescale
} }
} }