gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/benchmarks/base/usage_test.go (about)

     1  // Copyright 2023 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package usage_test
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/docker/docker/api/types"
    25  	"gvisor.dev/gvisor/pkg/test/dockerutil"
    26  	"gvisor.dev/gvisor/test/benchmarks/base"
    27  	"gvisor.dev/gvisor/test/benchmarks/harness"
    28  	"gvisor.dev/gvisor/test/benchmarks/tools"
    29  	"gvisor.dev/gvisor/test/metricsviz"
    30  )
    31  
    32  // BenchmarkSizeEmpty creates N alpine containers and reads memory usage using `docker stats`.
    33  func BenchmarkSizeEmpty(b *testing.B) {
    34  	ctx := context.Background()
    35  	machine, err := harness.GetMachine()
    36  	if err != nil {
    37  		b.Fatalf("failed to get machine: %v", err)
    38  	}
    39  	defer machine.CleanUp()
    40  
    41  	var sumMemoryUsage uint64
    42  
    43  	// Make N containers.
    44  	for i := 0; i < b.N; i++ {
    45  		container := machine.GetContainer(ctx, b)
    46  		defer container.CleanUp(ctx)
    47  		if err := container.Spawn(ctx, dockerutil.RunOpts{
    48  			Image: "benchmarks/alpine",
    49  		}, "sh", "-c", "echo Hello && sleep 1000"); err != nil {
    50  			b.Fatalf("failed to run container: %v", err)
    51  		}
    52  		if i == 0 {
    53  			defer metricsviz.FromContainerLogs(ctx, b, container)
    54  		}
    55  		if _, err := container.WaitForOutputSubmatch(ctx, "Hello", 5*time.Second); err != nil {
    56  			b.Fatalf("failed to read container output: %v", err)
    57  		}
    58  
    59  		stats, err := container.Stats(ctx)
    60  		if err != nil {
    61  			b.Fatalf("failed to get container stats: %v", err)
    62  		}
    63  		if err := validateStats(stats); err != nil {
    64  			b.Fatalf("failed to validate container stats: %v", err)
    65  		}
    66  		sumMemoryUsage += stats.Stats.MemoryStats.Usage
    67  	}
    68  	reportMemoryUsage(b, sumMemoryUsage)
    69  }
    70  
    71  // BenchmarkSizeNginx starts N containers running Nginx, checks that they're
    72  // serving, and checks memory usage from `docker stats`.
    73  func BenchmarkSizeNginx(b *testing.B) {
    74  	ctx := context.Background()
    75  	machine, err := harness.GetMachine()
    76  	if err != nil {
    77  		b.Fatalf("failed to get machine with: %v", err)
    78  	}
    79  	defer machine.CleanUp()
    80  
    81  	// Make N Nginx containers.
    82  	runOpts := dockerutil.RunOpts{
    83  		Image: "benchmarks/nginx",
    84  	}
    85  	const port = 80
    86  	var sumMemoryUsage uint64
    87  	for i := 0; i < b.N; i++ {
    88  		server, err := base.StartServer(ctx, b,
    89  			base.ServerArgs{
    90  				Machine: machine,
    91  				Port:    port,
    92  				RunOpts: runOpts,
    93  				Cmd:     []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"},
    94  			})
    95  		if err != nil {
    96  			b.Fatalf("failed to start server: %v", err)
    97  		}
    98  		defer server.CleanUp(ctx)
    99  		if i == 0 {
   100  			defer metricsviz.FromContainerLogs(ctx, b, server)
   101  		}
   102  		stats, err := server.Stats(ctx)
   103  		if err != nil {
   104  			b.Fatalf("failed to get container stats: %v", err)
   105  		}
   106  		if err := validateStats(stats); err != nil {
   107  			b.Fatalf("failed to validate container stats: %v", err)
   108  		}
   109  		sumMemoryUsage += stats.Stats.MemoryStats.Usage
   110  	}
   111  	reportMemoryUsage(b, sumMemoryUsage)
   112  }
   113  
   114  // BenchmarkSizeNode starts N containers running a Node app, checks that
   115  // they're serving, and checks memory used based on /proc/meminfo.
   116  func BenchmarkSizeNode(b *testing.B) {
   117  	ctx := context.Background()
   118  	machine, err := harness.GetMachine()
   119  	if err != nil {
   120  		b.Fatalf("failed to get machine with: %v", err)
   121  	}
   122  	defer machine.CleanUp()
   123  
   124  	// Make a redis instance for Node to connect.
   125  	redis := base.RedisInstance(ctx, b, machine)
   126  	defer redis.CleanUp(ctx)
   127  
   128  	// Create N Node servers.
   129  	runOpts := dockerutil.RunOpts{
   130  		Image:   "benchmarks/node",
   131  		WorkDir: "/usr/src/app",
   132  		Links:   []string{redis.MakeLink("redis")},
   133  	}
   134  	nodeCmd := []string{"node", "index.js", "redis"}
   135  	const port = 8080
   136  	var sumMemoryUsage uint64
   137  	for i := 0; i < b.N; i++ {
   138  		server, err := base.StartServer(ctx, b,
   139  			base.ServerArgs{
   140  				Machine: machine,
   141  				Port:    port,
   142  				RunOpts: runOpts,
   143  				Cmd:     nodeCmd,
   144  			})
   145  		if err != nil {
   146  			b.Fatalf("failed to start server: %v", err)
   147  		}
   148  		defer server.CleanUp(ctx)
   149  		if i == 0 {
   150  			defer metricsviz.FromContainerLogs(ctx, b, server)
   151  		}
   152  		stats, err := server.Stats(ctx)
   153  		if err != nil {
   154  			b.Fatalf("failed to get container stats: %v", err)
   155  		}
   156  		if err := validateStats(stats); err != nil {
   157  			b.Fatalf("failed to validate container stats: %v", err)
   158  		}
   159  		sumMemoryUsage += stats.Stats.MemoryStats.Usage
   160  	}
   161  	reportMemoryUsage(b, sumMemoryUsage)
   162  }
   163  
   164  func validateStats(stats *types.StatsJSON) error {
   165  	// The runc empty container is on the order of multiple kB, so if this is smaller than that,
   166  	// there is probably something wrong.
   167  	var memoryAtLeast uint64 = 1000
   168  	if stats.MemoryStats.Usage < memoryAtLeast {
   169  		return fmt.Errorf("reported memory usage below sanity check minimum: %d:  reported: %d", memoryAtLeast, stats.MemoryStats.Usage)
   170  	}
   171  	return nil
   172  }
   173  
   174  func reportMemoryUsage(b *testing.B, sumMemoryUsage uint64) {
   175  	averageUsage := float64(sumMemoryUsage) / float64(b.N)
   176  	tools.ReportCustomMetric(b, averageUsage, "average_container_memory_usage", "bytes")
   177  }
   178  
   179  // TestMain is the main method for this package.
   180  func TestMain(m *testing.M) {
   181  	harness.Init()
   182  	os.Exit(m.Run())
   183  }