├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── size.go └── size_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | example 18 | .idea 19 | go.sum -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dmitriy Titov (Дмитрий Титов) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # size - calculates variable's memory consumption at runtime 2 | 3 | ### Part of the [Transflow Project](http://transflow.ru/) 4 | 5 | Sometimes you may need a tool to measure the size of object in your Go program at runtime. This package makes an attempt to do so. Package based on `binary.Size()` from Go standard library. 6 | 7 | Features: 8 | - supports non-fixed size variables and struct fields: `struct`, `int`, `slice`, `string`, `map`; 9 | - supports complex types including structs with non-fixed size fields; 10 | - supports all basic types (numbers, bool); 11 | - supports `chan` and `interface`; 12 | - supports pointers; 13 | - implements infinite recursion detection (i.e. pointer inside struct field references to parent struct). 14 | 15 | ### Usage example 16 | 17 | ``` 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | 23 | // Use latest tag. 24 | "github.com/DmitriyVTitov/size" 25 | ) 26 | 27 | func main() { 28 | a := struct { 29 | a int 30 | b string 31 | c bool 32 | d int32 33 | e []byte 34 | f [3]int64 35 | }{ 36 | a: 10, // 8 bytes 37 | b: "Text", // 16 (string itself) + 4 = 20 bytes 38 | c: true, // 1 byte 39 | d: 25, // 4 bytes 40 | e: []byte{'c', 'd', 'e'}, // 24 (slice itself) + 3 = 27 bytes 41 | f: [3]int64{1, 2, 3}, // 3 * 8 = 24 bytes 42 | } // 84 + 3 (padding) = 87 bytes 43 | 44 | fmt.Println(size.Of(a)) 45 | } 46 | 47 | // Output: 87 48 | ``` 49 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DmitriyVTitov/size 2 | 3 | go 1.14 4 | 5 | require github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da 6 | -------------------------------------------------------------------------------- /size.go: -------------------------------------------------------------------------------- 1 | // Package size implements run-time calculation of size of the variable. 2 | // Source code is based on "binary.Size()" function from Go standard library. 3 | // size.Of() omits size of slices, arrays and maps containers itself (24, 24 and 8 bytes). 4 | // When counting maps separate calculations are done for keys and values. 5 | package size 6 | 7 | import ( 8 | "reflect" 9 | "unsafe" 10 | ) 11 | 12 | // Of returns the size of 'v' in bytes. 13 | // If there is an error during calculation, Of returns -1. 14 | func Of(v interface{}) int { 15 | // Cache with every visited pointer so we don't count two pointers 16 | // to the same memory twice. 17 | cache := make(map[uintptr]bool) 18 | return sizeOf(reflect.Indirect(reflect.ValueOf(v)), cache) 19 | } 20 | 21 | // sizeOf returns the number of bytes the actual data represented by v occupies in memory. 22 | // If there is an error, sizeOf returns -1. 23 | func sizeOf(v reflect.Value, cache map[uintptr]bool) int { 24 | switch v.Kind() { 25 | 26 | case reflect.Array: 27 | sum := 0 28 | for i := 0; i < v.Len(); i++ { 29 | s := sizeOf(v.Index(i), cache) 30 | if s < 0 { 31 | return -1 32 | } 33 | sum += s 34 | } 35 | 36 | return sum + (v.Cap()-v.Len())*int(v.Type().Elem().Size()) 37 | 38 | case reflect.Slice: 39 | // return 0 if this node has been visited already 40 | if cache[v.Pointer()] { 41 | return 0 42 | } 43 | cache[v.Pointer()] = true 44 | 45 | sum := 0 46 | for i := 0; i < v.Len(); i++ { 47 | s := sizeOf(v.Index(i), cache) 48 | if s < 0 { 49 | return -1 50 | } 51 | sum += s 52 | } 53 | 54 | sum += (v.Cap() - v.Len()) * int(v.Type().Elem().Size()) 55 | 56 | return sum + int(v.Type().Size()) 57 | 58 | case reflect.Struct: 59 | sum := 0 60 | for i, n := 0, v.NumField(); i < n; i++ { 61 | s := sizeOf(v.Field(i), cache) 62 | if s < 0 { 63 | return -1 64 | } 65 | sum += s 66 | } 67 | 68 | // Look for struct padding. 69 | padding := int(v.Type().Size()) 70 | for i, n := 0, v.NumField(); i < n; i++ { 71 | padding -= int(v.Field(i).Type().Size()) 72 | } 73 | 74 | return sum + padding 75 | 76 | case reflect.String: 77 | s := v.String() 78 | hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) 79 | if cache[hdr.Data] { 80 | return int(v.Type().Size()) 81 | } 82 | cache[hdr.Data] = true 83 | return len(s) + int(v.Type().Size()) 84 | 85 | case reflect.Ptr: 86 | // return Ptr size if this node has been visited already (infinite recursion) 87 | if cache[v.Pointer()] { 88 | return int(v.Type().Size()) 89 | } 90 | cache[v.Pointer()] = true 91 | if v.IsNil() { 92 | return int(reflect.New(v.Type()).Type().Size()) 93 | } 94 | s := sizeOf(reflect.Indirect(v), cache) 95 | if s < 0 { 96 | return -1 97 | } 98 | return s + int(v.Type().Size()) 99 | 100 | case reflect.Bool, 101 | reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 102 | reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 103 | reflect.Int, reflect.Uint, 104 | reflect.Chan, 105 | reflect.Uintptr, 106 | reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, 107 | reflect.Func: 108 | return int(v.Type().Size()) 109 | 110 | case reflect.Map: 111 | // return 0 if this node has been visited already (infinite recursion) 112 | if cache[v.Pointer()] { 113 | return 0 114 | } 115 | cache[v.Pointer()] = true 116 | sum := 0 117 | keys := v.MapKeys() 118 | for i := range keys { 119 | val := v.MapIndex(keys[i]) 120 | // calculate size of key and value separately 121 | sv := sizeOf(val, cache) 122 | if sv < 0 { 123 | return -1 124 | } 125 | sum += sv 126 | sk := sizeOf(keys[i], cache) 127 | if sk < 0 { 128 | return -1 129 | } 130 | sum += sk 131 | } 132 | // Include overhead due to unused map buckets. 10.79 comes 133 | // from https://golang.org/src/runtime/map.go. 134 | return sum + int(v.Type().Size()) + int(float64(len(keys))*10.79) 135 | 136 | case reflect.Interface: 137 | return sizeOf(v.Elem(), cache) + int(v.Type().Size()) 138 | 139 | } 140 | 141 | return -1 142 | } 143 | -------------------------------------------------------------------------------- /size_test.go: -------------------------------------------------------------------------------- 1 | package size 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/golang/groupcache/lru" 7 | ) 8 | 9 | func TestOf(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | v interface{} 13 | want int 14 | }{ 15 | { 16 | name: "Array", 17 | v: [3]int32{1, 2, 3}, // 3 * 4 = 12 18 | want: 12, 19 | }, 20 | { 21 | name: "Slice", 22 | v: make([]int64, 2, 5), // 5 * 8 + 24 = 64 23 | want: 64, 24 | }, 25 | { 26 | name: "String", 27 | v: "ABCdef", // 6 + 16 = 22 28 | want: 22, 29 | }, 30 | { 31 | name: "Map", 32 | // (8 + 3 + 16) + (8 + 4 + 16) = 55 33 | // 55 + 8 + 10.79 * 2 = 84 34 | v: map[int64]string{0: "ABC", 1: "DEFG"}, 35 | want: 84, 36 | }, 37 | { 38 | name: "Struct", 39 | v: struct { 40 | slice []int64 41 | array [2]bool 42 | structure struct { 43 | i int8 44 | s string 45 | } 46 | }{ 47 | slice: []int64{12345, 67890}, // 2 * 8 + 24 = 40 48 | array: [2]bool{true, false}, // 2 * 1 = 2 49 | structure: struct { 50 | i int8 51 | s string 52 | }{ 53 | i: 5, // 1 54 | s: "abc", // 3 * 1 + 16 = 19 55 | }, // 20 + 7 (padding) = 27 56 | }, // 40 + 2 + 27 = 69 + 6 (padding) = 75 57 | want: 75, 58 | }, 59 | { 60 | name: "Struct With Func", 61 | v: lru.Cache{ 62 | MaxEntries: 0, // 8 63 | OnEvicted: nil, // 0 64 | }, // + 16 (two more pointers) = 24 65 | want: 24, 66 | }, 67 | } 68 | for _, tt := range tests { 69 | t.Run(tt.name, func(t *testing.T) { 70 | if got := Of(tt.v); got != tt.want { 71 | t.Errorf("Of() = %v, want %v", got, tt.want) 72 | } 73 | }) 74 | } 75 | } 76 | --------------------------------------------------------------------------------