github.com/eth-easl/loader@v0.0.0-20230908084258-8a37e1d94279/pkg/driver/trace_driver.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 driver
    26  
    27  import (
    28  	"encoding/csv"
    29  	"encoding/json"
    30  	"fmt"
    31  	"math"
    32  	"os"
    33  	"sync"
    34  	"sync/atomic"
    35  	"time"
    36  
    37  	"strconv"
    38  
    39  	"github.com/eth-easl/loader/pkg/common"
    40  	"github.com/eth-easl/loader/pkg/config"
    41  	"github.com/eth-easl/loader/pkg/generator"
    42  	mc "github.com/eth-easl/loader/pkg/metric"
    43  	"github.com/eth-easl/loader/pkg/trace"
    44  	"github.com/gocarina/gocsv"
    45  	log "github.com/sirupsen/logrus"
    46  )
    47  
    48  type DriverConfiguration struct {
    49  	LoaderConfiguration *config.LoaderConfiguration
    50  	IATDistribution     common.IatDistribution
    51  	ShiftIAT            bool // shift the invocations inside minute
    52  	TraceGranularity    common.TraceGranularity
    53  	TraceDuration       int // in minutes
    54  
    55  	YAMLPath string
    56  	TestMode bool
    57  
    58  	Functions []*common.Function
    59  }
    60  
    61  type Driver struct {
    62  	Configuration          *DriverConfiguration
    63  	SpecificationGenerator *generator.SpecificationGenerator
    64  }
    65  
    66  func NewDriver(driverConfig *DriverConfiguration) *Driver {
    67  	return &Driver{
    68  		Configuration:          driverConfig,
    69  		SpecificationGenerator: generator.NewSpecificationGenerator(driverConfig.LoaderConfiguration.Seed),
    70  	}
    71  }
    72  
    73  func (c *DriverConfiguration) WithWarmup() bool {
    74  	if c.LoaderConfiguration.WarmupDuration > 0 {
    75  		return true
    76  	} else {
    77  		return false
    78  	}
    79  }
    80  
    81  // ///////////////////////////////////////
    82  // HELPER METHODS
    83  // ///////////////////////////////////////
    84  func (d *Driver) outputFilename(name string) string {
    85  	return fmt.Sprintf("%s_%s_%d.csv", d.Configuration.LoaderConfiguration.OutputPathPrefix, name, d.Configuration.TraceDuration)
    86  }
    87  
    88  func (d *Driver) runCSVWriter(records chan interface{}, filename string, writerDone *sync.WaitGroup) {
    89  	log.Debugf("Starting writer for %s", filename)
    90  
    91  	file, err := os.Create(filename)
    92  	common.Check(err)
    93  	defer file.Close()
    94  
    95  	writer := gocsv.NewSafeCSVWriter(csv.NewWriter(file))
    96  	if err := gocsv.MarshalChan(records, writer); err != nil {
    97  		log.Fatal(err)
    98  	}
    99  
   100  	writerDone.Done()
   101  }
   102  
   103  /////////////////////////////////////////
   104  // METRICS SCRAPPERS
   105  /////////////////////////////////////////
   106  
   107  func (d *Driver) CreateMetricsScrapper(interval time.Duration,
   108  	signalReady *sync.WaitGroup, finishCh chan int, allRecordsWritten *sync.WaitGroup) func() {
   109  	timer := time.NewTicker(interval)
   110  
   111  	return func() {
   112  		signalReady.Done()
   113  		knStatRecords := make(chan interface{}, 100)
   114  		scaleRecords := make(chan interface{}, 100)
   115  		writerDone := sync.WaitGroup{}
   116  
   117  		clusterUsageFile, err := os.Create(d.outputFilename("cluster_usage"))
   118  		common.Check(err)
   119  		defer clusterUsageFile.Close()
   120  
   121  		writerDone.Add(1)
   122  		go d.runCSVWriter(knStatRecords, d.outputFilename("kn_stats"), &writerDone)
   123  
   124  		writerDone.Add(1)
   125  		go d.runCSVWriter(scaleRecords, d.outputFilename("deployment_scale"), &writerDone)
   126  
   127  		for {
   128  			select {
   129  			case <-timer.C:
   130  				recCluster := mc.ScrapeClusterUsage()
   131  				recCluster.Timestamp = time.Now().UnixMicro()
   132  
   133  				byteArr, err := json.Marshal(recCluster)
   134  				common.Check(err)
   135  
   136  				_, err = clusterUsageFile.Write(byteArr)
   137  				common.Check(err)
   138  
   139  				_, err = clusterUsageFile.WriteString("\n")
   140  				common.Check(err)
   141  
   142  				recScale := mc.ScrapeDeploymentScales()
   143  				timestamp := time.Now().UnixMicro()
   144  				for _, rec := range recScale {
   145  					rec.Timestamp = timestamp
   146  					scaleRecords <- rec
   147  				}
   148  
   149  				recKnative := mc.ScrapeKnStats()
   150  				recKnative.Timestamp = time.Now().UnixMicro()
   151  				knStatRecords <- recKnative
   152  			case <-finishCh:
   153  				close(knStatRecords)
   154  				close(scaleRecords)
   155  
   156  				writerDone.Wait()
   157  				allRecordsWritten.Done()
   158  
   159  				return
   160  			}
   161  		}
   162  	}
   163  }
   164  
   165  /////////////////////////////////////////
   166  // DRIVER LOGIC
   167  /////////////////////////////////////////
   168  
   169  type InvocationMetadata struct {
   170  	Function              *common.Function
   171  	RuntimeSpecifications *common.RuntimeSpecification
   172  	Phase                 common.ExperimentPhase
   173  
   174  	MinuteIndex     int
   175  	InvocationIndex int
   176  
   177  	SuccessCount        *int64
   178  	FailedCount         *int64
   179  	FailedCountByMinute []int64
   180  
   181  	RecordOutputChannel   chan interface{}
   182  	AnnounceDoneWG        *sync.WaitGroup
   183  	AnnouceDoneExe        *sync.WaitGroup
   184  	ReadOpenWhiskMetadata *sync.Mutex
   185  }
   186  
   187  func composeInvocationID(timeGranularity common.TraceGranularity, minuteIndex int, invocationIndex int) string {
   188  	var timePrefix string
   189  
   190  	switch timeGranularity {
   191  	case common.MinuteGranularity:
   192  		timePrefix = "min"
   193  	case common.SecondGranularity:
   194  		timePrefix = "sec"
   195  	default:
   196  		log.Fatal("Invalid trace granularity parameter.")
   197  	}
   198  
   199  	return fmt.Sprintf("%s%d.inv%d", timePrefix, minuteIndex, invocationIndex)
   200  }
   201  
   202  func (d *Driver) invokeFunction(metadata *InvocationMetadata) {
   203  	defer metadata.AnnounceDoneWG.Done()
   204  
   205  	var success bool
   206  
   207  	if d.Configuration.LoaderConfiguration.Platform == "Knative" {
   208  		var record *mc.ExecutionRecord
   209  		success, record = Invoke(metadata.Function, metadata.RuntimeSpecifications, d.Configuration.LoaderConfiguration)
   210  
   211  		record.Phase = int(metadata.Phase)
   212  		record.InvocationID = composeInvocationID(d.Configuration.TraceGranularity, metadata.MinuteIndex, metadata.InvocationIndex)
   213  
   214  		metadata.RecordOutputChannel <- record
   215  	} else if d.Configuration.LoaderConfiguration.Platform == "OpenWhisk" {
   216  		var record *mc.ExecutionRecordOpenWhisk
   217  		success, record = InvokeOpenWhisk(metadata.Function, metadata.RuntimeSpecifications, d.Configuration.LoaderConfiguration, metadata.AnnouceDoneExe, metadata.ReadOpenWhiskMetadata)
   218  
   219  		record.Phase = int(metadata.Phase)
   220  		record.InvocationID = composeInvocationID(d.Configuration.TraceGranularity, metadata.MinuteIndex, metadata.InvocationIndex)
   221  
   222  		metadata.RecordOutputChannel <- record
   223  	}
   224  
   225  	if success {
   226  		atomic.AddInt64(metadata.SuccessCount, 1)
   227  	} else {
   228  		atomic.AddInt64(metadata.FailedCount, 1)
   229  		atomic.AddInt64(&metadata.FailedCountByMinute[metadata.MinuteIndex], 1)
   230  	}
   231  }
   232  
   233  func (d *Driver) individualFunctionDriver(function *common.Function, announceFunctionDone *sync.WaitGroup,
   234  	addInvocationsToGroup *sync.WaitGroup, readOpenWhiskMetadata *sync.Mutex, totalSuccessful *int64,
   235  	totalFailed *int64, totalIssued *int64, recordOutputChannel chan interface{}) {
   236  
   237  	numberOfInvocations := 0
   238  	for i := 0; i < len(function.InvocationStats.Invocations); i++ {
   239  		numberOfInvocations += function.InvocationStats.Invocations[i]
   240  	}
   241  	addInvocationsToGroup.Add(numberOfInvocations)
   242  
   243  	totalTraceDuration := d.Configuration.TraceDuration
   244  	minuteIndex, invocationIndex := 0, 0
   245  
   246  	IAT, runtimeSpecification := function.Specification.IAT, function.Specification.RuntimeSpecification
   247  
   248  	var successfulInvocations int64
   249  	var failedInvocations int64
   250  	var failedInvocationByMinute = make([]int64, totalTraceDuration)
   251  	var numberOfIssuedInvocations int64
   252  	var currentPhase = common.ExecutionPhase
   253  
   254  	waitForInvocations := sync.WaitGroup{}
   255  
   256  	if d.Configuration.WithWarmup() {
   257  		currentPhase = common.WarmupPhase
   258  		// skip the first minute because of profiling
   259  		minuteIndex = 1
   260  
   261  		log.Infof("Warmup phase has started.")
   262  	}
   263  
   264  	startOfMinute := time.Now()
   265  	var previousIATSum int64
   266  
   267  	for {
   268  		if minuteIndex >= totalTraceDuration {
   269  			// Check whether the end of trace has been reached
   270  			break
   271  		} else if function.InvocationStats.Invocations[minuteIndex] == 0 {
   272  			// Sleep for a minute if there are no invocations
   273  			if d.proceedToNextMinute(function, &minuteIndex, &invocationIndex,
   274  				&startOfMinute, true, &currentPhase, failedInvocationByMinute, &previousIATSum) {
   275  				break
   276  			}
   277  
   278  			switch d.Configuration.TraceGranularity {
   279  			case common.MinuteGranularity:
   280  				time.Sleep(time.Minute)
   281  			case common.SecondGranularity:
   282  				time.Sleep(time.Second)
   283  			default:
   284  				log.Fatal("Unsupported trace granularity.")
   285  			}
   286  
   287  			continue
   288  		}
   289  
   290  		iat := time.Duration(IAT[minuteIndex][invocationIndex]) * time.Microsecond
   291  
   292  		currentTime := time.Now()
   293  		schedulingDelay := currentTime.Sub(startOfMinute).Microseconds() - previousIATSum
   294  		sleepFor := iat.Microseconds() - schedulingDelay
   295  		time.Sleep(time.Duration(sleepFor) * time.Microsecond)
   296  
   297  		previousIATSum += iat.Microseconds()
   298  
   299  		if function.InvocationStats.Invocations[minuteIndex] == invocationIndex || hasMinuteExpired(startOfMinute) {
   300  			readyToBreak := d.proceedToNextMinute(function, &minuteIndex, &invocationIndex, &startOfMinute,
   301  				false, &currentPhase, failedInvocationByMinute, &previousIATSum)
   302  
   303  			if readyToBreak {
   304  				break
   305  			}
   306  		} else {
   307  			if !d.Configuration.TestMode {
   308  				waitForInvocations.Add(1)
   309  
   310  				go d.invokeFunction(&InvocationMetadata{
   311  					Function:              function,
   312  					RuntimeSpecifications: &runtimeSpecification[minuteIndex][invocationIndex],
   313  					Phase:                 currentPhase,
   314  					MinuteIndex:           minuteIndex,
   315  					InvocationIndex:       invocationIndex,
   316  					SuccessCount:          &successfulInvocations,
   317  					FailedCount:           &failedInvocations,
   318  					FailedCountByMinute:   failedInvocationByMinute,
   319  					RecordOutputChannel:   recordOutputChannel,
   320  					AnnounceDoneWG:        &waitForInvocations,
   321  					AnnouceDoneExe:        addInvocationsToGroup,
   322  					ReadOpenWhiskMetadata: readOpenWhiskMetadata,
   323  				})
   324  			} else {
   325  				// To be used from within the Golang testing framework
   326  				log.Debugf("Test mode invocation fired.\n")
   327  
   328  				recordOutputChannel <- &mc.ExecutionRecordBase{
   329  					Phase:        int(currentPhase),
   330  					InvocationID: composeInvocationID(d.Configuration.TraceGranularity, minuteIndex, invocationIndex),
   331  					StartTime:    time.Now().UnixNano(),
   332  				}
   333  
   334  				successfulInvocations++
   335  			}
   336  			numberOfIssuedInvocations++
   337  			invocationIndex++
   338  		}
   339  	}
   340  
   341  	waitForInvocations.Wait()
   342  
   343  	log.Debugf("All the invocations for function %s have been completed.\n", function.Name)
   344  	announceFunctionDone.Done()
   345  
   346  	atomic.AddInt64(totalSuccessful, successfulInvocations)
   347  	atomic.AddInt64(totalFailed, failedInvocations)
   348  	atomic.AddInt64(totalIssued, numberOfIssuedInvocations)
   349  }
   350  
   351  func (d *Driver) proceedToNextMinute(function *common.Function, minuteIndex *int, invocationIndex *int, startOfMinute *time.Time,
   352  	skipMinute bool, currentPhase *common.ExperimentPhase, failedInvocationByMinute []int64, previousIATSum *int64) bool {
   353  
   354  	if d.Configuration.TraceGranularity == common.MinuteGranularity {
   355  		if !isRequestTargetAchieved(function.InvocationStats.Invocations[*minuteIndex], *invocationIndex, common.RequestedVsIssued) {
   356  			// Not fatal because we want to keep the measurements to be written to the output file
   357  			log.Warnf("Relative difference between requested and issued number of invocations is greater than %.2f%%. Terminating function driver for %s!\n", common.RequestedVsIssuedTerminateThreshold*100, function.Name)
   358  
   359  			return true
   360  		}
   361  
   362  		for i := 0; i <= *minuteIndex; i++ {
   363  			notFailedCount := function.InvocationStats.Invocations[i] - int(atomic.LoadInt64(&failedInvocationByMinute[i]))
   364  			if !isRequestTargetAchieved(function.InvocationStats.Invocations[i], notFailedCount, common.IssuedVsFailed) {
   365  				// Not fatal because we want to keep the measurements to be written to the output file
   366  				log.Warnf("Percentage of failed request is greater than %.2f%%. Terminating function driver for %s!\n", common.FailedTerminateThreshold*100, function.Name)
   367  
   368  				return true
   369  			}
   370  		}
   371  	}
   372  
   373  	*minuteIndex++
   374  	*invocationIndex = 0
   375  	*previousIATSum = 0
   376  
   377  	if d.Configuration.WithWarmup() && *minuteIndex == (d.Configuration.LoaderConfiguration.WarmupDuration+1) {
   378  		*currentPhase = common.ExecutionPhase
   379  		log.Infof("Warmup phase has finished. Starting the execution phase.")
   380  	}
   381  
   382  	if !skipMinute {
   383  		*startOfMinute = time.Now()
   384  	} else {
   385  		switch d.Configuration.TraceGranularity {
   386  		case common.MinuteGranularity:
   387  			*startOfMinute = time.Now().Add(time.Minute)
   388  		case common.SecondGranularity:
   389  			*startOfMinute = time.Now().Add(time.Second)
   390  		default:
   391  			log.Fatal("Unsupported trace granularity.")
   392  		}
   393  	}
   394  
   395  	return false
   396  }
   397  
   398  func isRequestTargetAchieved(ideal int, real int, assertType common.RuntimeAssertType) bool {
   399  	if ideal == 0 {
   400  		return true
   401  	}
   402  
   403  	ratio := float64(ideal-real) / float64(ideal)
   404  
   405  	var warnBound float64
   406  	var terminationBound float64
   407  	var warnMessage string
   408  
   409  	switch assertType {
   410  	case common.RequestedVsIssued:
   411  		warnBound = common.RequestedVsIssuedWarnThreshold
   412  		terminationBound = common.RequestedVsIssuedTerminateThreshold
   413  		warnMessage = fmt.Sprintf("Relative difference between requested and issued number of invocations has reached %.2f.", ratio)
   414  	case common.IssuedVsFailed:
   415  		warnBound = common.FailedWarnThreshold
   416  		terminationBound = common.FailedTerminateThreshold
   417  		warnMessage = fmt.Sprintf("Percentage of failed invocations within a minute has reached %.2f.", ratio)
   418  	default:
   419  		log.Fatal("Invalid type of assertion at runtime.")
   420  	}
   421  
   422  	if ratio < 0 || ratio > 1 {
   423  		log.Fatalf("Invalid arguments provided to runtime assertion.\n")
   424  	} else if ratio >= terminationBound {
   425  		return false
   426  	}
   427  
   428  	if ratio >= warnBound && ratio < terminationBound {
   429  		log.Warn(warnMessage)
   430  	}
   431  
   432  	return true
   433  }
   434  
   435  func hasMinuteExpired(t1 time.Time) bool {
   436  	return time.Since(t1) > time.Minute
   437  }
   438  
   439  func (d *Driver) globalTimekeeper(totalTraceDuration int, signalReady *sync.WaitGroup) {
   440  	ticker := time.NewTicker(time.Minute)
   441  	globalTimeCounter := 0
   442  
   443  	signalReady.Done()
   444  
   445  	for {
   446  		<-ticker.C
   447  
   448  		log.Debugf("End of minute %d\n", globalTimeCounter)
   449  		globalTimeCounter++
   450  		if globalTimeCounter >= totalTraceDuration {
   451  			break
   452  		}
   453  
   454  		log.Debugf("Start of minute %d\n", globalTimeCounter)
   455  	}
   456  
   457  	ticker.Stop()
   458  }
   459  
   460  func (d *Driver) createGlobalMetricsCollector(filename string, collector chan interface{},
   461  	signalReady *sync.WaitGroup, signalEverythingWritten *sync.WaitGroup, totalIssuedChannel chan int64) {
   462  
   463  	// NOTE: totalNumberOfInvocations is initialized to MaxInt64 not to allow collector to complete before
   464  	// the end signal is received on totalIssuedChannel, which deliver the total number of issued invocations.
   465  	// This number is known once all the individual function drivers finish issuing invocations and
   466  	// when all the invocations return
   467  	var totalNumberOfInvocations int64 = math.MaxInt64
   468  	var currentlyWritten int64
   469  
   470  	file, err := os.Create(filename)
   471  	common.Check(err)
   472  	defer file.Close()
   473  
   474  	signalReady.Done()
   475  
   476  	records := make(chan interface{}, 100)
   477  	writerDone := sync.WaitGroup{}
   478  	writerDone.Add(1)
   479  	go d.runCSVWriter(records, filename, &writerDone)
   480  
   481  	for {
   482  		select {
   483  		case record := <-collector:
   484  			records <- record
   485  
   486  			currentlyWritten++
   487  		case record := <-totalIssuedChannel:
   488  			totalNumberOfInvocations = record
   489  		}
   490  
   491  		if currentlyWritten == totalNumberOfInvocations {
   492  			close(records)
   493  			writerDone.Wait()
   494  			(*signalEverythingWritten).Done()
   495  
   496  			return
   497  		}
   498  	}
   499  }
   500  
   501  func (d *Driver) startBackgroundProcesses(allRecordsWritten *sync.WaitGroup) (*sync.WaitGroup, chan interface{}, chan int64, chan int) {
   502  	auxiliaryProcessBarrier := &sync.WaitGroup{}
   503  
   504  	finishCh := make(chan int, 1)
   505  
   506  	if d.Configuration.LoaderConfiguration.EnableMetricsScrapping {
   507  		auxiliaryProcessBarrier.Add(1)
   508  
   509  		allRecordsWritten.Add(1)
   510  		metricsScrapper := d.CreateMetricsScrapper(time.Second*time.Duration(d.Configuration.LoaderConfiguration.MetricScrapingPeriodSeconds), auxiliaryProcessBarrier, finishCh, allRecordsWritten)
   511  		go metricsScrapper()
   512  	}
   513  
   514  	auxiliaryProcessBarrier.Add(2)
   515  
   516  	globalMetricsCollector := make(chan interface{})
   517  	totalIssuedChannel := make(chan int64)
   518  	go d.createGlobalMetricsCollector(d.outputFilename("duration"), globalMetricsCollector, auxiliaryProcessBarrier, allRecordsWritten, totalIssuedChannel)
   519  
   520  	traceDurationInMinutes := d.Configuration.TraceDuration
   521  	go d.globalTimekeeper(traceDurationInMinutes, auxiliaryProcessBarrier)
   522  
   523  	return auxiliaryProcessBarrier, globalMetricsCollector, totalIssuedChannel, finishCh
   524  }
   525  
   526  func (d *Driver) internalRun(iatOnly bool, generated bool) {
   527  	var successfulInvocations int64
   528  	var failedInvocations int64
   529  	var invocationsIssued int64
   530  
   531  	readOpenWhiskMetadata := sync.Mutex{}
   532  	allFunctionsInvoked := sync.WaitGroup{}
   533  	allIndividualDriversCompleted := sync.WaitGroup{}
   534  	allRecordsWritten := sync.WaitGroup{}
   535  	allRecordsWritten.Add(1)
   536  
   537  	backgroundProcessesInitializationBarrier, globalMetricsCollector, totalIssuedChannel, scraperFinishCh := d.startBackgroundProcesses(&allRecordsWritten)
   538  
   539  	if !iatOnly {
   540  		log.Info("Generating IAT and runtime specifications for all the functions")
   541  		for i, function := range d.Configuration.Functions {
   542  			spec := d.SpecificationGenerator.GenerateInvocationData(
   543  				function,
   544  				d.Configuration.IATDistribution,
   545  				d.Configuration.ShiftIAT,
   546  				d.Configuration.TraceGranularity,
   547  			)
   548  
   549  			d.Configuration.Functions[i].Specification = spec
   550  		}
   551  	}
   552  
   553  	backgroundProcessesInitializationBarrier.Wait()
   554  
   555  	if generated {
   556  		for i := range d.Configuration.Functions {
   557  			var spec common.FunctionSpecification
   558  
   559  			iatFile, _ := os.ReadFile("iat" + strconv.Itoa(i) + ".json")
   560  			err := json.Unmarshal(iatFile, &spec)
   561  			if err != nil {
   562  				log.Fatalf("Failed tu unmarshal iat file: %s", err)
   563  			}
   564  
   565  			d.Configuration.Functions[i].Specification = &spec
   566  		}
   567  	}
   568  
   569  	log.Infof("Starting function invocation driver\n")
   570  	for _, function := range d.Configuration.Functions {
   571  		allIndividualDriversCompleted.Add(1)
   572  
   573  		go d.individualFunctionDriver(
   574  			function,
   575  			&allIndividualDriversCompleted,
   576  			&allFunctionsInvoked,
   577  			&readOpenWhiskMetadata,
   578  			&successfulInvocations,
   579  			&failedInvocations,
   580  			&invocationsIssued,
   581  			globalMetricsCollector,
   582  		)
   583  	}
   584  
   585  	allIndividualDriversCompleted.Wait()
   586  	if atomic.LoadInt64(&successfulInvocations)+atomic.LoadInt64(&failedInvocations) != 0 {
   587  		log.Debugf("Waiting for all the invocations record to be written.\n")
   588  
   589  		totalIssuedChannel <- atomic.LoadInt64(&invocationsIssued)
   590  		scraperFinishCh <- 0 // Ask the scraper to finish metrics collection
   591  
   592  		allRecordsWritten.Wait()
   593  	}
   594  
   595  	log.Infof("Trace has finished executing function invocation driver\n")
   596  	log.Infof("Number of successful invocations: \t%d\n", atomic.LoadInt64(&successfulInvocations))
   597  	log.Infof("Number of failed invocations: \t%d\n", atomic.LoadInt64(&failedInvocations))
   598  }
   599  
   600  func (d *Driver) RunExperiment(iatOnly bool, generated bool) {
   601  	if iatOnly {
   602  		log.Info("Generating IAT and runtime specifications for all the functions")
   603  		for i, function := range d.Configuration.Functions {
   604  			spec := d.SpecificationGenerator.GenerateInvocationData(
   605  				function,
   606  				d.Configuration.IATDistribution,
   607  				d.Configuration.ShiftIAT,
   608  				d.Configuration.TraceGranularity,
   609  			)
   610  			d.Configuration.Functions[i].Specification = spec
   611  
   612  			file, _ := json.MarshalIndent(spec, "", " ")
   613  			err := os.WriteFile("iat"+strconv.Itoa(i)+".json", file, 0644)
   614  			if err != nil {
   615  				log.Fatalf("Writing the loader config file failed: %s", err)
   616  			}
   617  		}
   618  
   619  		return
   620  	}
   621  
   622  	if d.Configuration.WithWarmup() {
   623  		trace.DoStaticTraceProfiling(d.Configuration.Functions)
   624  	}
   625  
   626  	trace.ApplyResourceLimits(d.Configuration.Functions)
   627  
   628  	if d.Configuration.LoaderConfiguration.Platform == "Knative" {
   629  		DeployFunctionsKnative(d.Configuration.Functions,
   630  			d.Configuration.YAMLPath,
   631  			d.Configuration.LoaderConfiguration.IsPartiallyPanic,
   632  			d.Configuration.LoaderConfiguration.EndpointPort,
   633  			d.Configuration.LoaderConfiguration.AutoscalingMetric)
   634  	} else if d.Configuration.LoaderConfiguration.Platform == "OpenWhisk" {
   635  		DeployFunctionsOpenWhisk(d.Configuration.Functions)
   636  	}
   637  
   638  	d.internalRun(iatOnly, generated)
   639  
   640  	if d.Configuration.LoaderConfiguration.Platform == "Knative" {
   641  		CleanKnative()
   642  	} else if d.Configuration.LoaderConfiguration.Platform == "OpenWhisk" {
   643  		CleanOpenWhisk(d.Configuration.Functions)
   644  	}
   645  }