diff --git a/cmd/geth/main.go b/cmd/geth/main.go index d500726ce1..09d9c493d1 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -223,6 +223,8 @@ func geth(ctx *cli.Context) error { // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the // miner. func startNode(ctx *cli.Context, stack *node.Node) { + debug.Memsize.Add("node", stack) + // Start up the node itself utils.StartNode(stack) diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 1f181bf8b0..5eb58e9eef 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -28,10 +28,13 @@ import ( "github.com/ethereum/go-ethereum/log/term" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/exp" + "github.com/fjl/memsize/memsizeui" colorable "github.com/mattn/go-colorable" "gopkg.in/urfave/cli.v1" ) +var Memsize memsizeui.Handler + var ( verbosityFlag = cli.IntFlag{ Name: "verbosity", @@ -129,21 +132,25 @@ func Setup(ctx *cli.Context) error { // pprof server if ctx.GlobalBool(pprofFlag.Name) { - // Hook go-metrics into expvar on any /debug/metrics request, load all vars - // from the registry into expvar, and execute regular expvar handler. - exp.Exp(metrics.DefaultRegistry) - address := fmt.Sprintf("%s:%d", ctx.GlobalString(pprofAddrFlag.Name), ctx.GlobalInt(pprofPortFlag.Name)) - go func() { - log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) - if err := http.ListenAndServe(address, nil); err != nil { - log.Error("Failure in running pprof server", "err", err) - } - }() + StartPProf(address) } return nil } +func StartPProf(address string) { + // Hook go-metrics into expvar on any /debug/metrics request, load all vars + // from the registry into expvar, and execute regular expvar handler. + exp.Exp(metrics.DefaultRegistry) + http.Handle("/memsize/", http.StripPrefix("/memsize", &Memsize)) + log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) + go func() { + if err := http.ListenAndServe(address, nil); err != nil { + log.Error("Failure in running pprof server", "err", err) + } + }() +} + // Exit stops all running profiles, flushing their output to the // respective file. func Exit() { diff --git a/mobile/geth.go b/mobile/geth.go index 488a4150fc..645b360eb3 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethstats" + "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" @@ -72,6 +73,9 @@ type NodeConfig struct { // WhisperEnabled specifies whether the node should run the Whisper protocol. WhisperEnabled bool + + // Listening address of pprof server. + PprofAddress string } // defaultNodeConfig contains the default node configuration values to use if all @@ -107,6 +111,11 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { if config.BootstrapNodes == nil || config.BootstrapNodes.Size() == 0 { config.BootstrapNodes = defaultNodeConfig.BootstrapNodes } + + if config.PprofAddress != "" { + debug.StartPProf(config.PprofAddress) + } + // Create the empty networking stack nodeConf := &node.Config{ Name: clientIdentifier, @@ -127,6 +136,8 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { return nil, err } + debug.Memsize.Add("node", rawStack) + var genesis *core.Genesis if config.EthereumGenesis != "" { // Parse the user supplied genesis spec if not mainnet diff --git a/vendor/github.com/fjl/memsize/LICENSE b/vendor/github.com/fjl/memsize/LICENSE new file mode 100644 index 0000000000..8b80456419 --- /dev/null +++ b/vendor/github.com/fjl/memsize/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Felix Lange + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/fjl/memsize/bitmap.go b/vendor/github.com/fjl/memsize/bitmap.go new file mode 100644 index 0000000000..47799ea8d3 --- /dev/null +++ b/vendor/github.com/fjl/memsize/bitmap.go @@ -0,0 +1,119 @@ +package memsize + +import ( + "math/bits" +) + +const ( + uintptrBits = 32 << (uint64(^uintptr(0)) >> 63) + uintptrBytes = uintptrBits / 8 + bmBlockRange = 1 * 1024 * 1024 // bytes covered by bmBlock + bmBlockWords = bmBlockRange / uintptrBits +) + +// bitmap is a sparse bitmap. +type bitmap struct { + blocks map[uintptr]*bmBlock +} + +func newBitmap() *bitmap { + return &bitmap{make(map[uintptr]*bmBlock)} +} + +// markRange sets n consecutive bits starting at addr. +func (b *bitmap) markRange(addr, n uintptr) { + for end := addr + n; addr < end; { + block, baddr := b.block(addr) + for i := baddr; i < bmBlockRange && addr < end; i++ { + block.mark(i) + addr++ + } + } +} + +// isMarked returns the value of the bit at the given address. +func (b *bitmap) isMarked(addr uintptr) bool { + block, baddr := b.block(addr) + return block.isMarked(baddr) +} + +// countRange returns the number of set bits in the range (addr,addr+n). +func (b *bitmap) countRange(addr, n uintptr) uintptr { + c := uintptr(0) + for end := addr + n; addr < end; { + block, baddr := b.block(addr) + bend := uintptr(bmBlockRange - 1) + if baddr+(end-addr) < bmBlockRange { + bend = baddr + (end - addr) + } + c += uintptr(block.count(baddr, bend)) + // Move addr to next block. + addr += bmBlockRange - baddr + } + return c +} + +// block finds the block corresponding to the given memory address. +// It also returns the block's starting address. +func (b *bitmap) block(addr uintptr) (*bmBlock, uintptr) { + index := addr / bmBlockRange + block := b.blocks[index] + if block == nil { + block = new(bmBlock) + b.blocks[index] = block + } + return block, addr % bmBlockRange +} + +// size returns the sum of the byte sizes of all blocks. +func (b *bitmap) size() uintptr { + return uintptr(len(b.blocks)) * bmBlockWords * uintptrBytes +} + +// utilization returns the mean percentage of one bits across all blocks. +func (b *bitmap) utilization() float32 { + var avg float32 + for _, block := range b.blocks { + avg += float32(block.count(0, bmBlockRange-1)) / float32(bmBlockRange) + } + return avg / float32(len(b.blocks)) +} + +// bmBlock is a bitmap block. +type bmBlock [bmBlockWords]uintptr + +// mark sets the i'th bit to one. +func (b *bmBlock) mark(i uintptr) { + b[i/uintptrBits] |= 1 << (i % uintptrBits) +} + +// isMarked returns the value of the i'th bit. +func (b *bmBlock) isMarked(i uintptr) bool { + return (b[i/uintptrBits] & (1 << (i % uintptrBits))) != 0 +} + +// count returns the number of set bits in the range (start,end). +func (b *bmBlock) count(start, end uintptr) (count int) { + br := b[start/uintptrBits : end/uintptrBits+1] + for i, w := range br { + if i == 0 { + w &= blockmask(start) + } + if i == len(br)-1 { + w &^= blockmask(end) + } + count += onesCountPtr(w) + } + return count +} + +func blockmask(x uintptr) uintptr { + return ^uintptr(0) << (x % uintptrBits) +} + +func onesCountPtr(x uintptr) int { + if uintptrBits == 64 { + return bits.OnesCount64(uint64(x)) + } + return bits.OnesCount32(uint32(x)) +} diff --git a/vendor/github.com/fjl/memsize/doc.go b/vendor/github.com/fjl/memsize/doc.go new file mode 100644 index 0000000000..640cfba5eb --- /dev/null +++ b/vendor/github.com/fjl/memsize/doc.go @@ -0,0 +1,16 @@ +/* +Package memsize computes the size of your object graph. + +So you made a spiffy algorithm and it works really well, but geez it's using +way too much memory. Where did it all go? memsize to the rescue! + +To get started, find a value that references all your objects and scan it. +This traverses the graph, counting sizes per type. + + sizes := memsize.Scan(myValue) + fmt.Println(sizes.Total) + +memsize can handle cycles just fine and tracks both private and public struct fields. +Unfortunately function closures cannot be inspected in any way. +*/ +package memsize diff --git a/vendor/github.com/fjl/memsize/memsize.go b/vendor/github.com/fjl/memsize/memsize.go new file mode 100644 index 0000000000..2664e87c46 --- /dev/null +++ b/vendor/github.com/fjl/memsize/memsize.go @@ -0,0 +1,243 @@ +package memsize + +import ( + "bytes" + "fmt" + "reflect" + "sort" + "strings" + "text/tabwriter" + "unsafe" +) + +// Scan traverses all objects reachable from v and counts how much memory +// is used per type. The value must be a non-nil pointer to any value. +func Scan(v interface{}) Sizes { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + panic("value to scan must be non-nil pointer") + } + + stopTheWorld("memsize scan") + defer startTheWorld() + + ctx := newContext() + ctx.scan(invalidAddr, rv, false) + ctx.s.BitmapSize = ctx.seen.size() + ctx.s.BitmapUtilization = ctx.seen.utilization() + return *ctx.s +} + +// Sizes is the result of a scan. +type Sizes struct { + Total uintptr + ByType map[reflect.Type]*TypeSize + // Internal stats (for debugging) + BitmapSize uintptr + BitmapUtilization float32 +} + +type TypeSize struct { + Total uintptr + Count uintptr +} + +func newSizes() *Sizes { + return &Sizes{ByType: make(map[reflect.Type]*TypeSize)} +} + +// Report returns a human-readable report. +func (s Sizes) Report() string { + type typLine struct { + name string + count uintptr + total uintptr + } + tab := []typLine{{"ALL", 0, s.Total}} + for _, typ := range s.ByType { + tab[0].count += typ.Count + } + maxname := 0 + for typ, s := range s.ByType { + line := typLine{typ.String(), s.Count, s.Total} + tab = append(tab, line) + if len(line.name) > maxname { + maxname = len(line.name) + } + } + sort.Slice(tab, func(i, j int) bool { return tab[i].total > tab[j].total }) + + buf := new(bytes.Buffer) + w := tabwriter.NewWriter(buf, 0, 0, 0, ' ', tabwriter.AlignRight) + for _, line := range tab { + namespace := strings.Repeat(" ", maxname-len(line.name)) + fmt.Fprintf(w, "%s%s\t %v\t %s\t\n", line.name, namespace, line.count, HumanSize(line.total)) + } + w.Flush() + return buf.String() +} + +// addValue is called during scan and adds the memory of given object. +func (s *Sizes) addValue(v reflect.Value, size uintptr) { + s.Total += size + rs := s.ByType[v.Type()] + if rs == nil { + rs = new(TypeSize) + s.ByType[v.Type()] = rs + } + rs.Total += size + rs.Count++ +} + +type context struct { + // We track previously scanned objects to prevent infinite loops + // when scanning cycles and to prevent counting objects more than once. + seen *bitmap + tc typCache + s *Sizes +} + +func newContext() *context { + return &context{seen: newBitmap(), tc: make(typCache), s: newSizes()} +} + +// scan walks all objects below v, determining their size. All scan* functions return the +// amount of 'extra' memory (e.g. slice data) that is referenced by the object. +func (c *context) scan(addr address, v reflect.Value, add bool) (extraSize uintptr) { + size := v.Type().Size() + var marked uintptr + if addr.valid() { + marked = c.seen.countRange(uintptr(addr), size) + if marked == size { + return 0 // Skip if we have already seen the whole object. + } + c.seen.markRange(uintptr(addr), size) + } + // fmt.Printf("%v: %v ⮑ (marked %d)\n", addr, v.Type(), marked) + if c.tc.needScan(v.Type()) { + extraSize = c.scanContent(addr, v) + } + // fmt.Printf("%v: %v %d (add %v, size %d, marked %d, extra %d)\n", addr, v.Type(), size+extraSize, add, v.Type().Size(), marked, extraSize) + if add { + size -= marked + size += extraSize + c.s.addValue(v, size) + } + return extraSize +} + +func (c *context) scanContent(addr address, v reflect.Value) uintptr { + switch v.Kind() { + case reflect.Array: + return c.scanArray(addr, v) + case reflect.Chan: + return c.scanChan(v) + case reflect.Func: + // can't do anything here + return 0 + case reflect.Interface: + return c.scanInterface(v) + case reflect.Map: + return c.scanMap(v) + case reflect.Ptr: + if !v.IsNil() { + c.scan(address(v.Pointer()), v.Elem(), true) + } + return 0 + case reflect.Slice: + return c.scanSlice(v) + case reflect.String: + return uintptr(v.Len()) + case reflect.Struct: + return c.scanStruct(addr, v) + default: + unhandledKind(v.Kind()) + return 0 + } +} + +func (c *context) scanChan(v reflect.Value) uintptr { + etyp := v.Type().Elem() + extra := uintptr(0) + if c.tc.needScan(etyp) { + // Scan the channel buffer. This is unsafe but doesn't race because + // the world is stopped during scan. + hchan := unsafe.Pointer(v.Pointer()) + for i := uint(0); i < uint(v.Cap()); i++ { + addr := chanbuf(hchan, i) + elem := reflect.NewAt(etyp, addr).Elem() + extra += c.scanContent(address(addr), elem) + } + } + return uintptr(v.Cap())*etyp.Size() + extra +} + +func (c *context) scanStruct(base address, v reflect.Value) uintptr { + extra := uintptr(0) + for i := 0; i < v.NumField(); i++ { + f := v.Type().Field(i) + if c.tc.needScan(f.Type) { + addr := base.addOffset(f.Offset) + extra += c.scanContent(addr, v.Field(i)) + } + } + return extra +} + +func (c *context) scanArray(addr address, v reflect.Value) uintptr { + esize := v.Type().Elem().Size() + extra := uintptr(0) + for i := 0; i < v.Len(); i++ { + extra += c.scanContent(addr, v.Index(i)) + addr = addr.addOffset(esize) + } + return extra +} + +func (c *context) scanSlice(v reflect.Value) uintptr { + slice := v.Slice(0, v.Cap()) + esize := slice.Type().Elem().Size() + base := slice.Pointer() + // Add size of the unscanned portion of the backing array to extra. + blen := uintptr(slice.Len()) * esize + marked := c.seen.countRange(base, blen) + extra := blen - marked + c.seen.markRange(uintptr(base), blen) + if c.tc.needScan(slice.Type().Elem()) { + // Elements may contain pointers, scan them individually. + addr := address(base) + for i := 0; i < slice.Len(); i++ { + extra += c.scanContent(addr, slice.Index(i)) + addr = addr.addOffset(esize) + } + } + return extra +} + +func (c *context) scanMap(v reflect.Value) uintptr { + var ( + typ = v.Type() + len = uintptr(v.Len()) + extra = uintptr(0) + ) + if c.tc.needScan(typ.Key()) || c.tc.needScan(typ.Elem()) { + for _, k := range v.MapKeys() { + extra += c.scan(invalidAddr, k, false) + extra += c.scan(invalidAddr, v.MapIndex(k), false) + } + } + return len*typ.Key().Size() + len*typ.Elem().Size() + extra +} + +func (c *context) scanInterface(v reflect.Value) uintptr { + elem := v.Elem() + if !elem.IsValid() { + return 0 // nil interface + } + c.scan(invalidAddr, elem, false) + if !c.tc.isPointer(elem.Type()) { + // Account for non-pointer size of the value. + return elem.Type().Size() + } + return 0 +} diff --git a/vendor/github.com/fjl/memsize/memsizeui/template.go b/vendor/github.com/fjl/memsize/memsizeui/template.go new file mode 100644 index 0000000000..b60fe6ba54 --- /dev/null +++ b/vendor/github.com/fjl/memsize/memsizeui/template.go @@ -0,0 +1,106 @@ +package memsizeui + +import ( + "html/template" + "strconv" + "sync" + + "github.com/fjl/memsize" +) + +var ( + base *template.Template // the "base" template + baseInitOnce sync.Once +) + +func baseInit() { + base = template.Must(template.New("base").Parse(` + +
+ +
+Root: {{quote $report.RootName}}
+Date: {{$report.Date}}
+Duration: {{$report.Duration}}
+Bitmap Size: {{$report.Sizes.BitmapSize | humansize}}
+Bitmap Utilization: {{$report.Sizes.BitmapUtilization}}
+
+
+{{$report.Sizes.Report}}
+
+`)
diff --git a/vendor/github.com/fjl/memsize/memsizeui/ui.go b/vendor/github.com/fjl/memsize/memsizeui/ui.go
new file mode 100644
index 0000000000..c48fc53f7f
--- /dev/null
+++ b/vendor/github.com/fjl/memsize/memsizeui/ui.go
@@ -0,0 +1,153 @@
+package memsizeui
+
+import (
+ "bytes"
+ "fmt"
+ "html/template"
+ "net/http"
+ "reflect"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/fjl/memsize"
+)
+
+type Handler struct {
+ init sync.Once
+ mux http.ServeMux
+ mu sync.Mutex
+ reports map[int]Report
+ roots map[string]interface{}
+ reportID int
+}
+
+type Report struct {
+ ID int
+ Date time.Time
+ Duration time.Duration
+ RootName string
+ Sizes memsize.Sizes
+}
+
+type templateInfo struct {
+ Roots []string
+ Reports map[int]Report
+ PathDepth int
+ Data interface{}
+}
+
+func (ti *templateInfo) Link(path ...string) string {
+ prefix := strings.Repeat("../", ti.PathDepth)
+ return prefix + strings.Join(path, "")
+}
+
+func (h *Handler) Add(name string, v interface{}) {
+ rv := reflect.ValueOf(v)
+ if rv.Kind() != reflect.Ptr || rv.IsNil() {
+ panic("root must be non-nil pointer")
+ }
+ h.mu.Lock()
+ if h.roots == nil {
+ h.roots = make(map[string]interface{})
+ }
+ h.roots[name] = v
+ h.mu.Unlock()
+}
+
+func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ h.init.Do(func() {
+ h.reports = make(map[int]Report)
+ h.mux.HandleFunc("/", h.handleRoot)
+ h.mux.HandleFunc("/scan", h.handleScan)
+ h.mux.HandleFunc("/report/", h.handleReport)
+ })
+ h.mux.ServeHTTP(w, r)
+}
+
+func (h *Handler) templateInfo(r *http.Request, data interface{}) *templateInfo {
+ h.mu.Lock()
+ roots := make([]string, 0, len(h.roots))
+ for name := range h.roots {
+ roots = append(roots, name)
+ }
+ h.mu.Unlock()
+ sort.Strings(roots)
+
+ return &templateInfo{
+ Roots: roots,
+ Reports: h.reports,
+ PathDepth: strings.Count(r.URL.Path, "/") - 1,
+ Data: data,
+ }
+}
+
+func (h *Handler) handleRoot(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.NotFound(w, r)
+ return
+ }
+ serveHTML(w, rootTemplate, http.StatusOK, h.templateInfo(r, nil))
+}
+
+func (h *Handler) handleScan(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "invalid HTTP method, want POST", http.StatusMethodNotAllowed)
+ return
+ }
+ ti := h.templateInfo(r, "Unknown root")
+ id, ok := h.scan(r.URL.Query().Get("root"))
+ if !ok {
+ serveHTML(w, notFoundTemplate, http.StatusNotFound, ti)
+ return
+ }
+ w.Header().Add("Location", ti.Link(fmt.Sprintf("report/%d", id)))
+ w.WriteHeader(http.StatusSeeOther)
+}
+
+func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) {
+ var id int
+ fmt.Sscan(strings.TrimPrefix(r.URL.Path, "/report/"), &id)
+ h.mu.Lock()
+ report, ok := h.reports[id]
+ h.mu.Unlock()
+
+ if !ok {
+ serveHTML(w, notFoundTemplate, http.StatusNotFound, h.templateInfo(r, "Report not found"))
+ } else {
+ serveHTML(w, reportTemplate, http.StatusOK, h.templateInfo(r, report))
+ }
+}
+
+func (h *Handler) scan(root string) (int, bool) {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ val, ok := h.roots[root]
+ if !ok {
+ return 0, false
+ }
+ id := h.reportID
+ start := time.Now()
+ sizes := memsize.Scan(val)
+ h.reports[id] = Report{
+ ID: id,
+ RootName: root,
+ Date: start.Truncate(1 * time.Second),
+ Duration: time.Since(start),
+ Sizes: sizes,
+ }
+ h.reportID++
+ return id, true
+}
+
+func serveHTML(w http.ResponseWriter, tpl *template.Template, status int, ti *templateInfo) {
+ w.Header().Set("content-type", "text/html")
+ var buf bytes.Buffer
+ if err := tpl.Execute(&buf, ti); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ buf.WriteTo(w)
+}
diff --git a/vendor/github.com/fjl/memsize/runtimefunc.go b/vendor/github.com/fjl/memsize/runtimefunc.go
new file mode 100644
index 0000000000..912a3e768d
--- /dev/null
+++ b/vendor/github.com/fjl/memsize/runtimefunc.go
@@ -0,0 +1,14 @@
+package memsize
+
+import "unsafe"
+
+var _ = unsafe.Pointer(nil)
+
+//go:linkname stopTheWorld runtime.stopTheWorld
+func stopTheWorld(reason string)
+
+//go:linkname startTheWorld runtime.startTheWorld
+func startTheWorld()
+
+//go:linkname chanbuf runtime.chanbuf
+func chanbuf(ch unsafe.Pointer, i uint) unsafe.Pointer
diff --git a/vendor/github.com/fjl/memsize/runtimefunc.s b/vendor/github.com/fjl/memsize/runtimefunc.s
new file mode 100644
index 0000000000..a091e2fa72
--- /dev/null
+++ b/vendor/github.com/fjl/memsize/runtimefunc.s
@@ -0,0 +1 @@
+// This file is required to make stub function declarations work.
diff --git a/vendor/github.com/fjl/memsize/type.go b/vendor/github.com/fjl/memsize/type.go
new file mode 100644
index 0000000000..5d6f59e9ff
--- /dev/null
+++ b/vendor/github.com/fjl/memsize/type.go
@@ -0,0 +1,119 @@
+package memsize
+
+import (
+ "fmt"
+ "reflect"
+)
+
+// address is a memory location.
+//
+// Code dealing with uintptr is oblivious to the zero address.
+// Code dealing with address is not: it treats the zero address
+// as invalid. Offsetting an invalid address doesn't do anything.
+//
+// This distinction is useful because there are objects that we can't
+// get the pointer to.
+type address uintptr
+
+const invalidAddr = address(0)
+
+func (a address) valid() bool {
+ return a != 0
+}
+
+func (a address) addOffset(off uintptr) address {
+ if !a.valid() {
+ return invalidAddr
+ }
+ return a + address(off)
+}
+
+func (a address) String() string {
+ if uintptrBits == 32 {
+ return fmt.Sprintf("%#0.8x", uintptr(a))
+ }
+ return fmt.Sprintf("%#0.16x", uintptr(a))
+}
+
+type typCache map[reflect.Type]typInfo
+
+type typInfo struct {
+ isPointer bool
+ needScan bool
+}
+
+// isPointer returns true for pointer-ish values. The notion of
+// pointer includes everything but plain values, i.e. slices, maps
+// channels, interfaces are 'pointer', too.
+func (tc *typCache) isPointer(typ reflect.Type) bool {
+ return tc.info(typ).isPointer
+}
+
+// needScan reports whether a value of the type needs to be scanned
+// recursively because it may contain pointers.
+func (tc *typCache) needScan(typ reflect.Type) bool {
+ return tc.info(typ).needScan
+}
+
+func (tc *typCache) info(typ reflect.Type) typInfo {
+ info, found := (*tc)[typ]
+ switch {
+ case found:
+ return info
+ case isPointer(typ):
+ info = typInfo{true, true}
+ default:
+ info = typInfo{false, tc.checkNeedScan(typ)}
+ }
+ (*tc)[typ] = info
+ return info
+}
+
+func (tc *typCache) checkNeedScan(typ reflect.Type) bool {
+ switch k := typ.Kind(); k {
+ case reflect.Struct:
+ // Structs don't need scan if none of their fields need it.
+ for i := 0; i < typ.NumField(); i++ {
+ if tc.needScan(typ.Field(i).Type) {
+ return true
+ }
+ }
+ case reflect.Array:
+ // Arrays don't need scan if their element type doesn't.
+ return tc.needScan(typ.Elem())
+ }
+ return false
+}
+
+func isPointer(typ reflect.Type) bool {
+ k := typ.Kind()
+ switch {
+ case k <= reflect.Complex128:
+ return false
+ case k == reflect.Array:
+ return false
+ case k >= reflect.Chan && k <= reflect.String:
+ return true
+ case k == reflect.Struct || k == reflect.UnsafePointer:
+ return false
+ default:
+ unhandledKind(k)
+ return false
+ }
+}
+
+func unhandledKind(k reflect.Kind) {
+ panic("unhandled kind " + k.String())
+}
+
+// HumanSize formats the given number of bytes as a readable string.
+func HumanSize(bytes uintptr) string {
+ switch {
+ case bytes < 1024:
+ return fmt.Sprintf("%d B", bytes)
+ case bytes < 1024*1024:
+ return fmt.Sprintf("%.3f KB", float64(bytes)/1024)
+ default:
+ return fmt.Sprintf("%.3f MB", float64(bytes)/1024/1024)
+ }
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 5e7ce7e038..e083a363ba 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -110,6 +110,18 @@
"revision": "5ec5d9d3c2cf82e9688b34e9bc27a94d616a7193",
"revisionTime": "2017-02-09T08:00:14Z"
},
+ {
+ "checksumSHA1": "Jq1rrHSGPfh689nA2hL1QVb62zE=",
+ "path": "github.com/fjl/memsize",
+ "revision": "ca190fb6ffbc076ff49197b7168a760f30182d2e",
+ "revisionTime": "2018-04-18T12:24:29Z"
+ },
+ {
+ "checksumSHA1": "Z13QAYTqeW4cTiglkc2F05gWLu4=",
+ "path": "github.com/fjl/memsize/memsizeui",
+ "revision": "ca190fb6ffbc076ff49197b7168a760f30182d2e",
+ "revisionTime": "2018-04-18T12:24:29Z"
+ },
{
"checksumSHA1": "0orwvPL96wFckVJyPl39fz2QsgA=",
"path": "github.com/gizak/termui",