github.com/iron-io/functions@v0.0.0-20180820112432-d59d7d1c40b2/test/fnlb-test-harness/main.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"flag"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"net/http"
    10  	"strings"
    11  	"time"
    12  )
    13  
    14  type execution struct {
    15  	DurationSeconds float64
    16  	Hostname        string
    17  	node            string
    18  	body            string
    19  	responseSeconds float64
    20  }
    21  
    22  var (
    23  	lbHostPort, nodesStr, route       string
    24  	numExecutions, maxPrime, numLoops int
    25  	nodes                             []string
    26  	nodesByContainerId                map[string]string = make(map[string]string)
    27  	verbose                           bool
    28  )
    29  
    30  func init() {
    31  	flag.StringVar(&lbHostPort, "lb", "localhost:8081", "host and port of load balancer")
    32  	flag.StringVar(&nodesStr, "nodes", "localhost:8080", "comma-delimited list of nodes (host:port) balanced by the load balancer (needed to discover container id of each)")
    33  	flag.StringVar(&route, "route", "/r/primesapp/primes", "path representing the route to the primes function")
    34  	flag.IntVar(&numExecutions, "calls", 100, "number of times to call the route")
    35  	flag.IntVar(&maxPrime, "max", 1000000, "maximum number to search for primes (higher number consumes more memory)")
    36  	flag.IntVar(&numLoops, "loops", 1, "number of times to execute the primes calculation (ex: 'loops=2' means run the primes calculation twice)")
    37  	flag.BoolVar(&verbose, "v", false, "true for more verbose output")
    38  	flag.Parse()
    39  
    40  	if maxPrime < 3 {
    41  		log.Fatal("-max must be 3 or greater")
    42  	}
    43  	if numLoops < 1 {
    44  		log.Fatal("-loops must be 1 or greater")
    45  	}
    46  
    47  	nodes = strings.Split(nodesStr, ",")
    48  }
    49  
    50  func executeFunction(hostPort, path string, max, loops int) (execution, error) {
    51  	var e execution
    52  
    53  	start := time.Now()
    54  	resp, err := http.Get(fmt.Sprintf("http://%s%s?max=%d&loops=%d", hostPort, path, max, loops))
    55  	e.responseSeconds = time.Since(start).Seconds()
    56  	if err != nil {
    57  		return e, err
    58  	}
    59  	defer resp.Body.Close()
    60  	if resp.StatusCode != http.StatusOK {
    61  		return e, fmt.Errorf("function returned status code: %d", resp.StatusCode)
    62  	}
    63  
    64  	body, err := ioutil.ReadAll(resp.Body)
    65  	if err != nil {
    66  		return e, err
    67  	}
    68  
    69  	err = json.Unmarshal(body, &e)
    70  	if err != nil {
    71  		e.body = string(body) // set the body in the execution so that it is available for logging
    72  		return e, err
    73  	}
    74  	e.node = nodesByContainerId[e.Hostname]
    75  
    76  	return e, nil
    77  }
    78  
    79  func invokeLoadBalancer(hostPort, path string, numExecutions, max, loops int) {
    80  	executionsByNode := make(map[string][]execution)
    81  	fmt.Printf("All primes will be calculated up to %d, a total of %d time(s)\n", maxPrime, numLoops)
    82  	fmt.Printf("Calling route %s %d times (through the load balancer)...\n", route, numExecutions)
    83  
    84  	for i := 0; i < numExecutions; i++ {
    85  		e, err := executeFunction(hostPort, path, max, loops)
    86  		if err == nil {
    87  			if ex, ok := executionsByNode[e.node]; ok {
    88  				executionsByNode[e.node] = append(ex, e)
    89  			} else {
    90  				// Create a slice to contain the list of executions for this host
    91  				executionsByNode[e.node] = []execution{e}
    92  			}
    93  			if verbose {
    94  				fmt.Printf("  %s in-function duration: %fsec, response time: %fsec\n", e.node, e.DurationSeconds, e.responseSeconds)
    95  			}
    96  		} else {
    97  			fmt.Printf("  Ignoring failed execution on node %s: %v\n", e.node, err)
    98  			fmt.Printf("    JSON: %s\n", e.body)
    99  		}
   100  	}
   101  
   102  	fmt.Println("Results (executions per node):")
   103  	for node, ex := range executionsByNode {
   104  		fmt.Printf("  %s %d\n", node, len(ex))
   105  	}
   106  }
   107  
   108  func discoverContainerIds() {
   109  	// Discover the Docker hostname of each node; create a mapping of hostnames to host/port.
   110  	// This is needed because IronFunctions doesn't make the host/port available to the function (as of Mar 2017).
   111  	fmt.Println("Discovering container ids for every node (use Docker's HOSTNAME env var as a container id)...")
   112  	for _, s := range nodes {
   113  		if e, err := executeFunction(s, route, 100, 1); err == nil {
   114  			nodesByContainerId[e.Hostname] = s
   115  			fmt.Printf("  %s %s\n", s, e.Hostname)
   116  		} else {
   117  			fmt.Printf("  Ignoring host %s which returned error: %v\n", s, err)
   118  			fmt.Printf("    JSON: %s\n", e.body)
   119  		}
   120  	}
   121  }
   122  
   123  func main() {
   124  	discoverContainerIds()
   125  	invokeLoadBalancer(lbHostPort, route, numExecutions, maxPrime, numLoops)
   126  }