github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/delta.go (about) 1 package phlaredb 2 3 import ( 4 "sync" 5 6 "github.com/prometheus/common/model" 7 8 phlaremodel "github.com/grafana/pyroscope/pkg/model" 9 schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" 10 ) 11 12 const ( 13 memoryProfileName = "memory" 14 allocObjectTypeName = "alloc_objects" 15 allocSpaceTypeName = "alloc_space" 16 blockProfileName = "block" 17 contentionsTypeName = "contentions" 18 delayTypeName = "delay" 19 ) 20 21 // deltaProfiles is a helper to compute delta of profiles. 22 type deltaProfiles struct { 23 mtx sync.Mutex 24 // todo cleanup sample profiles that are not used anymore using a cleanup goroutine. 25 highestSamples map[model.Fingerprint]map[uint32]uint64 26 } 27 28 func newDeltaProfiles() *deltaProfiles { 29 return &deltaProfiles{ 30 highestSamples: make(map[model.Fingerprint]map[uint32]uint64), 31 } 32 } 33 34 func newSampleDict(samples schemav1.Samples) map[uint32]uint64 { 35 dict := make(map[uint32]uint64) 36 for i, s := range samples.StacktraceIDs { 37 dict[s] += samples.Values[i] 38 } 39 return dict 40 } 41 42 func (d *deltaProfiles) computeDelta(ps schemav1.InMemoryProfile) schemav1.Samples { 43 d.mtx.Lock() 44 defer d.mtx.Unlock() 45 46 // we store all series ref so fetching with one work. 47 lastSamples, ok := d.highestSamples[ps.SeriesFingerprint] 48 if !ok { 49 // if we don't have the last profile, we can't compute the delta. 50 // so we remove the delta from the list of labels and profiles. 51 d.highestSamples[ps.SeriesFingerprint] = newSampleDict(ps.Samples) 52 53 return schemav1.Samples{} 54 } 55 56 // we have the last profile, we can compute the delta. 57 // samples are sorted by stacktrace id. 58 // we need to compute the delta for each stacktrace. 59 if len(lastSamples) == 0 { 60 return ps.Samples.Compact(false) 61 } 62 63 reset := deltaSamples(lastSamples, ps.Samples) 64 if reset { 65 // if we reset the delta, we can't compute the delta anymore. 66 // so we remove the delta from the list of labels and profiles. 67 d.highestSamples[ps.SeriesFingerprint] = newSampleDict(ps.Samples) 68 return schemav1.Samples{} 69 } 70 71 return ps.Samples.Compact(false).Clone() 72 } 73 74 func isDeltaSupported(lbs phlaremodel.Labels) bool { 75 // only compute delta for allocs memory profile. 76 if lbs.Get(model.MetricNameLabel) == memoryProfileName { 77 ty := lbs.Get(phlaremodel.LabelNameType) 78 if ty == allocObjectTypeName || ty == allocSpaceTypeName { 79 return true 80 } 81 } 82 return false 83 } 84 85 func deltaSamples(highest map[uint32]uint64, new schemav1.Samples) bool { 86 for i, id := range new.StacktraceIDs { 87 newValue := new.Values[i] 88 if s, ok := highest[id]; ok { 89 if s <= newValue { 90 new.Values[i] -= s 91 highest[id] = newValue 92 } else { 93 // this is a reset, we can't compute the delta anymore. 94 return true 95 } 96 continue 97 } 98 highest[id] = newValue 99 } 100 return false 101 }