| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package generators |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "os" |
| "os/exec" |
| "path/filepath" |
| |
| "golang.org/x/benchmarks/sweet/common" |
| "golang.org/x/benchmarks/sweet/common/fileutil" |
| "golang.org/x/benchmarks/sweet/harnesses" |
| |
| osi "github.com/opencontainers/runtime-spec/specs-go" |
| ) |
| |
| // GVisor is a dynamic assets Generator for the gvisor benchmark. |
| type GVisor struct{} |
| |
| // Generate builds binaries for workloads that will run under gVisor |
| // as part of the benchmark. The sources for these workloads live in |
| // the source assets directory and are relatively short Go programs. |
| // |
| // It also copies over static assets which are necessary to run the |
| // benchmarks. |
| func (_ GVisor) Generate(cfg *common.GenConfig) error { |
| goTool := *cfg.GoTool |
| goTool.Env = goTool.Env.MustSet("CGO_ENABLED=0") // Disable CGO for workloads. |
| |
| // Build workload sources into binaries in the output directory, |
| // with one binary for each supported platform. |
| workloads := []string{ |
| "http", |
| "syscall", |
| } |
| for _, workload := range workloads { |
| workloadSrcDir := filepath.Join(cfg.SourceAssetsDir, workload) |
| workloadOutDir := filepath.Join(cfg.OutputDir, workload) |
| if err := os.MkdirAll(workloadOutDir, 0755); err != nil { |
| return err |
| } |
| for _, p := range common.SupportedPlatforms { |
| // Generate the output directory. |
| platformDirName := fmt.Sprintf("%s-%s", p.GOOS, p.GOARCH) |
| workloadBinOutDir := filepath.Join(workloadOutDir, "bin", platformDirName) |
| if err := os.MkdirAll(workloadBinOutDir, 0755); err != nil { |
| return err |
| } |
| goTool := common.Go{Tool: goTool.Tool, Env: p.BuildEnv(goTool.Env)} |
| |
| // Build the workload. |
| err := goTool.BuildPath(workloadSrcDir, filepath.Join(workloadBinOutDir, "workload")) |
| if err != nil { |
| return fmt.Errorf("building workload %s for %s: %v", workload, p, err) |
| } |
| } |
| } |
| |
| // In order to regenerate startup/config.json, we require a working |
| // copy of runsc. Get and build it from the harness. |
| // |
| // Create a temporary directory where we can put the gVisor source. |
| tmpDir, err := os.MkdirTemp("", "gvisor-gen") |
| if err != nil { |
| return err |
| } |
| srcDir := filepath.Join(tmpDir, "src") |
| if err := os.MkdirAll(srcDir, os.ModePerm); err != nil { |
| return err |
| } |
| if err := (harnesses.GVisor{}).Get(srcDir); err != nil { |
| return err |
| } |
| |
| // Ensure the startup subdirectory exists. |
| if err := os.MkdirAll(filepath.Join(cfg.OutputDir, "startup"), 0755); err != nil { |
| return err |
| } |
| |
| // Build the runsc package in the repository. CGO_ENABLED must be 0. |
| // See https://github.com/google/gvisor#using-go-get. |
| cfg.GoTool.Env = cfg.GoTool.Env.MustSet("CGO_ENABLED=0") |
| runscBin := filepath.Join(tmpDir, "runsc") |
| if err := cfg.GoTool.BuildPath(filepath.Join(srcDir, "runsc"), runscBin); err != nil { |
| return err |
| } |
| |
| // Delete config.json if it already exists, because runsc |
| // will fail otherwise. |
| specFile := filepath.Join(cfg.OutputDir, "startup", "config.json") |
| if err := os.Remove(specFile); err != nil && !errors.Is(err, os.ErrNotExist) { |
| return err |
| } |
| |
| // Generate config.json. |
| cmd := exec.Command(runscBin, "spec") |
| cmd.Dir = filepath.Join(cfg.OutputDir, "startup") |
| if err := cmd.Run(); err != nil { |
| return err |
| } |
| |
| // Mutate the config.json slightly for our purposes and write it back out. |
| specBytes, err := os.ReadFile(specFile) |
| if err != nil { |
| return err |
| } |
| var spec osi.Spec |
| if err := json.Unmarshal(specBytes, &spec); err != nil { |
| return err |
| } |
| spec.Process.Terminal = false |
| spec.Process.Args = []string{"/hello"} |
| var buf bytes.Buffer |
| enc := json.NewEncoder(&buf) |
| enc.SetIndent("", " ") |
| if err := enc.Encode(&spec); err != nil { |
| return err |
| } |
| if err := os.WriteFile(specFile, buf.Bytes(), 0666); err != nil { |
| return err |
| } |
| |
| // Everything below this point is static assets. If we're in the |
| // same directory, just stop here. |
| if cfg.AssetsDir == cfg.OutputDir { |
| return nil |
| } |
| |
| // Generate additional directory structure for static assets |
| // that isn't already generated by the build process above. |
| if err := os.MkdirAll(filepath.Join(cfg.OutputDir, "http", "assets"), 0755); err != nil { |
| return err |
| } |
| |
| // Copy static assets over. |
| staticAssets := []string{ |
| filepath.Join("http", "assets", "gopherhat.jpg"), |
| filepath.Join("http", "assets", "gophermega.jpg"), |
| filepath.Join("http", "assets", "gopherswim.jpg"), |
| filepath.Join("http", "assets", "gopherhelmet.jpg"), |
| filepath.Join("http", "assets", "gopherrunning.jpg"), |
| filepath.Join("http", "assets", "gopherswrench.jpg"), |
| filepath.Join("http", "README.md"), |
| filepath.Join("startup", "README.md"), |
| filepath.Join("syscall", "README.md"), |
| } |
| if err := copyFiles(cfg.OutputDir, cfg.AssetsDir, staticAssets); err != nil { |
| return err |
| } |
| |
| // As a special case, copy everything under startup/rootfs. |
| // It's a rootfs, so enumerating everything here would be tedious |
| // and not really useful. |
| // |
| // TODO(mknyszek): Generate this directory from a container image. |
| // There's some complications to this, because Cloud Build runs |
| // inside docker, and this is generated from a docker image. |
| return fileutil.CopyDir( |
| filepath.Join(cfg.OutputDir, "startup", "rootfs"), |
| filepath.Join(cfg.AssetsDir, "startup", "rootfs"), |
| nil, |
| ) |
| } |