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  }