-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a44851c
Showing
12 changed files
with
554 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
name: Lint | ||
on: | ||
push: | ||
pull_request: | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
golangci: | ||
name: lint | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-go@v4 | ||
with: | ||
go-version: '1.21' | ||
cache: false | ||
- name: golangci-lint | ||
uses: golangci/golangci-lint-action@v3 | ||
with: | ||
version: v1.54 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
heapdump* | ||
heapview* | ||
dist/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# heapview | ||
|
||
A tiny, experimental heap dump viewer for Go heap dumps. (for heap dumps produced by `debug.WriteHeapDump()`) | ||
|
||
Tested on Go 1.21.0. | ||
|
||
## Installation | ||
|
||
The easiest way to get started is to install `heapview` by downloading the [releases](https://github.com/burntcarrot/heapview/releases). | ||
|
||
## Usage | ||
|
||
```sh | ||
heapview -file=<heapdump_path> | ||
``` | ||
|
||
On running `heapview`, the server would serve the HTML view at `localhost:8080`: | ||
|
||
![Records View](./static/records-view.png) | ||
|
||
Graph view: | ||
|
||
![Graph View](./static/graph-view.png) | ||
|
||
## Future work | ||
|
||
`heapview` is a small tool, but can be improved with the following features: | ||
|
||
- a good, responsive Object Graph viewer, which could redirect to the record on interactions with the nodes | ||
- a way to extract type information from the heap dumps | ||
- an easier way to be in sync with the Go runtime changes | ||
|
||
If you'd like to contribute to the following, please consider raising a pull request! | ||
|
||
## Acknowledgements | ||
|
||
- https://github.com/golang/go/wiki/heapdump15-through-heapdump17, which documents the current Go heap dump format. (and was the main reference while I was building [heaputil](https://github.com/burntcarrot/heaputil)) | ||
- https://github.com/golang/go/issues/16410, the Go heap dump viewer proposal | ||
- https://github.com/adamroach/heapspurs, which aims to provide a set of utilities to play around with the Go heap dump. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/burntcarrot/heapview | ||
|
||
go 1.21.0 | ||
|
||
require github.com/burntcarrot/heaputil v0.0.0-20230927162808-497024fb706a |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
github.com/burntcarrot/heaputil v0.0.0-20230927162808-497024fb706a h1:w1S7K4+qL19+czjhyWrgyc8QmaseY1f3mkP4YPcAdFM= | ||
github.com/burntcarrot/heaputil v0.0.0-20230927162808-497024fb706a/go.mod h1:LwbcObA3AsK5SN1Bet7dQjOkxuKqJ/B0iM0Qn6VdxPk= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
project_name: heapview | ||
|
||
builds: | ||
- id: "heapview" | ||
binary: heapview | ||
goos: | ||
- linux | ||
- darwin | ||
- windows | ||
- openbsd | ||
goarch: | ||
- amd64 | ||
- arm64 | ||
mod_timestamp: '{{ .CommitTimestamp }}' | ||
env: | ||
- CGO_ENABLED=0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"html/template" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/burntcarrot/heaputil" | ||
"github.com/burntcarrot/heaputil/record" | ||
) | ||
|
||
type templateData struct { | ||
RecordTypes []RecordInfo | ||
Records []heaputil.RecordData | ||
GraphVizContent string | ||
} | ||
|
||
func GenerateHTML(records []heaputil.RecordData, graphContent string) (string, error) { | ||
tmpl, err := template.ParseFiles("index.html") | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
data := templateData{ | ||
RecordTypes: GetUniqueRecordTypes(records), | ||
Records: records, | ||
GraphVizContent: graphContent, | ||
} | ||
|
||
var htmlBuilder strings.Builder | ||
err = tmpl.Execute(&htmlBuilder, data) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return htmlBuilder.String(), nil | ||
} | ||
|
||
func GenerateGraph(rd *bufio.Reader) (string, error) { | ||
err := record.ReadHeader(rd) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
var dotContent strings.Builder | ||
|
||
// Write DOT file header | ||
dotContent.WriteString("digraph GoHeapDump {\n") | ||
|
||
// Create the "heap" node as a cluster | ||
dotContent.WriteString(" subgraph cluster_heap {\n") | ||
dotContent.WriteString(" label=\"Heap\";\n") | ||
dotContent.WriteString(" style=dotted;\n") | ||
|
||
var dumpParams *record.DumpParamsRecord | ||
counter := 0 | ||
|
||
for { | ||
r, err := record.ReadRecord(rd) | ||
if err != nil { | ||
return dotContent.String(), err | ||
} | ||
|
||
_, isEOF := r.(*record.EOFRecord) | ||
if isEOF { | ||
break | ||
} | ||
|
||
dp, isDumpParams := r.(*record.DumpParamsRecord) | ||
if isDumpParams { | ||
dumpParams = dp | ||
} | ||
|
||
// Filter out objects. If the record isn't of the type Object, ignore. | ||
_, isObj := r.(*record.ObjectRecord) | ||
if !isObj { | ||
continue | ||
} | ||
|
||
// Create a DOT node for each record | ||
nodeName := fmt.Sprintf("Node%d", counter) | ||
counter++ | ||
name, address := ParseNameAndAddress(r.Repr()) | ||
nodeLabel := fmt.Sprintf("[%s] %s", name, address) | ||
|
||
// Write DOT node entry within the "heap" cluster | ||
s := fmt.Sprintf(" %s [label=\"%s\"];\n", nodeName, nodeLabel) | ||
dotContent.WriteString(s) | ||
|
||
// Check if the record has pointers | ||
p, isParent := r.(record.ParentGuard) | ||
if isParent { | ||
_, outgoing := record.ParsePointers(p, dumpParams) | ||
for i := 0; i < len(outgoing); i++ { | ||
if outgoing[i] != 0 { | ||
childNodeName := fmt.Sprintf("Pointer0x%x", outgoing[i]) | ||
|
||
// Create an edge from the current record to the child record | ||
s := fmt.Sprintf(" %s -> %s;\n", nodeName, childNodeName) | ||
dotContent.WriteString(s) | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Close the "heap" cluster | ||
dotContent.WriteString(" }\n") | ||
|
||
// Write DOT file footer | ||
dotContent.WriteString("}\n") | ||
|
||
return dotContent.String(), nil | ||
} | ||
|
||
func ParseNameAndAddress(input string) (name, address string) { | ||
// Define a regular expression pattern to match the desired format | ||
// The pattern captures the node name (before " at address") and the address. | ||
re := regexp.MustCompile(`^(.*?) at address (0x[0-9a-fA-F]+).*?$`) | ||
|
||
// Find the submatches in the input string | ||
matches := re.FindStringSubmatch(input) | ||
|
||
// If there are no matches, return empty strings for both name and address | ||
if len(matches) != 3 { | ||
return "", "" | ||
} | ||
|
||
// The first submatch (matches[1]) contains the node name, and the second submatch (matches[2]) contains the address. | ||
return matches[1], matches[2] | ||
} |
Oops, something went wrong.