github.com/eth-easl/loader@v0.0.0-20230908084258-8a37e1d94279/pkg/generator/specification.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 "math/rand" 29 30 "github.com/eth-easl/loader/pkg/common" 31 log "github.com/sirupsen/logrus" 32 ) 33 34 type SpecificationGenerator struct { 35 iatRand *rand.Rand 36 specRand *rand.Rand 37 } 38 39 func NewSpecificationGenerator(seed int64) *SpecificationGenerator { 40 return &SpecificationGenerator{ 41 iatRand: rand.New(rand.NewSource(seed)), 42 specRand: rand.New(rand.NewSource(seed)), 43 } 44 } 45 46 ////////////////////////////////////////////////// 47 // IAT GENERATION 48 ////////////////////////////////////////////////// 49 50 // generateIATPerGranularity generates IAT for one minute based on given number of invocations and the given distribution 51 func (s *SpecificationGenerator) generateIATPerGranularity(numberOfInvocations int, iatDistribution common.IatDistribution, shiftIAT bool, granularity common.TraceGranularity) ([]float64, float64) { 52 if numberOfInvocations == 0 { 53 return []float64{}, 0.0 54 } 55 56 var iatResult []float64 57 totalDuration := 0.0 // total non-scaled duration 58 59 for i := 0; i < numberOfInvocations; i++ { 60 var iat float64 61 62 switch iatDistribution { 63 case common.Exponential: 64 // NOTE: Serverless in the Wild - pg. 6, paragraph 1 65 iat = s.iatRand.ExpFloat64() 66 case common.Uniform: 67 iat = s.iatRand.Float64() 68 case common.Equidistant: 69 equalDistance := common.OneSecondInMicroseconds / float64(numberOfInvocations) 70 if granularity == common.MinuteGranularity { 71 equalDistance *= 60.0 72 } 73 74 iat = equalDistance 75 default: 76 log.Fatal("Unsupported IAT distribution.") 77 } 78 79 if iat == 0 { 80 // No nanoseconds-level granularity, only microsecond 81 log.Fatal("Generated IAT is equal to zero (unsupported). Consider increasing the clock precision.") 82 } 83 84 iatResult = append(iatResult, iat) 85 totalDuration += iat 86 } 87 88 if iatDistribution == common.Uniform || iatDistribution == common.Exponential { 89 // Uniform: we need to scale IAT from [0, 1) to [0, 60 seconds) 90 // Exponential: we need to scale IAT from [0, +MaxFloat64) to [0, 60 seconds) 91 for i := 0; i < len(iatResult); i++ { 92 // how much does the IAT contributes to the total IAT sum 93 iatResult[i] = iatResult[i] / totalDuration 94 // convert relative contribution to absolute on 60 second interval 95 iatResult[i] = iatResult[i] * common.OneSecondInMicroseconds 96 97 if granularity == common.MinuteGranularity { 98 iatResult[i] *= 60.0 99 } 100 } 101 } 102 103 if shiftIAT { 104 // Cut the IAT array at random place to move the first invocation from the beginning of the minute 105 split := s.iatRand.Float64() * common.OneSecondInMicroseconds 106 if granularity == common.MinuteGranularity { 107 split *= 60.0 108 } 109 sum, i := 0.0, 0 110 for ; i < len(iatResult); i++ { 111 sum += iatResult[i] 112 if sum > split { 113 break 114 } 115 } 116 beginningIAT := sum - split 117 endIAT := iatResult[i] - beginningIAT 118 finalIAT := append([]float64{beginningIAT}, iatResult[i+1:]...) 119 finalIAT = append(finalIAT, iatResult[:i]...) 120 iatResult = append(finalIAT, endIAT) 121 } else { 122 iatResult = append([]float64{0.0}, iatResult...) 123 } 124 125 return iatResult, totalDuration 126 } 127 128 // GenerateIAT generates IAT according to the given distribution. Number of minutes is the length of invocationsPerMinute array 129 func (s *SpecificationGenerator) generateIAT(invocationsPerMinute []int, iatDistribution common.IatDistribution, shiftIAT bool, granularity common.TraceGranularity) (common.IATMatrix, common.ProbabilisticDuration) { 130 var IAT [][]float64 131 var nonScaledDuration []float64 132 133 numberOfMinutes := len(invocationsPerMinute) 134 for i := 0; i < numberOfMinutes; i++ { 135 minuteIAT, duration := s.generateIATPerGranularity(invocationsPerMinute[i], iatDistribution, shiftIAT, granularity) 136 137 IAT = append(IAT, minuteIAT) 138 nonScaledDuration = append(nonScaledDuration, duration) 139 } 140 141 return IAT, nonScaledDuration 142 } 143 144 func (s *SpecificationGenerator) GenerateInvocationData(function *common.Function, iatDistribution common.IatDistribution, shiftIAT bool, granularity common.TraceGranularity) *common.FunctionSpecification { 145 invocationsPerMinute := function.InvocationStats.Invocations 146 147 // Generating IAT 148 iat, rawDuration := s.generateIAT(invocationsPerMinute, iatDistribution, shiftIAT, granularity) 149 150 // Generating runtime specifications 151 var runtimeMatrix common.RuntimeSpecificationMatrix 152 for i := 0; i < len(invocationsPerMinute); i++ { 153 var row []common.RuntimeSpecification 154 155 for j := 0; j < invocationsPerMinute[i]; j++ { 156 row = append(row, s.generateExecutionSpecs(function)) 157 } 158 159 runtimeMatrix = append(runtimeMatrix, row) 160 } 161 162 return &common.FunctionSpecification{ 163 IAT: iat, 164 RawDuration: rawDuration, 165 RuntimeSpecification: runtimeMatrix, 166 } 167 } 168 169 ////////////////////////////////////////////////// 170 // RUNTIME AND MEMORY GENERATION 171 ////////////////////////////////////////////////// 172 173 // Choose a random number in between. Not thread safe. 174 func (s *SpecificationGenerator) randIntBetween(min, max float64) int { 175 intMin, intMax := int(min), int(max) 176 177 if intMax < intMin { 178 log.Fatal("Invalid runtime/memory specification.") 179 } 180 181 if intMax == intMin { 182 return intMin 183 } else { 184 return s.specRand.Intn(intMax-intMin) + intMin 185 } 186 } 187 188 // Should be called only when specRand is locked with its mutex 189 func (s *SpecificationGenerator) determineExecutionSpecSeedQuantiles() (float64, float64) { 190 //* Generate uniform quantiles in [0, 1). 191 runQtl := s.specRand.Float64() 192 memQtl := s.specRand.Float64() 193 194 return runQtl, memQtl 195 } 196 197 // Should be called only when specRand is locked with its mutex 198 func (s *SpecificationGenerator) generateExecuteSpec(runQtl float64, runStats *common.FunctionRuntimeStats) (runtime int) { 199 switch { 200 case runQtl == 0: 201 runtime = int(runStats.Percentile0) 202 case runQtl <= 0.01: 203 runtime = s.randIntBetween(runStats.Percentile0, runStats.Percentile1) 204 case runQtl <= 0.25: 205 runtime = s.randIntBetween(runStats.Percentile1, runStats.Percentile25) 206 case runQtl <= 0.50: 207 runtime = s.randIntBetween(runStats.Percentile25, runStats.Percentile50) 208 case runQtl <= 0.75: 209 runtime = s.randIntBetween(runStats.Percentile50, runStats.Percentile75) 210 case runQtl <= 0.99: 211 runtime = s.randIntBetween(runStats.Percentile75, runStats.Percentile99) 212 case runQtl < 1: 213 runtime = s.randIntBetween(runStats.Percentile99, runStats.Percentile100) 214 } 215 216 return runtime 217 } 218 219 // Should be called only when specRand is locked with its mutex 220 func (s *SpecificationGenerator) generateMemorySpec(memQtl float64, memStats *common.FunctionMemoryStats) (memory int) { 221 switch { 222 case memQtl <= 0.01: 223 memory = int(memStats.Percentile1) 224 case memQtl <= 0.05: 225 memory = s.randIntBetween(memStats.Percentile1, memStats.Percentile5) 226 case memQtl <= 0.25: 227 memory = s.randIntBetween(memStats.Percentile5, memStats.Percentile25) 228 case memQtl <= 0.50: 229 memory = s.randIntBetween(memStats.Percentile25, memStats.Percentile50) 230 case memQtl <= 0.75: 231 memory = s.randIntBetween(memStats.Percentile50, memStats.Percentile75) 232 case memQtl <= 0.95: 233 memory = s.randIntBetween(memStats.Percentile75, memStats.Percentile95) 234 case memQtl <= 0.99: 235 memory = s.randIntBetween(memStats.Percentile95, memStats.Percentile99) 236 case memQtl < 1: 237 memory = s.randIntBetween(memStats.Percentile99, memStats.Percentile100) 238 } 239 240 return memory 241 } 242 243 func (s *SpecificationGenerator) generateExecutionSpecs(function *common.Function) common.RuntimeSpecification { 244 runStats, memStats := function.RuntimeStats, function.MemoryStats 245 if runStats.Count <= 0 || memStats.Count <= 0 { 246 log.Fatal("Invalid duration or memory specification of the function '" + function.Name + "'.") 247 } 248 249 runQtl, memQtl := s.determineExecutionSpecSeedQuantiles() 250 runtime := common.MinOf(common.MaxExecTimeMilli, common.MaxOf(common.MinExecTimeMilli, s.generateExecuteSpec(runQtl, runStats))) 251 memory := common.MinOf(common.MaxMemQuotaMib, common.MaxOf(common.MinMemQuotaMib, s.generateMemorySpec(memQtl, memStats))) 252 253 return common.RuntimeSpecification{ 254 Runtime: runtime, 255 Memory: memory, 256 } 257 }