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 }