github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/sizeof/sizeof.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  	"errors"
     9  	"reflect"
    10  	"unsafe"
    11  
    12  	"github.com/aristanetworks/goarista/areflect"
    13  )
    14  
    15  // blocks are used to keep track of which memory areas were already
    16  // been visited.
    17  type block struct {
    18  	start uintptr
    19  	end   uintptr
    20  }
    21  
    22  func (b block) size() uintptr {
    23  	return b.end - b.start
    24  }
    25  
    26  // DeepSizeof returns total memory occupied by each type for val.
    27  // The value passed in argument must be a pointer.
    28  func DeepSizeof(val interface{}) (map[string]uintptr, error) {
    29  	value := reflect.ValueOf(val)
    30  	// We want to force val to be a pointer to the original value, because if we get a copy, we
    31  	// can get some pointers that will point back to our original value.
    32  	if value.Kind() != reflect.Ptr {
    33  		return nil, errors.New("cannot get the deep size of a non-pointer value")
    34  	}
    35  	m := make(map[string]uintptr)
    36  	ptrsTypes := make(map[uintptr]map[string]struct{})
    37  	sizeof(value.Elem(), m, ptrsTypes, false, block{start: uintptr(value.Pointer())}, nil)
    38  	return m, nil
    39  }
    40  
    41  // Check if curBlock overlap tmpBlock
    42  func isOverlapping(curBlock, tmpBlock block) bool {
    43  	return curBlock.start <= tmpBlock.end && tmpBlock.start <= curBlock.end
    44  }
    45  
    46  func getOverlappingBlocks(curBlock block, seen []block) ([]block, int) {
    47  	var tmp []block
    48  	for idx, a := range seen {
    49  		if a.start > curBlock.end {
    50  			return tmp, idx
    51  		}
    52  		if isOverlapping(curBlock, a) {
    53  			tmp = append(tmp, a)
    54  		}
    55  	}
    56  	return tmp, len(seen)
    57  }
    58  
    59  func insertBlock(curBlock block, idxToInsert int, seen []block) []block {
    60  	seen = append(seen, block{})
    61  	copy(seen[idxToInsert+1:], seen[idxToInsert:])
    62  	seen[idxToInsert] = curBlock
    63  	return seen
    64  }
    65  
    66  // get the size of our current block that is not overlapping other blocks.
    67  func getUnseenSizeOfCurrentBlock(curBlock block, overlappingBlocks []block) uintptr {
    68  	var size uintptr
    69  	for idx, a := range overlappingBlocks {
    70  		if idx == 0 && curBlock.start < a.start {
    71  			size += a.start - curBlock.start
    72  		}
    73  		if idx == len(overlappingBlocks)-1 {
    74  			if curBlock.end > a.end {
    75  				size += curBlock.end - a.end
    76  			}
    77  		} else {
    78  			size += overlappingBlocks[idx+1].start - a.end
    79  		}
    80  	}
    81  	return size
    82  }
    83  
    84  func updateSeenBlocks(curBlock block, seen []block) ([]block, uintptr) {
    85  	if len(seen) == 0 {
    86  		return []block{curBlock}, curBlock.size()
    87  	}
    88  	overlappingBlocks, idx := getOverlappingBlocks(curBlock, seen)
    89  	if len(overlappingBlocks) == 0 {
    90  		// No overlap, so we will insert our new block in our list.
    91  		return insertBlock(curBlock, idx, seen), curBlock.size()
    92  	}
    93  	unseenSize := getUnseenSizeOfCurrentBlock(curBlock, overlappingBlocks)
    94  	idxFirstOverlappingBlock := idx - len(overlappingBlocks)
    95  	firstOverlappingBlock := &seen[idxFirstOverlappingBlock]
    96  	lastOverlappingBlock := seen[idx-1]
    97  	if firstOverlappingBlock.start > curBlock.start {
    98  		firstOverlappingBlock.start = curBlock.start
    99  	}
   100  	if lastOverlappingBlock.end < curBlock.end {
   101  		firstOverlappingBlock.end = curBlock.end
   102  	} else {
   103  		firstOverlappingBlock.end = lastOverlappingBlock.end
   104  	}
   105  	tailLen := len(seen[idx:])
   106  	copy(seen[idxFirstOverlappingBlock+1:], seen[idx:])
   107  	return seen[:idxFirstOverlappingBlock+1+tailLen], unseenSize
   108  }
   109  
   110  // Check if this current block is already fully contained in our list of seen blocks
   111  func isKnownBlock(curBlock block, seen []block) bool {
   112  	for _, a := range seen {
   113  		if a.start <= curBlock.start &&
   114  			a.end >= curBlock.end {
   115  			// curBlock is fully contained in an other block
   116  			// that we already know
   117  			return true
   118  		}
   119  		if a.start > curBlock.start {
   120  			// Our slice of seens block is order by pointer address.
   121  			// That means, if curBlock was not contained in a previous known
   122  			// block, there is no need to continue.
   123  			return false
   124  		}
   125  	}
   126  	return false
   127  }
   128  
   129  func sizeof(v reflect.Value, m map[string]uintptr, ptrsTypes map[uintptr]map[string]struct{},
   130  	counted bool, curBlock block, seen []block) []block {
   131  	if !v.IsValid() {
   132  		return seen
   133  	}
   134  	vn := v.Type().String()
   135  	vs := v.Type().Size()
   136  	curBlock.end = vs + curBlock.start
   137  	if counted {
   138  		// already accounted for the size (field in struct, in array, etc)
   139  		vs = 0
   140  	}
   141  	if curBlock.start != 0 {
   142  		// A pointer can point to the same memory area than a previous pointer,
   143  		// but its type should be different (see tests struct_5 and struct_6).
   144  		if types, ok := ptrsTypes[curBlock.start]; ok {
   145  			if _, ok := types[vn]; ok {
   146  				return seen
   147  			}
   148  			types[vn] = struct{}{}
   149  		} else {
   150  			ptrsTypes[curBlock.start] = make(map[string]struct{})
   151  		}
   152  		if isKnownBlock(curBlock, seen) {
   153  			// we don't want to count this size if we have a known block
   154  			vs = 0
   155  		} else {
   156  			var tmpVs uintptr
   157  			seen, tmpVs = updateSeenBlocks(curBlock, seen)
   158  			if !counted {
   159  				vs = tmpVs
   160  			}
   161  		}
   162  	}
   163  	switch v.Kind() {
   164  	case reflect.Interface:
   165  		seen = sizeof(v.Elem(), m, ptrsTypes, false, block{}, seen)
   166  	case reflect.Ptr:
   167  		if v.IsNil() {
   168  			break
   169  		}
   170  		seen = sizeof(v.Elem(), m, ptrsTypes, false, block{start: uintptr(v.Pointer())}, seen)
   171  	case reflect.Array:
   172  		// get size of all elements in the array in case there are pointers
   173  		l := v.Len()
   174  		for i := 0; i < l; i++ {
   175  			seen = sizeof(v.Index(i), m, ptrsTypes, true, block{}, seen)
   176  		}
   177  	case reflect.Slice:
   178  		// get size of all elements in the slice in case there are pointers
   179  		// TODO: count elements that are not accessible after reslicing
   180  		l := v.Len()
   181  		vLen := v.Type().Elem().Size()
   182  		for i := 0; i < l; i++ {
   183  			e := v.Index(i)
   184  			eStart := uintptr(e.UnsafeAddr())
   185  			eBlock := block{
   186  				start: eStart,
   187  				end:   eStart + vLen,
   188  			}
   189  			if !isKnownBlock(eBlock, seen) {
   190  				vs += vLen
   191  				seen = sizeof(e, m, ptrsTypes, true, eBlock, seen)
   192  			}
   193  		}
   194  		capStart := uintptr(v.Pointer()) + (v.Type().Elem().Size() * uintptr(v.Len()))
   195  		capEnd := uintptr(v.Pointer()) + (v.Type().Elem().Size() * uintptr(v.Cap()))
   196  		capBlock := block{start: capStart, end: capEnd}
   197  		if isKnownBlock(capBlock, seen) {
   198  			break
   199  		}
   200  		var tmpSize uintptr
   201  		seen, tmpSize = updateSeenBlocks(capBlock, seen)
   202  		vs += tmpSize
   203  	case reflect.Map:
   204  		if v.IsNil() {
   205  			break
   206  		}
   207  		var tmpSize uintptr
   208  		if tmpSize, seen = sizeofmap(v, seen); tmpSize == 0 {
   209  			// we saw this map
   210  			break
   211  		}
   212  		vs += tmpSize
   213  		for _, k := range v.MapKeys() {
   214  			kv := v.MapIndex(k)
   215  			seen = sizeof(k, m, ptrsTypes, true, block{}, seen)
   216  			seen = sizeof(kv, m, ptrsTypes, true, block{}, seen)
   217  		}
   218  	case reflect.Struct:
   219  		for i, n := 0, v.NumField(); i < n; i++ {
   220  			vf := areflect.ForceExport(v.Field(i))
   221  			seen = sizeof(vf, m, ptrsTypes, true, block{}, seen)
   222  		}
   223  	case reflect.String:
   224  		str := v.String()
   225  		strHdr := (*reflect.StringHeader)(unsafe.Pointer(&str))
   226  		tmpSize := uintptr(strHdr.Len)
   227  		strBlock := block{start: strHdr.Data, end: strHdr.Data + tmpSize}
   228  		if isKnownBlock(strBlock, seen) {
   229  			break
   230  		}
   231  		seen, tmpSize = updateSeenBlocks(strBlock, seen)
   232  		vs += tmpSize
   233  	case reflect.Chan:
   234  		var tmpSize uintptr
   235  		tmpSize, seen = sizeofChan(v, m, ptrsTypes, seen)
   236  		vs += tmpSize
   237  	}
   238  	if vs != 0 {
   239  		m[vn] += vs
   240  	}
   241  	return seen
   242  }
   243  
   244  //go:linkname typesByString reflect.typesByString
   245  func typesByString(s string) []unsafe.Pointer
   246  
   247  // these types need to be kept in sync with src/runtime/type.go
   248  type tflag uint8
   249  type nameOff int32
   250  type typeOff int32
   251  
   252  // needs to be kept in sync with src/runtime/runtime2.go#eface
   253  type eface struct {
   254  	_type *_type
   255  	data  unsafe.Pointer
   256  }
   257  
   258  // needs to be kept in sync with src/runtime/type.go#_type
   259  type _type struct {
   260  	size       uintptr
   261  	ptrdata    uintptr // size of memory prefix holding all pointers
   262  	hash       uint32
   263  	tflag      tflag
   264  	align      uint8
   265  	fieldAlign uint8
   266  	kind       uint8
   267  	// function for comparing objects of this type
   268  	// (ptr to object A, ptr to object B) -> ==?
   269  	equal func(unsafe.Pointer, unsafe.Pointer) bool
   270  	// gcdata stores the GC type data for the garbage collector.
   271  	// If the KindGCProg bit is set in kind, gcdata is a GC program.
   272  	// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
   273  	gcdata    *byte
   274  	str       nameOff
   275  	ptrToThis typeOff
   276  }
   277  
   278  // needs to be kept in sync with src/runtime/type.go#maptype
   279  type maptype struct {
   280  	typ    _type
   281  	key    *_type
   282  	elem   *_type
   283  	bucket *_type // internal type representing a hash bucket
   284  	// function for hashing keys (ptr to key, seed) -> hash
   285  	hasher     func(unsafe.Pointer, uintptr) uintptr
   286  	keysize    uint8  // size of key slot
   287  	elemsize   uint8  // size of elem slot
   288  	bucketsize uint16 // size of bucket
   289  	flags      uint32
   290  }
   291  
   292  func sizeofmap(v reflect.Value, seen []block) (uintptr, []block) {
   293  	// find the size of the buckets used in this map
   294  	iface := v.Interface()
   295  	efacev := (*eface)(unsafe.Pointer(&iface))
   296  	maptypev := (*maptype)(unsafe.Pointer(efacev._type))
   297  	bucketsize := uint64(maptypev.bucketsize)
   298  
   299  	// get hmap
   300  	hmap := (*unsafe.Pointer)(unsafe.Pointer(&iface))
   301  	*hmap = typesByString("*runtime.hmap")[0]
   302  
   303  	hmapv := reflect.ValueOf(iface)
   304  	// account for the size of the hmap, buckets and oldbuckets
   305  	hmapv = reflect.Indirect(hmapv)
   306  	mapBlock := block{
   307  		start: hmapv.UnsafeAddr(),
   308  		end:   hmapv.UnsafeAddr() + hmapv.Type().Size(),
   309  	}
   310  	// is it a map we already saw?
   311  	if isKnownBlock(mapBlock, seen) {
   312  		return 0, seen
   313  	}
   314  	seen, _ = updateSeenBlocks(mapBlock, seen)
   315  	B := hmapv.FieldByName("B").Uint()
   316  	oldbuckets := hmapv.FieldByName("oldbuckets").Pointer()
   317  	buckets := hmapv.FieldByName("buckets").Pointer()
   318  	noverflow := int16(hmapv.FieldByName("noverflow").Uint())
   319  	nb := 2
   320  	if B == 0 {
   321  		nb = 1
   322  	}
   323  	size := uint64((nb << B)) * bucketsize
   324  	if noverflow != 0 {
   325  		size += uint64(noverflow) * bucketsize
   326  	}
   327  	seen, _ = updateSeenBlocks(block{start: buckets, end: buckets + uintptr(size)},
   328  		seen)
   329  	// As defined in /go/src/runtime/hashmap.go in struct hmap, oldbuckets is the
   330  	// previous bucket array that is half the size of the current one. We need to
   331  	// also take that in consideration since there is still a pointer to this previous bucket.
   332  	if oldbuckets != 0 {
   333  		tmp := (2 << (B - 1)) * bucketsize
   334  		size += tmp
   335  		seen, _ = updateSeenBlocks(block{
   336  			start: oldbuckets,
   337  			end:   oldbuckets + uintptr(tmp),
   338  		}, seen)
   339  	}
   340  	return hmapv.Type().Size() + uintptr(size), seen
   341  }
   342  
   343  func getSliceToChanBuffer(buff unsafe.Pointer, buffLen uint, dataSize uint) []byte {
   344  	var slice []byte
   345  	sliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
   346  	sliceHdr.Len = int(buffLen * dataSize)
   347  	sliceHdr.Cap = sliceHdr.Len
   348  	sliceHdr.Data = uintptr(buff)
   349  	return slice
   350  }
   351  
   352  func sizeofChan(v reflect.Value, m map[string]uintptr, ptrsTypes map[uintptr]map[string]struct{},
   353  	seen []block) (uintptr, []block) {
   354  	var c interface{} = v.Interface()
   355  	hchan := (*unsafe.Pointer)(unsafe.Pointer(&c))
   356  	*hchan = typesByString("*runtime.hchan")[0]
   357  
   358  	hchanv := reflect.ValueOf(c)
   359  	hchanv = reflect.Indirect(hchanv)
   360  	chanBlock := block{
   361  		start: hchanv.UnsafeAddr(),
   362  		end:   hchanv.UnsafeAddr() + hchanv.Type().Size(),
   363  	}
   364  	// is it a chan we already saw?
   365  	if isKnownBlock(chanBlock, seen) {
   366  		return 0, seen
   367  	}
   368  	seen, _ = updateSeenBlocks(chanBlock, seen)
   369  	elemType := unsafe.Pointer(hchanv.FieldByName("elemtype").Pointer())
   370  	buff := unsafe.Pointer(hchanv.FieldByName("buf").Pointer())
   371  	buffLen := hchanv.FieldByName("dataqsiz").Uint()
   372  	elemSize := uint16(hchanv.FieldByName("elemsize").Uint())
   373  	seen, _ = updateSeenBlocks(block{
   374  		start: uintptr(buff),
   375  		end:   uintptr(buff) + uintptr(buffLen*uint64(elemSize)),
   376  	}, seen)
   377  
   378  	buffSlice := getSliceToChanBuffer(buff, uint(buffLen), uint(elemSize))
   379  	recvx := hchanv.FieldByName("recvx").Uint()
   380  	qcount := hchanv.FieldByName("qcount").Uint()
   381  
   382  	var tmp interface{}
   383  	eface := (*struct {
   384  		typ unsafe.Pointer
   385  		ptr unsafe.Pointer
   386  	})(unsafe.Pointer(&tmp))
   387  	eface.typ = elemType
   388  	for i := uint64(0); buffLen > 0 && i < qcount; i++ {
   389  		idx := (recvx + i) % buffLen
   390  		// get the pointer to the data inside the chan buffer.
   391  		elem := unsafe.Pointer(&buffSlice[uint64(elemSize)*idx])
   392  		eface.ptr = elem
   393  		ev := reflect.ValueOf(tmp)
   394  		var blk block
   395  		k := ev.Kind()
   396  		if k == reflect.Ptr || k == reflect.Chan || k == reflect.Map || k == reflect.Func {
   397  			// let's say our chan is a chan *whatEver, or chan chan whatEver or
   398  			// chan map[whatEver]whatEver. In this case elemType will
   399  			// be either of type *whatEver, chan whatEver or map[whatEver]whatEver
   400  			// but what we set eface.ptr = elem above, we make it point to a pointer
   401  			// to where the data is sotred in the buffer of our channel.
   402  			// So the interface tmp would look like:
   403  			// chan *whatEver -> (type=*whatEver, ptr=**whatEver)
   404  			// chan chan whatEver -> (type= chan whatEver, ptr=*chan whatEver)
   405  			// chan map[whatEver]whatEver -> (type= map[whatEver]whatEver{},
   406  			// ptr=*map[whatEver]whatEver)
   407  			// So we need to take the ptr which is stored into the buffer and replace
   408  			// the ptr to the data of our interface tmp.
   409  			ptr := (*unsafe.Pointer)(elem)
   410  			eface.ptr = *ptr
   411  			ev = reflect.ValueOf(tmp)
   412  			ev = reflect.Indirect(ev)
   413  			blk.start = uintptr(*ptr)
   414  		}
   415  		// It seems that when the chan is of type chan *whatEver, the type in eface
   416  		// will be whatEver and not *whatEver, but ev.Kind() will be a reflect.ptr.
   417  		// So if k is a reflect.Ptr (i.e. a pointer) to a struct, then we want to take
   418  		// the size of the struct into account because
   419  		// vs := v.Type().Size() will return us the size of the struct and not the size
   420  		// of the pointer that is in the channel's buffer.
   421  		seen = sizeof(ev, m, ptrsTypes, true && k != reflect.Ptr, blk, seen)
   422  	}
   423  	return hchanv.Type().Size() + uintptr(uint64(elemSize)*buffLen), seen
   424  }