diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index d500726ce18a3f3ce3891660c212a981115ef0ca..09d9c493d1cd3aaecbca5b66d728504596afabb2 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 1f181bf8b0f08253a880b7e8649cfd174f19aa06..5eb58e9eef1cbdd57308a61596df3a6c3d3f5976 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 488a4150fcabcfb1c6e453f72b489273efe77d01..645b360eb3a9293468b4deff2bb8722807f43410 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 0000000000000000000000000000000000000000..8b8045641902eae40410f4859189f491abf96315
--- /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 0000000000000000000000000000000000000000..47799ea8d342cdcece4cc24da31fb5b67d9a787a
--- /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 0000000000000000000000000000000000000000..640cfba5ebf81421172a5c0d4dc8a8e269c5a9ac
--- /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 0000000000000000000000000000000000000000..2664e87c4604b5d81f2dda2d8551ad32c8b0093f
--- /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 0000000000000000000000000000000000000000..b60fe6ba549a755430fb5b2bedc48fb2f145d068
--- /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(`<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<title>memsize</title>
+		<style>
+		body {
+			 font-family: sans-serif;
+		}
+		button, .button {
+			 display: inline-block;
+			 font-weight: bold;
+			 color: black;
+			 text-decoration: none;
+			 font-size: inherit;
+			 padding: 3pt;
+			 margin: 3pt;
+			 background-color: #eee;
+			 border: 1px solid #999;
+			 border-radius: 2pt;
+		}
+		form.inline {
+			display: inline-block;
+		}
+		</style>
+	</head>
+	<body>
+		{{template "content" .}}
+	</body>
+</html>`))
+
+	base.Funcs(template.FuncMap{
+		"quote":     strconv.Quote,
+		"humansize": memsize.HumanSize,
+	})
+
+	template.Must(base.New("rootbuttons").Parse(`
+<a class="button" href="{{$.Link ""}}">Overview</a>
+{{- range $root := .Roots -}}
+<form class="inline" method="POST" action="{{$.Link "scan?root=" $root}}">
+	<button type="submit">Scan {{quote $root}}</button>
+</form>
+{{- end -}}`))
+}
+
+func contentTemplate(source string) *template.Template {
+	baseInitOnce.Do(baseInit)
+	t := template.Must(base.Clone())
+	template.Must(t.New("content").Parse(source))
+	return t
+}
+
+var rootTemplate = contentTemplate(`
+<h1>Memsize</h1>
+{{template "rootbuttons" .}}
+<hr/>
+<h3>Reports</h3>
+<ul>
+	{{range .Reports}}
+		<li><a href="{{printf "%d" | $.Link "report/"}}">{{quote .RootName}} @ {{.Date}}</a></li>
+	{{else}}
+		No reports yet, hit a scan button to create one.
+	{{end}}
+</ul>
+`)
+
+var notFoundTemplate = contentTemplate(`
+<h1>{{.Data}}</h1>
+{{template "rootbuttons" .}}
+`)
+
+var reportTemplate = contentTemplate(`
+{{- $report := .Data -}}
+<h1>Memsize Report {{$report.ID}}</h1>
+<form method="POST" action="{{$.Link "scan?root=" $report.RootName}}">
+	<a class="button" href="{{$.Link ""}}">Overview</a>
+	<button type="submit">Scan Again</button>
+</form>
+<pre>
+Root: {{quote $report.RootName}}
+Date: {{$report.Date}}
+Duration: {{$report.Duration}}
+Bitmap Size: {{$report.Sizes.BitmapSize | humansize}}
+Bitmap Utilization: {{$report.Sizes.BitmapUtilization}}
+</pre>
+<hr/>
+<pre>
+{{$report.Sizes.Report}}
+</pre>
+`)
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 0000000000000000000000000000000000000000..c48fc53f7f6c826df9dbf7686f6363a0bc3228c2
--- /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 0000000000000000000000000000000000000000..912a3e768d4de5abdb9356ba9a16ce64727c7c77
--- /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 0000000000000000000000000000000000000000..a091e2fa72436bd674e0400eac6e1c61dde04b5e
--- /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 0000000000000000000000000000000000000000..5d6f59e9ff2ebf01c7c55819910d58b7d882b51e
--- /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 5e7ce7e0388e59a5f187107274747f61e8a0d8de..e083a363bab2eae8ec87cd3c38445dc00845214b 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",