Pyroscope: Exemplar support for series queries (#113926)

* feat(pyroscope): Exemplar support for series queries

use enum flag, add exemplar flag to explore

disable exemplars on explore as well

tests

feature toggle

fixing tests

* resolve conflicts

* lint
This commit is contained in:
Alberto
2026-01-07 13:25:42 +01:00
committed by GitHub
parent 0b9046be15
commit 6fee200327
25 changed files with 306 additions and 32 deletions
@@ -5,10 +5,11 @@ import (
"testing"
"connectrpc.com/connect"
"github.com/stretchr/testify/require"
googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1"
typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
"github.com/stretchr/testify/require"
)
func Test_PyroscopeClient(t *testing.T) {
@@ -19,7 +20,7 @@ func Test_PyroscopeClient(t *testing.T) {
t.Run("GetSeries", func(t *testing.T) {
limit := int64(42)
resp, err := client.GetSeries(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, []string{}, &limit, 15)
resp, err := client.GetSeries(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, []string{}, &limit, 15, typesv1.ExemplarType_EXEMPLAR_TYPE_NONE)
require.Nil(t, err)
series := &SeriesResponse{
@@ -32,6 +33,21 @@ func Test_PyroscopeClient(t *testing.T) {
require.Equal(t, series, resp)
})
t.Run("GetSeriesWithExemplars", func(t *testing.T) {
limit := int64(42)
resp, err := client.GetSeries(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, []string{}, &limit, 15, typesv1.ExemplarType_EXEMPLAR_TYPE_INDIVIDUAL)
require.Nil(t, err)
series := &SeriesResponse{
Series: []*Series{
{Labels: []*LabelPair{{Name: "foo", Value: "bar"}}, Points: []*Point{{Timestamp: int64(1000), Value: 30, Exemplars: []*Exemplar{{Id: "id1", Value: 3, Timestamp: 1000}}}, {Timestamp: int64(2000), Value: 10, Exemplars: []*Exemplar{{Id: "id2", Value: 1, Timestamp: 2000}}}}},
},
Units: "short",
Label: "alloc_objects",
}
require.Equal(t, series, resp)
})
t.Run("GetProfile", func(t *testing.T) {
maxNodes := int64(-1)
resp, err := client.GetProfile(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, &maxNodes)
@@ -115,6 +131,21 @@ func (f *FakePyroscopeConnectClient) SelectMergeStacktraces(ctx context.Context,
func (f *FakePyroscopeConnectClient) SelectSeries(ctx context.Context, req *connect.Request[querierv1.SelectSeriesRequest]) (*connect.Response[querierv1.SelectSeriesResponse], error) {
f.Req = req
if req.Msg.ExemplarType == typesv1.ExemplarType_EXEMPLAR_TYPE_INDIVIDUAL {
return &connect.Response[querierv1.SelectSeriesResponse]{
Msg: &querierv1.SelectSeriesResponse{
Series: []*typesv1.Series{
{
Labels: []*typesv1.LabelPair{{Name: "foo", Value: "bar"}},
Points: []*typesv1.Point{
{Timestamp: int64(1000), Value: 30, Exemplars: []*typesv1.Exemplar{{Timestamp: int64(1000), Value: 3, ProfileId: "id1"}}},
{Timestamp: int64(2000), Value: 10, Exemplars: []*typesv1.Exemplar{{Timestamp: int64(2000), Value: 1, ProfileId: "id2"}}},
},
},
},
},
}, nil
}
return &connect.Response[querierv1.SelectSeriesResponse]{
Msg: &querierv1.SelectSeriesResponse{
Series: []*typesv1.Series{