diff options
author | Dean Deng <deandeng@google.com> | 2021-04-16 17:52:12 -0700 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-04-16 17:56:16 -0700 |
commit | 0c3e8daf503e011f0ef3e2a1c6d8b6ffd946acab (patch) | |
tree | 31e2eb139d91d463a0468f24d24832783bca698e /runsc | |
parent | ee45334f147c9d5ff75d10c619c9c99ce4ba51ca (diff) |
Allow runsc to generate coverage reports.
Add a coverage-report flag that will cause the sandbox to generate a coverage
report (with suffix .cov) in the debug log directory upon exiting. For the
report to be generated, runsc must have been built with the following Bazel
flags: `--collect_code_coverage --instrumentation_filter=...`.
With coverage reports, we should be able to aggregate results across all tests
to surface code coverage statistics for the project as a whole.
The report is simply a text file with each line representing a covered block
as `file:start_line.start_col,end_line.end_col`. Note that this is similar to
the format of coverage reports generated with `go test -coverprofile`,
although we omit the count and number of statements, which are not useful for
us.
Some simple ways of getting coverage reports:
bazel test <some_test> --collect_code_coverage \
--instrumentation_filter=//pkg/...
bazel build //runsc --collect_code_coverage \
--instrumentation_filter=//pkg/...
runsc -coverage-report=dir/ <other_flags> do ...
PiperOrigin-RevId: 368952911
Diffstat (limited to 'runsc')
-rw-r--r-- | runsc/boot/BUILD | 1 | ||||
-rw-r--r-- | runsc/boot/loader.go | 8 | ||||
-rw-r--r-- | runsc/cli/BUILD | 1 | ||||
-rw-r--r-- | runsc/cli/main.go | 8 | ||||
-rw-r--r-- | runsc/cmd/symbolize.go | 6 | ||||
-rw-r--r-- | runsc/config/config.go | 3 | ||||
-rw-r--r-- | runsc/config/flags.go | 3 | ||||
-rw-r--r-- | runsc/sandbox/BUILD | 1 | ||||
-rw-r--r-- | runsc/sandbox/sandbox.go | 41 |
9 files changed, 52 insertions, 20 deletions
diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD index 579edaa2c..a79afbdc4 100644 --- a/runsc/boot/BUILD +++ b/runsc/boot/BUILD @@ -30,6 +30,7 @@ go_library( "//pkg/cleanup", "//pkg/context", "//pkg/control/server", + "//pkg/coverage", "//pkg/cpuid", "//pkg/eventchannel", "//pkg/fd", diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index 95daf1f00..5d6e67279 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -29,6 +29,7 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/bpf" "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/coverage" "gvisor.dev/gvisor/pkg/cpuid" "gvisor.dev/gvisor/pkg/fd" "gvisor.dev/gvisor/pkg/log" @@ -1000,6 +1001,13 @@ func (l *Loader) waitContainer(cid string, waitStatus *uint32) error { // consider the container exited. ws := l.wait(tg) *waitStatus = ws + + // Write coverage report after the root container has exited. This guarantees + // that the report is written in cases where the sandbox is killed by a signal + // after the ContainerWait request is completed. + if l.root.procArgs.ContainerID == cid { + coverage.Report() + } return nil } diff --git a/runsc/cli/BUILD b/runsc/cli/BUILD index f1e3cce68..705738aef 100644 --- a/runsc/cli/BUILD +++ b/runsc/cli/BUILD @@ -10,6 +10,7 @@ go_library( "//runsc:__pkg__", ], deps = [ + "//pkg/coverage", "//pkg/log", "//pkg/refs", "//pkg/sentry/platform", diff --git a/runsc/cli/main.go b/runsc/cli/main.go index 6db6614cc..79eb85cff 100644 --- a/runsc/cli/main.go +++ b/runsc/cli/main.go @@ -27,6 +27,7 @@ import ( "github.com/google/subcommands" "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/coverage" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/refs" "gvisor.dev/gvisor/pkg/sentry/platform" @@ -50,6 +51,7 @@ var ( logFD = flag.Int("log-fd", -1, "file descriptor to log to. If set, the 'log' flag is ignored.") debugLogFD = flag.Int("debug-log-fd", -1, "file descriptor to write debug logs to. If set, the 'debug-log-dir' flag is ignored.") panicLogFD = flag.Int("panic-log-fd", -1, "file descriptor to write Go's runtime messages.") + coverageFD = flag.Int("coverage-fd", -1, "file descriptor to write Go coverage output.") ) // Main is the main entrypoint. @@ -205,6 +207,10 @@ func Main(version string) { } else if conf.AlsoLogToStderr { e = &log.MultiEmitter{e, newEmitter(conf.DebugLogFormat, os.Stderr)} } + if *coverageFD >= 0 { + f := os.NewFile(uintptr(*coverageFD), "coverage file") + coverage.EnableReport(f) + } log.SetTarget(e) @@ -234,6 +240,8 @@ func Main(version string) { // Call the subcommand and pass in the configuration. var ws unix.WaitStatus subcmdCode := subcommands.Execute(context.Background(), conf, &ws) + // Write coverage report before os.Exit(). + coverage.Report() if subcmdCode == subcommands.ExitSuccess { log.Infof("Exiting with status: %v", ws) if ws.Signaled() { diff --git a/runsc/cmd/symbolize.go b/runsc/cmd/symbolize.go index fc0c69358..0fa4bfda1 100644 --- a/runsc/cmd/symbolize.go +++ b/runsc/cmd/symbolize.go @@ -65,13 +65,15 @@ func (c *Symbolize) Execute(_ context.Context, f *flag.FlagSet, args ...interfac f.Usage() return subcommands.ExitUsageError } - if !coverage.KcovAvailable() { + if !coverage.Available() { return Errorf("symbolize can only be used when coverage is available.") } coverage.InitCoverageData() if c.dumpAll { - coverage.WriteAllBlocks(os.Stdout) + if err := coverage.WriteAllBlocks(os.Stdout); err != nil { + return Errorf("Failed to write out blocks: %v", err) + } return subcommands.ExitSuccess } diff --git a/runsc/config/config.go b/runsc/config/config.go index 0b2b97cc5..fa550ebf7 100644 --- a/runsc/config/config.go +++ b/runsc/config/config.go @@ -55,6 +55,9 @@ type Config struct { // PanicLog is the path to log GO's runtime messages, if not empty. PanicLog string `flag:"panic-log"` + // CoverageReport is the path to write Go coverage information, if not empty. + CoverageReport string `flag:"coverage-report"` + // DebugLogFormat is the log format for debug. DebugLogFormat string `flag:"debug-log-format"` diff --git a/runsc/config/flags.go b/runsc/config/flags.go index 13a1a0163..c3dca2352 100644 --- a/runsc/config/flags.go +++ b/runsc/config/flags.go @@ -44,7 +44,8 @@ func RegisterFlags() { // Debugging flags. flag.String("debug-log", "", "additional location for logs. If it ends with '/', log files are created inside the directory with default names. The following variables are available: %TIMESTAMP%, %COMMAND%.") - flag.String("panic-log", "", "file path were panic reports and other Go's runtime messages are written.") + flag.String("panic-log", "", "file path where panic reports and other Go's runtime messages are written.") + flag.String("coverage-report", "", "file path where Go coverage reports are written. Reports will only be generated if runsc is built with --collect_code_coverage and --instrumentation_filter Bazel flags.") flag.Bool("log-packets", false, "enable network packet logging.") flag.String("debug-log-format", "text", "log format: text (default), json, or json-k8s.") flag.Bool("alsologtostderr", false, "send log messages to stderr.") diff --git a/runsc/sandbox/BUILD b/runsc/sandbox/BUILD index f0a551a1e..bc4a3fa32 100644 --- a/runsc/sandbox/BUILD +++ b/runsc/sandbox/BUILD @@ -16,6 +16,7 @@ go_library( "//pkg/cleanup", "//pkg/control/client", "//pkg/control/server", + "//pkg/coverage", "//pkg/log", "//pkg/sentry/control", "//pkg/sentry/platform", diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index 47da2dd10..f3f60f116 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -34,6 +34,7 @@ import ( "gvisor.dev/gvisor/pkg/cleanup" "gvisor.dev/gvisor/pkg/control/client" "gvisor.dev/gvisor/pkg/control/server" + "gvisor.dev/gvisor/pkg/coverage" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/control" "gvisor.dev/gvisor/pkg/sentry/platform" @@ -399,15 +400,15 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn cmd.Args = append(cmd.Args, "--log-fd="+strconv.Itoa(nextFD)) nextFD++ } - if conf.DebugLog != "" { - test := "" - if len(conf.TestOnlyTestNameEnv) != 0 { - // Fetch test name if one is provided and the test only flag was set. - if t, ok := specutils.EnvVar(args.Spec.Process.Env, conf.TestOnlyTestNameEnv); ok { - test = t - } - } + test := "" + if len(conf.TestOnlyTestNameEnv) != 0 { + // Fetch test name if one is provided and the test only flag was set. + if t, ok := specutils.EnvVar(args.Spec.Process.Env, conf.TestOnlyTestNameEnv); ok { + test = t + } + } + if conf.DebugLog != "" { debugLogFile, err := specutils.DebugLogFile(conf.DebugLog, "boot", test) if err != nil { return fmt.Errorf("opening debug log file in %q: %v", conf.DebugLog, err) @@ -418,23 +419,29 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn nextFD++ } if conf.PanicLog != "" { - test := "" - if len(conf.TestOnlyTestNameEnv) != 0 { - // Fetch test name if one is provided and the test only flag was set. - if t, ok := specutils.EnvVar(args.Spec.Process.Env, conf.TestOnlyTestNameEnv); ok { - test = t - } - } - panicLogFile, err := specutils.DebugLogFile(conf.PanicLog, "panic", test) if err != nil { - return fmt.Errorf("opening debug log file in %q: %v", conf.PanicLog, err) + return fmt.Errorf("opening panic log file in %q: %v", conf.PanicLog, err) } defer panicLogFile.Close() cmd.ExtraFiles = append(cmd.ExtraFiles, panicLogFile) cmd.Args = append(cmd.Args, "--panic-log-fd="+strconv.Itoa(nextFD)) nextFD++ } + covFilename := conf.CoverageReport + if covFilename == "" { + covFilename = os.Getenv("GO_COVERAGE_FILE") + } + if covFilename != "" && coverage.Available() { + covFile, err := specutils.DebugLogFile(covFilename, "cov", test) + if err != nil { + return fmt.Errorf("opening debug log file in %q: %v", covFilename, err) + } + defer covFile.Close() + cmd.ExtraFiles = append(cmd.ExtraFiles, covFile) + cmd.Args = append(cmd.Args, "--coverage-fd="+strconv.Itoa(nextFD)) + nextFD++ + } // Add the "boot" command to the args. // |