forked from mirror/go-ethereum
cmd/geth, mobile: add memsize to pprof server (#16532)
* cmd/geth, mobile: add memsize to pprof server This is a temporary change, to be reverted before the next release. * cmd/geth: fix variable namerelease/1.8
parent
9586f2acc7
commit
e7067be94f
@ -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. |
@ -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)) |
||||
} |
@ -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 |
@ -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 |
||||
} |
@ -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> |
||||
`) |
@ -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) |
||||
} |
@ -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 |
@ -0,0 +1 @@ |
||||
// This file is required to make stub function declarations work. |
@ -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) |
||||
} |
||||
} |
Loading…
Reference in new issue