Skip to content

Commit

Permalink
Add freespace command to list free regions of the heap.
Browse files Browse the repository at this point in the history
Useful for debugging possible fragmentation issues.
  • Loading branch information
randall77 committed Dec 7, 2017
1 parent 2e3f4fc commit 8273ee2
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 0 deletions.
50 changes: 50 additions & 0 deletions gocore/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,53 @@ func edges1(p *Process, r *Root, off int64, t *Type, fn func(int64, Object, int6
}
return true
}

// ForEachFreeRegion calls fn with each free region in the Go heap.
// It calls fn with:
// the address of the start of the free region
// the size of the free region
// If fn returns false, ForEachFreeRegion returns immediately.
// A free region is one which could satisfy an allocation of the reported size or less.
// Free regions do not include garbage objects (those which aren't reachable, but
// haven't been noticed as unreachable yet by the runtime).
func (p *Process) ForEachFreeRegion(fn func(core.Address, int64) bool) {
mheap := p.rtGlobals["mheap_"]
allspans := mheap.Field("allspans")
n := allspans.SliceLen()
pageSize := p.rtConstants["_PageSize"]
spanInUse := uint8(p.rtConstants["_MSpanInUse"])
spanFree := uint8(p.rtConstants["_MSpanFree"])

for i := int64(0); i < n; i++ {
s := allspans.SliceIndex(i).Deref()
min := core.Address(s.Field("startAddr").Uintptr())
nPages := int64(s.Field("npages").Uintptr())
size := nPages * pageSize
elemSize := int64(s.Field("elemsize").Uintptr())
switch s.Field("state").Cast("uint8").Uint8() {
case spanFree:
if !fn(min, size) {
return
}
case spanInUse:
n := int64(s.Field("nelems").Uintptr())
alloc := make([]bool, n)
for i := int64(0); i < n; i++ {
alloc[i] = p.proc.ReadUint8(min.Add(i/8))>>uint(i%8)&1 != 0
}
k := int64(s.Field("freeindex").Uintptr())
for i := int64(0); i < k; i++ {
alloc[i] = true
}
for i := int64(0); i < n; i++ {
if alloc[i] {
continue
}
if !fn(min.Add(i*elemSize), elemSize) {
return
}
}

}
}
}
27 changes: 27 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ The commands are:
goroutines: list goroutines
histogram: print histogram of heap memory use by Go type
breakdown: print memory use by class
freespace: print free regions of heap by size
objects: print a list of all live objects
objgraph: dump object graph to a .dot file
reachable: find path from root to an object
Expand Down Expand Up @@ -75,6 +76,7 @@ func main() {
case "histogram":
flags = gocore.FlagTypes
case "breakdown":
case "freespace":
case "objgraph":
flags = gocore.FlagTypes
case "objects":
Expand Down Expand Up @@ -223,6 +225,31 @@ func main() {
printStat(c.Stats(), "")
t.Flush()

case "freespace":
// hist is a map from size to # of slots of that size
hist := map[int64]int64{}
c.ForEachFreeRegion(func(a core.Address, size int64) bool {
hist[size]++
return true
})
type entry struct {
size int64
count int64
}
entries := make([]entry, 0, len(hist))
for size, count := range hist {
entries = append(entries, entry{size: size, count: count})
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].size > entries[j].size
})
t := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.AlignRight)
fmt.Fprintf(t, "size\tcount\ttotal\t\n")
for _, e := range entries {
fmt.Fprintf(t, "%d\t%d\t%d\t\n", e.size, e.count, e.size*e.count)
}
t.Flush()

case "objgraph":
// Dump object graph to output file.
w, err := os.Create("tmp.dot")
Expand Down

0 comments on commit 8273ee2

Please sign in to comment.