github.com/grafana/pyroscope@v1.18.0/pkg/querybackend/query_time_series.go (about) 1 package querybackend 2 3 import ( 4 "strings" 5 "sync" 6 "time" 7 8 "github.com/grafana/dskit/runutil" 9 "github.com/opentracing/opentracing-go" 10 "github.com/parquet-go/parquet-go" 11 "google.golang.org/grpc/codes" 12 "google.golang.org/grpc/status" 13 14 queryv1 "github.com/grafana/pyroscope/api/gen/proto/go/query/v1" 15 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 16 "github.com/grafana/pyroscope/pkg/block" 17 phlaremodel "github.com/grafana/pyroscope/pkg/model" 18 parquetquery "github.com/grafana/pyroscope/pkg/phlaredb/query" 19 schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" 20 ) 21 22 func init() { 23 registerQueryType( 24 queryv1.QueryType_QUERY_TIME_SERIES, 25 queryv1.ReportType_REPORT_TIME_SERIES, 26 queryTimeSeries, 27 newTimeSeriesAggregator, 28 true, 29 []block.Section{ 30 block.SectionTSDB, 31 block.SectionProfiles, 32 }..., 33 ) 34 } 35 36 func queryTimeSeries(q *queryContext, query *queryv1.Query) (r *queryv1.Report, err error) { 37 includeExemplars, err := validateExemplarType(query.TimeSeries.ExemplarType) 38 if err != nil { 39 return nil, err 40 } 41 42 span := opentracing.SpanFromContext(q.ctx) 43 span.SetTag("exemplars.enabled", includeExemplars) 44 span.SetTag("exemplars.type", query.TimeSeries.ExemplarType.String()) 45 46 opts := []profileIteratorOption{ 47 withFetchPartition(false), // Partition data not needed, as we don't access stacktraces at all 48 } 49 50 if includeExemplars { 51 opts = append(opts, 52 withAllLabels(), 53 withFetchProfileIDs(true), 54 ) 55 } else { 56 opts = append(opts, 57 withGroupByLabels(query.TimeSeries.GroupBy...), 58 ) 59 } 60 61 entries, err := profileEntryIterator(q, opts...) 62 if err != nil { 63 return nil, err 64 } 65 defer runutil.CloseWithErrCapture(&err, entries, "failed to close profile entry iterator") 66 67 column, err := schemav1.ResolveColumnByPath(q.ds.Profiles().Schema(), strings.Split("TotalValue", ".")) 68 if err != nil { 69 return nil, err 70 } 71 72 // these columns might not be present 73 annotationKeysColumn, _ := schemav1.ResolveColumnByPath(q.ds.Profiles().Schema(), schemav1.AnnotationKeyColumnPath) 74 annotationValuesColumn, _ := schemav1.ResolveColumnByPath(q.ds.Profiles().Schema(), schemav1.AnnotationValueColumnPath) 75 76 rows := parquetquery.NewRepeatedRowIteratorBatchSize( 77 q.ctx, 78 entries, 79 q.ds.Profiles().RowGroups(), 80 bigBatchSize, 81 column.ColumnIndex, 82 annotationKeysColumn.ColumnIndex, 83 annotationValuesColumn.ColumnIndex, 84 ) 85 defer runutil.CloseWithErrCapture(&err, rows, "failed to close column iterator") 86 87 builder := phlaremodel.NewTimeSeriesBuilder(query.TimeSeries.GroupBy...) 88 for rows.Next() { 89 row := rows.At() 90 annotations := schemav1.Annotations{ 91 Keys: make([]string, 0), 92 Values: make([]string, 0), 93 } 94 for _, e := range row.Values { 95 if e[0].Column() == annotationKeysColumn.ColumnIndex && e[0].Kind() == parquet.ByteArray { 96 annotations.Keys = append(annotations.Keys, e[0].String()) 97 } 98 if e[0].Column() == annotationValuesColumn.ColumnIndex && e[0].Kind() == parquet.ByteArray { 99 annotations.Values = append(annotations.Values, e[0].String()) 100 } 101 } 102 103 builder.Add( 104 row.Row.Fingerprint, 105 row.Row.Labels, 106 int64(row.Row.Timestamp), 107 float64(row.Values[0][0].Int64()), 108 annotations, 109 row.Row.ID, // Profile ID comes from ProfileEntry now 110 ) 111 } 112 if err = rows.Err(); err != nil { 113 return nil, err 114 } 115 116 var timeSeries []*typesv1.Series 117 if includeExemplars { 118 timeSeries = builder.BuildWithExemplars() 119 span.SetTag("exemplars.raw_count", builder.ExemplarCount()) 120 } else { 121 timeSeries = builder.Build() 122 } 123 124 resp := &queryv1.Report{ 125 TimeSeries: &queryv1.TimeSeriesReport{ 126 Query: query.TimeSeries.CloneVT(), 127 TimeSeries: timeSeries, 128 }, 129 } 130 131 return resp, nil 132 } 133 134 type timeSeriesAggregator struct { 135 init sync.Once 136 startTime int64 137 endTime int64 138 query *queryv1.TimeSeriesQuery 139 series *phlaremodel.TimeSeriesMerger 140 } 141 142 func newTimeSeriesAggregator(req *queryv1.InvokeRequest) aggregator { 143 return &timeSeriesAggregator{ 144 startTime: req.StartTime, 145 endTime: req.EndTime, 146 } 147 } 148 149 func (a *timeSeriesAggregator) aggregate(report *queryv1.Report) error { 150 r := report.TimeSeries 151 a.init.Do(func() { 152 a.series = phlaremodel.NewTimeSeriesMerger(true) 153 a.query = r.Query.CloneVT() 154 }) 155 a.series.MergeTimeSeries(r.TimeSeries) 156 return nil 157 } 158 159 func (a *timeSeriesAggregator) build() *queryv1.Report { 160 // TODO(kolesnikovae): Average aggregation should be implemented in 161 // the way that it can be distributed (count + sum), and should be done 162 // at "aggregate" call. 163 sum := typesv1.TimeSeriesAggregationType_TIME_SERIES_AGGREGATION_TYPE_SUM 164 stepMilli := time.Duration(a.query.GetStep() * float64(time.Second)).Milliseconds() 165 seriesIterator := phlaremodel.NewTimeSeriesMergeIterator(a.series.TimeSeries()) 166 series := phlaremodel.RangeSeries( 167 seriesIterator, 168 a.startTime, 169 a.endTime, 170 stepMilli, 171 &sum, 172 ) 173 174 return &queryv1.Report{ 175 TimeSeries: &queryv1.TimeSeriesReport{ 176 Query: a.query, 177 TimeSeries: series, 178 }, 179 } 180 } 181 182 // validateExemplarType validates the exemplar type and returns whether to include exemplars. 183 func validateExemplarType(exemplarType typesv1.ExemplarType) (bool, error) { 184 switch exemplarType { 185 case typesv1.ExemplarType_EXEMPLAR_TYPE_UNSPECIFIED, 186 typesv1.ExemplarType_EXEMPLAR_TYPE_NONE: 187 return false, nil 188 case typesv1.ExemplarType_EXEMPLAR_TYPE_INDIVIDUAL: 189 return true, nil 190 case typesv1.ExemplarType_EXEMPLAR_TYPE_SPAN: 191 return false, status.Error(codes.Unimplemented, "exemplar type span is not implemented") 192 default: 193 return false, status.Errorf(codes.InvalidArgument, "unknown exemplar type: %v", exemplarType) 194 } 195 }