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 }