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 }