github.com/v2pro/plz@v0.0.0-20221028024117-e5f9aec5b631/dump/map.go (about) 1 package dump 2 3 import ( 4 "context" 5 "unsafe" 6 "github.com/v2pro/plz/msgfmt/jsonfmt" 7 "math" 8 "reflect" 9 "encoding/json" 10 ) 11 12 // A header for a Go map. 13 type hmap struct { 14 count int // # live cells == size of map. Must be first (used by len() builtin) 15 flags uint8 16 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) 17 noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details 18 hash0 uint32 // hash seed 19 20 buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. 21 oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing 22 nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) 23 24 extra *mapextra // optional fields 25 } 26 27 const bucketCntBits = 3 28 const bucketCnt = 1 << bucketCntBits 29 30 // A bucket for a Go map. 31 type bmap struct { 32 // tophash generally contains the top byte of the hash value 33 // for each key in this bucket. If tophash[0] < minTopHash, 34 // tophash[0] is a bucket evacuation state instead. 35 tophash [bucketCnt]uint8 36 // Followed by bucketCnt keys and then bucketCnt values. 37 // NOTE: packing all the keys together and then all the values together makes the 38 // code a bit more complicated than alternating key/value/key/value/... but it allows 39 // us to eliminate padding which would be needed for, e.g., map[int64]int8. 40 // Followed by an overflow pointer. 41 } 42 43 // mapextra holds fields that are not present on all maps. 44 type mapextra struct { 45 // If both key and value do not contain pointers and are inline, then we mark bucket 46 // type as containing no pointers. This avoids scanning such maps. 47 // However, bmap.overflow is a pointer. In order to keep overflow buckets 48 // alive, we store pointers to all overflow buckets in hmap.overflow and h.map.oldoverflow. 49 // overflow and oldoverflow are only used if key and value do not contain pointers. 50 // overflow contains overflow buckets for hmap.buckets. 51 // oldoverflow contains overflow buckets for hmap.oldbuckets. 52 // The indirection allows to store a pointer to the slice in hiter. 53 overflow *[]*bmap 54 oldoverflow *[]*bmap 55 56 // nextOverflow holds a pointer to a free overflow bucket. 57 nextOverflow *bmap 58 } 59 60 var topHashEncoder = jsonfmt.EncoderOf(reflect.ArrayOf(bucketCnt, reflect.TypeOf(uint8(0)))) 61 62 type mapEncoder struct { 63 bucketSize uintptr 64 keysSize uintptr 65 keysEncoder jsonfmt.Encoder 66 elemsEncoder jsonfmt.Encoder 67 } 68 69 func newMapEncoder(api jsonfmt.API, valType reflect.Type) *mapEncoder { 70 keysEncoder := api.EncoderOf(reflect.ArrayOf(bucketCnt, valType.Key())) 71 elemsEncoder := api.EncoderOf(reflect.ArrayOf(bucketCnt, valType.Elem())) 72 keysSize := valType.Key().Size() * bucketCnt 73 elemsSize := valType.Elem().Size() * bucketCnt 74 return &mapEncoder{ 75 bucketSize: bucketCnt + keysSize + elemsSize + 8, 76 keysSize: keysSize, 77 keysEncoder: keysEncoder, 78 elemsEncoder: elemsEncoder, 79 } 80 } 81 82 func (encoder *mapEncoder) Encode(ctx context.Context, space []byte, ptr unsafe.Pointer) []byte { 83 hmap := (*hmap)(ptr) 84 space = append(space, `{"count":`...) 85 space = jsonfmt.WriteInt64(space, int64(hmap.count)) 86 space = append(space, `,"flags":`...) 87 space = jsonfmt.WriteUint8(space, hmap.flags) 88 space = append(space, `,"B":`...) 89 space = jsonfmt.WriteUint8(space, hmap.B) 90 space = append(space, `,"noverflow":`...) 91 space = jsonfmt.WriteUint16(space, hmap.noverflow) 92 space = append(space, `,"hash0":`...) 93 space = jsonfmt.WriteUint32(space, hmap.hash0) 94 space = append(space, `,"buckets":{"__ptr__":"`...) 95 bucketsPtr := ptrToStr(uintptr(hmap.buckets)) 96 space = append(space, bucketsPtr...) 97 space = append(space, `"},"oldbuckets":{"__ptr__":"`...) 98 oldbucketsPtr := ptrToStr(uintptr(hmap.oldbuckets)) 99 space = append(space, oldbucketsPtr...) 100 space = append(space, `"},"nevacuate":`...) 101 space = jsonfmt.WriteUint64(space, uint64(hmap.nevacuate)) 102 space = append(space, `,"extra":{"__ptr__":"`...) 103 extraPtr := ptrToStr(uintptr(unsafe.Pointer(hmap.extra))) 104 space = append(space, extraPtr...) 105 space = append(space, `"}}`...) 106 bucketsCount := int(math.Pow(2, float64(hmap.B))) 107 if hmap.buckets != nil { 108 bucketsData := encoder.encodeBuckets(ctx, nil, bucketsCount, hmap.buckets) 109 addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage) 110 addrMap[bucketsPtr] = json.RawMessage(bucketsData) 111 } 112 if hmap.oldbuckets != nil { 113 oldbucketsData := encoder.encodeBuckets(ctx, nil, bucketsCount / 2, hmap.oldbuckets) 114 addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage) 115 addrMap[oldbucketsPtr] = json.RawMessage(oldbucketsData) 116 } 117 if hmap.extra != nil { 118 extraData := encoder.encodeExtra(ctx, nil, unsafe.Pointer(hmap.extra)) 119 addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage) 120 addrMap[extraPtr] = json.RawMessage(extraData) 121 } 122 return space 123 } 124 125 func (encoder *mapEncoder) encodeExtra(ctx context.Context, space []byte, ptr unsafe.Pointer) []byte { 126 extra := (*mapextra)(ptr) 127 space = append(space, `{"overflow":{"__ptr__":"`...) 128 overflowPtr := ptrToStr(uintptr(unsafe.Pointer(extra.overflow))) 129 space = append(space, overflowPtr...) 130 space = append(space, `"},"oldoverflow":{"__ptr__":"`...) 131 oldoverflowPtr := ptrToStr(uintptr(unsafe.Pointer(extra.oldoverflow))) 132 space = append(space, oldoverflowPtr...) 133 space = append(space, `"},"nextOverflow":{"__ptr__":"`...) 134 nextOverflowPtr := ptrToStr(uintptr(unsafe.Pointer(extra.nextOverflow))) 135 space = append(space, nextOverflowPtr...) 136 space = append(space, `"}}`...) 137 if extra.overflow != nil { 138 overflowData := encoder.encodeBucketPtrSlice(ctx, nil, unsafe.Pointer(extra.overflow)) 139 addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage) 140 addrMap[overflowPtr] = overflowData 141 } 142 if extra.oldoverflow != nil { 143 oldoverflowData := encoder.encodeBucketPtrSlice(ctx, nil, unsafe.Pointer(extra.oldoverflow)) 144 addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage) 145 addrMap[oldoverflowPtr] = oldoverflowData 146 } 147 if extra.nextOverflow != nil { 148 nextOverflowData := encoder.encodeBucket(ctx, nil, unsafe.Pointer(extra.nextOverflow)) 149 addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage) 150 addrMap[nextOverflowPtr] = nextOverflowData 151 } 152 return space 153 } 154 155 // []*bmap 156 func (encoder *mapEncoder) encodeBucketPtrSlice(ctx context.Context, space []byte, ptr unsafe.Pointer) []byte { 157 header := (*sliceHeader)(ptr) 158 space = append(space, `{"data":{"__ptr__":"`...) 159 ptrStr := ptrToStr(uintptr(header.data)) 160 space = append(space, ptrStr...) 161 space = append(space, `"},"len":`...) 162 space = jsonfmt.WriteInt64(space, int64(header.len)) 163 space = append(space, `,"cap":`...) 164 space = jsonfmt.WriteInt64(space, int64(header.cap)) 165 space = append(space, `}`...) 166 data := encoder.encodeBucketPtrArray(ctx, nil, header.len, header.data) 167 if uintptr(header.data) != 0 { 168 addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage) 169 addrMap[ptrStr] = json.RawMessage(data) 170 } 171 return space 172 } 173 174 // [N]*bmap 175 func (encoder *mapEncoder) encodeBucketPtrArray(ctx context.Context, space []byte, count int, ptr unsafe.Pointer) []byte { 176 cursor := uintptr(ptr) 177 addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage) 178 space = append(space, '[') 179 for i := 0; i < count; i++ { 180 if i != 0 { 181 space = append(space, ',', ' ') 182 } 183 elemPtr := *(*unsafe.Pointer)(unsafe.Pointer(cursor + 8 * uintptr(i))) 184 space = append(space, `{"__ptr__":"`...) 185 elemPtrStr := ptrToStr(uintptr(elemPtr)) 186 space = append(space, elemPtrStr...) 187 space = append(space, `"}`...) 188 if elemPtr != nil { 189 elemData := encoder.encodeBucket(ctx, nil, elemPtr) 190 addrMap[elemPtrStr] = json.RawMessage(elemData) 191 } 192 } 193 space = append(space, ']') 194 return space 195 } 196 197 func (encoder *mapEncoder) encodeBuckets(ctx context.Context, space []byte, count int, ptr unsafe.Pointer) []byte { 198 space = append(space, '[') 199 cursor := uintptr(ptr) 200 for i := 0; i < count; i++ { 201 if i != 0 { 202 space = append(space, ',', ' ') 203 } 204 space = encoder.encodeBucket(ctx, space, unsafe.Pointer(cursor)) 205 cursor += encoder.bucketSize 206 } 207 space = append(space, ']') 208 return space 209 } 210 211 func (encoder *mapEncoder) encodeBucket(ctx context.Context, space []byte, ptr unsafe.Pointer) []byte { 212 bmap := (*bmap)(ptr) 213 space = append(space, `{"tophash":`...) 214 space = topHashEncoder.Encode(ctx, space, reflect2.PtrOf(bmap.tophash)) 215 space = append(space, `,"keys":`...) 216 keysPtr := uintptr(ptr) + bucketCnt 217 space = encoder.keysEncoder.Encode(ctx, space, unsafe.Pointer(keysPtr)) 218 space = append(space, `,"elems":`...) 219 space = encoder.elemsEncoder.Encode(ctx, space, unsafe.Pointer(keysPtr+encoder.keysSize)) 220 space = append(space, `,"overflow":{"__ptr__":"`...) 221 overflowPtr := *(*uintptr)(unsafe.Pointer(uintptr(ptr) + encoder.bucketSize - 8)) 222 overflowPtrStr := ptrToStr(overflowPtr) 223 space = append(space, overflowPtrStr...) 224 space = append(space, `"}}`...) 225 if overflowPtr != 0 { 226 addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage) 227 overflow := encoder.encodeBucket(ctx, nil, unsafe.Pointer(overflowPtr)) 228 addrMap[overflowPtrStr] = json.RawMessage(overflow) 229 } 230 return space 231 }