From 8fc1a84b1b2fd28b68d512da6041e1c079a2b5ac Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Sun, 8 Dec 2019 17:46:02 +0000 Subject: [PATCH] limits --- cmd/internal/topic.go | 1 + cmd/wasmcloud-executor/main.go | 68 +++++++++--------- cmd/wasmcloud/main.go | 9 +-- cmd/wasmcloud/run.go | 127 +++++++++++++++++++++++++++++++++ executor/executor.go | 93 ++++++++++++++++++++++++ go.mod | 2 + go.sum | 5 ++ 7 files changed, 268 insertions(+), 37 deletions(-) create mode 100644 cmd/wasmcloud/run.go create mode 100644 executor/executor.go diff --git a/cmd/internal/topic.go b/cmd/internal/topic.go index 31731e6..8bdfaa5 100644 --- a/cmd/internal/topic.go +++ b/cmd/internal/topic.go @@ -7,6 +7,7 @@ const TopicName = "wasmcloud-to-executor-09d7e475-71ac-4bdd-be37-050af7a81c58" type ExecRequest struct { WASMCID string `json:"wasmcid"` + Name string `json:"name"` Data []byte `json:"data"` ABI ABI `json:"abi"` Env map[string]string `json:"env"` diff --git a/cmd/wasmcloud-executor/main.go b/cmd/wasmcloud-executor/main.go index 8e0cc43..ac0e2d4 100644 --- a/cmd/wasmcloud-executor/main.go +++ b/cmd/wasmcloud-executor/main.go @@ -6,7 +6,6 @@ import ( "encoding/json" "flag" "fmt" - "io" "io/ioutil" "log" "os" @@ -16,16 +15,17 @@ import ( "time" shell "github.com/ipfs/go-ipfs-api" - "github.com/perlin-network/life/compiler" "github.com/perlin-network/life/exec" "github.com/rogpeppe/go-internal/txtar" "tulpa.dev/within/wasmcloud/cmd/internal" - "within.website/olin/abi/cwa" + "tulpa.dev/within/wasmcloud/executor" ) var ( ipfsURL = flag.String("ipfs-host", "localhost:5001", "IPFS host (must have pubsub experiment enabled)") workerCount = flag.Int("worker-count", 1, "number of wasm executor workers") + gasLimit = flag.Int("gas-limit", 1048576, "number of wasm instructions per execution") + ramLimit = flag.Int("ram-limit", 128, "number of wasm pages that can be used") ) func main() { @@ -44,6 +44,9 @@ func main() { go func() { <-c cancel() + log.Println("press ^C again to kill all of this") + <-c + os.Exit(0) }() var wg sync.WaitGroup @@ -83,6 +86,7 @@ func waitForNewWASM(ctx context.Context, wg *sync.WaitGroup, sh *shell.Shell, su stdin := bytes.NewBuffer(er.Data) stdout := bytes.NewBuffer(nil) + stderr := bytes.NewBuffer(nil) logBuf := bytes.NewBuffer(nil) bin, err := sh.Cat(er.WASMCID) @@ -98,49 +102,47 @@ func waitForNewWASM(ctx context.Context, wg *sync.WaitGroup, sh *shell.Shell, su continue } - p := cwa.NewProcess(er.WASMCID, nil, er.Env) - p.Stdin = stdin - p.Stdout = stdout - p.Stderr = stdout - p.Logger = log.New(io.MultiWriter(logBuf, os.Stdout), er.WASMCID+" ", log.LstdFlags) + c := executor.Config{ + VMConfig: exec.VMConfig{ + GasLimit: uint64(*gasLimit), + ReturnOnGasLimitExceeded: true, + MaxMemoryPages: *ramLimit, + }, - gp := &compiler.SimpleGasPolicy{GasPerInstruction: 1} - vm, err := exec.NewVirtualMachine(wasmBin, exec.VMConfig{}, p, gp) + Name: er.Name, + FuncName: "cwa_main", + Env: er.Env, + Binary: wasmBin, + Stdin: stdin, + Stdout: stdout, + Stderr: stderr, + LogSink: logBuf, + } + + result, err := executor.Run(c) if err != nil { - p.Logger.Printf("[SYSTEM] can't create VM: %v", err) - continue + log.Printf("can't run binary: %v", err) } - main, ok := vm.GetFunctionExport("cwa_main") - if !ok { - p.Logger.Printf("[SYSTEM] can't get main function") - continue - } - - begin := time.Now() - ret, err := vm.Run(main) - if err != nil { - p.Logger.Printf("error running main: %v", err) - continue - } - dur := time.Since(begin) - - p.Logger.Printf("[SYSTEM] return status: %v", ret) - arc := txtar.Archive{ - Comment: []byte(fmt.Sprintf("%s: execution of %s at %s", er.UUID, er.WASMCID, begin.Format(time.RFC3339))), + Comment: []byte(fmt.Sprintf("%s: execution of %s at %s", er.UUID, er.WASMCID, result.StartTime.Format(time.RFC3339))), Files: []txtar.File{ { - Name: "vmstats.txt", - Data: []byte(fmt.Sprintf("execution time: %s\ngas used: %v\nsyscall count: %v", dur, vm.Gas, p.SyscallCount())), + Name: "logs.txt", + Data: logBuf.Bytes(), }, { Name: "stdout.txt", Data: stdout.Bytes(), }, { - Name: "logs.txt", - Data: logBuf.Bytes(), + Name: "stderr.txt", + Data: stderr.Bytes(), + }, + result.ToFile(), + { + Name: "wasm.cid", + Data: []byte(er.WASMCID), }, }, } diff --git a/cmd/wasmcloud/main.go b/cmd/wasmcloud/main.go index 57f4e56..a55dfdc 100644 --- a/cmd/wasmcloud/main.go +++ b/cmd/wasmcloud/main.go @@ -18,10 +18,11 @@ func main() { subcommands.Register(subcommands.HelpCommand(), "") subcommands.Register(subcommands.FlagsCommand(), "") subcommands.Register(subcommands.CommandsCommand(), "") - subcommands.Register(&loginCmd{}, "") - subcommands.Register(&whoamiCmd{}, "") - subcommands.Register(namegenCmd{}, "util") - subcommands.Register(&testWorkCommand{}, "util") + subcommands.Register(&loginCmd{}, "api") + subcommands.Register(&whoamiCmd{}, "api") + subcommands.Register(namegenCmd{}, "utils") + subcommands.Register(&testWorkCommand{}, "utils") + subcommands.Register(&runCmd{}, "utils") subcommands.ImportantFlag("api-server") subcommands.ImportantFlag("config") diff --git a/cmd/wasmcloud/run.go b/cmd/wasmcloud/run.go new file mode 100644 index 0000000..77f0894 --- /dev/null +++ b/cmd/wasmcloud/run.go @@ -0,0 +1,127 @@ +package main + +import ( + "bytes" + "context" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + "time" + + "github.com/google/subcommands" + "github.com/perlin-network/life/exec" + "github.com/rogpeppe/go-internal/txtar" + "tulpa.dev/within/wasmcloud/executor" +) + +type runCmd struct { + funcName, abi string + gasLimit uint64 + ramLimit int +} + +func (runCmd) Name() string { return "run" } +func (runCmd) Synopsis() string { + return "run a webassembly file with the same environment as production servers" +} +func (runCmd) Usage() string { + return `wasmcloud run [options] + +$ wasmcloud run olinfetch.wasm +$ wasmcloud run -abi dagger -func-name dagger_main hello_dagger.wasm + +Run a given webassembly binary and return the output. + +Flags: +` +} + +func (r *runCmd) SetFlags(fs *flag.FlagSet) { + fs.StringVar(&r.funcName, "func-name", "cwa_main", "entrypoint function to call") + fs.StringVar(&r.abi, "abi", "cwa", "ABI to use") + fs.Uint64Var(&r.gasLimit, "gas-limit", 1048576, "number of wasm instructions per execution") + fs.IntVar(&r.ramLimit, "ram-limit", 128, "number of wasm pages that can be used") +} + +func (r runCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + if fs.NArg() != 1 { + fmt.Println(r.Usage()) + return subcommands.ExitUsageError + } + + fname := fs.Arg(0) + + stdout := bytes.NewBuffer(nil) + stderr := bytes.NewBuffer(nil) + logBuf := bytes.NewBuffer(nil) + + wasmBin, err := ioutil.ReadFile(fname) + if err != nil { + log.Fatal(err) + } + + getenvironment := func(data []string, getkeyval func(item string) (key, val string)) map[string]string { + items := make(map[string]string) + for _, item := range data { + key, val := getkeyval(item) + items[key] = val + } + return items + } + environment := getenvironment(os.Environ(), func(item string) (key, val string) { + splits := strings.Split(item, "=") + key = splits[0] + val = splits[1] + return + }) + + c := executor.Config{ + VMConfig: exec.VMConfig{ + GasLimit: r.gasLimit, + ReturnOnGasLimitExceeded: true, + MaxMemoryPages: r.ramLimit, + }, + + Name: filepath.Base(fname), + FuncName: r.funcName, + Env: environment, + Binary: wasmBin, + Stdin: os.Stdin, + Stdout: stdout, + Stderr: stderr, + LogSink: logBuf, + } + + result, err := executor.Run(c) + if err != nil { + log.Printf("can't run binary: %v", err) + return subcommands.ExitFailure + } + + arc := txtar.Archive{ + Comment: []byte(fmt.Sprintf("execution of %s at %s", fname, result.StartTime.Format(time.RFC3339))), + Files: []txtar.File{ + { + Name: "logs.txt", + Data: logBuf.Bytes(), + }, + { + Name: "stdout.txt", + Data: stdout.Bytes(), + }, + { + Name: "stderr.txt", + Data: stderr.Bytes(), + }, + result.ToFile(), + }, + } + + fmt.Println(string(txtar.Format(&arc))) + + return subcommands.ExitSuccess +} diff --git a/executor/executor.go b/executor/executor.go new file mode 100644 index 0000000..8465f30 --- /dev/null +++ b/executor/executor.go @@ -0,0 +1,93 @@ +package executor + +import ( + "errors" + "fmt" + "io" + "log" + "os" + "time" + + "github.com/gosuri/uitable" + "github.com/perlin-network/life/compiler" + "github.com/perlin-network/life/exec" + "github.com/rogpeppe/go-internal/txtar" + "within.website/olin/abi/cwa" +) + +var ( + ErrNoCWAMain = errors.New("executor: no cwa_main function defined") +) + +type Config struct { + exec.VMConfig + + Name string + FuncName string + Env map[string]string + Binary []byte + Stdin io.Reader + Stdout, Stderr io.Writer + LogSink io.Writer +} + +type Result struct { + Status int64 + GasUsed uint64 + SyscallCount int64 + ExecDur time.Duration + PagesUsed int + StartTime time.Time +} + +func (r Result) ToFile() txtar.File { + table := uitable.New() + + table.AddRow("Status", fmt.Sprint(r.Status)) + table.AddRow("Gas Used", fmt.Sprint(r.GasUsed)) + table.AddRow("Syscall Count", fmt.Sprint(r.SyscallCount)) + table.AddRow("Exec Duration", r.ExecDur.String()) + table.AddRow("Pages Used", fmt.Sprint(r.PagesUsed)) + + return txtar.File{ + Name: "vmstats.txt", + Data: []byte(table.String()), + } +} + +func Run(c Config) (*Result, error) { + p := cwa.NewProcess(c.Name, nil, c.Env) + p.Stdin = c.Stdin + p.Stdout = c.Stdout + p.Stderr = c.Stderr + p.Logger = log.New(io.MultiWriter(c.LogSink, os.Stdout), c.Name+" ", log.LstdFlags) + + gp := &compiler.SimpleGasPolicy{GasPerInstruction: 1} + vm, err := exec.NewVirtualMachine(c.Binary, c.VMConfig, p, gp) + if err != nil { + return nil, err + } + + main, ok := vm.GetFunctionExport(c.FuncName) + if !ok { + return nil, ErrNoCWAMain + } + + begin := time.Now() + ret, err := vm.Run(main) + if err != nil { + return nil, err + } + dur := time.Since(begin) + + p.Logger.Printf("[SYSTEM] return status: %v", ret) + + return &Result{ + Status: ret, + GasUsed: vm.Gas, + SyscallCount: p.SyscallCount(), + ExecDur: dur, + PagesUsed: len(vm.Memory) / 65536, + StartTime: begin, + }, nil +} diff --git a/go.mod b/go.mod index 2574fca..12377d3 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,11 @@ replace github.com/go-interpreter/wagon v0.0.0 => github.com/perlin-network/wago require ( github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456 + github.com/fatih/color v1.7.0 // indirect github.com/google/subcommands v1.0.1 github.com/google/uuid v1.1.1 github.com/gorilla/mux v1.7.3 + github.com/gosuri/uitable v0.0.4 github.com/ipfs/go-ipfs-api v0.0.2 github.com/jinzhu/gorm v1.9.11 github.com/manifoldco/promptui v0.6.0 diff --git a/go.sum b/go.sum index 10f5f7f..4e019c7 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/primitive v0.0.0-20190214200932-673f57e7b1b5/go.mod h1:Tm6t8LbdhSCXNfpjTwoL1mdjCnyKHkMyf6PqQXo7Or8= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -128,6 +130,8 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gxed/hashland/keccakpg v0.0.1 h1:wrk3uMNaMxbXiHibbPO4S0ymqJMm41WiudyFSs7UnsU= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= @@ -194,6 +198,7 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=