github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/storage_get.go (about)

     1  package storage
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/big"
     7  	"runtime/trace"
     8  	"time"
     9  
    10  	"github.com/sirupsen/logrus"
    11  
    12  	"github.com/pyroscope-io/pyroscope/pkg/flameql"
    13  	"github.com/pyroscope-io/pyroscope/pkg/storage/dimension"
    14  	"github.com/pyroscope-io/pyroscope/pkg/storage/metadata"
    15  	"github.com/pyroscope-io/pyroscope/pkg/storage/segment"
    16  	"github.com/pyroscope-io/pyroscope/pkg/storage/tree"
    17  )
    18  
    19  type GetInput struct {
    20  	StartTime time.Time
    21  	EndTime   time.Time
    22  	Key       *segment.Key
    23  	Query     *flameql.Query
    24  	// TODO: make this a part of the query
    25  	GroupBy string
    26  }
    27  
    28  type GetOutput struct {
    29  	Tree     *tree.Tree
    30  	Timeline *segment.Timeline
    31  	Groups   map[string]*segment.Timeline
    32  	Count    uint64
    33  
    34  	// TODO: Replace with metadata.Metadata
    35  	SpyName         string
    36  	SampleRate      uint32
    37  	Units           metadata.Units
    38  	AggregationType metadata.AggregationType
    39  
    40  	Telemetry map[string]interface{}
    41  }
    42  
    43  const (
    44  	averageAggregationType = "average"
    45  
    46  	traceTaskGet        = "storage.Get"
    47  	traceCatGetKey      = traceTaskGet
    48  	traceCatGetCallback = traceTaskGet + ".Callback"
    49  )
    50  
    51  func (s *Storage) Get(ctx context.Context, gi *GetInput) (*GetOutput, error) {
    52  	var t *trace.Task
    53  	ctx, t = trace.NewTask(ctx, traceTaskGet)
    54  	defer t.End()
    55  	logger := logrus.WithFields(logrus.Fields{
    56  		"startTime": gi.StartTime.String(),
    57  		"endTime":   gi.EndTime.String(),
    58  	})
    59  
    60  	var dimensionKeys func() []dimension.Key
    61  	switch {
    62  	case gi.Key != nil:
    63  		logger = logger.WithField("key", gi.Key.Normalized())
    64  		dimensionKeys = s.dimensionKeysByKey(gi.Key)
    65  	case gi.Query != nil:
    66  		logger = logger.WithField("query", gi.Query)
    67  		dimensionKeys = s.dimensionKeysByQuery(ctx, gi.Query)
    68  	default:
    69  		// Should never happen.
    70  		return nil, fmt.Errorf("key or query must be specified")
    71  	}
    72  
    73  	s.getTotal.Inc()
    74  	logger.Debug("storage.Get")
    75  	trace.Logf(ctx, traceCatGetKey, "%+v", gi)
    76  
    77  	// Profiles can be fetched by ID using query – this should be deprecated,
    78  	// and GetExemplar should be used instead.
    79  	if gi.Query != nil {
    80  		out, ok, err := s.tryGetExemplar(ctx, gi)
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  		if ok {
    85  			return out, nil
    86  		}
    87  	}
    88  
    89  	var (
    90  		resultTrie  *tree.Tree
    91  		lastSegment *segment.Segment
    92  		writesTotal uint64
    93  		timeline    = segment.GenerateTimeline(gi.StartTime, gi.EndTime)
    94  		timelines   = make(map[string]*segment.Timeline)
    95  	)
    96  
    97  	for _, k := range dimensionKeys() {
    98  		// TODO: refactor, store `Key`s in dimensions
    99  		parsedKey, err := segment.ParseKey(string(k))
   100  		if err != nil {
   101  			s.logger.Errorf("parse key: %v: %v", string(k), err)
   102  			continue
   103  		}
   104  		key := parsedKey.SegmentKey()
   105  		res, ok := s.segments.Lookup(key)
   106  		if !ok {
   107  			continue
   108  		}
   109  
   110  		st := res.(*segment.Segment)
   111  		timelineKey := "*"
   112  		if v, ok := parsedKey.Labels()[gi.GroupBy]; ok {
   113  			timelineKey = v
   114  		}
   115  		if _, ok := timelines[timelineKey]; !ok {
   116  			timelines[timelineKey] = segment.GenerateTimeline(gi.StartTime, gi.EndTime)
   117  		}
   118  
   119  		timeline.PopulateTimeline(st)
   120  		timelines[timelineKey].PopulateTimeline(st)
   121  		lastSegment = st
   122  
   123  		trace.Logf(ctx, traceCatGetCallback, "segment_key=%s", key)
   124  		st.GetContext(ctx, gi.StartTime, gi.EndTime, func(depth int, samples, writes uint64, t time.Time, r *big.Rat) {
   125  			tk := parsedKey.TreeKey(depth, t)
   126  			res, ok = s.trees.Lookup(tk)
   127  			trace.Logf(ctx, traceCatGetCallback, "tree_found=%v time=%d r=%v", ok, t.Unix(), r)
   128  			if ok {
   129  				x := res.(*tree.Tree).Clone(r)
   130  				writesTotal += writes
   131  				if resultTrie == nil {
   132  					resultTrie = x
   133  					return
   134  				}
   135  				resultTrie.Merge(x)
   136  			}
   137  		})
   138  	}
   139  
   140  	if resultTrie == nil || lastSegment == nil {
   141  		return nil, nil
   142  	}
   143  
   144  	md := lastSegment.GetMetadata()
   145  	switch md.AggregationType {
   146  	case averageAggregationType, "avg":
   147  		resultTrie = resultTrie.Clone(big.NewRat(1, int64(writesTotal)))
   148  	}
   149  
   150  	return &GetOutput{
   151  		Tree:            resultTrie,
   152  		Timeline:        timeline,
   153  		Groups:          timelines,
   154  		SpyName:         md.SpyName,
   155  		SampleRate:      md.SampleRate,
   156  		Units:           md.Units,
   157  		AggregationType: md.AggregationType,
   158  		Count:           writesTotal,
   159  	}, nil
   160  }
   161  
   162  func (s *Storage) tryGetExemplar(ctx context.Context, gi *GetInput) (*GetOutput, bool, error) {
   163  	ids := make([]string, 0, len(gi.Query.Matchers))
   164  	for _, m := range gi.Query.Matchers {
   165  		if m.Key != segment.ProfileIDLabelName {
   166  			continue
   167  		}
   168  		if m.Op != flameql.OpEqual {
   169  			return nil, true, fmt.Errorf("only '=' operator is allowed for %q label", segment.ProfileIDLabelName)
   170  		}
   171  		ids = append(ids, m.Value)
   172  	}
   173  	if len(ids) == 0 {
   174  		return nil, false, nil
   175  	}
   176  
   177  	m, err := s.MergeExemplars(ctx, MergeExemplarsInput{
   178  		AppName:    gi.Query.AppName,
   179  		StartTime:  gi.StartTime,
   180  		EndTime:    gi.EndTime,
   181  		ProfileIDs: ids,
   182  	})
   183  	if err != nil {
   184  		return nil, true, err
   185  	}
   186  
   187  	out := GetOutput{
   188  		Tree:  m.Tree,
   189  		Count: m.Count,
   190  
   191  		Timeline: segment.GenerateTimeline(gi.StartTime, gi.EndTime),
   192  		Groups:   make(map[string]*segment.Timeline),
   193  
   194  		SpyName:         m.Metadata.SpyName,
   195  		SampleRate:      m.Metadata.SampleRate,
   196  		Units:           m.Metadata.Units,
   197  		AggregationType: m.Metadata.AggregationType,
   198  	}
   199  
   200  	return &out, true, nil
   201  }
   202  
   203  func (s *Storage) execQuery(_ context.Context, qry *flameql.Query) []dimension.Key {
   204  	app, found := s.lookupAppDimension(qry.AppName)
   205  	if !found {
   206  		return nil
   207  	}
   208  	if len(qry.Matchers) == 0 {
   209  		return app.Keys
   210  	}
   211  
   212  	r := []*dimension.Dimension{app}
   213  	var n []*dimension.Dimension // Keys to be removed from the result.
   214  
   215  	for _, m := range qry.Matchers {
   216  		switch m.Op {
   217  		case flameql.OpEqual:
   218  			if d, ok := s.lookupDimension(m); ok {
   219  				r = append(r, d)
   220  			} else {
   221  				return nil
   222  			}
   223  		case flameql.OpNotEqual:
   224  			if d, ok := s.lookupDimension(m); ok {
   225  				n = append(n, d)
   226  			}
   227  		case flameql.OpEqualRegex:
   228  			if d, ok := s.lookupDimensionRegex(m); ok {
   229  				r = append(r, d)
   230  			} else {
   231  				return nil
   232  			}
   233  		case flameql.OpNotEqualRegex:
   234  			if d, ok := s.lookupDimensionRegex(m); ok {
   235  				n = append(n, d)
   236  			}
   237  		}
   238  	}
   239  
   240  	i := dimension.Intersection(r...)
   241  	if len(n) == 0 {
   242  		return i
   243  	}
   244  
   245  	return dimension.AndNot(
   246  		&dimension.Dimension{Keys: i},
   247  		&dimension.Dimension{Keys: dimension.Union(n...)})
   248  }
   249  
   250  func (s *Storage) dimensionKeysByQuery(ctx context.Context, qry *flameql.Query) func() []dimension.Key {
   251  	return func() []dimension.Key { return s.execQuery(ctx, qry) }
   252  }
   253  
   254  func (s *Storage) dimensionKeysByKey(key *segment.Key) func() []dimension.Key {
   255  	return func() []dimension.Key {
   256  		d, ok := s.lookupAppDimension(key.AppName())
   257  		if !ok {
   258  			return nil
   259  		}
   260  		l := key.Labels()
   261  		if len(l) == 1 {
   262  			// No tags specified: return application dimension keys.
   263  			return d.Keys
   264  		}
   265  		dimensions := []*dimension.Dimension{d}
   266  		for k, v := range l {
   267  			if flameql.IsTagKeyReserved(k) {
   268  				continue
   269  			}
   270  			if d, ok = s.lookupDimensionKV(k, v); ok {
   271  				dimensions = append(dimensions, d)
   272  			}
   273  		}
   274  		if len(dimensions) == 1 {
   275  			// Tags specified but not found.
   276  			return nil
   277  		}
   278  		return dimension.Intersection(dimensions...)
   279  	}
   280  }
   281  
   282  func (s *Storage) lookupAppDimension(app string) (*dimension.Dimension, bool) {
   283  	return s.lookupDimensionKV("__name__", app)
   284  }
   285  
   286  func (s *Storage) lookupDimension(m *flameql.TagMatcher) (*dimension.Dimension, bool) {
   287  	return s.lookupDimensionKV(m.Key, m.Value)
   288  }
   289  
   290  func (s *Storage) lookupDimensionRegex(m *flameql.TagMatcher) (*dimension.Dimension, bool) {
   291  	d := dimension.New()
   292  	s.labels.GetValues(m.Key, func(v string) bool {
   293  		if m.R.MatchString(v) {
   294  			if x, ok := s.lookupDimensionKV(m.Key, v); ok {
   295  				d.Keys = append(d.Keys, x.Keys...)
   296  			}
   297  		}
   298  		return true
   299  	})
   300  	if len(d.Keys) > 0 {
   301  		return d, true
   302  	}
   303  	return nil, false
   304  }
   305  
   306  func (s *Storage) lookupDimensionKV(k, v string) (*dimension.Dimension, bool) {
   307  	r, ok := s.dimensions.Lookup(k + ":" + v)
   308  	if ok {
   309  		return r.(*dimension.Dimension), true
   310  	}
   311  	return nil, false
   312  }