github.com/eth-easl/loader@v0.0.0-20230908084258-8a37e1d94279/pkg/trace/parser.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 trace 26 27 import ( 28 "encoding/csv" 29 "fmt" 30 "github.com/eth-easl/loader/pkg/common" 31 "github.com/gocarina/gocsv" 32 "io" 33 "math/rand" 34 "os" 35 "strconv" 36 "strings" 37 "time" 38 39 log "github.com/sirupsen/logrus" 40 ) 41 42 type AzureTraceParser struct { 43 DirectoryPath string 44 45 duration int 46 functionNameGenerator *rand.Rand 47 } 48 49 func NewAzureParser(directoryPath string, totalDuration int) *AzureTraceParser { 50 return &AzureTraceParser{ 51 DirectoryPath: directoryPath, 52 53 duration: totalDuration, 54 functionNameGenerator: rand.New(rand.NewSource(time.Now().UnixNano())), 55 } 56 } 57 58 func createRuntimeMap(runtime *[]common.FunctionRuntimeStats) map[string]*common.FunctionRuntimeStats { 59 result := make(map[string]*common.FunctionRuntimeStats) 60 61 for i := 0; i < len(*runtime); i++ { 62 result[(*runtime)[i].HashFunction] = &(*runtime)[i] 63 } 64 65 return result 66 } 67 68 func createMemoryMap(runtime *[]common.FunctionMemoryStats) map[string]*common.FunctionMemoryStats { 69 result := make(map[string]*common.FunctionMemoryStats) 70 71 for i := 0; i < len(*runtime); i++ { 72 result[(*runtime)[i].HashFunction] = &(*runtime)[i] 73 } 74 75 return result 76 } 77 78 func (p *AzureTraceParser) extractFunctions(invocations *[]common.FunctionInvocationStats, 79 runtime *[]common.FunctionRuntimeStats, memory *[]common.FunctionMemoryStats) []*common.Function { 80 81 var result []*common.Function 82 83 runtimeByHashFunction := createRuntimeMap(runtime) 84 memoryByHashFunction := createMemoryMap(memory) 85 86 for i := 0; i < len(*invocations); i++ { 87 invocationStats := (*invocations)[i] 88 89 function := &common.Function{ 90 Name: fmt.Sprintf("%s-%d-%d", common.FunctionNamePrefix, i, p.functionNameGenerator.Uint64()), 91 92 InvocationStats: &invocationStats, 93 RuntimeStats: runtimeByHashFunction[invocationStats.HashFunction], 94 MemoryStats: memoryByHashFunction[invocationStats.HashFunction], 95 } 96 97 result = append(result, function) 98 } 99 100 return result 101 } 102 103 func (p *AzureTraceParser) Parse() []*common.Function { 104 invocationPath := p.DirectoryPath + "/invocations.csv" 105 runtimePath := p.DirectoryPath + "/durations.csv" 106 memoryPath := p.DirectoryPath + "/memory.csv" 107 108 invocationTrace := parseInvocationTrace(invocationPath, p.duration) 109 runtimeTrace := parseRuntimeTrace(runtimePath) 110 memoryTrace := parseMemoryTrace(memoryPath) 111 112 return p.extractFunctions(invocationTrace, runtimeTrace, memoryTrace) 113 } 114 115 func parseInvocationTrace(traceFile string, traceDuration int) *[]common.FunctionInvocationStats { 116 log.Debugf("Parsing function invocation trace %s (duration: %d min)", traceFile, traceDuration) 117 118 // Fit duration on (0, 1440] interval 119 traceDuration = common.MaxOf(common.MinOf(traceDuration, 1440), 1) 120 121 var result []common.FunctionInvocationStats 122 123 invocationIndices := make([][]int, traceDuration) 124 totalInvocations := make([]int, traceDuration) 125 126 csvfile, err := os.Open(traceFile) 127 if err != nil { 128 log.Fatal("Failed to open invocation CSV file.", err) 129 } 130 131 reader := csv.NewReader(csvfile) 132 133 rowID := -1 134 hashOwnerIndex, hashAppIndex, hashFunctionIndex, invocationColumnIndex := -1, -1, -1, -1 135 136 for { 137 record, err := reader.Read() 138 139 if err != nil { 140 if err == io.EOF { 141 break 142 } 143 log.Fatal(err) 144 } 145 146 if rowID == -1 { 147 // Parse header 148 for i := 0; i < 4; i++ { 149 switch strings.ToLower(record[i]) { 150 case "hashowner": 151 hashOwnerIndex = i 152 case "hashapp": 153 hashAppIndex = i 154 case "hashfunction": 155 hashFunctionIndex = i 156 case "trigger": //! Unused field. 157 invocationColumnIndex = i + 1 158 } 159 } 160 161 if hashOwnerIndex == -1 || hashAppIndex == -1 || hashFunctionIndex == -1 { 162 log.Fatal("Invocation trace does not contain at least one of the hashes.") 163 } 164 165 if invocationColumnIndex == -1 { 166 invocationColumnIndex = 3 167 } 168 } else { 169 // Parse invocations 170 var invocations []int 171 172 for i := invocationColumnIndex; i < invocationColumnIndex+traceDuration; i++ { 173 minute := i - invocationColumnIndex 174 num, err := strconv.Atoi(record[i]) 175 common.Check(err) 176 177 invocations = append(invocations, num) 178 179 for j := 0; j < num; j++ { 180 invocationIndices[minute] = append(invocationIndices[minute], rowID) 181 } 182 totalInvocations[minute] = totalInvocations[minute] + num 183 } 184 185 result = append(result, common.FunctionInvocationStats{ 186 HashOwner: record[hashOwnerIndex], 187 HashApp: record[hashAppIndex], 188 HashFunction: record[hashFunctionIndex], 189 Trigger: record[invocationColumnIndex-1], 190 Invocations: invocations, 191 }) 192 } 193 194 rowID++ 195 } 196 197 return &result 198 } 199 200 func parseRuntimeTrace(traceFile string) *[]common.FunctionRuntimeStats { 201 log.Debugf("Parsing function duration trace: %s\n", traceFile) 202 203 f, err := os.Open(traceFile) 204 if err != nil { 205 log.Fatal("Failed to open trace runtime specification file.") 206 } 207 defer f.Close() 208 209 var runtime []common.FunctionRuntimeStats 210 err = gocsv.UnmarshalFile(f, &runtime) 211 if err != nil { 212 log.Fatal("Failed to parse trace runtime specification.") 213 } 214 215 return &runtime 216 } 217 218 func parseMemoryTrace(traceFile string) *[]common.FunctionMemoryStats { 219 log.Infof("Parsing function memory trace: %s", traceFile) 220 221 f, err := os.Open(traceFile) 222 if err != nil { 223 log.Fatal("Failed to open trace memory specification file.") 224 } 225 defer f.Close() 226 227 var memory []common.FunctionMemoryStats 228 err = gocsv.UnmarshalFile(f, &memory) 229 if err != nil { 230 log.Fatal("Failed to parse trace runtime specification.") 231 } 232 233 return &memory 234 }