mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Pyroscope: Fix backend panic when querying out of bounds (#76053)
This commit is contained in:
parent
41bcb5e07f
commit
1cbae72a9e
@ -149,6 +149,11 @@ func (c *PyroscopeClient) GetProfile(ctx context.Context, profileTypeID, labelSe
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.Msg.Flamegraph == nil {
|
||||||
|
// Not an error, can happen when querying data oout of range.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
levels := make([]*Level, len(resp.Msg.Flamegraph.Levels))
|
levels := make([]*Level, len(resp.Msg.Flamegraph.Levels))
|
||||||
for i, level := range resp.Msg.Flamegraph.Levels {
|
for i, level := range resp.Msg.Flamegraph.Levels {
|
||||||
levels[i] = &Level{
|
levels[i] = &Level{
|
||||||
|
@ -51,10 +51,21 @@ func Test_PyroscopeClient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
require.Equal(t, series, resp)
|
require.Equal(t, series, resp)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("GetProfile with empty response", func(t *testing.T) {
|
||||||
|
connectClient.SendEmptyProfileResponse = true
|
||||||
|
maxNodes := int64(-1)
|
||||||
|
resp, err := client.GetProfile(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, &maxNodes)
|
||||||
|
require.Nil(t, err)
|
||||||
|
// Mainly ensuring this does not panic like before
|
||||||
|
require.Nil(t, resp)
|
||||||
|
connectClient.SendEmptyProfileResponse = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakePyroscopeConnectClient struct {
|
type FakePyroscopeConnectClient struct {
|
||||||
Req any
|
Req any
|
||||||
|
SendEmptyProfileResponse bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakePyroscopeConnectClient) ProfileTypes(ctx context.Context, c *connect.Request[querierv1.ProfileTypesRequest]) (*connect.Response[querierv1.ProfileTypesResponse], error) {
|
func (f *FakePyroscopeConnectClient) ProfileTypes(ctx context.Context, c *connect.Request[querierv1.ProfileTypesRequest]) (*connect.Response[querierv1.ProfileTypesResponse], error) {
|
||||||
@ -75,6 +86,9 @@ func (f *FakePyroscopeConnectClient) Series(ctx context.Context, c *connect.Requ
|
|||||||
|
|
||||||
func (f *FakePyroscopeConnectClient) SelectMergeStacktraces(ctx context.Context, c *connect.Request[querierv1.SelectMergeStacktracesRequest]) (*connect.Response[querierv1.SelectMergeStacktracesResponse], error) {
|
func (f *FakePyroscopeConnectClient) SelectMergeStacktraces(ctx context.Context, c *connect.Request[querierv1.SelectMergeStacktracesRequest]) (*connect.Response[querierv1.SelectMergeStacktracesResponse], error) {
|
||||||
f.Req = c
|
f.Req = c
|
||||||
|
if f.SendEmptyProfileResponse {
|
||||||
|
return &connect.Response[querierv1.SelectMergeStacktracesResponse]{Msg: &querierv1.SelectMergeStacktracesResponse{}}, nil
|
||||||
|
}
|
||||||
return &connect.Response[querierv1.SelectMergeStacktracesResponse]{
|
return &connect.Response[querierv1.SelectMergeStacktracesResponse]{
|
||||||
Msg: &querierv1.SelectMergeStacktracesResponse{
|
Msg: &querierv1.SelectMergeStacktracesResponse{
|
||||||
Flamegraph: &querierv1.FlameGraph{
|
Flamegraph: &querierv1.FlameGraph{
|
||||||
|
@ -91,22 +91,30 @@ func (d *PyroscopeDatasource) query(ctx context.Context, pCtx backend.PluginCont
|
|||||||
logger.Error("Error GetProfile()", "err", err)
|
logger.Error("Error GetProfile()", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
frame := responseToDataFrames(prof)
|
|
||||||
|
var frame *data.Frame
|
||||||
|
if prof != nil {
|
||||||
|
frame = responseToDataFrames(prof)
|
||||||
|
|
||||||
|
// If query called with streaming on then return a channel
|
||||||
|
// to subscribe on a client-side and consume updates from a plugin.
|
||||||
|
// Feel free to remove this if you don't need streaming for your datasource.
|
||||||
|
if qm.WithStreaming {
|
||||||
|
channel := live.Channel{
|
||||||
|
Scope: live.ScopeDatasource,
|
||||||
|
Namespace: pCtx.DataSourceInstanceSettings.UID,
|
||||||
|
Path: "stream",
|
||||||
|
}
|
||||||
|
frame.SetMeta(&data.FrameMeta{Channel: channel.String()})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We still send empty data frame to give feedback that query really run, just didn't return any data.
|
||||||
|
frame = getEmptyDataFrame()
|
||||||
|
}
|
||||||
responseMutex.Lock()
|
responseMutex.Lock()
|
||||||
response.Frames = append(response.Frames, frame)
|
response.Frames = append(response.Frames, frame)
|
||||||
responseMutex.Unlock()
|
responseMutex.Unlock()
|
||||||
|
|
||||||
// If query called with streaming on then return a channel
|
|
||||||
// to subscribe on a client-side and consume updates from a plugin.
|
|
||||||
// Feel free to remove this if you don't need streaming for your datasource.
|
|
||||||
if qm.WithStreaming {
|
|
||||||
channel := live.Channel{
|
|
||||||
Scope: live.ScopeDatasource,
|
|
||||||
Namespace: pCtx.DataSourceInstanceSettings.UID,
|
|
||||||
Path: "stream",
|
|
||||||
}
|
|
||||||
frame.SetMeta(&data.FrameMeta{Channel: channel.String()})
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -273,6 +281,18 @@ func (pt *ProfileTree) String() string {
|
|||||||
return tree.String()
|
return tree.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getEmptyDataFrame() *data.Frame {
|
||||||
|
var emptyProfileDataFrame = data.NewFrame("response")
|
||||||
|
emptyProfileDataFrame.Meta = &data.FrameMeta{PreferredVisualization: "flamegraph"}
|
||||||
|
emptyProfileDataFrame.Fields = data.Fields{
|
||||||
|
data.NewField("level", nil, []int64{}),
|
||||||
|
data.NewField("value", nil, []int64{}),
|
||||||
|
data.NewField("self", nil, []int64{}),
|
||||||
|
data.NewField("label", nil, []string{}),
|
||||||
|
}
|
||||||
|
return emptyProfileDataFrame
|
||||||
|
}
|
||||||
|
|
||||||
type CustomMeta struct {
|
type CustomMeta struct {
|
||||||
ProfileTypeID string
|
ProfileTypeID string
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user