aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tools/syz-testbed/checkout.go53
-rw-r--r--tools/syz-testbed/html.go8
-rw-r--r--tools/syz-testbed/instance.go134
-rw-r--r--tools/syz-testbed/stats.go29
-rw-r--r--tools/syz-testbed/targets.go83
-rw-r--r--tools/syz-testbed/testbed.go178
6 files changed, 312 insertions, 173 deletions
diff --git a/tools/syz-testbed/checkout.go b/tools/syz-testbed/checkout.go
index 3a4265013..75e79d7a3 100644
--- a/tools/syz-testbed/checkout.go
+++ b/tools/syz-testbed/checkout.go
@@ -8,6 +8,7 @@ import (
"fmt"
"log"
"path/filepath"
+ "sync"
"time"
syz_instance "github.com/google/syzkaller/pkg/instance"
@@ -16,23 +17,52 @@ import (
)
type Checkout struct {
- Path string
- Name string
+ Path string
+ Name string
+
ManagerConfig json.RawMessage
- Running []*Instance
- Completed []*RunResult
+ Running map[Instance]bool
+ Completed []RunResult
+ LastRunning time.Time
+ mu sync.Mutex
+}
+
+func (checkout *Checkout) AddRunning(instance Instance) {
+ checkout.mu.Lock()
+ defer checkout.mu.Unlock()
+ checkout.Running[instance] = true
+ checkout.LastRunning = time.Now()
}
-func (checkout *Checkout) ArchiveRunning() error {
- for _, instance := range checkout.Running {
+func (checkout *Checkout) ArchiveInstance(instance Instance) error {
+ checkout.mu.Lock()
+ defer checkout.mu.Unlock()
+ result, err := instance.FetchResult()
+ if err != nil {
+ return err
+ }
+ checkout.Completed = append(checkout.Completed, result)
+ delete(checkout.Running, instance)
+ return nil
+}
+
+func (checkout *Checkout) GetRunningResults() []RunResult {
+ checkout.mu.Lock()
+ defer checkout.mu.Unlock()
+ running := []RunResult{}
+ for instance := range checkout.Running {
result, err := instance.FetchResult()
- if err != nil {
- return err
+ if err == nil {
+ running = append(running, result)
}
- checkout.Completed = append(checkout.Completed, result)
}
- checkout.Running = []*Instance{}
- return nil
+ return running
+}
+
+func (checkout *Checkout) GetCompletedResults() []RunResult {
+ checkout.mu.Lock()
+ defer checkout.mu.Unlock()
+ return append([]RunResult{}, checkout.Completed...)
}
func (ctx *TestbedContext) NewCheckout(config *CheckoutConfig, mgrConfig json.RawMessage) (*Checkout, error) {
@@ -40,6 +70,7 @@ func (ctx *TestbedContext) NewCheckout(config *CheckoutConfig, mgrConfig json.Ra
Name: config.Name,
Path: filepath.Join(ctx.Config.Workdir, "checkouts", config.Name),
ManagerConfig: mgrConfig,
+ Running: make(map[Instance]bool),
}
log.Printf("[%s] Checking out", checkout.Name)
if osutil.IsExist(checkout.Path) {
diff --git a/tools/syz-testbed/html.go b/tools/syz-testbed/html.go
index 5a9b2f0b9..f76601a41 100644
--- a/tools/syz-testbed/html.go
+++ b/tools/syz-testbed/html.go
@@ -166,11 +166,17 @@ type uiMainPage struct {
}
func (ctx *TestbedContext) getTableTypes() []uiTableType {
- typeList := []uiTableType{
+ allTypeList := []uiTableType{
{HTMLStatsTable, "Statistics", ctx.httpMainStatsTable},
{HTMLBugsTable, "Bugs", ctx.genSimpleTableController((StatView).GenerateBugTable, true)},
{HTMLBugCountsTable, "Bug Counts", ctx.genSimpleTableController((StatView).GenerateBugCountsTable, false)},
}
+ typeList := []uiTableType{}
+ for _, t := range allTypeList {
+ if ctx.Target.SupportsHTMLView(t.Key) {
+ typeList = append(typeList, t)
+ }
+ }
return typeList
}
diff --git a/tools/syz-testbed/instance.go b/tools/syz-testbed/instance.go
index 888ad9ff7..f34df8a98 100644
--- a/tools/syz-testbed/instance.go
+++ b/tools/syz-testbed/instance.go
@@ -14,28 +14,35 @@ import (
"github.com/google/syzkaller/pkg/osutil"
)
+type Instance interface {
+ Run() error
+ Stop()
+ FetchResult() (RunResult, error)
+}
+
// The essential information about an active instance.
-type Instance struct {
+type InstanceCommon struct {
Name string
- Workdir string
- BenchFile string
LogFile string
ExecCommand string
ExecCommandArgs []string
stopChannel chan bool
}
-func (inst *Instance) Run() error {
+func (inst *InstanceCommon) Run() error {
const stopDelay = time.Minute
- logfile, err := os.Create(inst.LogFile)
- if err != nil {
- return fmt.Errorf("[%s] failed to create logfile: %s", inst.Name, err)
- }
log.Printf("[%s] starting", inst.Name)
cmd := osutil.GraciousCommand(inst.ExecCommand, inst.ExecCommandArgs...)
- cmd.Stdout = logfile
- cmd.Stderr = logfile
+
+ if inst.LogFile != "" {
+ logfile, err := os.Create(inst.LogFile)
+ if err != nil {
+ return fmt.Errorf("[%s] failed to create logfile: %s", inst.Name, err)
+ }
+ cmd.Stdout = logfile
+ cmd.Stderr = logfile
+ }
complete := make(chan error)
cmd.Start()
@@ -44,8 +51,8 @@ func (inst *Instance) Run() error {
}()
select {
- case err := <-complete:
- return fmt.Errorf("[%s] stopped: %s", inst.Name, err)
+ case <-complete:
+ return nil
case <-inst.stopChannel:
// TODO: handle other OSes?
cmd.Process.Signal(os.Interrupt)
@@ -62,14 +69,20 @@ func (inst *Instance) Run() error {
}
}
-func (inst *Instance) Stop() {
+func (inst *InstanceCommon) Stop() {
select {
case inst.stopChannel <- true:
default:
}
}
-func (inst *Instance) FetchResult() (*RunResult, error) {
+type SyzManagerInstance struct {
+ InstanceCommon
+ SyzkallerInfo
+ RunTime time.Duration
+}
+
+func (inst *SyzManagerInstance) FetchResult() (RunResult, error) {
bugs, err := collectBugs(inst.Workdir)
if err != nil {
return nil, err
@@ -78,39 +91,45 @@ func (inst *Instance) FetchResult() (*RunResult, error) {
if err != nil {
return nil, err
}
- return &RunResult{
- Workdir: inst.Workdir,
+ return &SyzManagerResult{
Bugs: bugs,
StatRecords: records,
}, nil
}
-func (ctx *TestbedContext) NewInstance(checkout *Checkout, mgrName string) (*Instance, error) {
- defer func() {
- ctx.NextInstanceID++
+func (inst *SyzManagerInstance) Run() error {
+ ret := make(chan error, 1)
+ go func() {
+ ret <- inst.InstanceCommon.Run()
}()
- name := fmt.Sprintf("%s-%d", checkout.Name, ctx.NextInstanceID)
- managerCfgPath := filepath.Join(checkout.Path, fmt.Sprintf("syz-%d.cnf", ctx.NextInstanceID))
- workdir := filepath.Join(checkout.Path, fmt.Sprintf("workdir_%d", ctx.NextInstanceID))
- bench := filepath.Join(checkout.Path, fmt.Sprintf("bench-%d.txt", ctx.NextInstanceID))
- logFile := filepath.Join(checkout.Path, fmt.Sprintf("log-%d.txt", ctx.NextInstanceID))
- log.Printf("[%s] Generating workdir", name)
+ select {
+ case err := <-ret:
+ // Syz-managers are not supposed to stop themselves under normal circumstances.
+ // If one of them did stop, there must have been a very good reason to do so.
+ return fmt.Errorf("[%s] stopped: %v", inst.Name, err)
+ case <-time.After(inst.RunTime):
+ inst.Stop()
+ <-ret
+ return nil
+ }
+}
+
+type SyzkallerInfo struct {
+ Workdir string
+ CfgFile string
+ BenchFile string
+}
+
+func SetupSyzkallerInstance(mgrName, folder string, checkout *Checkout) (*SyzkallerInfo, error) {
+ workdir := filepath.Join(folder, "workdir")
+ log.Printf("[%s] Generating workdir", mgrName)
err := osutil.MkdirAll(workdir)
if err != nil {
return nil, fmt.Errorf("failed to create workdir %s", workdir)
}
-
- if ctx.Config.Corpus != "" {
- log.Printf("[%s] Copying corpus", name)
- corpusPath := filepath.Join(workdir, "corpus.db")
- err = osutil.CopyFile(ctx.Config.Corpus, corpusPath)
- if err != nil {
- return nil, fmt.Errorf("failed to copy corpus from %s: %s", ctx.Config.Corpus, err)
- }
- }
-
- log.Printf("[%s] Generating syz-manager config", name)
+ log.Printf("[%s] Generating syz-manager config", mgrName)
+ cfgFile := filepath.Join(folder, "manager.cfg")
managerCfg, err := config.PatchJSON(checkout.ManagerConfig, map[string]interface{}{
"name": mgrName,
"workdir": workdir,
@@ -119,19 +138,40 @@ func (ctx *TestbedContext) NewInstance(checkout *Checkout, mgrName string) (*Ins
if err != nil {
return nil, fmt.Errorf("failed to patch mgr config")
}
-
- err = osutil.WriteFile(managerCfgPath, managerCfg)
+ err = osutil.WriteFile(cfgFile, managerCfg)
if err != nil {
- return nil, fmt.Errorf("failed to save manager config to %s: %s", managerCfgPath, err)
+ return nil, fmt.Errorf("failed to save manager config to %s: %s", cfgFile, err)
}
+ return &SyzkallerInfo{
+ Workdir: workdir,
+ CfgFile: cfgFile,
+ BenchFile: filepath.Join(folder, "bench.txt"),
+ }, nil
+}
- return &Instance{
- Name: name,
- Workdir: workdir,
- BenchFile: bench,
- LogFile: logFile,
- ExecCommand: filepath.Join(checkout.Path, "bin", "syz-manager"),
- ExecCommandArgs: []string{"-config", managerCfgPath, "-bench", bench},
- stopChannel: make(chan bool, 1),
+func (t *SyzManagerTarget) newSyzManagerInstance(slotName, uniqName string, checkout *Checkout) (Instance, error) {
+ folder := filepath.Join(checkout.Path, fmt.Sprintf("run-%s", uniqName))
+ common, err := SetupSyzkallerInstance(slotName, folder, checkout)
+ if err != nil {
+ return nil, err
+ }
+ if t.config.Corpus != "" {
+ log.Printf("[%s] Copying corpus", uniqName)
+ corpusPath := filepath.Join(common.Workdir, "corpus.db")
+ err = osutil.CopyFile(t.config.Corpus, corpusPath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to copy corpus from %s: %s", t.config.Corpus, err)
+ }
+ }
+ return &SyzManagerInstance{
+ InstanceCommon: InstanceCommon{
+ Name: uniqName,
+ LogFile: filepath.Join(folder, "log.txt"),
+ ExecCommand: filepath.Join(checkout.Path, "bin", "syz-manager"),
+ ExecCommandArgs: []string{"-config", common.CfgFile, "-bench", common.BenchFile},
+ stopChannel: make(chan bool, 1),
+ },
+ SyzkallerInfo: *common,
+ RunTime: t.config.RunTime.Duration,
}, nil
}
diff --git a/tools/syz-testbed/stats.go b/tools/syz-testbed/stats.go
index 0db75c45c..77fed55b7 100644
--- a/tools/syz-testbed/stats.go
+++ b/tools/syz-testbed/stats.go
@@ -19,9 +19,10 @@ type BugInfo struct {
Title string
}
+type RunResult interface{}
+
// The information collected from a syz-manager instance.
-type RunResult struct {
- Workdir string
+type SyzManagerResult struct {
Bugs []BugInfo
StatRecords []StatRecord
}
@@ -32,7 +33,7 @@ type StatRecord map[string]uint64
// The grouping of single instance results. Taken by all stat generating routines.
type RunResultGroup struct {
Name string
- Results []*RunResult
+ Results []RunResult
}
// Different "views" of the statistics, e.g. only completed instances or completed + running.
@@ -105,7 +106,7 @@ type BugSummary struct {
func summarizeBugs(groups []RunResultGroup) ([]*BugSummary, error) {
bugsMap := make(map[string]*BugSummary)
for _, group := range groups {
- for _, result := range group.Results {
+ for _, result := range group.SyzManagerResults() {
for _, bug := range result.Bugs {
summary := bugsMap[bug.Title]
if summary == nil {
@@ -171,6 +172,17 @@ func (view StatView) GenerateBugCountsTable() (*Table, error) {
return table, nil
}
+func (group RunResultGroup) SyzManagerResults() []*SyzManagerResult {
+ ret := []*SyzManagerResult{}
+ for _, rawRes := range group.Results {
+ res, ok := rawRes.(*SyzManagerResult)
+ if ok {
+ ret = append(ret, res)
+ }
+ }
+ return ret
+}
+
func (group RunResultGroup) AvgStatRecords() []map[string]uint64 {
ret := []map[string]uint64{}
commonLen := group.minResultLength()
@@ -188,8 +200,9 @@ func (group RunResultGroup) minResultLength() int {
if len(group.Results) == 0 {
return 0
}
- ret := len(group.Results[0].StatRecords)
- for _, result := range group.Results {
+ results := group.SyzManagerResults()
+ ret := len(results[0].StatRecords)
+ for _, result := range results {
currLen := len(result.StatRecords)
if currLen < ret {
ret = currLen
@@ -200,7 +213,7 @@ func (group RunResultGroup) minResultLength() int {
func (group RunResultGroup) groupNthRecord(i int) map[string]*stats.Sample {
records := []StatRecord{}
- for _, result := range group.Results {
+ for _, result := range group.SyzManagerResults() {
records = append(records, result.StatRecords[i])
}
return groupSamples(records)
@@ -264,7 +277,7 @@ func (view StatView) InstanceStatsTable() (*Table, error) {
for i, result := range group.Results {
newView.Groups = append(newView.Groups, RunResultGroup{
Name: fmt.Sprintf("%s-%d", group.Name, i),
- Results: []*RunResult{result},
+ Results: []RunResult{result},
})
}
}
diff --git a/tools/syz-testbed/targets.go b/tools/syz-testbed/targets.go
new file mode 100644
index 000000000..c19dec2ca
--- /dev/null
+++ b/tools/syz-testbed/targets.go
@@ -0,0 +1,83 @@
+// Copyright 2022 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "path/filepath"
+ "sync"
+
+ "github.com/google/syzkaller/pkg/osutil"
+)
+
+// TestbedTarget represents all behavioral differences between specific testbed targets.
+type TestbedTarget interface {
+ NewJob(slotName string, checkouts []*Checkout) (*Checkout, Instance, error)
+ SaveStatView(view StatView, dir string) error
+ SupportsHTMLView(key string) bool
+}
+
+type SyzManagerTarget struct {
+ config *TestbedConfig
+ nextCheckoutID int
+ nextInstanceID int
+ mu sync.Mutex
+}
+
+var targetConstructors = map[string]func(cfg *TestbedConfig) TestbedTarget{
+ "syz-manager": func(cfg *TestbedConfig) TestbedTarget {
+ return &SyzManagerTarget{
+ config: cfg,
+ }
+ },
+}
+
+func (t *SyzManagerTarget) NewJob(slotName string, checkouts []*Checkout) (*Checkout, Instance, error) {
+ // Round-robin strategy should suffice.
+ t.mu.Lock()
+ checkout := checkouts[t.nextCheckoutID%len(checkouts)]
+ instanceID := t.nextInstanceID
+ t.nextCheckoutID++
+ t.nextInstanceID++
+ t.mu.Unlock()
+ uniqName := fmt.Sprintf("%s-%d", checkout.Name, instanceID)
+ instance, err := t.newSyzManagerInstance(slotName, uniqName, checkout)
+ if err != nil {
+ return nil, nil, err
+ }
+ return checkout, instance, nil
+}
+
+func (t *SyzManagerTarget) SupportsHTMLView(key string) bool {
+ supported := map[string]bool{
+ HTMLBugsTable: true,
+ HTMLBugCountsTable: true,
+ HTMLStatsTable: true,
+ }
+ return supported[key]
+}
+
+func (t *SyzManagerTarget) SaveStatView(view StatView, dir string) error {
+ benchDir := filepath.Join(dir, "benches")
+ err := osutil.MkdirAll(benchDir)
+ if err != nil {
+ return fmt.Errorf("failed to create %s: %s", benchDir, err)
+ }
+ tableStats := map[string]func(view StatView) (*Table, error){
+ "bugs.csv": (StatView).GenerateBugTable,
+ "checkout_stats.csv": (StatView).StatsTable,
+ "instance_stats.csv": (StatView).InstanceStatsTable,
+ }
+ for fileName, genFunc := range tableStats {
+ table, err := genFunc(view)
+ if err == nil {
+ table.SaveAsCsv(filepath.Join(dir, fileName))
+ } else {
+ log.Printf("stat generation error: %s", err)
+ }
+ }
+ _, err = view.SaveAvgBenches(benchDir)
+ return err
+}
diff --git a/tools/syz-testbed/testbed.go b/tools/syz-testbed/testbed.go
index f83d1dfa9..6f7a1868d 100644
--- a/tools/syz-testbed/testbed.go
+++ b/tools/syz-testbed/testbed.go
@@ -30,6 +30,7 @@ var (
type TestbedConfig struct {
Name string `json:"name"` // name of the testbed
+ Target string `json:"target"` // what application to test
MaxInstances int `json:"max_instances"` // max # of simultaneously running instances
RunTime DurationConfig `json:"run_time"` // lifetime of an instance (default "24h")
HTTP string `json:"http"` // on which port to set up a simple web dashboard
@@ -54,16 +55,17 @@ type CheckoutConfig struct {
type TestbedContext struct {
Config *TestbedConfig
Checkouts []*Checkout
- NextRestart time.Time
NextCheckoutID int
NextInstanceID int
- statMutex sync.Mutex
+ Target TestbedTarget
+ mu sync.Mutex
}
func main() {
flag.Parse()
cfg := &TestbedConfig{
Name: "testbed",
+ Target: "syz-manager",
RunTime: DurationConfig{24 * time.Hour},
}
err := config.LoadFile(*flagConfig, &cfg)
@@ -77,6 +79,7 @@ func main() {
}
ctx := TestbedContext{
Config: cfg,
+ Target: targetConstructors[cfg.Target](cfg),
}
go ctx.setupHTTPServer()
@@ -123,20 +126,15 @@ func (ctx *TestbedContext) GetStatViews() ([]StatView, error) {
groupsCompleted := []RunResultGroup{}
groupsAll := []RunResultGroup{}
for _, checkout := range ctx.Checkouts {
- running := []*RunResult{}
- for _, instance := range checkout.Running {
- result, err := instance.FetchResult()
- if err == nil {
- running = append(running, result)
- }
- }
+ running := checkout.GetRunningResults()
+ completed := checkout.GetCompletedResults()
groupsCompleted = append(groupsCompleted, RunResultGroup{
Name: checkout.Name,
- Results: checkout.Completed,
+ Results: completed,
})
groupsAll = append(groupsAll, RunResultGroup{
Name: checkout.Name,
- Results: append(checkout.Completed, running...),
+ Results: append(completed, running...),
})
}
return []StatView{
@@ -151,56 +149,35 @@ func (ctx *TestbedContext) GetStatViews() ([]StatView, error) {
}, nil
}
-func (ctx *TestbedContext) saveStatView(view StatView) error {
- dir := filepath.Join(ctx.Config.Workdir, "stats_"+view.Name)
- benchDir := filepath.Join(dir, "benches")
- err := osutil.MkdirAll(benchDir)
- if err != nil {
- return fmt.Errorf("failed to create %s: %s", benchDir, err)
- }
- tableStats := map[string]func(view StatView) (*Table, error){
- "bugs.csv": (StatView).GenerateBugTable,
- "checkout_stats.csv": (StatView).StatsTable,
- "instance_stats.csv": (StatView).InstanceStatsTable,
- }
- for fileName, genFunc := range tableStats {
- table, err := genFunc(view)
- if err == nil {
- table.SaveAsCsv(filepath.Join(dir, fileName))
- } else {
- log.Printf("stat generation error: %s", err)
- }
- }
- _, err = view.SaveAvgBenches(benchDir)
- return err
-}
-
func (ctx *TestbedContext) TestbedStatsTable() *Table {
- table := NewTable("Checkout", "Running", "Completed", "Until reset")
+ table := NewTable("Checkout", "Running", "Completed", "Last started")
for _, checkout := range ctx.Checkouts {
- until := "-"
- if ctx.NextRestart.After(time.Now()) {
- until = time.Until(ctx.NextRestart).Round(time.Second).String()
+ checkout.mu.Lock()
+ last := ""
+ if !checkout.LastRunning.IsZero() {
+ last = time.Since(checkout.LastRunning).Round(time.Second).String()
}
table.AddRow(checkout.Name,
fmt.Sprintf("%d", len(checkout.Running)),
fmt.Sprintf("%d", len(checkout.Completed)),
- until,
+ last,
)
+ checkout.mu.Unlock()
}
return table
}
func (ctx *TestbedContext) SaveStats() error {
// Preventing concurrent saving of the stats.
- ctx.statMutex.Lock()
- defer ctx.statMutex.Unlock()
+ ctx.mu.Lock()
+ defer ctx.mu.Unlock()
views, err := ctx.GetStatViews()
if err != nil {
return err
}
for _, view := range views {
- err := ctx.saveStatView(view)
+ dir := filepath.Join(ctx.Config.Workdir, "stats_"+view.Name)
+ err := ctx.Target.SaveStatView(view, dir)
if err != nil {
return err
}
@@ -209,84 +186,69 @@ func (ctx *TestbedContext) SaveStats() error {
return table.SaveAsCsv(filepath.Join(ctx.Config.Workdir, "testbed.csv"))
}
-func (ctx *TestbedContext) generateInstances(count int) ([]*Instance, error) {
+func (ctx *TestbedContext) Slot(slotID int, stop chan struct{}, ret chan error) {
// It seems that even gracefully finished syz-managers can leak GCE instances.
- // To allow for that strange behavior, let's reuse syz-manager names, so that
- // they will in turn reuse the names of the leaked GCE instances.
- instances := []*Instance{}
- for idx := 1; idx <= count; idx++ {
- checkout := ctx.Checkouts[ctx.NextCheckoutID]
- ctx.NextCheckoutID = (ctx.NextCheckoutID + 1) % len(ctx.Checkouts)
- instance, err := ctx.NewInstance(checkout, fmt.Sprintf("%s-%d", ctx.Config.Name, idx))
- if err != nil {
- return nil, err
- }
- checkout.Running = append(checkout.Running, instance)
- instances = append(instances, instance)
- }
- return instances, nil
-}
-
-// Create instances, run them, stop them, archive them, and so on...
-func (ctx *TestbedContext) Loop(stop chan struct{}) {
- duration := ctx.Config.RunTime.Duration
- mustStop := false
- for !mustStop {
- log.Printf("setting up instances")
- instances, err := ctx.generateInstances(ctx.Config.MaxInstances)
+ // To allow for that strange behavior, let's reuse syz-manager names in each slot,
+ // so that its VMs will in turn reuse the names of the leaked ones.
+ slotName := fmt.Sprintf("%s-%d", ctx.Config.Name, slotID)
+ for {
+ checkout, instance, err := ctx.Target.NewJob(slotName, ctx.Checkouts)
if err != nil {
- tool.Failf("failed to set up intances: %s", err)
- }
- log.Printf("starting instances")
- instanceStatuses := make(chan error, len(instances))
- var wg sync.WaitGroup
- for _, inst := range instances {
- wg.Add(1)
- go func(instance *Instance) {
- instanceStatuses <- instance.Run()
- wg.Done()
- }(inst)
+ ret <- fmt.Errorf("failed to create instance: %s", err)
+ return
}
+ checkout.AddRunning(instance)
+ retChannel := make(chan error)
+ go func() {
+ retChannel <- instance.Run()
+ }()
- ctx.NextRestart = time.Now().Add(duration)
+ var retErr error
select {
- case err := <-instanceStatuses:
- // Syz-managers are not supposed to stop under normal circumstances.
- // If one of them did stop, there must have been a very good reason to.
- // For now, we just shut down the whole experiment in such a case.
- log.Printf("an instance has failed (%s), stopping everything", err)
- mustStop = true
case <-stop:
- log.Printf("stopping the experiment")
- mustStop = true
- case <-time.After(duration):
- log.Printf("run period has finished")
- }
-
- // Wait for all instances to finish.
- for _, instance := range instances {
instance.Stop()
+ <-retChannel
+ retErr = fmt.Errorf("instance was killed")
+ case retErr = <-retChannel:
}
- wg.Wait()
- // Only mark instances completed if they've indeed been running the whole iteration.
- if !mustStop {
- for _, checkout := range ctx.Checkouts {
- err = checkout.ArchiveRunning()
- if err != nil {
- tool.Failf("ArchiveRunning error: %s", err)
- }
- }
+ // For now, we only archive instances that finished normally (ret == nil).
+ // syz-testbed will anyway stop after such an error, so it's not a problem
+ // that they remain in Running.
+ if retErr != nil {
+ ret <- retErr
+ return
}
-
- log.Printf("collecting statistics")
- err = ctx.SaveStats()
+ err = checkout.ArchiveInstance(instance)
if err != nil {
- log.Printf("stats saving error: %s", err)
+ ret <- fmt.Errorf("a call to ArchiveInstance failed: %s", err)
+ return
}
}
}
+// Create instances, run them, stop them, archive them, and so on...
+func (ctx *TestbedContext) Loop(stop chan struct{}) {
+ stopAll := make(chan struct{})
+ errors := make(chan error)
+ for i := 0; i < ctx.Config.MaxInstances; i++ {
+ go ctx.Slot(i, stopAll, errors)
+ }
+
+ exited := 0
+ select {
+ case <-stop:
+ log.Printf("stopping the experiment")
+ case err := <-errors:
+ exited = 1
+ log.Printf("an instance has failed (%s), stopping everything", err)
+ }
+ close(stopAll)
+ for ; exited < ctx.Config.MaxInstances; exited++ {
+ <-errors
+ }
+}
+
func (d *DurationConfig) UnmarshalJSON(data []byte) error {
var v interface{}
if err := json.Unmarshal(data, &v); err != nil {
@@ -302,6 +264,7 @@ func (d *DurationConfig) UnmarshalJSON(data []byte) error {
}
return err
}
+
func (d *DurationConfig) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
@@ -328,6 +291,9 @@ func checkConfig(cfg *TestbedConfig) error {
if cfg.BenchCmp != "" && !osutil.IsExist(cfg.BenchCmp) {
return fmt.Errorf("benchmp path is specified, but %s does not exist", cfg.BenchCmp)
}
+ if _, ok := targetConstructors[cfg.Target]; !ok {
+ return fmt.Errorf("unknown target %s", cfg.Target)
+ }
cfg.Corpus = osutil.Abs(cfg.Corpus)
names := make(map[string]bool)
for idx := range cfg.Checkouts {