From 7cf43c59dd214288eb3de61065ccf6a4a95eb3ad Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Wed, 11 Jul 2018 17:43:42 +0100 Subject: [PATCH] Initial starting point for a inspect command --- cmd/inspect.go | 39 +++ inspect/dump.go | 74 +++++ inspect/inspector.go | 47 ++++ inspect/object.go | 62 +++++ inspect/object_easyjson.go | 543 +++++++++++++++++++++++++++++++++++++ 5 files changed, 765 insertions(+) create mode 100644 cmd/inspect.go create mode 100644 inspect/dump.go create mode 100644 inspect/inspector.go create mode 100644 inspect/object.go create mode 100644 inspect/object_easyjson.go diff --git a/cmd/inspect.go b/cmd/inspect.go new file mode 100644 index 0000000..fad49a3 --- /dev/null +++ b/cmd/inspect.go @@ -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] ", + 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", + ) +} diff --git a/inspect/dump.go b/inspect/dump.go new file mode 100644 index 0000000..6c91a2d --- /dev/null +++ b/inspect/dump.go @@ -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) + } +} diff --git a/inspect/inspector.go b/inspect/inspector.go new file mode 100644 index 0000000..f99b1e5 --- /dev/null +++ b/inspect/inspector.go @@ -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) + } +} diff --git a/inspect/object.go b/inspect/object.go new file mode 100644 index 0000000..d764f4f --- /dev/null +++ b/inspect/object.go @@ -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 diff --git a/inspect/object_easyjson.go b/inspect/object_easyjson.go new file mode 100644 index 0000000..efcfe4c --- /dev/null +++ b/inspect/object_easyjson.go @@ -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) +}