github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/container/formatter_stats_test.go (about)

     1  package container
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  
     7  	"github.com/docker/cli/cli/command/formatter"
     8  	"github.com/docker/docker/pkg/stringid"
     9  	"gotest.tools/v3/assert"
    10  	is "gotest.tools/v3/assert/cmp"
    11  )
    12  
    13  func TestContainerStatsContext(t *testing.T) {
    14  	containerID := stringid.GenerateRandomID()
    15  
    16  	var ctx statsContext
    17  	tt := []struct {
    18  		stats     StatsEntry
    19  		osType    string
    20  		expValue  string
    21  		expHeader string
    22  		call      func() string
    23  	}{
    24  		{StatsEntry{Container: containerID}, "", containerID, containerHeader, ctx.Container},
    25  		{StatsEntry{CPUPercentage: 5.5}, "", "5.50%", cpuPercHeader, ctx.CPUPerc},
    26  		{StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "", "--", cpuPercHeader, ctx.CPUPerc},
    27  		{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "", "0.31B / 12.3B", netIOHeader, ctx.NetIO},
    28  		{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "", "--", netIOHeader, ctx.NetIO},
    29  		{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "", "0.1B / 2.3B", blockIOHeader, ctx.BlockIO},
    30  		{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, "", "--", blockIOHeader, ctx.BlockIO},
    31  		{StatsEntry{MemoryPercentage: 10.2}, "", "10.20%", memPercHeader, ctx.MemPerc},
    32  		{StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, "", "--", memPercHeader, ctx.MemPerc},
    33  		{StatsEntry{MemoryPercentage: 10.2}, "windows", "--", memPercHeader, ctx.MemPerc},
    34  		{StatsEntry{Memory: 24, MemoryLimit: 30}, "", "24B / 30B", memUseHeader, ctx.MemUsage},
    35  		{StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "", "-- / --", memUseHeader, ctx.MemUsage},
    36  		{StatsEntry{Memory: 24, MemoryLimit: 30}, "windows", "24B", winMemUseHeader, ctx.MemUsage},
    37  		{StatsEntry{PidsCurrent: 10}, "", "10", pidsHeader, ctx.PIDs},
    38  		{StatsEntry{PidsCurrent: 10, IsInvalid: true}, "", "--", pidsHeader, ctx.PIDs},
    39  		{StatsEntry{PidsCurrent: 10}, "windows", "--", pidsHeader, ctx.PIDs},
    40  	}
    41  
    42  	for _, te := range tt {
    43  		ctx = statsContext{s: te.stats, os: te.osType}
    44  		if v := te.call(); v != te.expValue {
    45  			t.Fatalf("Expected %q, got %q", te.expValue, v)
    46  		}
    47  	}
    48  }
    49  
    50  func TestContainerStatsContextWrite(t *testing.T) {
    51  	tt := []struct {
    52  		context  formatter.Context
    53  		expected string
    54  	}{
    55  		{
    56  			formatter.Context{Format: "{{InvalidFunction}}"},
    57  			`template parsing error: template: :1: function "InvalidFunction" not defined`,
    58  		},
    59  		{
    60  			formatter.Context{Format: "{{nil}}"},
    61  			`template parsing error: template: :1:2: executing "" at <nil>: nil is not a command`,
    62  		},
    63  		{
    64  			formatter.Context{Format: "table {{.MemUsage}}"},
    65  			`MEM USAGE / LIMIT
    66  20B / 20B
    67  -- / --
    68  `,
    69  		},
    70  		{
    71  			formatter.Context{Format: "{{.Container}}  {{.ID}}  {{.Name}}"},
    72  			`container1  abcdef  foo
    73  container2    --
    74  `,
    75  		},
    76  		{
    77  			formatter.Context{Format: "{{.Container}}  {{.CPUPerc}}"},
    78  			`container1  20.00%
    79  container2  --
    80  `,
    81  		},
    82  	}
    83  
    84  	for _, te := range tt {
    85  		stats := []StatsEntry{
    86  			{
    87  				Container:        "container1",
    88  				ID:               "abcdef",
    89  				Name:             "/foo",
    90  				CPUPercentage:    20,
    91  				Memory:           20,
    92  				MemoryLimit:      20,
    93  				MemoryPercentage: 20,
    94  				NetworkRx:        20,
    95  				NetworkTx:        20,
    96  				BlockRead:        20,
    97  				BlockWrite:       20,
    98  				PidsCurrent:      2,
    99  				IsInvalid:        false,
   100  			},
   101  			{
   102  				Container:        "container2",
   103  				CPUPercentage:    30,
   104  				Memory:           30,
   105  				MemoryLimit:      30,
   106  				MemoryPercentage: 30,
   107  				NetworkRx:        30,
   108  				NetworkTx:        30,
   109  				BlockRead:        30,
   110  				BlockWrite:       30,
   111  				PidsCurrent:      3,
   112  				IsInvalid:        true,
   113  			},
   114  		}
   115  		var out bytes.Buffer
   116  		te.context.Output = &out
   117  		err := statsFormatWrite(te.context, stats, "linux", false)
   118  		if err != nil {
   119  			assert.Error(t, err, te.expected)
   120  		} else {
   121  			assert.Check(t, is.Equal(te.expected, out.String()))
   122  		}
   123  	}
   124  }
   125  
   126  func TestContainerStatsContextWriteWindows(t *testing.T) {
   127  	cases := []struct {
   128  		context  formatter.Context
   129  		expected string
   130  	}{
   131  		{
   132  			formatter.Context{Format: "table {{.MemUsage}}"},
   133  			`PRIV WORKING SET
   134  20B
   135  -- / --
   136  `,
   137  		},
   138  		{
   139  			formatter.Context{Format: "{{.Container}}  {{.CPUPerc}}"},
   140  			`container1  20.00%
   141  container2  --
   142  `,
   143  		},
   144  		{
   145  			formatter.Context{Format: "{{.Container}}  {{.MemPerc}}  {{.PIDs}}"},
   146  			`container1  --  --
   147  container2  --  --
   148  `,
   149  		},
   150  	}
   151  	stats := []StatsEntry{
   152  		{
   153  			Container:        "container1",
   154  			CPUPercentage:    20,
   155  			Memory:           20,
   156  			MemoryLimit:      20,
   157  			MemoryPercentage: 20,
   158  			NetworkRx:        20,
   159  			NetworkTx:        20,
   160  			BlockRead:        20,
   161  			BlockWrite:       20,
   162  			PidsCurrent:      2,
   163  			IsInvalid:        false,
   164  		},
   165  		{
   166  			Container:        "container2",
   167  			CPUPercentage:    30,
   168  			Memory:           30,
   169  			MemoryLimit:      30,
   170  			MemoryPercentage: 30,
   171  			NetworkRx:        30,
   172  			NetworkTx:        30,
   173  			BlockRead:        30,
   174  			BlockWrite:       30,
   175  			PidsCurrent:      3,
   176  			IsInvalid:        true,
   177  		},
   178  	}
   179  
   180  	for _, tc := range cases {
   181  		tc := tc
   182  		t.Run(string(tc.context.Format), func(t *testing.T) {
   183  			var out bytes.Buffer
   184  			tc.context.Output = &out
   185  			err := statsFormatWrite(tc.context, stats, "windows", false)
   186  			if err != nil {
   187  				assert.Error(t, err, tc.expected)
   188  			} else {
   189  				assert.Equal(t, out.String(), tc.expected)
   190  			}
   191  		})
   192  	}
   193  }
   194  
   195  func TestContainerStatsContextWriteWithNoStats(t *testing.T) {
   196  	var out bytes.Buffer
   197  
   198  	cases := []struct {
   199  		context  formatter.Context
   200  		expected string
   201  	}{
   202  		{
   203  			formatter.Context{
   204  				Format: "{{.Container}}",
   205  				Output: &out,
   206  			},
   207  			"",
   208  		},
   209  		{
   210  			formatter.Context{
   211  				Format: "table {{.Container}}",
   212  				Output: &out,
   213  			},
   214  			"CONTAINER\n",
   215  		},
   216  		{
   217  			formatter.Context{
   218  				Format: "table {{.Container}}\t{{.CPUPerc}}",
   219  				Output: &out,
   220  			},
   221  			"CONTAINER   CPU %\n",
   222  		},
   223  	}
   224  
   225  	for _, tc := range cases {
   226  		tc := tc
   227  		t.Run(string(tc.context.Format), func(t *testing.T) {
   228  			err := statsFormatWrite(tc.context, []StatsEntry{}, "linux", false)
   229  			assert.NilError(t, err)
   230  			assert.Equal(t, out.String(), tc.expected)
   231  			// Clean buffer
   232  			out.Reset()
   233  		})
   234  	}
   235  }
   236  
   237  func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
   238  	var out bytes.Buffer
   239  
   240  	cases := []struct {
   241  		context  formatter.Context
   242  		expected string
   243  	}{
   244  		{
   245  			formatter.Context{
   246  				Format: "{{.Container}}",
   247  				Output: &out,
   248  			},
   249  			"",
   250  		},
   251  		{
   252  			formatter.Context{
   253  				Format: "table {{.Container}}\t{{.MemUsage}}",
   254  				Output: &out,
   255  			},
   256  			"CONTAINER   PRIV WORKING SET\n",
   257  		},
   258  		{
   259  			formatter.Context{
   260  				Format: "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}",
   261  				Output: &out,
   262  			},
   263  			"CONTAINER   CPU %     PRIV WORKING SET\n",
   264  		},
   265  	}
   266  
   267  	for _, tc := range cases {
   268  		tc := tc
   269  		t.Run(string(tc.context.Format), func(t *testing.T) {
   270  			err := statsFormatWrite(tc.context, []StatsEntry{}, "windows", false)
   271  			assert.NilError(t, err)
   272  			assert.Equal(t, out.String(), tc.expected)
   273  			out.Reset()
   274  		})
   275  	}
   276  }
   277  
   278  func TestContainerStatsContextWriteTrunc(t *testing.T) {
   279  	var out bytes.Buffer
   280  
   281  	contexts := []struct {
   282  		context  formatter.Context
   283  		trunc    bool
   284  		expected string
   285  	}{
   286  		{
   287  			formatter.Context{
   288  				Format: "{{.ID}}",
   289  				Output: &out,
   290  			},
   291  			false,
   292  			"b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n",
   293  		},
   294  		{
   295  			formatter.Context{
   296  				Format: "{{.ID}}",
   297  				Output: &out,
   298  			},
   299  			true,
   300  			"b95a83497c91\n",
   301  		},
   302  	}
   303  
   304  	for _, context := range contexts {
   305  		statsFormatWrite(context.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", context.trunc)
   306  		assert.Check(t, is.Equal(context.expected, out.String()))
   307  		// Clean buffer
   308  		out.Reset()
   309  	}
   310  }
   311  
   312  func BenchmarkStatsFormat(b *testing.B) {
   313  	b.ReportAllocs()
   314  	stats := genStats()
   315  
   316  	for i := 0; i < b.N; i++ {
   317  		for _, s := range stats {
   318  			_ = s.CPUPerc()
   319  			_ = s.MemUsage()
   320  			_ = s.MemPerc()
   321  			_ = s.NetIO()
   322  			_ = s.BlockIO()
   323  			_ = s.PIDs()
   324  		}
   325  	}
   326  }
   327  
   328  func genStats() []statsContext {
   329  	entry := statsContext{s: StatsEntry{
   330  		CPUPercentage:    12.3456789,
   331  		Memory:           123.456789,
   332  		MemoryLimit:      987.654321,
   333  		MemoryPercentage: 12.3456789,
   334  		BlockRead:        123.456789,
   335  		BlockWrite:       987.654321,
   336  		NetworkRx:        123.456789,
   337  		NetworkTx:        987.654321,
   338  		PidsCurrent:      123456789,
   339  	}}
   340  	stats := make([]statsContext, 100)
   341  	for i := 0; i < 100; i++ {
   342  		stats = append(stats, entry)
   343  	}
   344  	return stats
   345  }