gopkg.in/dedis/onet.v2@v2.0.0-20181115163211-c8f3724038a7/simul/build.go (about)

     1  package simul
     2  
     3  import (
     4  	"flag"
     5  	"os"
     6  	"path/filepath"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"errors"
    11  	"math"
    12  	"time"
    13  
    14  	"gopkg.in/dedis/onet.v2/log"
    15  	"gopkg.in/dedis/onet.v2/simul/monitor"
    16  	"gopkg.in/dedis/onet.v2/simul/platform"
    17  )
    18  
    19  // Configuration-variables
    20  var platformDst = "localhost"
    21  var nobuild = false
    22  var clean = true
    23  var build = ""
    24  var machines = 3
    25  var monitorPort = monitor.DefaultSinkPort
    26  var simRange = ""
    27  var race = false
    28  var runWait = 180 * time.Second
    29  var experimentWait = 0 * time.Second
    30  
    31  func init() {
    32  	flag.StringVar(&platformDst, "platform", platformDst, "platform to deploy to [localhost,mininet,deterlab]")
    33  	flag.BoolVar(&nobuild, "nobuild", false, "Don't rebuild all helpers")
    34  	flag.BoolVar(&clean, "clean", false, "Only clean platform")
    35  	flag.StringVar(&build, "build", "", "List of packages to build")
    36  	flag.BoolVar(&race, "race", false, "Build with go's race detection enabled (doesn't work on all platforms)")
    37  	flag.IntVar(&machines, "machines", machines, "Number of machines on Deterlab")
    38  	flag.IntVar(&monitorPort, "mport", monitorPort, "Port-number for monitor")
    39  	flag.StringVar(&simRange, "range", simRange, "Range of simulations to run. 0: or 3:4 or :4")
    40  	flag.DurationVar(&runWait, "runwait", runWait, "How long to wait for each simulation to finish - overwrites .toml-value")
    41  	flag.DurationVar(&experimentWait, "experimentwait", experimentWait, "How long to wait for the whole experiment to finish")
    42  	log.RegisterFlags()
    43  }
    44  
    45  // Reads in the platform that we want to use and prepares for the tests
    46  func startBuild() {
    47  	flag.Parse()
    48  	deployP := platform.NewPlatform(platformDst)
    49  	if deployP == nil {
    50  		log.Fatal("Platform not recognized.", platformDst)
    51  	}
    52  	log.Lvl1("Deploying to", platformDst)
    53  
    54  	simulations := flag.Args()
    55  	if len(simulations) == 0 {
    56  		log.Fatal("Please give a simulation to run")
    57  	}
    58  
    59  	for _, simulation := range simulations {
    60  		runconfigs := platform.ReadRunFile(deployP, simulation)
    61  
    62  		if len(runconfigs) == 0 {
    63  			log.Fatal("No tests found in", simulation)
    64  		}
    65  		deployP.Configure(&platform.Config{
    66  			MonitorPort: monitorPort,
    67  			Debug:       log.DebugVisible(),
    68  			Suite:       runconfigs[0].Get("Suite"),
    69  		})
    70  
    71  		if clean {
    72  			err := deployP.Deploy(runconfigs[0])
    73  			if err != nil {
    74  				log.Fatal("Couldn't deploy:", err)
    75  			}
    76  			if err := deployP.Cleanup(); err != nil {
    77  				log.Error("Couldn't cleanup correctly:", err)
    78  			}
    79  		} else {
    80  			logname := strings.Replace(filepath.Base(simulation), ".toml", "", 1)
    81  			testsDone := make(chan bool)
    82  			timeout, err := getExperimentWait(runconfigs)
    83  			if err != nil {
    84  				log.Fatal("ExperimentWait:", err)
    85  			}
    86  			go func() {
    87  				RunTests(deployP, logname, runconfigs)
    88  				testsDone <- true
    89  			}()
    90  			select {
    91  			case <-testsDone:
    92  				log.Lvl3("Done with test", simulation)
    93  			case <-time.After(timeout):
    94  				log.Fatal("Test failed to finish in", timeout, "seconds")
    95  			}
    96  		}
    97  	}
    98  }
    99  
   100  // RunTests the given tests and puts the output into the
   101  // given file name. It outputs RunStats in a CSV format.
   102  func RunTests(deployP platform.Platform, name string, runconfigs []*platform.RunConfig) {
   103  
   104  	if nobuild == false {
   105  		if race {
   106  			if err := deployP.Build(build, "-race"); err != nil {
   107  				log.Error("Couln't finish build without errors:",
   108  					err)
   109  			}
   110  		} else {
   111  			if err := deployP.Build(build); err != nil {
   112  				log.Error("Couln't finish build without errors:",
   113  					err)
   114  			}
   115  		}
   116  	}
   117  
   118  	mkTestDir()
   119  	var f *os.File
   120  	args := os.O_CREATE | os.O_RDWR | os.O_TRUNC
   121  	// If a range is given, we only append
   122  	if simRange != "" {
   123  		args = os.O_CREATE | os.O_RDWR | os.O_APPEND
   124  	}
   125  	f, err := os.OpenFile(testFile(name), args, 0660)
   126  	if err != nil {
   127  		log.Fatal("error opening test file:", err)
   128  	}
   129  	defer func() {
   130  		if err := f.Close(); err != nil {
   131  			log.Error("Couln't close", f.Name())
   132  		}
   133  	}()
   134  	err = f.Sync()
   135  	if err != nil {
   136  		log.Fatal("error syncing test file:", err)
   137  	}
   138  
   139  	start, stop := getStartStop(len(runconfigs))
   140  	for i, rc := range runconfigs {
   141  		// Implement a simple range-argument that will skip checks not in range
   142  		if i < start || i > stop {
   143  			log.Lvl2("Skipping", rc, "because of range")
   144  			continue
   145  		}
   146  
   147  		// run test t nTimes times
   148  		// take the average of all successful runs
   149  		log.Lvl1("Running test", rc)
   150  		stats, err := RunTest(deployP, rc)
   151  		if err != nil {
   152  			log.Error("Error running test:", err)
   153  			continue
   154  		}
   155  		log.Lvl1("Test results:", stats)
   156  
   157  		if i == 0 {
   158  			stats.WriteHeader(f)
   159  		}
   160  		if rc.Get("IndividualStats") != "" {
   161  			err := stats.WriteIndividualStats(f)
   162  			log.ErrFatal(err)
   163  		} else {
   164  			stats.WriteValues(f)
   165  		}
   166  		err = f.Sync()
   167  		if err != nil {
   168  			log.Fatal("error syncing data to test file:", err)
   169  		}
   170  	}
   171  }
   172  
   173  // RunTest a single test - takes a test-file as a string that will be copied
   174  // to the deterlab-server
   175  func RunTest(deployP platform.Platform, rc *platform.RunConfig) (*monitor.Stats, error) {
   176  	CheckHosts(rc)
   177  	rc.Delete("simulation")
   178  	rs := monitor.NewStats(rc.Map(), "hosts", "bf")
   179  
   180  	if err := deployP.Cleanup(); err != nil {
   181  		log.Error(err)
   182  		return rs, err
   183  	}
   184  
   185  	if err := deployP.Deploy(rc); err != nil {
   186  		log.Error(err)
   187  		return rs, err
   188  	}
   189  
   190  	monitor := monitor.NewMonitor(rs)
   191  	monitor.SinkPort = uint16(monitorPort)
   192  	done := make(chan error)
   193  	go func() {
   194  		if err := monitor.Listen(); err != nil {
   195  			log.Error("error while closing monitor: " + err.Error())
   196  		}
   197  	}()
   198  
   199  	go func() {
   200  		// Start monitor before so ssh tunnel can connect to the monitor
   201  		// in case of deterlab.
   202  		err := deployP.Start()
   203  		if err != nil {
   204  			done <- err
   205  		}
   206  
   207  		if err = deployP.Wait(); err != nil {
   208  			log.Error("Test failed:", err)
   209  			if err := deployP.Cleanup(); err != nil {
   210  				log.Lvl3("Couldn't cleanup platform:", err)
   211  			}
   212  			monitor.Stop()
   213  			done <- err
   214  		}
   215  		done <- nil
   216  	}()
   217  
   218  	timeout, err := getRunWait(rc)
   219  	if err != nil {
   220  		log.Fatal("RunWait:", err)
   221  	}
   222  
   223  	// can timeout the command if it takes too long
   224  	select {
   225  	case err := <-done:
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  		return rs, nil
   230  	case <-time.After(timeout):
   231  		monitor.Stop()
   232  		return rs, errors.New("Simulation timeout")
   233  	}
   234  }
   235  
   236  // CheckHosts verifies that there is either a 'Hosts' or a 'Depth/BF'
   237  // -parameter in the Runconfig
   238  func CheckHosts(rc *platform.RunConfig) {
   239  	hosts, _ := rc.GetInt("hosts")
   240  	bf, _ := rc.GetInt("bf")
   241  	depth, _ := rc.GetInt("depth")
   242  	if hosts == 0 {
   243  		if depth == 0 || bf == 0 {
   244  			log.Fatal("No Hosts and no Depth or BF given - stopping")
   245  		}
   246  		hosts = calcHosts(bf, depth)
   247  		rc.Put("hosts", strconv.Itoa(hosts))
   248  	}
   249  	if bf == 0 {
   250  		if depth == 0 || hosts == 0 {
   251  			log.Fatal("No BF and no Depth or hosts given - stopping")
   252  		}
   253  		bf = 1
   254  		for calcHosts(bf, depth) < hosts {
   255  			bf++
   256  		}
   257  		rc.Put("bf", strconv.Itoa(bf))
   258  	}
   259  	if depth == 0 {
   260  		depth = 1
   261  		for calcHosts(bf, depth) < hosts {
   262  			depth++
   263  		}
   264  		rc.Put("depth", strconv.Itoa(depth))
   265  	}
   266  }
   267  
   268  // Geometric sum to count the total number of nodes:
   269  // Root-node: 1
   270  // 1st level: bf (branching-factor)*/
   271  // 2nd level: bf^2 (each child has bf children)
   272  // 3rd level: bf^3
   273  // So total: sum(level=0..depth)(bf^level)
   274  func calcHosts(bf, depth int) int {
   275  	if bf <= 0 {
   276  		log.Fatal("illegal branching-factor")
   277  	} else if depth <= 0 {
   278  		log.Fatal("illegal depth")
   279  	} else if bf == 1 {
   280  		return depth + 1
   281  	}
   282  	return int((1 - math.Pow(float64(bf), float64(depth+1))) /
   283  		float64(1-bf))
   284  }
   285  
   286  type runFile struct {
   287  	Machines int
   288  	Args     string
   289  	Runs     string
   290  }
   291  
   292  func mkTestDir() {
   293  	err := os.MkdirAll("test_data/", 0777)
   294  	if err != nil {
   295  		log.Fatal("failed to make test directory")
   296  	}
   297  }
   298  
   299  func testFile(name string) string {
   300  	return "test_data/" + name + ".csv"
   301  }
   302  
   303  // returns a tuple of start and stop configurations to run
   304  func getStartStop(rcs int) (int, int) {
   305  	ssStr := strings.Split(simRange, ":")
   306  	start, err := strconv.Atoi(ssStr[0])
   307  	stop := rcs - 1
   308  	if err == nil {
   309  		stop = start
   310  		if len(ssStr) > 1 {
   311  			stop, err = strconv.Atoi(ssStr[1])
   312  			if err != nil {
   313  				stop = rcs
   314  			}
   315  		}
   316  	}
   317  	log.Lvl2("Range is", start, ":", stop)
   318  	return start, stop
   319  }
   320  
   321  // getRunWait returns either the command-line value or the value from the runconfig
   322  // file
   323  func getRunWait(rc *platform.RunConfig) (time.Duration, error) {
   324  	rcWait, err := rc.GetDuration("runwait")
   325  	if err == platform.ErrorFieldNotPresent {
   326  		return runWait, nil
   327  	}
   328  	if err == nil {
   329  		return rcWait, nil
   330  	}
   331  	return 0, err
   332  }
   333  
   334  // getExperimentWait returns, in the following order of precedence:
   335  // 1. the command-line value
   336  // 2. the value from runconfig
   337  // 3. #runconfigs * runWait
   338  func getExperimentWait(rcs []*platform.RunConfig) (time.Duration, error) {
   339  	if experimentWait > 0 {
   340  		return experimentWait, nil
   341  	}
   342  	rcExp, err := rcs[0].GetDuration("experimentwait")
   343  	if err == nil {
   344  		return rcExp, nil
   345  	}
   346  	// Probably a parse error parsing the duration.
   347  	if err != platform.ErrorFieldNotPresent {
   348  		return 0, err
   349  	}
   350  
   351  	// Otherwise calculate a useful default.
   352  	wait := 0 * time.Second
   353  	for _, rc := range rcs {
   354  		w, err := getRunWait(rc)
   355  		if err != nil {
   356  			return 0, err
   357  		}
   358  		wait += w
   359  	}
   360  	return wait, nil
   361  }