github.com/grafana/pyroscope@v1.18.0/pkg/frontend/split_by_interval.go (about)

     1  package frontend
     2  
     3  import (
     4  	"time"
     5  )
     6  
     7  // TimeIntervalIterator splits a time range into non-overlapping sub-ranges,
     8  // where the boundary adjoining on the left is not included, e.g:
     9  //
    10  //	[t1, t2), [t3, t4), ..., [tn-1, tn].
    11  //
    12  // By default, a sub-range start time is a multiple of the interval.
    13  // See WithAlignment option, if a custom alignment is needed.
    14  type TimeIntervalIterator struct {
    15  	startTime int64
    16  	endTime   int64
    17  	interval  int64
    18  	alignment int64
    19  }
    20  
    21  type TimeInterval struct{ Start, End time.Time }
    22  
    23  type TimeIntervalIteratorOption func(*TimeIntervalIterator)
    24  
    25  // WithAlignment causes a sub-range start time to be a multiple of the
    26  // alignment. This makes it possible for a sub-range to be shorter
    27  // than the interval specified, but not more than by the alignment.
    28  //
    29  // The interval can't be less than the alignment.
    30  func WithAlignment(a time.Duration) TimeIntervalIteratorOption {
    31  	return func(i *TimeIntervalIterator) {
    32  		i.alignment = a.Nanoseconds()
    33  	}
    34  }
    35  
    36  // NewTimeIntervalIterator returns a new interval iterator.
    37  // If the interval is zero, the entire time span is taken as a single interval.
    38  func NewTimeIntervalIterator(startTime, endTime time.Time, interval time.Duration,
    39  	options ...TimeIntervalIteratorOption) *TimeIntervalIterator {
    40  	i := &TimeIntervalIterator{
    41  		startTime: startTime.UnixNano(),
    42  		endTime:   endTime.UnixNano(),
    43  		interval:  interval.Nanoseconds(),
    44  	}
    45  	if interval == 0 {
    46  		i.interval = 2 * endTime.Sub(startTime).Nanoseconds()
    47  	}
    48  	for _, option := range options {
    49  		option(i)
    50  	}
    51  	i.interval = max(i.interval, i.alignment)
    52  	return i
    53  }
    54  
    55  func (i *TimeIntervalIterator) Next() bool { return i.startTime < i.endTime }
    56  
    57  func (i *TimeIntervalIterator) At() TimeInterval {
    58  	t := TimeInterval{Start: time.Unix(0, i.startTime)}
    59  	i.startTime += i.interval
    60  	if i.alignment > 0 {
    61  		// Sub-ranges start at a multiple of 'alignment'.
    62  		i.startTime -= i.interval % i.alignment
    63  	} else {
    64  		// Sub-ranges start at a multiple of 'interval'.
    65  		i.startTime -= i.startTime % i.interval
    66  	}
    67  	if i.endTime > i.startTime {
    68  		// -1 to ensure the adjacent ranges don't overlap.
    69  		// Could be an option.
    70  		t.End = time.Unix(0, i.startTime-1)
    71  	} else {
    72  		t.End = time.Unix(0, i.endTime)
    73  	}
    74  	return t
    75  }
    76  
    77  func (*TimeIntervalIterator) Err() error { return nil }
    78  
    79  func (*TimeIntervalIterator) Close() error { return nil }