github.com/eth-easl/loader@v0.0.0-20230908084258-8a37e1d94279/pkg/driver/http_client.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  	"bytes"
    29  	"crypto/tls"
    30  	"encoding/json"
    31  	"net/http"
    32  	"os/exec"
    33  	"strings"
    34  	"sync"
    35  	"time"
    36  
    37  	"github.com/eth-easl/loader/pkg/common"
    38  	"github.com/eth-easl/loader/pkg/config"
    39  	mc "github.com/eth-easl/loader/pkg/metric"
    40  	log "github.com/sirupsen/logrus"
    41  )
    42  
    43  type ActivationMetadata struct {
    44  	Duration  uint32 //ms
    45  	StartType mc.StartType
    46  	WaitTime  int64 //ms
    47  	InitTime  int64 //ms
    48  }
    49  
    50  func InvokeOpenWhisk(function *common.Function, runtimeSpec *common.RuntimeSpecification, cfg *config.LoaderConfiguration, AnnouceDoneExe *sync.WaitGroup, ReadOpenWhiskMetadata *sync.Mutex) (bool, *mc.ExecutionRecordOpenWhisk) {
    51  	log.Tracef("(Invoke)\t %s: %d[ms], %d[MiB]", function.Name, runtimeSpec.Runtime, runtimeSpec.Memory)
    52  
    53  	record := &mc.ExecutionRecordOpenWhisk{
    54  		ExecutionRecordBase: mc.ExecutionRecordBase{
    55  			RequestedDuration: uint32(runtimeSpec.Runtime * 1e3),
    56  		},
    57  	}
    58  
    59  	////////////////////////////////////
    60  	// INVOKE FUNCTION
    61  	////////////////////////////////////
    62  	start := time.Now()
    63  	record.StartTime = start.UnixMicro()
    64  	record.Instance = function.Name
    65  
    66  	http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
    67  	requestURL := function.Endpoint
    68  	req, err := http.NewRequest(http.MethodGet, requestURL, nil)
    69  	if err != nil {
    70  		log.Debugf("http request creation failed for function %s - %s", function.Name, err)
    71  
    72  		record.ResponseTime = time.Since(start).Microseconds()
    73  		record.ConnectionTimeout = true
    74  
    75  		AnnouceDoneExe.Done()
    76  
    77  		return false, record
    78  	}
    79  
    80  	res, err := http.DefaultClient.Do(req)
    81  	if err != nil {
    82  		log.Debugf("http timeout exceeded for function %s - %s", function.Name, err)
    83  
    84  		record.ResponseTime = time.Since(start).Microseconds()
    85  		record.ConnectionTimeout = true
    86  
    87  		AnnouceDoneExe.Done()
    88  
    89  		return false, record
    90  	}
    91  
    92  	record.HttpStatusCode = res.StatusCode
    93  	if record.HttpStatusCode < 200 || record.HttpStatusCode >= 300 {
    94  		log.Debugf("http request for function %s failed - error code: %d", function.Name, record.HttpStatusCode)
    95  
    96  		record.ResponseTime = time.Since(start).Microseconds()
    97  		record.ConnectionTimeout = true
    98  
    99  		AnnouceDoneExe.Done()
   100  
   101  		return false, record
   102  	}
   103  
   104  	record.ActivationID = res.Header.Get("X-Openwhisk-Activation-Id")
   105  	record.ResponseTime = time.Since(start).Microseconds()
   106  
   107  	AnnouceDoneExe.Done()
   108  	AnnouceDoneExe.Wait()
   109  
   110  	ReadOpenWhiskMetadata.Lock()
   111  
   112  	//read data from OpenWhisk based on the activation ID
   113  	cmd := exec.Command("wsk", "-i", "activation", "get", record.ActivationID)
   114  	var out bytes.Buffer
   115  	cmd.Stdout = &out
   116  	err = cmd.Run()
   117  	if err != nil {
   118  		log.Debugf("error reading activation information from OpenWhisk %s - %s", function.Name, err)
   119  
   120  		ReadOpenWhiskMetadata.Unlock()
   121  
   122  		return false, record
   123  	}
   124  
   125  	ReadOpenWhiskMetadata.Unlock()
   126  
   127  	err, activationMetadata := parseActivationMetadata(out.String())
   128  	if err != nil {
   129  		log.Debugf("error parsing activation metadata %s - %s", function.Name, err)
   130  
   131  		return false, record
   132  	}
   133  
   134  	record.ActualDuration = activationMetadata.Duration * 1000 //ms to micro sec
   135  	record.StartType = activationMetadata.StartType
   136  	record.InitTime = activationMetadata.InitTime * 1000 //ms to micro sec
   137  	record.WaitTime = activationMetadata.WaitTime * 1000 //ms to micro sec
   138  
   139  	log.Tracef("(Replied)\t %s: %d[ms]", function.Name, record.ActualDuration)
   140  	log.Tracef("(E2E Latency) %s: %.2f[ms]\n", function.Name, float64(record.ResponseTime)/1e3)
   141  	log.Tracef("(Client status code) %s: %d", function.Name, record.HttpStatusCode)
   142  
   143  	return true, record
   144  }
   145  
   146  func parseActivationMetadata(response string) (error, ActivationMetadata) {
   147  	var result ActivationMetadata
   148  	var jsonMap map[string]interface{}
   149  
   150  	ind := strings.Index(response, "{")
   151  	err := json.Unmarshal([]byte(response[ind:]), &jsonMap)
   152  	if err != nil {
   153  		return err, result
   154  	}
   155  
   156  	result.Duration = uint32(jsonMap["duration"].(float64))
   157  	result.StartType = mc.Hot
   158  	result.InitTime = 0
   159  	annotations := jsonMap["annotations"].([]interface{})
   160  	for i := 0; i < len(annotations); i++ {
   161  		annotation := annotations[i].(map[string]interface{})
   162  
   163  		if annotation["key"] == "waitTime" {
   164  			result.WaitTime = int64(annotation["value"].(float64))
   165  		} else if annotation["key"] == "initTime" {
   166  			result.StartType = mc.Cold
   167  			result.InitTime = int64(annotation["value"].(float64))
   168  		}
   169  	}
   170  
   171  	return nil, result
   172  }