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  }