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 }