github.com/network-quality/goresponsiveness@v0.0.0-20240129151524-343954285090/series/series.go (about)

     1  /*
     2   * This file is part of Go Responsiveness.
     3   *
     4   * Go Responsiveness is free software: you can redistribute it and/or modify it under
     5   * the terms of the GNU General Public License as published by the Free Software Foundation,
     6   * either version 2 of the License, or (at your option) any later version.
     7   * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY
     8   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
     9   * PARTICULAR PURPOSE. See the GNU General Public License for more details.
    10   *
    11   * You should have received a copy of the GNU General Public License along
    12   * with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>.
    13   */
    14  package series
    15  
    16  import (
    17  	"cmp"
    18  	"fmt"
    19  	"slices"
    20  	"sync"
    21  
    22  	"github.com/network-quality/goresponsiveness/utilities"
    23  )
    24  
    25  type WindowSeriesDuration int
    26  
    27  const (
    28  	Forever    WindowSeriesDuration = iota
    29  	WindowOnly WindowSeriesDuration = iota
    30  )
    31  
    32  type WindowSeries[Data any, Bucket utilities.Number] interface {
    33  	fmt.Stringer
    34  
    35  	Reserve(b Bucket) error
    36  	Fill(b Bucket, d Data) error
    37  
    38  	Count() (some int, none int)
    39  
    40  	ForEach(func(Bucket, *utilities.Optional[Data]))
    41  
    42  	GetValues() []utilities.Optional[Data]
    43  	Complete() bool
    44  	GetType() WindowSeriesDuration
    45  
    46  	ExtractBoundedSeries() WindowSeries[Data, Bucket]
    47  
    48  	Append(appended *WindowSeries[Data, Bucket])
    49  	BoundedAppend(appended *WindowSeries[Data, Bucket])
    50  
    51  	GetBucketBounds() (Bucket, Bucket)
    52  
    53  	SetTrimmingBucketBounds(Bucket, Bucket)
    54  	ResetTrimmingBucketBounds()
    55  }
    56  
    57  type windowSeriesWindowOnlyImpl[Data any, Bucket utilities.Number] struct {
    58  	windowSize         int
    59  	data               []utilities.Pair[Bucket, utilities.Optional[Data]]
    60  	latestIndex        int // invariant: newest data is there.
    61  	empty              bool
    62  	lock               sync.RWMutex
    63  	lowerTrimmingBound utilities.Optional[Bucket]
    64  	upperTrimmingBound utilities.Optional[Bucket]
    65  }
    66  
    67  /*
    68   * Beginning of WindowSeries interface methods.
    69   */
    70  
    71  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) Reserve(b Bucket) error {
    72  	if !wsi.empty && b <= wsi.data[wsi.latestIndex].First {
    73  		return fmt.Errorf("reserving must be monotonically increasing")
    74  	}
    75  	wsi.lock.Lock()
    76  	defer wsi.lock.Unlock()
    77  
    78  	if wsi.empty {
    79  		/* Special case if we are empty: The latestIndex is where we want this value to go! */
    80  		wsi.data[wsi.latestIndex] = utilities.Pair[Bucket, utilities.Optional[Data]]{
    81  			First: b, Second: utilities.None[Data](),
    82  		}
    83  	} else {
    84  		/* Otherwise, bump ourselves forward and place the new reservation there. */
    85  		wsi.latestIndex = wsi.nextIndex(wsi.latestIndex)
    86  		wsi.data[wsi.latestIndex] = utilities.Pair[Bucket, utilities.Optional[Data]]{
    87  			First: b, Second: utilities.None[Data](),
    88  		}
    89  	}
    90  	wsi.empty = false
    91  	return nil
    92  }
    93  
    94  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) BucketBounds() (Bucket, Bucket) {
    95  	newestBucket := wsi.data[wsi.latestIndex].First
    96  	oldestBucket := wsi.data[wsi.nextIndex(wsi.latestIndex)].First
    97  
    98  	return oldestBucket, newestBucket
    99  }
   100  
   101  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) Fill(b Bucket, d Data) error {
   102  	wsi.lock.Lock()
   103  	defer wsi.lock.Unlock()
   104  	iterator := wsi.latestIndex
   105  	for {
   106  		if wsi.data[iterator].First == b {
   107  			wsi.data[iterator].Second = utilities.Some[Data](d)
   108  			return nil
   109  		}
   110  		iterator = wsi.nextIndex(iterator)
   111  		if iterator == wsi.latestIndex {
   112  			break
   113  		}
   114  	}
   115  	return fmt.Errorf("attempting to fill a bucket that does not exist")
   116  }
   117  
   118  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) Count() (some int, none int) {
   119  	wsi.lock.Lock()
   120  	defer wsi.lock.Unlock()
   121  	some = 0
   122  	none = 0
   123  	for _, v := range wsi.data {
   124  		if utilities.IsSome[Data](v.Second) {
   125  			some++
   126  		} else {
   127  			none++
   128  		}
   129  	}
   130  	return
   131  }
   132  
   133  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) Complete() bool {
   134  	wsi.lock.Lock()
   135  	defer wsi.lock.Unlock()
   136  	for _, v := range wsi.data {
   137  		if utilities.IsNone(v.Second) {
   138  			return false
   139  		}
   140  	}
   141  	return true
   142  }
   143  
   144  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) nextIndex(currentIndex int) int {
   145  	// Internal functions should be called with the lock held!
   146  	if wsi.lock.TryLock() {
   147  		panic("windowSeriesWindowOnlyImpl nextIndex called without lock held.")
   148  	}
   149  	return (currentIndex + 1) % wsi.windowSize
   150  }
   151  
   152  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) previousIndex(currentIndex int) int {
   153  	// Internal functions should be called with the lock held!
   154  	if wsi.lock.TryLock() {
   155  		panic("windowSeriesWindowOnlyImpl nextIndex called without lock held.")
   156  	}
   157  	nextIndex := currentIndex - 1
   158  	if nextIndex < 0 {
   159  		nextIndex += wsi.windowSize
   160  	}
   161  	return nextIndex
   162  }
   163  
   164  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) toArray() []utilities.Optional[Data] {
   165  	// Internal functions should be called with the lock held!
   166  	if wsi.lock.TryLock() {
   167  		panic("windowSeriesWindowOnlyImpl nextIndex called without lock held.")
   168  	}
   169  	result := make([]utilities.Optional[Data], wsi.windowSize)
   170  
   171  	var lowerTrimmingBound, upperTrimmingBound Bucket
   172  	hasBounds := false
   173  	if utilities.IsSome(wsi.lowerTrimmingBound) {
   174  		hasBounds = true
   175  		lowerTrimmingBound = utilities.GetSome(wsi.lowerTrimmingBound)
   176  		upperTrimmingBound = utilities.GetSome(wsi.upperTrimmingBound)
   177  		result = make([]utilities.Optional[Data],
   178  			int(upperTrimmingBound-lowerTrimmingBound)+1)
   179  	}
   180  
   181  	if wsi.empty {
   182  		return result
   183  	}
   184  	iterator := wsi.latestIndex
   185  	parallelIterator := 0
   186  	for {
   187  		if !hasBounds || (lowerTrimmingBound <= wsi.data[iterator].First &&
   188  			wsi.data[iterator].First <= upperTrimmingBound) {
   189  			result[parallelIterator] = wsi.data[iterator].Second
   190  			parallelIterator++
   191  		}
   192  		iterator = wsi.previousIndex(iterator)
   193  		if iterator == wsi.latestIndex {
   194  			break
   195  		}
   196  	}
   197  	return result
   198  }
   199  
   200  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) ExtractBoundedSeries() WindowSeries[Data, Bucket] {
   201  	wsi.lock.Lock()
   202  	defer wsi.lock.Unlock()
   203  
   204  	result := NewWindowSeries[Data, Bucket](WindowOnly, wsi.windowSize)
   205  
   206  	var lowerTrimmingBound, upperTrimmingBound Bucket
   207  	hasBounds := false
   208  	if utilities.IsSome(wsi.lowerTrimmingBound) {
   209  		hasBounds = true
   210  		lowerTrimmingBound = utilities.GetSome(wsi.lowerTrimmingBound)
   211  		upperTrimmingBound = utilities.GetSome(wsi.upperTrimmingBound)
   212  	}
   213  
   214  	for _, v := range wsi.data {
   215  		if hasBounds && (v.First < lowerTrimmingBound || v.First > upperTrimmingBound) {
   216  			continue
   217  		}
   218  		result.Reserve(v.First)
   219  		if utilities.IsSome(v.Second) {
   220  			result.Fill(v.First, utilities.GetSome(v.Second))
   221  		}
   222  	}
   223  	return result
   224  }
   225  
   226  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) GetValues() []utilities.Optional[Data] {
   227  	wsi.lock.Lock()
   228  	defer wsi.lock.Unlock()
   229  	return wsi.toArray()
   230  }
   231  
   232  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) GetType() WindowSeriesDuration {
   233  	return WindowOnly
   234  }
   235  
   236  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) ForEach(eacher func(b Bucket, d *utilities.Optional[Data])) {
   237  	wsi.lock.Lock()
   238  	defer wsi.lock.Unlock()
   239  	for _, v := range wsi.data {
   240  		eacher(v.First, &v.Second)
   241  	}
   242  }
   243  
   244  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) String() string {
   245  	wsi.lock.Lock()
   246  	defer wsi.lock.Unlock()
   247  	result := fmt.Sprintf("Window series (window (%d) only, latest index: %v): ", wsi.windowSize, wsi.latestIndex)
   248  	for _, v := range wsi.data {
   249  		valueString := "None"
   250  		if utilities.IsSome[Data](v.Second) {
   251  			valueString = fmt.Sprintf("%v", utilities.GetSome[Data](v.Second))
   252  		}
   253  		result += fmt.Sprintf("%v: %v; ", v.First, valueString)
   254  	}
   255  	return result
   256  }
   257  
   258  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) SetTrimmingBucketBounds(lower Bucket, upper Bucket) {
   259  	wsi.lowerTrimmingBound = utilities.Some[Bucket](lower)
   260  	wsi.upperTrimmingBound = utilities.Some[Bucket](upper)
   261  }
   262  
   263  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) ResetTrimmingBucketBounds() {
   264  	wsi.lowerTrimmingBound = utilities.None[Bucket]()
   265  	wsi.upperTrimmingBound = utilities.None[Bucket]()
   266  }
   267  
   268  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) GetBucketBounds() (Bucket, Bucket) {
   269  	wsi.lock.Lock()
   270  	defer wsi.lock.Unlock()
   271  	return wsi.data[wsi.nextIndex(wsi.latestIndex)].First, wsi.data[wsi.latestIndex].First
   272  }
   273  
   274  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) Append(appended *WindowSeries[Data, Bucket]) {
   275  	panic("Append is unimplemented on a window-only Window Series")
   276  }
   277  
   278  func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) BoundedAppend(appended *WindowSeries[Data, Bucket]) {
   279  	panic("BoundedAppend is unimplemented on a window-only Window Series")
   280  }
   281  
   282  func newWindowSeriesWindowOnlyImpl[Data any, Bucket utilities.Number](
   283  	windowSize int,
   284  ) *windowSeriesWindowOnlyImpl[Data, Bucket] {
   285  	result := windowSeriesWindowOnlyImpl[Data, Bucket]{windowSize: windowSize, latestIndex: 0, empty: true}
   286  
   287  	result.data = make([]utilities.Pair[Bucket, utilities.Optional[Data]], windowSize)
   288  
   289  	return &result
   290  }
   291  
   292  /*
   293   * End of WindowSeries interface methods.
   294   */
   295  
   296  type windowSeriesForeverImpl[Data any, Bucket utilities.Number] struct {
   297  	data               []utilities.Pair[Bucket, utilities.Optional[Data]]
   298  	empty              bool
   299  	lock               sync.RWMutex
   300  	lowerTrimmingBound utilities.Optional[Bucket]
   301  	upperTrimmingBound utilities.Optional[Bucket]
   302  }
   303  
   304  func (wsi *windowSeriesForeverImpl[Data, Bucket]) Reserve(b Bucket) error {
   305  	wsi.lock.Lock()
   306  	defer wsi.lock.Unlock()
   307  	if !wsi.empty && b <= wsi.data[len(wsi.data)-1].First {
   308  		fmt.Printf("reserving must be monotonically increasing: %v vs %v", b, wsi.data[len(wsi.data)-1].First)
   309  		return fmt.Errorf("reserving must be monotonically increasing")
   310  	}
   311  
   312  	wsi.empty = false
   313  	wsi.data = append(wsi.data, utilities.Pair[Bucket, utilities.Optional[Data]]{First: b, Second: utilities.None[Data]()})
   314  	return nil
   315  }
   316  
   317  func (wsi *windowSeriesForeverImpl[Data, Bucket]) Fill(b Bucket, d Data) error {
   318  	wsi.lock.Lock()
   319  	defer wsi.lock.Unlock()
   320  	for i := range wsi.data {
   321  		if wsi.data[i].First == b {
   322  			wsi.data[i].Second = utilities.Some[Data](d)
   323  			return nil
   324  		}
   325  	}
   326  	return fmt.Errorf("attempting to fill a bucket that does not exist")
   327  }
   328  
   329  func (wsi *windowSeriesForeverImpl[Data, Bucket]) ExtractBoundedSeries() WindowSeries[Data, Bucket] {
   330  	wsi.lock.Lock()
   331  	defer wsi.lock.Unlock()
   332  
   333  	var lowerTrimmingBound, upperTrimmingBound Bucket
   334  	hasBounds := false
   335  	if utilities.IsSome(wsi.lowerTrimmingBound) {
   336  		hasBounds = true
   337  		lowerTrimmingBound = utilities.GetSome(wsi.lowerTrimmingBound)
   338  		upperTrimmingBound = utilities.GetSome(wsi.upperTrimmingBound)
   339  	}
   340  
   341  	result := NewWindowSeries[Data, Bucket](Forever, 0)
   342  
   343  	for _, v := range wsi.data {
   344  		if hasBounds && (v.First < lowerTrimmingBound || v.First > upperTrimmingBound) {
   345  			continue
   346  		}
   347  		result.Reserve(v.First)
   348  		if utilities.IsSome(v.Second) {
   349  			result.Fill(v.First, utilities.GetSome(v.Second))
   350  		}
   351  	}
   352  	return result
   353  }
   354  
   355  func (wsi *windowSeriesForeverImpl[Data, Bucket]) GetValues() []utilities.Optional[Data] {
   356  	wsi.lock.Lock()
   357  	defer wsi.lock.Unlock()
   358  	result := make([]utilities.Optional[Data], len(wsi.data))
   359  
   360  	var lowerTrimmingBound, upperTrimmingBound Bucket
   361  	hasBounds := false
   362  	if utilities.IsSome(wsi.lowerTrimmingBound) {
   363  		hasBounds = true
   364  		lowerTrimmingBound = utilities.GetSome(wsi.lowerTrimmingBound)
   365  		upperTrimmingBound = utilities.GetSome(wsi.upperTrimmingBound)
   366  		result = make([]utilities.Optional[Data],
   367  			int(upperTrimmingBound-lowerTrimmingBound)+1)
   368  	}
   369  
   370  	index := 0
   371  	for _, v := range wsi.data {
   372  		if !hasBounds || (lowerTrimmingBound <= v.First && v.First <= upperTrimmingBound) {
   373  			result[index] = v.Second
   374  			index++
   375  		}
   376  	}
   377  
   378  	return result
   379  }
   380  
   381  func (wsi *windowSeriesForeverImpl[Data, Bucket]) Count() (some int, none int) {
   382  	wsi.lock.Lock()
   383  	defer wsi.lock.Unlock()
   384  	some = 0
   385  	none = 0
   386  	for _, v := range wsi.data {
   387  		if utilities.IsSome[Data](v.Second) {
   388  			some++
   389  		} else {
   390  			none++
   391  		}
   392  	}
   393  	return
   394  }
   395  
   396  func (wsi *windowSeriesForeverImpl[Data, Bucket]) Complete() bool {
   397  	wsi.lock.Lock()
   398  	defer wsi.lock.Unlock()
   399  	for _, v := range wsi.data {
   400  		if utilities.IsNone(v.Second) {
   401  			return false
   402  		}
   403  	}
   404  	return true
   405  }
   406  
   407  func (wsi *windowSeriesForeverImpl[Data, Bucket]) GetType() WindowSeriesDuration {
   408  	return Forever
   409  }
   410  
   411  func newWindowSeriesForeverImpl[Data any, Bucket utilities.Number]() *windowSeriesForeverImpl[Data, Bucket] {
   412  	result := windowSeriesForeverImpl[Data, Bucket]{
   413  		empty:              true,
   414  		lowerTrimmingBound: utilities.None[Bucket](),
   415  		upperTrimmingBound: utilities.None[Bucket](),
   416  	}
   417  
   418  	result.data = nil
   419  
   420  	return &result
   421  }
   422  
   423  func (wsi *windowSeriesForeverImpl[Data, Bucket]) ForEach(eacher func(b Bucket, d *utilities.Optional[Data])) {
   424  	wsi.lock.Lock()
   425  	defer wsi.lock.Unlock()
   426  	for _, v := range wsi.data {
   427  		eacher(v.First, &v.Second)
   428  	}
   429  }
   430  
   431  func (wsi *windowSeriesForeverImpl[Data, Bucket]) String() string {
   432  	wsi.lock.Lock()
   433  	defer wsi.lock.Unlock()
   434  	result := "Window series (forever): "
   435  	for _, v := range wsi.data {
   436  		valueString := "None"
   437  		if utilities.IsSome[Data](v.Second) {
   438  			valueString = fmt.Sprintf("%v", utilities.GetSome[Data](v.Second))
   439  		}
   440  		result += fmt.Sprintf("%v: %v; ", v.First, valueString)
   441  	}
   442  	return result
   443  }
   444  
   445  func (wsi *windowSeriesForeverImpl[Data, Bucket]) SetTrimmingBucketBounds(lower Bucket, upper Bucket) {
   446  	wsi.lowerTrimmingBound = utilities.Some[Bucket](lower)
   447  	wsi.upperTrimmingBound = utilities.Some[Bucket](upper)
   448  }
   449  
   450  func (wsi *windowSeriesForeverImpl[Data, Bucket]) ResetTrimmingBucketBounds() {
   451  	wsi.lowerTrimmingBound = utilities.None[Bucket]()
   452  	wsi.upperTrimmingBound = utilities.None[Bucket]()
   453  }
   454  
   455  func (wsi *windowSeriesForeverImpl[Data, Bucket]) GetBucketBounds() (Bucket, Bucket) {
   456  	wsi.lock.Lock()
   457  	defer wsi.lock.Unlock()
   458  	if wsi.empty {
   459  		return 0, 0
   460  	}
   461  	return wsi.data[0].First, wsi.data[len(wsi.data)-1].First
   462  }
   463  
   464  // Sort the data according to the bucket ids (ascending)
   465  func (wsi *windowSeriesForeverImpl[Data, Bucket]) sort() {
   466  	slices.SortFunc(wsi.data, func(left, right utilities.Pair[Bucket, utilities.Optional[Data]]) int {
   467  		return cmp.Compare(left.First, right.First)
   468  	})
   469  }
   470  
   471  func (wsi *windowSeriesForeverImpl[Data, Bucket]) Append(appended *WindowSeries[Data, Bucket]) {
   472  	result, ok := (*appended).(*windowSeriesForeverImpl[Data, Bucket])
   473  	if !ok {
   474  		panic("Cannot merge a forever window series with a non-forever window series.")
   475  	}
   476  	wsi.lock.Lock()
   477  	defer wsi.lock.Unlock()
   478  	result.lock.Lock()
   479  	defer result.lock.Unlock()
   480  
   481  	wsi.data = append(wsi.data, result.data...)
   482  	// Because the series that we are appending in may have overlapping buckets,
   483  	// we will sort them to maintain the invariant that the data items are sorted
   484  	// by bucket ids (increasing).
   485  	wsi.sort()
   486  }
   487  
   488  func (wsi *windowSeriesForeverImpl[Data, Bucket]) BoundedAppend(appended *WindowSeries[Data, Bucket]) {
   489  	result, ok := (*appended).(*windowSeriesForeverImpl[Data, Bucket])
   490  	if !ok {
   491  		panic("Cannot merge a forever window series with a non-forever window series.")
   492  	}
   493  	wsi.lock.Lock()
   494  	defer wsi.lock.Unlock()
   495  	result.lock.Lock()
   496  	defer result.lock.Unlock()
   497  
   498  	if utilities.IsNone(result.lowerTrimmingBound) ||
   499  		utilities.IsNone(result.upperTrimmingBound) {
   500  		wsi.sort()
   501  		wsi.data = append(wsi.data, result.data...)
   502  		return
   503  	} else {
   504  		lowerTrimmingBound := utilities.GetSome(result.lowerTrimmingBound)
   505  		upperTrimmingBound := utilities.GetSome(result.upperTrimmingBound)
   506  
   507  		toAppend := utilities.Filter(result.data, func(
   508  			element utilities.Pair[Bucket, utilities.Optional[Data]],
   509  		) bool {
   510  			bucket := element.First
   511  			return lowerTrimmingBound <= bucket && bucket <= upperTrimmingBound
   512  		})
   513  		wsi.data = append(wsi.data, toAppend...)
   514  	}
   515  	// Because the series that we are appending in may have overlapping buckets,
   516  	// we will sort them to maintain the invariant that the data items are sorted
   517  	// by bucket ids (increasing).
   518  	wsi.sort()
   519  }
   520  
   521  /*
   522   * End of WindowSeries interface methods.
   523   */
   524  
   525  func NewWindowSeries[Data any, Bucket utilities.Number](tipe WindowSeriesDuration, windowSize int) WindowSeries[Data, Bucket] {
   526  	if tipe == WindowOnly {
   527  		return newWindowSeriesWindowOnlyImpl[Data, Bucket](windowSize)
   528  	} else if tipe == Forever {
   529  		return newWindowSeriesForeverImpl[Data, Bucket]()
   530  	}
   531  	panic("Attempting to create a new window series with an invalid type.")
   532  }
   533  
   534  type NumericBucketGenerator[T utilities.Number] struct {
   535  	mt           sync.Mutex
   536  	currentValue T
   537  }
   538  
   539  func (bg *NumericBucketGenerator[T]) Generate() T {
   540  	bg.mt.Lock()
   541  	defer bg.mt.Unlock()
   542  
   543  	bg.currentValue++
   544  	return bg.currentValue
   545  }
   546  
   547  func NewNumericBucketGenerator[T utilities.Number](initialValue T) NumericBucketGenerator[T] {
   548  	return NumericBucketGenerator[T]{currentValue: initialValue}
   549  }