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 }