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 name
This commit is contained in:
parent
9586f2acc7
commit
e7067be94f
@ -223,6 +223,8 @@ func geth(ctx *cli.Context) error {
|
|||||||
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
|
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
|
||||||
// miner.
|
// miner.
|
||||||
func startNode(ctx *cli.Context, stack *node.Node) {
|
func startNode(ctx *cli.Context, stack *node.Node) {
|
||||||
|
debug.Memsize.Add("node", stack)
|
||||||
|
|
||||||
// Start up the node itself
|
// Start up the node itself
|
||||||
utils.StartNode(stack)
|
utils.StartNode(stack)
|
||||||
|
|
||||||
|
@ -28,10 +28,13 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/log/term"
|
"github.com/ethereum/go-ethereum/log/term"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/metrics/exp"
|
"github.com/ethereum/go-ethereum/metrics/exp"
|
||||||
|
"github.com/fjl/memsize/memsizeui"
|
||||||
colorable "github.com/mattn/go-colorable"
|
colorable "github.com/mattn/go-colorable"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var Memsize memsizeui.Handler
|
||||||
|
|
||||||
var (
|
var (
|
||||||
verbosityFlag = cli.IntFlag{
|
verbosityFlag = cli.IntFlag{
|
||||||
Name: "verbosity",
|
Name: "verbosity",
|
||||||
@ -129,21 +132,25 @@ func Setup(ctx *cli.Context) error {
|
|||||||
|
|
||||||
// pprof server
|
// pprof server
|
||||||
if ctx.GlobalBool(pprofFlag.Name) {
|
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))
|
address := fmt.Sprintf("%s:%d", ctx.GlobalString(pprofAddrFlag.Name), ctx.GlobalInt(pprofPortFlag.Name))
|
||||||
go func() {
|
StartPProf(address)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
return nil
|
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
|
// Exit stops all running profiles, flushing their output to the
|
||||||
// respective file.
|
// respective file.
|
||||||
func Exit() {
|
func Exit() {
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/ethereum/go-ethereum/ethstats"
|
"github.com/ethereum/go-ethereum/ethstats"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/les"
|
"github.com/ethereum/go-ethereum/les"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
@ -72,6 +73,9 @@ type NodeConfig struct {
|
|||||||
|
|
||||||
// WhisperEnabled specifies whether the node should run the Whisper protocol.
|
// WhisperEnabled specifies whether the node should run the Whisper protocol.
|
||||||
WhisperEnabled bool
|
WhisperEnabled bool
|
||||||
|
|
||||||
|
// Listening address of pprof server.
|
||||||
|
PprofAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultNodeConfig contains the default node configuration values to use if all
|
// 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 {
|
if config.BootstrapNodes == nil || config.BootstrapNodes.Size() == 0 {
|
||||||
config.BootstrapNodes = defaultNodeConfig.BootstrapNodes
|
config.BootstrapNodes = defaultNodeConfig.BootstrapNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.PprofAddress != "" {
|
||||||
|
debug.StartPProf(config.PprofAddress)
|
||||||
|
}
|
||||||
|
|
||||||
// Create the empty networking stack
|
// Create the empty networking stack
|
||||||
nodeConf := &node.Config{
|
nodeConf := &node.Config{
|
||||||
Name: clientIdentifier,
|
Name: clientIdentifier,
|
||||||
@ -127,6 +136,8 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug.Memsize.Add("node", rawStack)
|
||||||
|
|
||||||
var genesis *core.Genesis
|
var genesis *core.Genesis
|
||||||
if config.EthereumGenesis != "" {
|
if config.EthereumGenesis != "" {
|
||||||
// Parse the user supplied genesis spec if not mainnet
|
// Parse the user supplied genesis spec if not mainnet
|
||||||
|
21
vendor/github.com/fjl/memsize/LICENSE
generated
vendored
Normal file
21
vendor/github.com/fjl/memsize/LICENSE
generated
vendored
Normal file
@ -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.
|
119
vendor/github.com/fjl/memsize/bitmap.go
generated
vendored
Normal file
119
vendor/github.com/fjl/memsize/bitmap.go
generated
vendored
Normal file
@ -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))
|
||||||
|
}
|
16
vendor/github.com/fjl/memsize/doc.go
generated
vendored
Normal file
16
vendor/github.com/fjl/memsize/doc.go
generated
vendored
Normal file
@ -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
|
243
vendor/github.com/fjl/memsize/memsize.go
generated
vendored
Normal file
243
vendor/github.com/fjl/memsize/memsize.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
106
vendor/github.com/fjl/memsize/memsizeui/template.go
generated
vendored
Normal file
106
vendor/github.com/fjl/memsize/memsizeui/template.go
generated
vendored
Normal file
@ -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>
|
||||||
|
`)
|
153
vendor/github.com/fjl/memsize/memsizeui/ui.go
generated
vendored
Normal file
153
vendor/github.com/fjl/memsize/memsizeui/ui.go
generated
vendored
Normal file
@ -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)
|
||||||
|
}
|
14
vendor/github.com/fjl/memsize/runtimefunc.go
generated
vendored
Normal file
14
vendor/github.com/fjl/memsize/runtimefunc.go
generated
vendored
Normal file
@ -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
|
1
vendor/github.com/fjl/memsize/runtimefunc.s
generated
vendored
Normal file
1
vendor/github.com/fjl/memsize/runtimefunc.s
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
// This file is required to make stub function declarations work.
|
119
vendor/github.com/fjl/memsize/type.go
generated
vendored
Normal file
119
vendor/github.com/fjl/memsize/type.go
generated
vendored
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
@ -110,6 +110,18 @@
|
|||||||
"revision": "5ec5d9d3c2cf82e9688b34e9bc27a94d616a7193",
|
"revision": "5ec5d9d3c2cf82e9688b34e9bc27a94d616a7193",
|
||||||
"revisionTime": "2017-02-09T08:00:14Z"
|
"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=",
|
"checksumSHA1": "0orwvPL96wFckVJyPl39fz2QsgA=",
|
||||||
"path": "github.com/gizak/termui",
|
"path": "github.com/gizak/termui",
|
||||||
|
Loading…
Reference in New Issue
Block a user