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  }