github.com/eth-easl/loader@v0.0.0-20230908084258-8a37e1d94279/pkg/generator/specification_test.go (about) 1 /* 2 * MIT License 3 * 4 * Copyright (c) 2023 EASL and the vHive community 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in all 14 * copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 */ 24 25 package generator 26 27 import ( 28 "fmt" 29 "math" 30 "os" 31 "os/exec" 32 "sync" 33 "testing" 34 35 "github.com/eth-easl/loader/pkg/common" 36 log "github.com/sirupsen/logrus" 37 ) 38 39 var testFunction = common.Function{ 40 RuntimeStats: &common.FunctionRuntimeStats{ 41 Average: 50, 42 Count: 100, 43 Minimum: 0, 44 Maximum: 100, 45 Percentile0: 0, 46 Percentile1: 1, 47 Percentile25: 25, 48 Percentile50: 50, 49 Percentile75: 75, 50 Percentile99: 99, 51 Percentile100: 100, 52 }, 53 MemoryStats: &common.FunctionMemoryStats{ 54 Average: 5000, 55 Count: 100, 56 Percentile1: 100, 57 Percentile5: 500, 58 Percentile25: 2500, 59 Percentile50: 5000, 60 Percentile75: 7500, 61 Percentile95: 9500, 62 Percentile99: 9900, 63 Percentile100: 10000, 64 }, 65 } 66 67 /* 68 TestSerialGenerateIAT tests the following scenarios: 69 70 - equidistant distribution within 1 minute and 5 minutes 71 - uniform distribution - spillover test, distribution test, single point 72 */ 73 func TestSerialGenerateIAT(t *testing.T) { 74 tests := []struct { 75 testName string 76 duration int // s 77 invocations []int 78 iatDistribution common.IatDistribution 79 shiftIAT bool 80 granularity common.TraceGranularity 81 expectedPoints [][]float64 // μs 82 testDistribution bool 83 }{ 84 { 85 testName: "no_invocations_equidistant", 86 invocations: []int{5}, 87 iatDistribution: common.Equidistant, 88 shiftIAT: false, 89 granularity: common.MinuteGranularity, 90 expectedPoints: [][]float64{}, 91 testDistribution: false, 92 }, 93 { 94 testName: "no_invocations_exponential", 95 invocations: []int{5}, 96 iatDistribution: common.Exponential, 97 shiftIAT: false, 98 granularity: common.MinuteGranularity, 99 expectedPoints: [][]float64{}, 100 testDistribution: false, 101 }, 102 { 103 testName: "no_invocations_exponential_shift", 104 invocations: []int{5}, 105 iatDistribution: common.Exponential, 106 shiftIAT: true, 107 granularity: common.MinuteGranularity, 108 expectedPoints: [][]float64{}, 109 testDistribution: false, 110 }, 111 { 112 testName: "one_invocations_exponential", 113 invocations: []int{1}, 114 iatDistribution: common.Exponential, 115 shiftIAT: false, 116 granularity: common.MinuteGranularity, 117 expectedPoints: [][]float64{{0, 60000000}}, 118 testDistribution: false, 119 }, 120 { 121 testName: "one_invocations_exponential_shift", 122 invocations: []int{1}, 123 iatDistribution: common.Exponential, 124 shiftIAT: true, 125 granularity: common.MinuteGranularity, 126 expectedPoints: [][]float64{{11689078.788397, 48310921.211603}}, 127 testDistribution: false, 128 }, 129 { 130 testName: "1min_5ipm_equidistant", 131 invocations: []int{5}, 132 iatDistribution: common.Equidistant, 133 shiftIAT: false, 134 granularity: common.MinuteGranularity, 135 expectedPoints: [][]float64{ 136 { 137 0, 138 12000000, 139 12000000, 140 12000000, 141 12000000, 142 12000000, 143 }, 144 }, 145 testDistribution: false, 146 }, 147 { 148 testName: "5min_5ipm_equidistant", 149 invocations: []int{5, 5, 5, 5, 5}, 150 iatDistribution: common.Equidistant, 151 shiftIAT: false, 152 granularity: common.MinuteGranularity, 153 expectedPoints: [][]float64{ 154 { 155 // min 1 156 0, 157 12000000, 158 12000000, 159 12000000, 160 12000000, 161 12000000, 162 }, 163 { 164 // min 2 165 0, 166 12000000, 167 12000000, 168 12000000, 169 12000000, 170 12000000, 171 }, 172 { 173 // min 3 174 0, 175 12000000, 176 12000000, 177 12000000, 178 12000000, 179 12000000, 180 }, 181 { 182 // min 4 183 0, 184 12000000, 185 12000000, 186 12000000, 187 12000000, 188 12000000, 189 }, 190 { 191 // min 5 192 0, 193 12000000, 194 12000000, 195 12000000, 196 12000000, 197 12000000, 198 }, 199 }, 200 testDistribution: false, 201 }, 202 { 203 testName: "1min_25ipm_uniform_shift", 204 invocations: []int{25}, 205 iatDistribution: common.Uniform, 206 shiftIAT: true, 207 granularity: common.MinuteGranularity, 208 expectedPoints: [][]float64{ 209 { 210 1193000.964808, 211 622524.819620, 212 2161625.000293, 213 2467158.610498, 214 3161216.965226, 215 120925.338482, 216 3461650.068734, 217 3681772.563419, 218 3591929.298027, 219 3062124.611863, 220 3223056.707367, 221 3042558.740794, 222 2099765.805752, 223 375008.683565, 224 3979289.345154, 225 1636869.797787, 226 1169442.102841, 227 2380243.616007, 228 2453428.612640, 229 1704231.066313, 230 42074.939233, 231 3115643.026141, 232 3460047.444726, 233 2849475.331077, 234 3187546.011741, 235 1757390.527891, 236 }, 237 }, 238 testDistribution: false, 239 }, 240 { 241 testName: "1min_1000000ipm_uniform", 242 invocations: []int{1000000}, 243 iatDistribution: common.Uniform, 244 shiftIAT: false, 245 granularity: common.MinuteGranularity, 246 expectedPoints: nil, 247 testDistribution: true, 248 }, 249 { 250 testName: "1min_25ipm_exponential", 251 invocations: []int{25}, 252 iatDistribution: common.Exponential, 253 shiftIAT: false, 254 granularity: common.MinuteGranularity, 255 expectedPoints: [][]float64{ 256 { 257 0, 258 1311929.341329, 259 3685871.430916, 260 1626476.996595, 261 556382.014270, 262 30703.105102, 263 3988584.779392, 264 2092271.836277, 265 1489855.293253, 266 3025094.199801, 267 2366337.4678820, 268 40667.5994150, 269 2778945.4898700, 270 4201722.5747150, 271 5339421.1460450, 272 3362048.1584080, 273 939526.5236740, 274 1113771.3822940, 275 4439636.5676460, 276 4623026.1098310, 277 2082985.6557600, 278 45937.1189860, 279 4542253.8756200, 280 2264414.9939920, 281 3872560.8680640, 282 179575.4708620, 283 }, 284 }, 285 testDistribution: false, 286 }, 287 { 288 testName: "1min_25ipm_exponential_shift", 289 invocations: []int{25}, 290 iatDistribution: common.Exponential, 291 shiftIAT: true, 292 granularity: common.MinuteGranularity, 293 expectedPoints: [][]float64{ 294 { 295 697544.471476, 296 5339421.146045, 297 3362048.158408, 298 939526.523674, 299 1113771.382294, 300 4439636.567646, 301 4623026.109831, 302 2082985.655760, 303 45937.118986, 304 4542253.875620, 305 2264414.993992, 306 3872560.868064, 307 179575.470862, 308 1311929.341329, 309 3685871.430916, 310 1626476.996595, 311 556382.014270, 312 30703.105102, 313 3988584.779392, 314 2092271.836277, 315 1489855.293253, 316 3025094.199801, 317 2366337.4678820, 318 40667.5994150, 319 2778945.4898700, 320 3504178.103239, 321 }, 322 }, 323 testDistribution: false, 324 }, 325 { 326 testName: "1min_1000000ipm_exponential", 327 invocations: []int{1000000}, 328 iatDistribution: common.Exponential, 329 shiftIAT: false, 330 granularity: common.MinuteGranularity, 331 expectedPoints: nil, 332 testDistribution: true, 333 }, 334 { 335 testName: "2sec_5qps_equidistant", 336 invocations: []int{5, 4, 2}, 337 iatDistribution: common.Equidistant, 338 shiftIAT: false, 339 granularity: common.SecondGranularity, 340 expectedPoints: [][]float64{ 341 { 342 // second 1 343 0, 344 200000, 345 200000, 346 200000, 347 200000, 348 200000, 349 }, 350 { 351 // second 2 352 0, 353 250000, 354 250000, 355 250000, 356 250000, 357 }, 358 { 359 // second 3 360 0, 361 500000, 362 500000, 363 }, 364 }, 365 testDistribution: false, 366 }, 367 } 368 369 var seed int64 = 123456789 370 epsilon := 10e-3 371 372 for _, test := range tests { 373 t.Run(test.testName, func(t *testing.T) { 374 sg := NewSpecificationGenerator(seed) 375 376 testFunction.InvocationStats = &common.FunctionInvocationStats{Invocations: test.invocations} 377 spec := sg.GenerateInvocationData(&testFunction, test.iatDistribution, test.shiftIAT, test.granularity) 378 IAT, nonScaledDuration := spec.IAT, spec.RawDuration 379 380 failed := false 381 382 if hasSpillover(IAT, test.granularity) { 383 t.Error("Generated IAT does not fit in the within the minute time window.") 384 } 385 386 if test.expectedPoints != nil { 387 for min := 0; min < len(test.expectedPoints); min++ { 388 for i := 0; i < len(test.expectedPoints[min]); i++ { 389 if len(test.expectedPoints[min]) != len(IAT[min]) { 390 log.Debug(fmt.Sprintf("wrong number of IATs in the minute, got: %d, expected: %d\n", len(IAT[min]), len(test.expectedPoints[min]))) 391 392 failed = true 393 break 394 } 395 if math.Abs(IAT[min][i]-test.expectedPoints[min][i]) > epsilon { 396 log.Debug(fmt.Sprintf("got: %f, expected: %f\n", IAT[min][i], test.expectedPoints[min][i])) 397 398 failed = true 399 // no break statement for debugging purpose 400 } 401 } 402 } 403 404 if failed { 405 t.Error("Test " + test.testName + " has failed due to incorrectly generated IAT.") 406 } 407 } 408 409 if test.testDistribution && test.iatDistribution != common.Equidistant && 410 !checkDistribution(IAT, nonScaledDuration, test.iatDistribution) { 411 412 t.Error("The provided sample does not satisfy the given distribution.") 413 } 414 }) 415 } 416 } 417 418 func hasSpillover(data [][]float64, granularity common.TraceGranularity) bool { 419 for min := 0; min < len(data); min++ { 420 sum := 0.0 421 epsilon := 1e-3 422 423 for i := 0; i < len(data[min]); i++ { 424 sum += data[min][i] 425 } 426 427 log.Debug(fmt.Sprintf("Total execution time: %f μs\n", sum)) 428 429 spilloverThreshold := common.OneSecondInMicroseconds 430 if granularity == common.MinuteGranularity { 431 spilloverThreshold *= 60 432 } 433 434 if math.Abs(sum-spilloverThreshold) > epsilon { 435 return true 436 } 437 } 438 439 return false 440 } 441 442 func checkDistribution(data [][]float64, nonScaledDuration []float64, distribution common.IatDistribution) bool { 443 // PREPARING ARGUMENTS 444 var dist string 445 inputFile := "test_data.txt" 446 447 switch distribution { 448 case common.Uniform: 449 dist = "uniform" 450 case common.Exponential: 451 dist = "exponential" 452 default: 453 log.Fatal("Unsupported distribution check") 454 } 455 456 result := false 457 458 for min := 0; min < len(data); min++ { 459 // WRITING DISTRIBUTION TO TEST 460 f, err := os.Create(inputFile) 461 if err != nil { 462 log.Fatal("Cannot write data for distribution tests.") 463 } 464 465 defer f.Close() 466 467 for _, iat := range data[min] { 468 _, _ = f.WriteString(fmt.Sprintf("%f\n", iat)) 469 } 470 471 // SETTING UP THE TESTING SCRIPT 472 args := []string{"specification_statistical_test.py", dist, inputFile, fmt.Sprintf("%f", nonScaledDuration[min])} 473 statisticalTest := exec.Command("python3", args...) 474 475 // CALLING THE TESTING SCRIPT AND PROCESSING ITS RESULTS 476 // NOTE: the script generates a histogram in PNG format that can be used as a sanity-check 477 if err := statisticalTest.Wait(); err != nil { 478 output, _ := statisticalTest.Output() 479 log.Debug(string(output)) 480 481 switch statisticalTest.ProcessState.ExitCode() { 482 case 0: 483 result = true // distribution satisfied 484 case 1: 485 return false // distribution not satisfied 486 case 2: 487 log.Fatal("Unsupported distribution by the statistical test.") 488 } 489 } 490 } 491 492 return result 493 } 494 495 func TestGenerateExecutionSpecifications(t *testing.T) { 496 tests := []struct { 497 testName string 498 iterations int 499 granularity common.TraceGranularity 500 expected map[common.RuntimeSpecification]struct{} 501 }{ 502 { 503 testName: "exec_spec_run_1", 504 iterations: 1, 505 granularity: common.MinuteGranularity, 506 expected: map[common.RuntimeSpecification]struct{}{ 507 common.RuntimeSpecification{Runtime: 89, Memory: 8217}: {}, 508 }, 509 }, 510 { 511 testName: "exec_spec_run_5", 512 iterations: 5, 513 granularity: common.MinuteGranularity, 514 expected: map[common.RuntimeSpecification]struct{}{ 515 common.RuntimeSpecification{Runtime: 89, Memory: 8217}: {}, 516 common.RuntimeSpecification{Runtime: 18, Memory: 9940}: {}, 517 common.RuntimeSpecification{Runtime: 50, Memory: 1222}: {}, 518 common.RuntimeSpecification{Runtime: 85, Memory: 7836}: {}, 519 common.RuntimeSpecification{Runtime: 67, Memory: 7490}: {}, 520 }, 521 }, 522 { 523 testName: "exec_spec_run_25", 524 iterations: 25, 525 granularity: common.MinuteGranularity, 526 expected: map[common.RuntimeSpecification]struct{}{ 527 common.RuntimeSpecification{Runtime: 89, Memory: 8217}: {}, 528 common.RuntimeSpecification{Runtime: 18, Memory: 9940}: {}, 529 common.RuntimeSpecification{Runtime: 67, Memory: 7490}: {}, 530 common.RuntimeSpecification{Runtime: 50, Memory: 1222}: {}, 531 common.RuntimeSpecification{Runtime: 90, Memory: 193}: {}, 532 common.RuntimeSpecification{Runtime: 85, Memory: 7836}: {}, 533 common.RuntimeSpecification{Runtime: 24, Memory: 4875}: {}, 534 common.RuntimeSpecification{Runtime: 42, Memory: 5785}: {}, 535 common.RuntimeSpecification{Runtime: 82, Memory: 6819}: {}, 536 common.RuntimeSpecification{Runtime: 22, Memory: 9838}: {}, 537 common.RuntimeSpecification{Runtime: 11, Memory: 2223}: {}, 538 common.RuntimeSpecification{Runtime: 81, Memory: 2832}: {}, 539 common.RuntimeSpecification{Runtime: 99, Memory: 5305}: {}, 540 common.RuntimeSpecification{Runtime: 99, Memory: 6582}: {}, 541 common.RuntimeSpecification{Runtime: 58, Memory: 4581}: {}, 542 common.RuntimeSpecification{Runtime: 25, Memory: 1813}: {}, 543 common.RuntimeSpecification{Runtime: 79, Memory: 9819}: {}, 544 common.RuntimeSpecification{Runtime: 2, Memory: 1660}: {}, 545 common.RuntimeSpecification{Runtime: 98, Memory: 3110}: {}, 546 common.RuntimeSpecification{Runtime: 18, Memory: 6178}: {}, 547 common.RuntimeSpecification{Runtime: 3, Memory: 7770}: {}, 548 common.RuntimeSpecification{Runtime: 100, Memory: 4063}: {}, 549 common.RuntimeSpecification{Runtime: 6, Memory: 5022}: {}, 550 common.RuntimeSpecification{Runtime: 35, Memory: 8003}: {}, 551 common.RuntimeSpecification{Runtime: 20, Memory: 3544}: {}, 552 }, 553 }, 554 } 555 556 var seed int64 = 123456789 557 558 for _, test := range tests { 559 t.Run(test.testName, func(t *testing.T) { 560 sg := NewSpecificationGenerator(seed) 561 562 results := make(map[common.RuntimeSpecification]struct{}) 563 564 wg := sync.WaitGroup{} 565 mutex := sync.Mutex{} 566 567 testFunction.InvocationStats = &common.FunctionInvocationStats{ 568 Invocations: []int{test.iterations}, 569 } 570 // distribution is irrelevant here 571 spec := sg.GenerateInvocationData(&testFunction, common.Equidistant, false, test.granularity).RuntimeSpecification 572 573 for i := 0; i < test.iterations; i++ { 574 wg.Add(1) 575 576 index := i 577 go func() { 578 runtime, memory := spec[0][index].Runtime, spec[0][index].Memory 579 580 mutex.Lock() 581 results[common.RuntimeSpecification{Runtime: runtime, Memory: memory}] = struct{}{} 582 mutex.Unlock() 583 584 wg.Done() 585 }() 586 } 587 588 wg.Wait() 589 590 for got := range results { 591 if _, ok := results[got]; !ok { 592 t.Error("Missing value for runtime specification.") 593 } 594 } 595 }) 596 } 597 }