mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
Speeds up the PGO profile pipeline. PiperOrigin-RevId: 784392458
206 lines
6.3 KiB
Go
206 lines
6.3 KiB
Go
// Copyright 2022 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package fsbench provides utility functions for filesystem benchmarks.
|
|
package fsbench
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gvisor.dev/gvisor/pkg/cleanup"
|
|
"gvisor.dev/gvisor/pkg/test/dockerutil"
|
|
"gvisor.dev/gvisor/test/benchmarks/harness"
|
|
"gvisor.dev/gvisor/test/benchmarks/tools"
|
|
"gvisor.dev/gvisor/test/metricsviz"
|
|
)
|
|
|
|
// FSBenchmark represents a set of work to perform within a container that is instrumented with
|
|
// different filesystem configurations.
|
|
type FSBenchmark struct {
|
|
// Image is the Docker image to load.
|
|
Image string
|
|
// WorkDir is where the action takes place.
|
|
// The commands below are run from a directory that has the same file as what the container image
|
|
// has at this directory.
|
|
WorkDir string
|
|
// RunCmd is the command to run to execute the benchmark.
|
|
RunCmd []string
|
|
// WantOutput, if set, is verified to be a substring of the output of RunCmd.
|
|
WantOutput string
|
|
// CleanCmd, if set, is run to clean up between benchmarks.
|
|
CleanCmd []string
|
|
// EnableForPGO is whether this benchmark should be run for PGO benchmarks.
|
|
EnableForPGO bool
|
|
// Variants is a list of benchmark variants to run.
|
|
// If unset, the typical set is used.
|
|
Variants []Variant
|
|
// Callback is an optional function that is called after each execution of
|
|
// the workload being benchmarked. It can be used to perform workload
|
|
// specific metric reporting.
|
|
Callback func(b *testing.B, output string)
|
|
}
|
|
|
|
// Variant is a specific configuration for a benchmark.
|
|
// Dimensions here are clean/dirty cache (do or don't drop caches)
|
|
// and if the mount on which we are compiling is a tmpfs/bind mount.
|
|
type Variant struct {
|
|
// EnableForPGO is whether this variant should be run for PGO benchmarks.
|
|
EnableForPGO bool
|
|
// clearCache drops caches before running.
|
|
clearCache bool
|
|
// fsType is the type of filesystem to use.
|
|
fsType harness.FileSystemType
|
|
}
|
|
|
|
// TypicalVariants returns the typical full set of benchmark variants.
|
|
func TypicalVariants() []Variant {
|
|
variants := make([]Variant, 0, 8)
|
|
for _, filesys := range []harness.FileSystemType{harness.BindFS, harness.TmpFS, harness.RootFS, harness.FuseFS} {
|
|
variants = append(variants, Variant{
|
|
EnableForPGO: true,
|
|
clearCache: true,
|
|
fsType: filesys,
|
|
})
|
|
variants = append(variants, Variant{
|
|
clearCache: false,
|
|
fsType: filesys,
|
|
})
|
|
}
|
|
return variants
|
|
}
|
|
|
|
// RunWithDifferentFilesystems runs a
|
|
func RunWithDifferentFilesystems(ctx context.Context, b *testing.B, machine harness.Machine, bm FSBenchmark) {
|
|
b.Helper()
|
|
if !bm.EnableForPGO {
|
|
dockerutil.SkipIfPGO(b)
|
|
}
|
|
|
|
benchmarkVariants := bm.Variants
|
|
if len(benchmarkVariants) == 0 {
|
|
benchmarkVariants = TypicalVariants()
|
|
}
|
|
for _, variant := range benchmarkVariants {
|
|
pageCache := tools.Parameter{
|
|
Name: "page_cache",
|
|
Value: "dirty",
|
|
}
|
|
if variant.clearCache {
|
|
pageCache.Value = "clean"
|
|
}
|
|
|
|
filesystem := tools.Parameter{
|
|
Name: "filesystem",
|
|
Value: string(variant.fsType),
|
|
}
|
|
name, err := tools.ParametersToName(pageCache, filesystem)
|
|
if err != nil {
|
|
b.Fatalf("Failed to parse parameters: %v", err)
|
|
}
|
|
|
|
b.Run(name, func(b *testing.B) {
|
|
if !variant.EnableForPGO {
|
|
dockerutil.SkipIfPGO(b)
|
|
}
|
|
// Grab a container.
|
|
container := machine.GetContainer(ctx, b)
|
|
cu := cleanup.Make(func() {
|
|
container.CleanUp(ctx)
|
|
})
|
|
defer cu.Clean()
|
|
mts, prefix, err := harness.MakeMount(machine, variant.fsType, &cu)
|
|
if err != nil {
|
|
b.Fatalf("Failed to make mount: %v", err)
|
|
}
|
|
|
|
runOpts := dockerutil.RunOpts{
|
|
Image: bm.Image,
|
|
Mounts: mts,
|
|
}
|
|
|
|
// Start a container and sleep.
|
|
if err := container.Spawn(ctx, runOpts, "sleep", "24h"); err != nil {
|
|
b.Fatalf("run failed with: %v", err)
|
|
}
|
|
defer metricsviz.FromContainerLogs(ctx, b, container)
|
|
|
|
// Ignore safetext/shsprintf linter suggestion.
|
|
mkdirCmd := fmt.Sprintf("mkdir -p %s", prefix)
|
|
out, err := container.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", mkdirCmd)
|
|
if err != nil {
|
|
b.Fatalf("failed to make directory: %v (%s)", err, out)
|
|
}
|
|
|
|
if variant.fsType == harness.FuseFS {
|
|
container.CopyFiles(&runOpts, "/fusebin", "test/runner/fuse/fuse")
|
|
_, err := container.ExecProcess(ctx, dockerutil.ExecOpts{
|
|
Privileged: true,
|
|
}, "/fusebin/fuse", "--dir="+prefix, "--debug=false")
|
|
if err != nil {
|
|
b.Fatalf("starting fuse server failed with: %v", err)
|
|
}
|
|
}
|
|
|
|
cpCmd := fmt.Sprintf("cp -r %s %s/.", bm.WorkDir, prefix)
|
|
if out, err := container.Exec(ctx, dockerutil.ExecOpts{},
|
|
"/bin/sh", "-c", cpCmd); err != nil {
|
|
b.Fatalf("failed to copy directory: %v (%s)", err, out)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
b.StopTimer()
|
|
|
|
// Drop Caches and bazel clean should happen inside the loop as we may use
|
|
// time options with b.N. (e.g. Run for an hour.)
|
|
for i := 0; i < b.N; i++ {
|
|
// Drop Caches for clear cache runs.
|
|
if variant.clearCache {
|
|
if err := harness.DropCaches(machine); err != nil {
|
|
b.Skipf("failed to drop caches: %v. You probably need root.", err)
|
|
}
|
|
}
|
|
|
|
b.StartTimer()
|
|
got, err := container.Exec(ctx, dockerutil.ExecOpts{
|
|
WorkDir: prefix + bm.WorkDir,
|
|
}, bm.RunCmd...)
|
|
if err != nil {
|
|
b.Fatalf("Command %v failed with: %v logs: %s", bm.RunCmd, err, got)
|
|
}
|
|
b.StopTimer()
|
|
|
|
if bm.WantOutput != "" && !strings.Contains(got, bm.WantOutput) {
|
|
b.Fatalf("string %s not in: %s", bm.WantOutput, got)
|
|
}
|
|
|
|
if bm.Callback != nil {
|
|
bm.Callback(b, got)
|
|
}
|
|
|
|
// Clean the container in case we are doing another run.
|
|
if i < b.N-1 && len(bm.CleanCmd) != 0 {
|
|
if _, err = container.Exec(ctx, dockerutil.ExecOpts{
|
|
WorkDir: prefix + bm.WorkDir,
|
|
}, bm.CleanCmd...); err != nil {
|
|
b.Fatalf("Cleanup command %v failed with: %v", bm.CleanCmd, err)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|