Files
visor/pkg/test/dockerutil
Adin ScannellandgVisor bot caec235b70 Simplify profiling and benchmarks.
- Tweak the benchmarks to work with b.N where appropriate. In many cases,
  b.N was simply being ignored. This creates an implicit dependency in the
  user passing a reasonable benchtime (less than or equal to the actual
  runtime of the test, or using the X syntax) otherwise the test runs
  forever.
- In cases where the above is impossible, explicitly set benchtime from
  the test wrapper, to prevent the above behavior (tensorflow).
- Drop the *Reverse variants, which are simply hey benchmarks. We should
  just add a hey benchmark. The platforms benchmarks already include a
  native platform, and thus these benchmarks are incredibly confusing.
  (In other words, BenchmarkNginxReverse has nothing to do with an nginx
  benchmark for runsc.)
- Remove the redunant Harness object, which contains no state, in order
  to slightly simplify the code.
- Make Block and Heap profiling actually work, but setting appropriate
  runtime parameters (and plumbing them through the config).
- Split the profiling into two phases: start and stop, since some will
  need to be started early, and others will need to happen at the end.

PiperOrigin-RevId: 349495377
2020-12-29 18:29:12 -08:00
..
2020-07-26 22:02:51 -07:00
2020-11-30 21:22:55 -08:00
2020-07-08 13:26:23 -07:00
2020-12-29 18:29:12 -08:00
2020-07-26 22:02:51 -07:00

dockerutil

This package is for creating and controlling docker containers for testing runsc, gVisor's docker/kubernetes binary. A simple test may look like:

 func TestSuperCool(t *testing.T) {
   ctx := context.Background()
   c := dockerutil.MakeContainer(ctx, t)
   got, err := c.Run(ctx, dockerutil.RunOpts{
     Image: "basic/alpine"
   }, "echo", "super cool")
   if err != nil {
      t.Fatalf("err was not nil: %v", err)
   }
   want := "super cool"
   if !strings.Contains(got, want){
     t.Fatalf("want: %s, got: %s", want, got)
   }
 }

For further examples, see many of our end to end tests elsewhere in the repo, such as those in //test/e2e or benchmarks at //test/benchmarks.

dockerutil uses the "official" docker golang api, which is very powerful. dockerutil is a thin wrapper around this API, allowing desired new use cases to be easily implemented.

Profiling

dockerutil is capable of generating profiles. Currently, the only option is to use pprof profiles generated by runsc debug. The profiler will generate Block, CPU, Heap, Goroutine, and Mutex profiles. To generate profiles:

  • Install runsc with the --profile flag: make configure RUNTIME=myrunsc ARGS="--profile" Also add other flags with ARGS like --platform=kvm or --vfs2.
  • Restart docker: sudo service docker restart

To run and generate CPU profiles run:

make sudo TARGETS=//path/to:target \
  ARGS="--runtime=myrunsc -test.v -test.bench=. --pprof-cpu" OPTIONS="-c opt"

Profiles would be at: /tmp/profile/myrunsc/CONTAINERNAME/cpu.pprof

Container name in most tests and benchmarks in gVisor is usually the test name and some random characters like so: BenchmarkABSL-CleanCache-JF2J2ZYF3U7SL47QAA727CSJI3C4ZAW2

Profiling requires root as runsc debug inspects running containers in /var/run among other things.

Writing for Profiling

The below shows an example of using profiles with dockerutil.

func TestSuperCool(t *testing.T){
  ctx := context.Background()
  // profiled and using runtime from dockerutil.runtime flag
  profiled := MakeContainer()

  // not profiled and using runtime runc
  native := MakeNativeContainer()

  err := profiled.Spawn(ctx, RunOpts{
    Image: "some/image",
  }, "sleep", "100000")
  // profiling has begun here
  ...
  expensive setup that I don't want to profile.
  ...
  profiled.RestartProfiles()
  // profiled activity
}

In the above example, profiled would be profiled and native would not. The call to RestartProfiles() restarts the clock on profiling. This is useful if the main activity being tested is done with docker exec or container.Spawn() followed by one or more container.Exec() calls.