github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/storage_exemplars_merge.go (about) 1 package storage 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "math/big" 8 "time" 9 10 "github.com/pyroscope-io/pyroscope/pkg/storage/heatmap" 11 "github.com/pyroscope-io/pyroscope/pkg/storage/metadata" 12 "github.com/pyroscope-io/pyroscope/pkg/storage/segment" 13 "github.com/pyroscope-io/pyroscope/pkg/storage/tree" 14 ) 15 16 type MergeExemplarsInput struct { 17 AppName string 18 ProfileIDs []string 19 StartTime time.Time 20 EndTime time.Time 21 22 // FIXME: Not implemented: parameters are ignored. 23 ExemplarsSelection ExemplarsSelection 24 HeatmapParams heatmap.HeatmapParams 25 } 26 27 type MergeExemplarsOutput struct { 28 Tree *tree.Tree 29 Count uint64 30 Metadata metadata.Metadata 31 HeatmapSketch heatmap.HeatmapSketch // FIXME: Not implemented: the field is never populated. 32 Telemetry map[string]interface{} 33 } 34 35 func (s *Storage) MergeExemplars(ctx context.Context, mi MergeExemplarsInput) (out MergeExemplarsOutput, err error) { 36 m, err := s.mergeExemplars(ctx, mi) 37 if err != nil { 38 return out, err 39 } 40 41 out.Tree = m.tree 42 out.Count = m.count 43 if m.segment != nil { 44 out.Metadata = m.segment.GetMetadata() 45 } 46 47 if out.Count > 1 && out.Metadata.AggregationType == metadata.AverageAggregationType { 48 out.Tree = out.Tree.Clone(big.NewRat(1, int64(out.Count))) 49 } 50 51 return out, nil 52 } 53 54 type exemplarsMerge struct { 55 tree *tree.Tree 56 count uint64 57 segment *segment.Segment 58 lastEntry *exemplarEntry 59 } 60 61 func (s *Storage) mergeExemplars(ctx context.Context, mi MergeExemplarsInput) (out exemplarsMerge, err error) { 62 out.tree = tree.New() 63 startTime := unixNano(mi.StartTime) 64 endTime := unixNano(mi.EndTime) 65 err = s.exemplars.fetch(ctx, mi.AppName, mi.ProfileIDs, func(e exemplarEntry) error { 66 if exemplarMatchesTimeRange(e, startTime, endTime) { 67 out.tree.Merge(e.Tree) 68 out.count++ 69 out.lastEntry = &e 70 } 71 return nil 72 }) 73 if err != nil || out.lastEntry == nil { 74 return out, err 75 } 76 // Note that exemplar entry labels don't contain the app name and profile ID. 77 if out.lastEntry.Labels == nil { 78 out.lastEntry.Labels = make(map[string]string) 79 } 80 r, ok := s.segments.Lookup(segment.AppSegmentKey(mi.AppName)) 81 if !ok { 82 return out, fmt.Errorf("no metadata found for app %q", mi.AppName) 83 } 84 out.segment = r.(*segment.Segment) 85 return out, nil 86 } 87 88 func unixNano(t time.Time) int64 { 89 if t.IsZero() { 90 return 0 91 } 92 return t.UnixNano() 93 } 94 95 // exemplarMatchesTimeRange reports whether the exemplar is eligible for the 96 // given time range. Potentially, we could take exact fraction and scale the 97 // exemplar proportionally, in the way we do it in aggregate queries. However, 98 // with exemplars down-sampling does not seem to be a good idea as it may be 99 // confusing. 100 // 101 // For backward compatibility, an exemplar is considered eligible if the time 102 // range is not specified, or if the exemplar does not have timestamps. 103 func exemplarMatchesTimeRange(e exemplarEntry, startTime, endTime int64) bool { 104 if startTime == 0 || endTime == 0 || e.StartTime == 0 || e.EndTime == 0 { 105 return true 106 } 107 return !math.IsNaN(overlap(startTime, endTime, e.StartTime, e.EndTime)) 108 } 109 110 // overlap returns the overlap of the ranges 111 // indicating the exemplar time range fraction. 112 // 113 // query: from – until 114 // exemplar: start – end 115 // 116 // Special cases: 117 // +Inf - query matches or includes exemplar 118 // NaN - ranges don't overlap 119 // 120 func overlap(from, until, start, end int64) float64 { 121 span := end - start 122 o := min(until, end) - max(from, start) 123 switch { 124 case o <= 0: 125 return math.NaN() 126 case o == span: 127 return math.Inf(0) 128 default: 129 return float64(o) / float64(span) 130 } 131 } 132 133 func min(a, b int64) int64 { 134 if b < a { 135 return b 136 } 137 return a 138 } 139 140 func max(a, b int64) int64 { 141 if b > a { 142 return b 143 } 144 return a 145 }