package executor import ( "encoding/json" "errors" "fmt" "io" "log" "net/http" "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 `json:"status"` GasUsed uint64 `json:"gas_used"` SyscallCount int64 `json:"syscall_count"` ExecDur time.Duration `json:"exec_dur"` PagesBeforeExec int `json:"pages_before_exec"` PagesUsed int `json:"pages_used"` StartTime time.Time `json:"start_time"` } func (r Result) StatsJSONFile() txtar.File { data, err := json.MarshalIndent(r, "", " ") if err != nil /* unlikely */ { panic(err) } return txtar.File{ Name: "vmstats.json", Data: data, } } func (r Result) StatsFile() 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)) table.AddRow("Page Delta", fmt.Sprint(r.PagesUsed-r.PagesBeforeExec)) 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) p.HC.Transport = WasmcloudBrandingTransport(c.Name, p.HC.Transport) 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 } pagesBefore := len(vm.Memory) / 65536 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, PagesBeforeExec: pagesBefore, PagesUsed: len(vm.Memory) / 65536, StartTime: begin, }, nil } func WasmcloudBrandingTransport(handlerName string, rt http.RoundTripper) http.RoundTripper { return wasmcloudBrandingTransport{ rt: rt, handlerName: handlerName, } } type wasmcloudBrandingTransport struct { rt http.RoundTripper handlerName string } func (w wasmcloudBrandingTransport) RoundTrip(r *http.Request) (*http.Response, error) { r.Header.Set("User-Agent", r.Header.Get("User-Agent")+" wasmcloud/"+w.handlerName) return w.rt.RoundTrip(r) }