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  }