github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/sizeof/sizeof_test.go (about)

     1  // Copyright (c) 2017 Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  package sizeof
     6  
     7  import (
     8  	"fmt"
     9  	"strconv"
    10  	"testing"
    11  	"unsafe"
    12  
    13  	"github.com/aristanetworks/goarista/test"
    14  )
    15  
    16  type yolo struct {
    17  	i int32
    18  	a [3]int8
    19  	p unsafe.Pointer
    20  }
    21  
    22  func (y yolo) String() string {
    23  	return "Yolo"
    24  }
    25  
    26  func TestDeepSizeof(t *testing.T) {
    27  	ptrSize := uintptr(unsafe.Sizeof(unsafe.Pointer(t)))
    28  	// hmapStructSize represent the size of struct hmap defined in
    29  	// file /go/src/runtime/hashmap.go
    30  	hmapStructSize := uintptr(unsafe.Sizeof(int(0)) + 2*1 + 2 + 4 +
    31  		2*ptrSize + ptrSize + ptrSize)
    32  	var alignement uintptr = 4
    33  	if ptrSize == 4 {
    34  		alignement = 0
    35  	}
    36  	strHdrSize := unsafe.Sizeof("") // int + ptr to data
    37  	sliceHdrSize := 3 * ptrSize     // ptr to data + 2 * int
    38  
    39  	// struct hchan is defined in /go/src/runtime/chan.go
    40  	chanHdrSize := 2*ptrSize + ptrSize + 2 + 2 /* padding */ + 4 + ptrSize + 2*ptrSize +
    41  		2*(2*ptrSize) + ptrSize
    42  	yoloSize := unsafe.Sizeof(yolo{})
    43  	interfaceSize := 2 * ptrSize
    44  	topHashSize := uintptr(8)
    45  	tests := map[string]struct {
    46  		getStruct    func() interface{}
    47  		expectedSize uintptr
    48  	}{
    49  		"bool": {
    50  			getStruct: func() interface{} {
    51  				var test bool
    52  				return &test
    53  			},
    54  			expectedSize: 1,
    55  		},
    56  		"int8": {
    57  			getStruct: func() interface{} {
    58  				test := int8(4)
    59  				return &test
    60  			},
    61  			expectedSize: 1,
    62  		},
    63  		"int16": {
    64  			getStruct: func() interface{} {
    65  				test := int16(4)
    66  				return &test
    67  			},
    68  			expectedSize: 2,
    69  		},
    70  		"int32": {
    71  			getStruct: func() interface{} {
    72  				test := int32(4)
    73  				return &test
    74  			},
    75  			expectedSize: 4,
    76  		},
    77  		"int64": {
    78  			getStruct: func() interface{} {
    79  				test := int64(4)
    80  				return &test
    81  			},
    82  			expectedSize: 8,
    83  		},
    84  		"uint": {
    85  			getStruct: func() interface{} {
    86  				test := uint(4)
    87  				return &test
    88  			},
    89  			expectedSize: ptrSize,
    90  		},
    91  		"uint8": {
    92  			getStruct: func() interface{} {
    93  				test := uint8(4)
    94  				return &test
    95  			},
    96  			expectedSize: 1,
    97  		},
    98  		"uint16": {
    99  			getStruct: func() interface{} {
   100  				test := uint16(4)
   101  				return &test
   102  			},
   103  			expectedSize: 2,
   104  		},
   105  		"uint32": {
   106  			getStruct: func() interface{} {
   107  				test := uint32(4)
   108  				return &test
   109  			},
   110  			expectedSize: 4,
   111  		},
   112  		"uint64": {
   113  			getStruct: func() interface{} {
   114  				test := uint64(4)
   115  				return &test
   116  			},
   117  			expectedSize: 8,
   118  		},
   119  		"uintptr": {
   120  			getStruct: func() interface{} {
   121  				test := uintptr(4)
   122  				return &test
   123  			},
   124  			expectedSize: ptrSize,
   125  		},
   126  		"float32": {
   127  			getStruct: func() interface{} {
   128  				test := float32(4)
   129  				return &test
   130  			},
   131  			expectedSize: 4,
   132  		},
   133  		"float64": {
   134  			getStruct: func() interface{} {
   135  				test := float64(4)
   136  				return &test
   137  			},
   138  			expectedSize: 8,
   139  		},
   140  		"complex64": {
   141  			getStruct: func() interface{} {
   142  				test := complex64(4 + 1i)
   143  				return &test
   144  			},
   145  			expectedSize: 8,
   146  		},
   147  		"complex128": {
   148  			getStruct: func() interface{} {
   149  				test := complex128(4 + 1i)
   150  				return &test
   151  			},
   152  			expectedSize: 16,
   153  		},
   154  		"string": {
   155  			getStruct: func() interface{} {
   156  				test := "Hello Dolly!"
   157  				return &test
   158  			},
   159  			expectedSize: strHdrSize + 12,
   160  		},
   161  		"unsafe_Pointer": {
   162  			getStruct: func() interface{} {
   163  				tmp := uint64(54)
   164  				var test unsafe.Pointer
   165  				test = unsafe.Pointer(&tmp)
   166  				return &test
   167  			},
   168  			expectedSize: ptrSize,
   169  		}, "rune": {
   170  			getStruct: func() interface{} {
   171  				test := rune('A')
   172  				return &test
   173  			},
   174  			expectedSize: 4,
   175  		}, "intPtr": {
   176  			getStruct: func() interface{} {
   177  				test := int(4)
   178  				return &test
   179  			},
   180  			expectedSize: ptrSize,
   181  		}, "FuncPtr": {
   182  			getStruct: func() interface{} {
   183  				test := TestDeepSizeof
   184  				return &test
   185  			},
   186  			expectedSize: ptrSize,
   187  		}, "struct_1": {
   188  			getStruct: func() interface{} {
   189  				v := struct {
   190  					a uint32
   191  					b *uint32
   192  					c struct {
   193  						e [8]byte
   194  						d string
   195  					}
   196  					f string
   197  				}{
   198  					a: 10,
   199  					c: struct {
   200  						e [8]byte
   201  						d string
   202  					}{
   203  						e: [8]byte{0, 1, 2, 3, 4, 5, 6, 7},
   204  						d: "Hello Test!",
   205  					},
   206  					f: "Hello Test!",
   207  				}
   208  				a := uint32(47)
   209  				v.b = &a
   210  				return &v
   211  			},
   212  			expectedSize: 4 + alignement + ptrSize + 8 + strHdrSize*2 +
   213  				11 /* "Hello Test!" */ + 4, /* uint32(47) */
   214  		}, "struct_2": {
   215  			getStruct: func() interface{} {
   216  				v := struct {
   217  					a []byte
   218  					b []byte
   219  					c []byte
   220  				}{
   221  					c: make([]byte, 32, 64),
   222  				}
   223  				v.a = v.c[20:32]
   224  				v.b = v.c[10:20]
   225  				return &v
   226  			},
   227  			expectedSize: 3*sliceHdrSize + 64, /*slice capacity*/
   228  		}, "struct_3": {
   229  			getStruct: func() interface{} {
   230  				type test struct {
   231  					a *byte
   232  					c []byte
   233  				}
   234  				tmp := make([]byte, 64, 128)
   235  				v := (*test)(unsafe.Pointer(&tmp[16]))
   236  				v.c = tmp
   237  				v.a = (*byte)(unsafe.Pointer(&tmp[5]))
   238  				return v
   239  			},
   240  			// we expect to see 128 bytes as struct test is part of the bytes slice
   241  			// and field c point to it.
   242  			expectedSize: 128,
   243  		}, "map_string_interface": {
   244  			getStruct: func() interface{} {
   245  				return &map[string]interface{}{}
   246  			},
   247  			expectedSize: ptrSize + topHashSize + hmapStructSize +
   248  				(8*(strHdrSize+interfaceSize) + ptrSize),
   249  		}, "map_interface_interface": {
   250  			getStruct: func() interface{} {
   251  				// All the map will use only one bucket because there is less than 8
   252  				// entries in each map. Also for amd64 and i386 the bucket size is
   253  				// computed like in function bucketOf in /go/src/reflect/type.go:
   254  				return &map[interface{}]interface{}{
   255  					// 2 + (8 + 4) 386
   256  					// 2 + (16 + 4) amd64
   257  					uint16(123): "yolo",
   258  					// (4 + 8) + (4 + 28 + 140) + (4 for SWAG) 386
   259  					// (4 + 16) + (8 + 48 + 272) + (4 for SWAG) amd64
   260  					"meow": map[string]string{"SWAG": "yolo"},
   261  					// (12) + (4 + 12) 386
   262  					// (16) + (8 + 16) amd64
   263  					yolo{i: 523}: &yolo{i: 126},
   264  					// (12) + (12) 386
   265  					// (16) + (16) amd64
   266  					fmt.Stringer(yolo{i: 123}): yolo{i: 234},
   267  				}
   268  			},
   269  			// Total
   270  			// 386: (4 + 28 + 140) + 2 + (8 + 4) + (4 + 8) + (4 + 28 + 140) + 4 + (12) +
   271  			// (4 + 12) + 12 + 12
   272  			// amd64: (8 + 48 + 272) + 2 + (16 + 4) + (4 + 16) + (8 + 48 + 272) + (4) +
   273  			// (16) + (8 + 16) + (16) + (16)
   274  			expectedSize: (ptrSize + topHashSize + hmapStructSize +
   275  				(8*(2*interfaceSize) + ptrSize)) +
   276  				(unsafe.Sizeof(uint16(123)) + strHdrSize + 4 /* "yolo" */) +
   277  				(strHdrSize + 4 /* "meow" */ +
   278  					(ptrSize + hmapStructSize + topHashSize +
   279  						(8*(2*strHdrSize) + ptrSize)) /*map[string]string*/ +
   280  					4 /* "SWAG" */) +
   281  				(yoloSize /* obj: */ + (ptrSize + yoloSize) /* &obj */) +
   282  				yoloSize*2,
   283  		}, "struct_4": {
   284  			getStruct: func() interface{} {
   285  				return &struct {
   286  					a map[interface{}]interface{}
   287  					c string
   288  					d []string
   289  				}{
   290  					a: map[interface{}]interface{}{
   291  						uint16(123):                "yolo",
   292  						"meow":                     map[string]string{"SWAG": "yolo"},
   293  						yolo{i: 127}:               &yolo{i: 124},
   294  						fmt.Stringer(yolo{i: 123}): yolo{i: 234},
   295  					}, // 4 (386) or 8 (amd64)
   296  					c: "Hello",                              // 8 (386) or 16 (amd64)
   297  					d: []string{"Bonjour", "Hello", "Hola"}, // 12 (386) or 24 (amd64)
   298  				}
   299  			},
   300  			// Total
   301  			// 386: sizeof(tmp map) + 8(test.c) + 12(test.d) +
   302  			// 3 * 8 (strSlice) + 16(len("Bonjour") + len("Hello")...)
   303  			// amd64: sizeof(tmp map) + 8 (test.b) + 16(test.c) + 24(test.d) +
   304  			// 3 * 16 (strSlice) + 16(len("Bonjour") + len("Hello")...)
   305  			expectedSize: (ptrSize + strHdrSize + sliceHdrSize) + (hmapStructSize +
   306  				topHashSize + (8*(2*2*ptrSize /* interface size */) + ptrSize) +
   307  				unsafe.Sizeof(uint16(123)) +
   308  				strHdrSize + 4 /* "yolo" */ + strHdrSize + 4 /* "meow" */ +
   309  				+(ptrSize + hmapStructSize + topHashSize + (8*(2*strHdrSize) + ptrSize) +
   310  					4 /* "SWAG" */) + yoloSize /* obj */ + (ptrSize + yoloSize) /* &obj */ +
   311  				yoloSize*2) + 5 /* "Hello" */ +
   312  				3*strHdrSize /*strings in strSlice*/ + 11, /* "Bonjour" + "Hola" */
   313  		}, "chan_int": {
   314  			getStruct: func() interface{} {
   315  				test := make(chan int)
   316  				return &test
   317  			},
   318  			// The expected size should be equal to the size of the struct hchan
   319  			// defined in /go/src/runtime/chan.go
   320  			expectedSize: ptrSize + chanHdrSize,
   321  		}, "chan_int_16": {
   322  			getStruct: func() interface{} {
   323  				test := make(chan int, 16)
   324  				return &test
   325  			},
   326  			expectedSize: ptrSize + chanHdrSize + 16*ptrSize,
   327  		}, "chan_yoloPtr_16": {
   328  			getStruct: func() interface{} {
   329  				test := make(chan *yolo, 16)
   330  				for i := 0; i < 16; i++ {
   331  					tmp := &yolo{
   332  						i: int32(i),
   333  					}
   334  					tmp.p = unsafe.Pointer(&tmp.i)
   335  					test <- tmp
   336  				}
   337  				return &test
   338  			},
   339  			expectedSize: ptrSize + chanHdrSize + 16*(ptrSize+yoloSize),
   340  		}, "struct_5": {
   341  			getStruct: func() interface{} {
   342  				tmp := make([]byte, 32)
   343  				test := struct {
   344  					a []byte
   345  					b **uint32
   346  				}{
   347  					a: tmp,
   348  				}
   349  				bob := uint32(42)
   350  				ptrInt := (*uintptr)(unsafe.Pointer(&tmp[0]))
   351  				*ptrInt = uintptr(unsafe.Pointer(&bob))
   352  				test.b = (**uint32)(unsafe.Pointer(&tmp[0]))
   353  				return &test
   354  			},
   355  			expectedSize: sliceHdrSize + ptrSize + 32 + 4,
   356  		}, "struct_6": {
   357  			getStruct: func() interface{} {
   358  				type A struct {
   359  					a uintptr
   360  					b *yolo
   361  				}
   362  				type B struct {
   363  					a *A
   364  					b uintptr
   365  				}
   366  				tmp := make([]byte, 32)
   367  				test := struct {
   368  					a []byte
   369  					b *B
   370  				}{
   371  					a: tmp,
   372  				}
   373  				y := yolo{i: 42}
   374  				test.b = (*B)(unsafe.Pointer(&tmp[0]))
   375  				test.b.a = (*A)(unsafe.Pointer(&tmp[0]))
   376  				test.b.a.b = &y
   377  				return &test
   378  			},
   379  			expectedSize: sliceHdrSize + ptrSize + 32 + yoloSize,
   380  		}, "chan_chan_int_16": {
   381  			getStruct: func() interface{} {
   382  				test := make(chan chan int, 16)
   383  				for i := 0; i < 16; i++ {
   384  					tmp := make(chan int)
   385  					test <- tmp
   386  				}
   387  				return &test
   388  			},
   389  			expectedSize: ptrSize + chanHdrSize*17 + 16*ptrSize,
   390  		}, "chan_yolo_16": {
   391  			getStruct: func() interface{} {
   392  				test := make(chan yolo, 16)
   393  				for i := 0; i < 16; i++ {
   394  					tmp := yolo{
   395  						i: int32(i),
   396  					}
   397  					test <- tmp
   398  				}
   399  				return &test
   400  			},
   401  			expectedSize: ptrSize + chanHdrSize + 16*yoloSize,
   402  		}, "chan_map_string_interface_16)": {
   403  			getStruct: func() interface{} {
   404  				test := make(chan map[string]interface{}, 16)
   405  				for i := 0; i < 16; i++ {
   406  					tmp := make(map[string]interface{})
   407  					test <- tmp
   408  				}
   409  				return &test
   410  			},
   411  			expectedSize: ptrSize + chanHdrSize + 16*(ptrSize+hmapStructSize+
   412  				(8*(1+strHdrSize+interfaceSize)+ptrSize)),
   413  		}, "chan_unsafe_Pointer_16": {
   414  			getStruct: func() interface{} {
   415  				test := make(chan unsafe.Pointer, 16)
   416  				for i := 0; i < 16; i++ {
   417  					var a int
   418  					ptrToA := (unsafe.Pointer)(unsafe.Pointer(&a))
   419  					test <- ptrToA
   420  				}
   421  				return &test
   422  			},
   423  			expectedSize: ptrSize + chanHdrSize + 16*ptrSize,
   424  		}, "chan_[]int_16": {
   425  			getStruct: func() interface{} {
   426  				test := make(chan []int, 16)
   427  				for i := 0; i < 8; i++ {
   428  					intSlice := make([]int, 16)
   429  					test <- intSlice
   430  				}
   431  				return &test
   432  			},
   433  			expectedSize: ptrSize + chanHdrSize + 16*sliceHdrSize + 8*16*ptrSize,
   434  		}, "chan_func": {
   435  			getStruct: func() interface{} {
   436  				test := make(chan func(), 16)
   437  				f := func() {
   438  					fmt.Printf("Hello!")
   439  				}
   440  				for i := 0; i < 8; i++ {
   441  					test <- f
   442  				}
   443  				return &test
   444  			},
   445  			expectedSize: ptrSize + chanHdrSize + 16*ptrSize,
   446  		},
   447  	}
   448  
   449  	for key, tcase := range tests {
   450  		t.Run(key, func(t *testing.T) {
   451  			v := tcase.getStruct()
   452  			m, err := DeepSizeof(v)
   453  			if err != nil {
   454  				t.Fatal(err)
   455  			}
   456  			var totalSize uintptr
   457  			for _, size := range m {
   458  				totalSize += size
   459  			}
   460  			expectedSize := tcase.expectedSize
   461  			if totalSize != expectedSize {
   462  				t.Fatalf("Expected size: %v, but got %v", expectedSize, totalSize)
   463  			}
   464  		})
   465  	}
   466  }
   467  
   468  func TestUpdateSeenAreas(t *testing.T) {
   469  	tests := []struct {
   470  		seen         []block
   471  		expectedSeen []block
   472  		expectedSize uintptr
   473  		update       block
   474  	}{{
   475  		seen: []block{
   476  			{start: 0x100000, end: 0x100050},
   477  		},
   478  		expectedSeen: []block{
   479  			{start: 0x100000, end: 0x100050},
   480  			{start: 0x100100, end: 0x100150},
   481  		},
   482  		expectedSize: 0x50,
   483  		update:       block{start: 0x100100, end: 0x100150},
   484  	}, {
   485  		seen: []block{
   486  			{start: 0x100000, end: 0x100050},
   487  		},
   488  		expectedSeen: []block{
   489  			{start: 0x100, end: 0x150},
   490  			{start: 0x100000, end: 0x100050},
   491  		},
   492  		expectedSize: 0x50,
   493  		update:       block{start: 0x100, end: 0x150},
   494  	}, {
   495  		seen: []block{
   496  			{start: 0x100000, end: 0x100500},
   497  		},
   498  		expectedSeen: []block{
   499  			{start: 0x100000, end: 0x100750},
   500  		},
   501  		expectedSize: 0x250,
   502  		update:       block{start: 0x100250, end: 0x100750},
   503  	}, {
   504  		seen: []block{
   505  			{start: 0x100250, end: 0x100750},
   506  		},
   507  		expectedSeen: []block{
   508  			{start: 0x100000, end: 0x100750},
   509  		},
   510  		expectedSize: 0x250,
   511  		update:       block{start: 0x100000, end: 0x100500},
   512  	}, {
   513  		seen: []block{
   514  			{start: 0x1000, end: 0x1250},
   515  			{start: 0x1500, end: 0x1750},
   516  		},
   517  		expectedSeen: []block{
   518  			{start: 0x1000, end: 0x1750},
   519  		},
   520  		expectedSize: 0x2B0,
   521  		update:       block{start: 0x1200, end: 0x1700},
   522  	}, {
   523  		seen: []block{
   524  			{start: 0x1000, end: 0x1250},
   525  			{start: 0x1500, end: 0x1750},
   526  			{start: 0x1F50, end: 0x21A0},
   527  		},
   528  		expectedSeen: []block{
   529  			{start: 0xF00, end: 0x1F00},
   530  			{start: 0x1F50, end: 0x21A0},
   531  		},
   532  		expectedSize: 0xB60,
   533  		update:       block{start: 0xF00, end: 0x1F00},
   534  	}, {
   535  		seen: []block{
   536  			{start: 0x1000, end: 0x1250},
   537  			{start: 0x1500, end: 0x1750},
   538  			{start: 0x1F00, end: 0x2150},
   539  		},
   540  		expectedSeen: []block{
   541  			{start: 0xF00, end: 0x2150},
   542  		},
   543  		expectedSize: 0xB60,
   544  		update:       block{start: 0xF00, end: 0x1F00},
   545  	}, {
   546  		seen: []block{
   547  			{start: 0x1000, end: 0x1250},
   548  			{start: 0x1500, end: 0x1750},
   549  			{start: 0x1F00, end: 0x2150},
   550  		},
   551  		expectedSeen: []block{
   552  			{start: 0x1000, end: 0x1750},
   553  			{start: 0x1F00, end: 0x2150},
   554  		},
   555  		expectedSize: 0x2B0,
   556  		update:       block{start: 0x1250, end: 0x1500},
   557  	}}
   558  
   559  	for i, tcase := range tests {
   560  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   561  			seen, size := updateSeenBlocks(tcase.update, tcase.seen)
   562  			if !test.DeepEqual(seen, tcase.expectedSeen) {
   563  				t.Fatalf("seen blocks %x for iterration %v are different than the "+
   564  					"one expected:\n %x", seen, i, tcase.expectedSeen)
   565  			}
   566  			if size != tcase.expectedSize {
   567  				t.Fatalf("Size does not match, expected 0x%x got 0x%x",
   568  					tcase.expectedSize, size)
   569  			}
   570  		})
   571  	}
   572  }