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  }