Initial starting point for a inspect command

This commit is contained in:
2018-07-11 17:43:42 +01:00
parent 796752b15d
commit 7cf43c59dd
5 changed files with 765 additions and 0 deletions

39
cmd/inspect.go Normal file
View File

@@ -0,0 +1,39 @@
package cmd
import (
"fmt"
"github.com/jimeh/rbheap/inspect"
"github.com/spf13/cobra"
)
// inspectCmd represents the inspect command
var inspectCmd = &cobra.Command{
Use: "inspect [flags] <dump-file>",
Short: "Inspect ObjectSpace dumps from Ruby proceees",
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
usage_er(cmd, fmt.Sprintf("requires 1 arg, received %d", len(args)))
}
inspector := inspect.New(args[0])
inspector.Verbose = inspectOpts.Verbose
inspector.Process()
inspector.PrintCountByFileAndLines()
},
}
var inspectOpts = struct {
Verbose bool
}{}
func init() {
rootCmd.AddCommand(inspectCmd)
inspectCmd.PersistentFlags().BoolVarP(
&inspectOpts.Verbose,
"verbose", "v", false,
"print verbose information",
)
}

74
inspect/dump.go Normal file
View File

@@ -0,0 +1,74 @@
package inspect
import (
"bufio"
"io"
"os"
"strconv"
)
func NewDump(filePath string) *Dump {
return &Dump{FilePath: filePath}
}
// Dump contains all relevant data for a single heap dump.
type Dump struct {
FilePath string
ByAddress map[string]*Object
ByFile map[string][]*Object
ByFileAndLine map[string][]*Object
ByGeneration map[int][]*Object
}
// Process processes the heap dump referenced in FilePath.
func (s *Dump) Process() error {
file, err := os.Open(s.FilePath)
defer file.Close()
if err != nil {
return err
}
s.ByAddress = map[string]*Object{}
s.ByFile = map[string][]*Object{}
s.ByFileAndLine = map[string][]*Object{}
s.ByGeneration = map[int][]*Object{}
reader := bufio.NewReader(file)
for {
line, err := reader.ReadBytes(byte('\n'))
if err == io.EOF {
break
} else if err != nil {
return err
}
object, err := NewObject(line)
if err != nil {
return err
}
s.AddObject(object)
}
return nil
}
// AddObject adds a *Object to the Dump.
func (s *Dump) AddObject(obj *Object) {
s.ByAddress[obj.Address] = obj
if obj.File != "" {
s.ByFile[obj.File] = append(s.ByFile[obj.File], obj)
}
if obj.File != "" && obj.Line != 0 {
key := obj.File + ":" + strconv.Itoa(obj.Line)
s.ByFileAndLine[key] = append(s.ByFileAndLine[key], obj)
}
if obj.Generation != 0 {
s.ByGeneration[obj.Generation] =
append(s.ByGeneration[obj.Generation], obj)
}
}

47
inspect/inspector.go Normal file
View File

@@ -0,0 +1,47 @@
package inspect
import (
"fmt"
"time"
)
func New(filePath string) *Inspector {
return &Inspector{
FilePath: filePath,
Dump: NewDump(filePath),
}
}
type Inspector struct {
FilePath string
Dump *Dump
Verbose bool
}
func (s *Inspector) Process() {
start := time.Now()
s.log(fmt.Sprintf("Parsing %s", s.FilePath))
s.Dump.Process()
elapsed := time.Now().Sub(start)
s.log(fmt.Sprintf(
"Parsed %d objects in %.6f seconds",
len(s.Dump.ByAddress),
elapsed.Seconds(),
))
}
func (s *Inspector) PrintCountByFileAndLines() {
for k, objects := range s.Dump.ByFileAndLine {
fmt.Printf("%s: %d objects\n", k, len(objects))
}
}
func (s *Inspector) log(msg string) {
if s.Verbose {
fmt.Println(msg)
}
}

62
inspect/object.go Normal file
View File

@@ -0,0 +1,62 @@
package inspect
import "encoding/json"
//go:generate easyjson -all object.go
// NewObject returns a new *Object instance with it's attributes populated from
// the given input JSON data.
func NewObject(inputJSON []byte) (*Object, error) {
var obj Object
err := json.Unmarshal(inputJSON, &obj)
return &obj, err
}
// Object is a representation of a Ruby heap object as exported from Ruby via
// `ObjectSpace.dump_all`.
type Object struct {
Address string `json:"address"`
Bytesize int `json:"bytesize"`
Capacity int `json:"capacity"`
Class string `json:"class"`
Default string `json:"default"`
Embedded bool `json:"embedded"`
Encoding string `json:"encoding"`
Fd int `json:"fd"`
File string `json:"file"`
Flags ObjectFlags `json:"flags"`
Frozen bool `json:"frozen"`
Fstring bool `json:"fstring"`
Generation int `json:"generation"`
ImemoType string `json:"imemo_type"`
Ivars int `json:"ivars"`
Length int `json:"length"`
Line int `json:"line"`
Memsize int `json:"memsize"`
Method string `json:"method"`
Name string `json:"name"`
References ObjectReferences `json:"references"`
Root string `json:"root"`
Shared bool `json:"shared"`
Size int `json:"size"`
Struct string `json:"struct"`
Type string `json:"type"`
Value string `json:"value"`
}
// Index returns a unique index for the given Object.
func (s *Object) Index() string {
return s.Address + ":" + s.Type
}
// ObjectFlags represents the available flags on an Object.
type ObjectFlags struct {
Marked bool `json:"marked"`
Old bool `json:"old"`
Uncollectible bool `json:"uncollectible"`
WbProtected bool `json:"wb_protected"`
}
// ObjectReferences represents the list of references in an Object.
type ObjectReferences []string

543
inspect/object_easyjson.go Normal file
View File

@@ -0,0 +1,543 @@
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package inspect
import (
json "encoding/json"
easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)
// suppress unused package warning
var (
_ *json.RawMessage
_ *jlexer.Lexer
_ *jwriter.Writer
_ easyjson.Marshaler
)
func easyjsonE44bcf2dDecodeGithubComJimehRbheapInspect(in *jlexer.Lexer, out *ObjectFlags) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "marked":
out.Marked = bool(in.Bool())
case "old":
out.Old = bool(in.Bool())
case "uncollectible":
out.Uncollectible = bool(in.Bool())
case "wb_protected":
out.WbProtected = bool(in.Bool())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjsonE44bcf2dEncodeGithubComJimehRbheapInspect(out *jwriter.Writer, in ObjectFlags) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"marked\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(in.Marked))
}
{
const prefix string = ",\"old\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(in.Old))
}
{
const prefix string = ",\"uncollectible\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(in.Uncollectible))
}
{
const prefix string = ",\"wb_protected\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(in.WbProtected))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v ObjectFlags) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjsonE44bcf2dEncodeGithubComJimehRbheapInspect(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v ObjectFlags) MarshalEasyJSON(w *jwriter.Writer) {
easyjsonE44bcf2dEncodeGithubComJimehRbheapInspect(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *ObjectFlags) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjsonE44bcf2dDecodeGithubComJimehRbheapInspect(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ObjectFlags) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjsonE44bcf2dDecodeGithubComJimehRbheapInspect(l, v)
}
func easyjsonE44bcf2dDecodeGithubComJimehRbheapInspect1(in *jlexer.Lexer, out *Object) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "address":
out.Address = string(in.String())
case "bytesize":
out.Bytesize = int(in.Int())
case "capacity":
out.Capacity = int(in.Int())
case "class":
out.Class = string(in.String())
case "default":
out.Default = string(in.String())
case "embedded":
out.Embedded = bool(in.Bool())
case "encoding":
out.Encoding = string(in.String())
case "fd":
out.Fd = int(in.Int())
case "file":
out.File = string(in.String())
case "flags":
if data := in.Raw(); in.Ok() {
in.AddError((out.Flags).UnmarshalJSON(data))
}
case "frozen":
out.Frozen = bool(in.Bool())
case "fstring":
out.Fstring = bool(in.Bool())
case "generation":
out.Generation = int(in.Int())
case "imemo_type":
out.ImemoType = string(in.String())
case "ivars":
out.Ivars = int(in.Int())
case "length":
out.Length = int(in.Int())
case "line":
out.Line = int(in.Int())
case "memsize":
out.Memsize = int(in.Int())
case "method":
out.Method = string(in.String())
case "name":
out.Name = string(in.String())
case "references":
if in.IsNull() {
in.Skip()
out.References = nil
} else {
in.Delim('[')
if out.References == nil {
if !in.IsDelim(']') {
out.References = make(ObjectReferences, 0, 4)
} else {
out.References = ObjectReferences{}
}
} else {
out.References = (out.References)[:0]
}
for !in.IsDelim(']') {
var v1 string
v1 = string(in.String())
out.References = append(out.References, v1)
in.WantComma()
}
in.Delim(']')
}
case "root":
out.Root = string(in.String())
case "shared":
out.Shared = bool(in.Bool())
case "size":
out.Size = int(in.Int())
case "struct":
out.Struct = string(in.String())
case "type":
out.Type = string(in.String())
case "value":
out.Value = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjsonE44bcf2dEncodeGithubComJimehRbheapInspect1(out *jwriter.Writer, in Object) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"address\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Address))
}
{
const prefix string = ",\"bytesize\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Bytesize))
}
{
const prefix string = ",\"capacity\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Capacity))
}
{
const prefix string = ",\"class\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Class))
}
{
const prefix string = ",\"default\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Default))
}
{
const prefix string = ",\"embedded\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(in.Embedded))
}
{
const prefix string = ",\"encoding\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Encoding))
}
{
const prefix string = ",\"fd\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Fd))
}
{
const prefix string = ",\"file\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.File))
}
{
const prefix string = ",\"flags\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Raw((in.Flags).MarshalJSON())
}
{
const prefix string = ",\"frozen\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(in.Frozen))
}
{
const prefix string = ",\"fstring\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(in.Fstring))
}
{
const prefix string = ",\"generation\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Generation))
}
{
const prefix string = ",\"imemo_type\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.ImemoType))
}
{
const prefix string = ",\"ivars\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Ivars))
}
{
const prefix string = ",\"length\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Length))
}
{
const prefix string = ",\"line\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Line))
}
{
const prefix string = ",\"memsize\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Memsize))
}
{
const prefix string = ",\"method\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Method))
}
{
const prefix string = ",\"name\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Name))
}
{
const prefix string = ",\"references\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
if in.References == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v2, v3 := range in.References {
if v2 > 0 {
out.RawByte(',')
}
out.String(string(v3))
}
out.RawByte(']')
}
}
{
const prefix string = ",\"root\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Root))
}
{
const prefix string = ",\"shared\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(in.Shared))
}
{
const prefix string = ",\"size\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Size))
}
{
const prefix string = ",\"struct\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Struct))
}
{
const prefix string = ",\"type\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Type))
}
{
const prefix string = ",\"value\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Value))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v Object) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjsonE44bcf2dEncodeGithubComJimehRbheapInspect1(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Object) MarshalEasyJSON(w *jwriter.Writer) {
easyjsonE44bcf2dEncodeGithubComJimehRbheapInspect1(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *Object) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjsonE44bcf2dDecodeGithubComJimehRbheapInspect1(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Object) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjsonE44bcf2dDecodeGithubComJimehRbheapInspect1(l, v)
}