github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/segment/timeline.go (about) 1 package segment 2 3 import ( 4 "time" 5 ) 6 7 type Timeline struct { 8 st time.Time 9 et time.Time 10 StartTime int64 `json:"startTime"` 11 Samples []uint64 `json:"samples"` 12 durationDelta time.Duration 13 DurationDeltaNormalized int64 `json:"durationDelta"` 14 15 // Watermarks map contains down-sampling watermarks (Unix timestamps) 16 // describing resolution levels of the timeline. 17 // 18 // Resolution in seconds is calculated as 10^k, where k is the map key. 19 // Meaning that any range within these 10^k seconds contains not more 20 // than one sample. Any sub-range less than 10^k shows down-sampled data. 21 // 22 // Given the map: 23 // 1: 1635508310 24 // 2: 1635507500 25 // 3: 1635506200 26 // 27 // This should be read as follows: 28 // 1. Data after 1635508310 is as precise as possible (10s resolution), 29 // down-sampling was not applied. 30 // 2. Data before 1635508310 has resolution 100s 31 // 3. Data before 1635507500 has resolution 1000s 32 // 4. Data before 1635506200 has resolution 10000s 33 Watermarks map[int]int64 `json:"watermarks"` 34 } 35 36 func GenerateTimeline(st, et time.Time) *Timeline { 37 st, et = normalize(st, et) 38 totalDuration := et.Sub(st) 39 minDuration := totalDuration / time.Duration(1024) 40 delta := durations[0] 41 for _, d := range durations { 42 if d < 0 { 43 break 44 } 45 if d < minDuration { 46 delta = d 47 } 48 } 49 return &Timeline{ 50 st: st, 51 et: et, 52 StartTime: st.Unix(), 53 Samples: make([]uint64, totalDuration/delta), 54 durationDelta: delta, 55 DurationDeltaNormalized: int64(delta / time.Second), 56 Watermarks: make(map[int]int64), 57 } 58 } 59 60 func (tl *Timeline) PopulateTimeline(s *Segment) { 61 s.m.Lock() 62 if s.root != nil { 63 s.root.populateTimeline(tl, s) 64 } 65 s.m.Unlock() 66 } 67 68 func (sn streeNode) populateTimeline(tl *Timeline, s *Segment) { 69 if sn.relationship(tl.st, tl.et) == outside { 70 return 71 } 72 73 var ( 74 currentDuration = durations[sn.depth] 75 levelWatermark time.Time 76 hasDataBefore bool 77 ) 78 79 if sn.depth > 0 { 80 levelWatermark = s.watermarks.levels[sn.depth-1] 81 } 82 83 if len(sn.children) > 0 && currentDuration >= tl.durationDelta { 84 for i, v := range sn.children { 85 if v != nil { 86 v.populateTimeline(tl, s) 87 hasDataBefore = true 88 continue 89 } 90 if hasDataBefore || levelWatermark.IsZero() || sn.isBefore(s.watermarks.absoluteTime) { 91 continue 92 } 93 if c := sn.createSampledChild(i); c.isBefore(levelWatermark) && c.isAfter(s.watermarks.absoluteTime) { 94 c.populateTimeline(tl, s) 95 if m := c.time.Add(durations[c.depth]); m.After(tl.st) { 96 tl.Watermarks[c.depth+1] = c.time.Add(durations[c.depth]).Unix() 97 } 98 } 99 } 100 return 101 } 102 103 nodeTime := sn.time 104 if currentDuration < tl.durationDelta { 105 currentDuration = tl.durationDelta 106 nodeTime = nodeTime.Truncate(currentDuration) 107 } 108 109 i := int(nodeTime.Sub(tl.st) / tl.durationDelta) 110 rightBoundary := i + int(currentDuration/tl.durationDelta) 111 112 l := len(tl.Samples) 113 for i < rightBoundary { 114 if i >= 0 && i < l { 115 if tl.Samples[i] == 0 { 116 tl.Samples[i] = 1 117 } 118 tl.Samples[i] += sn.samples 119 } 120 i++ 121 } 122 } 123 124 func (sn *streeNode) createSampledChild(i int) *streeNode { 125 s := &streeNode{ 126 depth: sn.depth - 1, 127 time: sn.time.Add(time.Duration(i) * durations[sn.depth-1]), 128 samples: sn.samples / multiplier, 129 writes: sn.samples / multiplier, 130 } 131 if s.depth > 0 { 132 s.children = make([]*streeNode, multiplier) 133 for j := range s.children { 134 s.children[j] = s.createSampledChild(j) 135 } 136 } 137 return s 138 }