blob: ed0b7db3891d2b4f3c94e965161d0492d0f2e809 [file] [log] [blame]
// 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"
"io/ioutil"
"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 := ioutil.TempDir("", "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,
)
}