wcharczuk / go-chart

go chart is a basic charting library in go.
MIT License
3.98k stars 326 forks source link

Flat series can't be rendered (CPU spinning) #219

Open mtojek opened 11 months ago

mtojek commented 11 months ago

Hi @wcharczuk,

I'm facing some issues with rendering a chart series with flat lines.

    var series []chart.Series

    for i, metricID := range metricIDs {
        ago := now.Add(-period).UTC()
        metrics, err := w.store.GetMetrics(metricID, ago)
        if err != nil {
            log.Printf("Can't fetch metrics (metricID: %d, period: %d): %s", metricID, period, err.Error())

            http.Error(rw, "Can't fetch metrics", http.StatusInternalServerError)
            return
        }

        serie := chart.TimeSeries{
            Name: VariablesLabels[metricID],
            Style: chart.Style{
                StrokeColor: chart.GetDefaultColor(i),
            },
            XValues: getTimestamps(metrics),
            YValues: getValues(metrics),
        }
        series = append(series, serie)
        series = append(series, chart.LastValueAnnotationSeries(serie))
    }

    graph := chart.Chart{
        Height: 512,
        Background: chart.Style{
            Padding: chart.Box{
                Top: 50,
            },
        },
        XAxis: chart.XAxis{
            ValueFormatter: chart.TimeMinuteValueFormatter,
            GridMajorStyle: chart.Style{
                StrokeColor: drawing.ColorBlack,
                StrokeWidth: 0.5,
            },
            GridMinorStyle: chart.Style{
                StrokeColor: drawing.Color{R: 0, G: 0, B: 0, A: 100},
                StrokeWidth: 0.25,
            },
        },
        YAxis: chart.YAxis{
            GridMajorStyle: chart.Style{
                StrokeColor: drawing.ColorBlack,
                StrokeWidth: 0.5,
            },
            GridMinorStyle: chart.Style{
                StrokeColor: drawing.Color{R: 0, G: 0, B: 0, A: 100},
                StrokeWidth: 0.25,
            },
        },
        Series: series,
    }

    graph.Elements = []chart.Renderable{
        chart.LegendThin(&graph),
    }

I can see neverending 100% CPU busy spin:

goroutine 178 [running]:
github.com/golang/freetype/raster.(*Rasterizer).Add1(0x85411c000, {0x0?, 0x0?})
        /home/stocznia/go/pkg/mod/github.com/golang/freetype@v0.0.0-20170609003504-e2365dfdc4a0/raster/raster.go:239 +0x4e2 fp=0x8543a3318 sp=0x8543a3250 pc=0xad34a2
github.com/wcharczuk/go-chart/v2/drawing.FtLineBuilder.LineTo(...)
        /home/stocznia/go/pkg/mod/github.com/wcharczuk/go-chart/v2@v2.1.1/drawing/free_type_path.go:20
github.com/wcharczuk/go-chart/v2/drawing.(*FtLineBuilder).LineTo(0x0?, 0x8000000000000000?, 0x0?)
        <autogenerated>:1 +0x4a fp=0x8543a3338 sp=0x8543a3318 pc=0xb1464a
github.com/wcharczuk/go-chart/v2/drawing.Transformer.LineTo({{0x3ff0000000000000, 0x0, 0x0, 0x3ff0000000000000, 0x0, 0x0}, {0x3d3dc0, 0x85424e0b0}}, 0x408e000000000000?, 0x43dfffffffffffff?)
        /home/stocznia/go/pkg/mod/github.com/wcharczuk/go-chart/v2@v2.1.1/drawing/transformer.go:23 +0x63 fp=0x8543a3360 sp=0x8543a3338 pc=0xb13b43
github.com/wcharczuk/go-chart/v2/drawing.(*Transformer).LineTo(0x3d3dc0?, 0x85424e0b0?, 0x8543a3440?)
        <autogenerated>:1 +0x8d fp=0x8543a3400 sp=0x8543a3360 pc=0xb1830d
github.com/wcharczuk/go-chart/v2/drawing.DemuxFlattener.LineTo(...)
        /home/stocznia/go/pkg/mod/github.com/wcharczuk/go-chart/v2@v2.1.1/drawing/demux_flattener.go:18
github.com/wcharczuk/go-chart/v2/drawing.(*DemuxFlattener).LineTo(0x8543a34b8?, 0x6bfbe8?, 0x8543a34c8?)
        <autogenerated>:1 +0x62 fp=0x8543a3450 sp=0x8543a3400 pc=0xb143e2
github.com/wcharczuk/go-chart/v2/drawing.Flatten(0x854452080, {0x3d3d80, 0x854492180}, 0x0?)
        /home/stocznia/go/pkg/mod/github.com/wcharczuk/go-chart/v2@v2.1.1/drawing/flattener.go:42 +0x22f fp=0x8543a3500 sp=0x8543a3450 pc=0xb0bf6f
github.com/wcharczuk/go-chart/v2/drawing.(*RasterGraphicContext).FillStroke(0x8540a4000, {0x0?, 0x43dfffffffffffff?, 0x4049800000000000?})
        /home/stocznia/go/pkg/mod/github.com/wcharczuk/go-chart/v2@v2.1.1/drawing/raster_graphic_context.go:276 +0x589 fp=0x8543a36c0 sp=0x8543a3500 pc=0xb117c9
github.com/wcharczuk/go-chart/v2.(*rasterRenderer).FillStroke(0x8541520f0)
        /home/stocznia/go/pkg/mod/github.com/wcharczuk/go-chart/v2@v2.1.1/raster_renderer.go:119 +0x125 fp=0x8543a3700 sp=0x8543a36c0 pc=0xb33685
github.com/wcharczuk/go-chart/v2.draw.Box({}, {0x3d8078?, 0x8541520f0}, {0x7ffffffffffffb15, 0x33, _, _, _}, {0x0, {0x0, ...}, ...})
        /home/stocznia/go/pkg/mod/github.com/wcharczuk/go-chart/v2@v2.1.1/draw.go:252 +0x2ea fp=0x8543a39c0 sp=0x8543a3700 pc=0xb2f3aa
github.com/wcharczuk/go-chart/v2.Chart.drawCanvas({{0x0, 0x0}, {0x0, {0x0, 0x0, 0x0, 0x0, 0x0}, {0x0, 0x0}, ...}, ...}, ...)
        /home/stocznia/go/pkg/mod/github.com/wcharczuk/go-chart/v2@v2.1.1/chart.go:472 +0x12e fp=0x8543a4d88 sp=0x8543a39c0 pc=0xb2b74e
github.com/wcharczuk/go-chart/v2.Chart.Render({{0x0, 0x0}, {0x0, {0x0, 0x0, 0x0, 0x0, 0x0}, {0x0, 0x0}, ...}, ...}, ...)
        /home/stocznia/go/pkg/mod/github.com/wcharczuk/go-chart/v2@v2.1.1/chart.go:137 +0x13c6 fp=0x8543ad218 sp=0x8543a4d88 pc=0xb274e6
github.com/mtojek/foobar/site.(*WebServer).chartHandlerFunc(0x854231c00, {0x3d2b50, 0x8541501c0}, 0x854bcc300?)
        /home/stocznia/code/foobar/site/chart.go:95 +0x946 fp=0x8543afa28 sp=0x8543ad218 pc=0xb805e6
github.com/mtojek/foobar/site.(*WebServer).chartHandlerFunc-fm({0x3d2b50?, 0x8541501c0?}, 0xffffffffffffffff?)
        <autogenerated>:1 +0x3c fp=0x8543afa58 sp=0x8543afa28 pc=0xb827dc
net/http.HandlerFunc.ServeHTTP(0x8541d8300?, {0x3d2b50?, 0x8541501c0?}, 0x6bfbe8?)
        /usr/local/go120/src/net/http/server.go:2122 +0x2f fp=0x8543afa80 sp=0x8543afa58 pc=0xa9088f
net/http.(*ServeMux).ServeHTTP(0x85402a18b?, {0x3d2b50, 0x8541501c0}, 0x854bcc300)
        /usr/local/go120/src/net/http/server.go:2500 +0x149 fp=0x8543afad0 sp=0x8543afa80 pc=0xa92229
net/http.serverHandler.ServeHTTP({0x8544422a0?}, {0x3d2b50, 0x8541501c0}, 0x854bcc300)
        /usr/local/go120/src/net/http/server.go:2936 +0x316 fp=0x8543afb80 sp=0x8543afad0 pc=0xa939d6
net/http.(*conn).serve(0x85412a3f0, {0x3d3048, 0x8542817a0})
        /usr/local/go120/src/net/http/server.go:1995 +0x612 fp=0x8543affb8 sp=0x8543afb80 pc=0xa8f3b2
net/http.(*Server).Serve.func3()
        /usr/local/go120/src/net/http/server.go:3089 +0x2e fp=0x8543affe0 sp=0x8543affb8 pc=0xa9432e
runtime.goexit()
        /usr/local/go120/src/runtime/asm_amd64.s:1598 +0x1 fp=0x8543affe8 sp=0x8543affe0 pc=0x717981
created by net/http.(*Server).Serve
        /usr/local/go120/src/net/http/server.go:3089 +0x5ed

timestamps: regular timestamp minute by minute values: 17.70, equally

Fun fact: it spins only when there is one flat series. When I use 2 series (does not matter if flat or not) it renders the chart instantly.

wcharczuk commented 11 months ago

This is super interesting. I can't look at it this second but I'll try and debug tonight. My current hunch is there is an issue with detecting the chart *ranges; can you try manually specifying the Y range?

mtojek commented 11 months ago

Thanks for the quick reply!

My current hunch is there is an issue with detecting the chart changes; can you try manually specifying the Y range?

With manually set range as chart.ContinuousRange, the problem does not occur. This is really sus...

        YAxis: chart.YAxis{
            GridMajorStyle: chart.Style{
                StrokeColor: drawing.ColorBlack,
                StrokeWidth: 0.5,
            },
            GridMinorStyle: chart.Style{
                StrokeColor: drawing.Color{R: 0, G: 0, B: 0, A: 100},
                StrokeWidth: 0.25,
            },
            Range: &chart.ContinuousRange{
                Min: 0,
                Max: 30,
            },
        },
maxsupermanhd commented 6 months ago

Hello there, I am trying to optimize draw speed of my chart by pre-computing SMASeries (it seems like it re-computes a LOT) and I replaced my SMASeries with TimeSeries and pre-computed values but instead of a speed increase I got an infinite loop somewhere in the draw call. I do have ContinuousRange on both primary and secondary axis, I even went so far as to clamp values inside averaging routine but it did not help. Were there any ideas or solutions to this issue? Debugging right now, will report any findings myself.

maxsupermanhd commented 6 months ago

After some trickery I managed to find correct thread and it seems like it is doing this: image (paused at setCell) Any ideas what can cause it?