added time axis autoscale
This commit is contained in:
parent
8caa058b42
commit
18c2085fd9
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
main.go
2
main.go
|
@ -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 {
|
||||||
|
|
|
@ -15,13 +15,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
chartHistoryReserve = 10
|
xAxisLegendWidth = 20
|
||||||
xAxisLabelsHeight = 1
|
xAxisLabelsHeight = 1
|
||||||
xAxisLabelsWidth = 8
|
xAxisLabelsWidth = 8
|
||||||
xAxisLabelsGap = 2
|
xAxisLabelsGap = 2
|
||||||
yAxisLabelsHeight = 1
|
xAxisGridWidth = xAxisLabelsGap + xAxisLabelsWidth
|
||||||
yAxisLabelsGap = 1
|
|
||||||
xAxisLegendWidth = 20
|
yAxisLabelsHeight = 1
|
||||||
|
yAxisLabelsGap = 1
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
@ -46,13 +50,12 @@ 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
|
valueExtremum ValueExtremum
|
||||||
valueExtremum ValueExtremum
|
timeExtremum TimeExtremum
|
||||||
timeExtremum TimeExtremum
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TimeExtremum struct {
|
type TimeExtremum struct {
|
||||||
|
@ -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, self.timescale),
|
||||||
timeExtremum: GetTimeExtremum(linesCount, paddingDuration),
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue