github.com/grafana/pyroscope@v1.18.0/pkg/querier/timeline/timeline.go (about)

     1  package timeline
     2  
     3  import (
     4  	"github.com/grafana/pyroscope/pkg/og/structs/flamebearer"
     5  
     6  	v1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
     7  )
     8  
     9  // New generates a FlamebearerTimeline, backfilling any missing data with zeros.
    10  // If startMs or endMs are in the middle of its durationDeltaSec bucket, they
    11  // will be snapped to the beginning of that bucket.
    12  //
    13  // It assumes:
    14  // * Ordered
    15  // * Series timestamps are within [startMs, endMs)
    16  func New(series *v1.Series, startMs int64, endMs int64, durationDeltaSec int64) *flamebearer.FlamebearerTimelineV1 {
    17  	// Snap startMs and endMs to bucket boundaries.
    18  	durationDeltaMs := durationDeltaSec * 1000
    19  	startMs = (startMs / durationDeltaMs) * durationDeltaMs
    20  	endMs = (endMs / durationDeltaMs) * durationDeltaMs
    21  
    22  	// ms to seconds
    23  	startSec := startMs / 1000
    24  	points := series.GetPoints()
    25  
    26  	timeline := &flamebearer.FlamebearerTimelineV1{
    27  		StartTime:     startSec,
    28  		DurationDelta: durationDeltaSec,
    29  		Samples:       []uint64{},
    30  	}
    31  	if startMs >= endMs {
    32  		return timeline
    33  	}
    34  	samples := make([]uint64, 0, sizeToBackfill(startMs, endMs, durationDeltaSec))
    35  
    36  	// Ensure the points slice has only values in [startMs, endMs).
    37  	points = boundPointsToWindow(points, startMs, endMs)
    38  	if len(points) < 1 {
    39  		if n := sizeToBackfill(startMs, endMs, durationDeltaSec); n > 0 {
    40  			timeline.Samples = samples[:n]
    41  		}
    42  		return timeline
    43  	}
    44  
    45  	// Backfill before the first data point.
    46  	firstAvailableData := points[0]
    47  	if n := sizeToBackfill(startMs, firstAvailableData.Timestamp, durationDeltaSec); n > 0 {
    48  		samples = samples[:len(samples)+int(n)]
    49  	}
    50  
    51  	prev := points[0]
    52  	for _, curr := range points {
    53  		if n := sizeToBackfill(prev.Timestamp, curr.Timestamp, durationDeltaSec) - 1; n > 0 {
    54  			// Insert backfill.
    55  			samples = samples[:len(samples)+int(n)]
    56  		}
    57  
    58  		samples = append(samples, uint64(curr.Value))
    59  		prev = curr
    60  	}
    61  
    62  	// Backfill to the end of the samples window.
    63  	samples = samples[:cap(samples)]
    64  
    65  	timeline.Samples = samples
    66  	return timeline
    67  }
    68  
    69  // sizeToBackfill indicates how many items are needed to backfill in the range
    70  // [startMs, endMs).
    71  func sizeToBackfill(startMs int64, endMs int64, stepSec int64) int64 {
    72  	startSec := startMs / 1000
    73  	endSec := endMs / 1000
    74  	size := (endSec - startSec) / stepSec
    75  	return size
    76  }
    77  
    78  // boundPointsToWindow will return a slice of points such that all the points
    79  // are constrained within [startMs, endMs).
    80  func boundPointsToWindow(points []*v1.Point, startMs int64, endMs int64) []*v1.Point {
    81  	start := 0
    82  	for ; start < len(points); start++ {
    83  		if points[start].Timestamp >= startMs {
    84  			break
    85  		}
    86  	}
    87  	points = points[start:]
    88  
    89  	end := len(points) - 1
    90  	for ; end >= 0; end-- {
    91  		if points[end].Timestamp < endMs {
    92  			break
    93  		}
    94  	}
    95  	points = points[:end+1]
    96  
    97  	return points
    98  }