Example #1
0
func main() {

	if len(os.Args) != 2 {
		fmt.Println("gohatr <heapfile>")
		os.Exit(1)
	}

	heapFile, err := heapfile.New(os.Args[1])
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	f, err := os.Create("image.png")
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	heapStart := heapFile.DumpParams().StartAddress
	heapEnd := heapFile.DumpParams().EndAddress
	rows := int(heapEnd-heapStart) / width // might need to check %

	img := image.NewRGBA(image.Rect(0, 0, width, rows))
	draw.Draw(img, img.Bounds(), &image.Uniform{blue}, image.ZP, draw.Src)

	for _, object := range heapFile.Objects() {
		offset := int(object.Address - heapStart)
		drawObject(img, offset, object.Size)
	}

	png.Encode(f, img)
	f.Close()
}
Example #2
0
func verifyHeapDumpFile(args []string) *heapfile.HeapFile {
	if len(args) < 1 {
		fmt.Println("heap file required")
		os.Exit(1)
	}
	heapFile, err := heapfile.New(args[0])
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}
	return heapFile
}
Example #3
0
func main() {
	var gohatCmd = &cobra.Command{
		Use:   "gohat",
		Short: "gohat is go heap dump analysis tool",
		Long: `Gohat can read and query go heap dump files.
Complete documentation is available at http://github.com/rubyist/gohat`,
		Run: func(cmd *cobra.Command, args []string) {
		},
	}

	var allocsCommand = &cobra.Command{
		Use:   "allocs",
		Short: "Dump the alloc stack trace samples",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			allocs := heapFile.Allocs()
			fmt.Println(len(allocs), "alloc samples")
			for _, alloc := range allocs {
				obj := alloc.Object()
				if obj.Type == nil {
					fmt.Println("<unknown>")
				} else {
					fmt.Println(obj.Type.Name)
				}
				record := alloc.Profile()
				fmt.Printf("%x %d %d %d\n", record.Record, record.Size, record.Allocs, record.Frees)
				for _, frame := range record.Frames {
					fmt.Printf("\t%s   %s:%d\n", frame.Name, frame.File, frame.Line)
				}
				fmt.Println()
			}
		},
	}
	gohatCmd.AddCommand(allocsCommand)

	var dataCommand = &cobra.Command{
		Use:   "data",
		Short: "Dump objects found in the data segment",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			data := heapFile.DataSegment()
			objects := data.Objects()
			fmt.Printf("Found %d objects in the data segment\n", len(objects))
			for _, object := range objects {
				displayObjectShort(object)
			}
		},
	}
	gohatCmd.AddCommand(dataCommand)

	var bssCommand = &cobra.Command{
		Use:   "bss",
		Short: "Dump the bss",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			data := heapFile.BSS()
			objects := data.Objects()
			fmt.Printf("Found %d objects in the data segment\n", len(objects))
			for _, object := range objects {
				displayObjectShort(object)
			}
		},
	}
	gohatCmd.AddCommand(bssCommand)

	var goroutinesCommand = &cobra.Command{
		Use:   "goroutines",
		Short: "Dump goroutines",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			for _, g := range heapFile.Goroutines() {
				fmt.Printf("Goroutine %d\n", g.Id)
				fmt.Printf("\tAddress: %x\n", g.Address)
				fmt.Printf("\tTop of stack: %x\n", g.Top)
				fmt.Printf("\tCreator Location: %x\n", g.Location)
				if g.System {
					fmt.Println("\tSystem Started Go routine")
				}
				if g.Background {
					fmt.Println("\tBackground Go routine")
				}
				fmt.Printf("\tStatus: %s\n", g.Status())
				if reason := g.ReasonWaiting(); reason != "" {
					fmt.Printf("\tReason Waiting: %s\n", reason)
				}
				fmt.Printf("\tLast Started Waiting: %d\n", g.LastWaiting)
				fmt.Printf("\tCurrent Frame: %x\n", g.CurrentFrame)
				fmt.Printf("\tOS Thread %d\n", g.OSThread)
				fmt.Printf("\tTop Defer Record: %x\n", g.DeferRecord)
				fmt.Printf("\tTop Panic Record: %x\n", g.PanicRecord)
				fmt.Println("")
			}
		},
	}
	gohatCmd.AddCommand(goroutinesCommand)

	var histBySize bool
	var histogramCommand = &cobra.Command{
		Use:   "histogram",
		Short: "Dump a histogram of object counts by type",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			counts := make(map[string]int, 0)
			for _, object := range heapFile.Objects() {
				if object.Type != nil {
					if histBySize {
						counts[object.Type.Name] += object.Size
					} else {
						counts[object.Type.Name] += 1
					}
				} else {
					if histBySize {
						counts["<unknown>"] += object.Size
					} else {
						counts["<unknown>"] += 1
					}
				}
			}

			histogram := NewHistSorter(counts)
			histogram.Sort()
			for i := 0; i < len(histogram.Keys); i++ {
				fmt.Printf("%d\t%s\n", histogram.Vals[i], histogram.Keys[i])
			}
		},
	}
	histogramCommand.Flags().BoolVarP(&histBySize, "size", "s", false, "Histogram by total object size")
	gohatCmd.AddCommand(histogramCommand)

	var memProfCommand = &cobra.Command{
		Use:   "memprof",
		Short: "Dump the alloc/free profile records",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			memProf := heapFile.MemProf()
			for _, record := range memProf {
				fmt.Printf("%x %d %d %d\n", record.Record, record.Size, record.Allocs, record.Frees)
				for _, frame := range record.Frames {
					fmt.Printf("\t%s   %s:%d\n", frame.Name, frame.File, frame.Line)
				}
			}
		},
	}
	gohatCmd.AddCommand(memProfCommand)

	var memStatsCommand = &cobra.Command{
		Use:   "memstats",
		Short: "Dump the memstats",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			memstats := heapFile.MemStats()
			fmt.Println("General statistics")
			fmt.Println("Alloc:", memstats.Alloc)
			fmt.Println("TotalAlloc:", memstats.TotalAlloc)
			fmt.Println("Sys:", memstats.Sys)
			fmt.Println("Lookups:", memstats.Lookups)
			fmt.Println("Mallocs:", memstats.Mallocs)
			fmt.Println("Frees:", memstats.Frees)
			fmt.Println("")
			fmt.Println("Main allocation heap statistics")
			fmt.Println("HeapAlloc:", memstats.HeapAlloc)
			fmt.Println("HeapSys:", memstats.HeapSys)
			fmt.Println("HeapIdle:", memstats.HeapIdle)
			fmt.Println("HeapInuse:", memstats.HeapInuse)
			fmt.Println("HeapReleased:", memstats.HeapReleased)
			fmt.Println("HeapObjects:", memstats.HeapObjects)
			fmt.Println("")
			fmt.Println("Low-level fixed-size structure allocator statistics")
			fmt.Println("StackInuse:", memstats.StackInuse)
			fmt.Println("StatckSys:", memstats.StackSys)
			fmt.Println("MSpanInuse:", memstats.MSpanInuse)
			fmt.Println("MSpanSys:", memstats.MSpanSys)
			fmt.Println("BuckHashSys:", memstats.BuckHashSys)
			fmt.Println("GCSys:", memstats.GCSys)
			fmt.Println("OtherSys:", memstats.OtherSys)
			fmt.Println("")
			fmt.Println("Garbage collector statistics")
			fmt.Println("NextGC:", memstats.NextGC)
			fmt.Println("LastGC:", memstats.LastGC)
			fmt.Println("PauseTotalNs:", memstats.PauseTotalNs)
			fmt.Println("NumGC:", memstats.NumGC)
			fmt.Println("Last GC Pauses:")
			fmt.Println(memstats.PauseNs)
		},
	}
	gohatCmd.AddCommand(memStatsCommand)

	var containsCommand = &cobra.Command{
		Use:   "contains",
		Short: "Find objects that point to an address",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			if len(args) != 2 {
				fmt.Println("contains <heap file> <address>")
				os.Exit(1)
			}

			addr, _ := strconv.ParseUint(args[1], 16, 64)

			// Check data segment
			for _, object := range heapFile.DataSegment().Objects() {
				if addr == object.Address {
					fmt.Printf("Found object in data segment\n")
					return
				}
			}

			// Check bss
			for _, object := range heapFile.BSS().Objects() {
				if uint64(addr) == object.Address {
					fmt.Printf("Found object in bss\n")
					return
				}
			}

			// Check objects
			for _, object := range heapFile.Objects() {
				for _, child := range object.Children() {
					if uint64(addr) == child.Address {
						fmt.Printf("Found in object %x\n", object.Address)
						return
					}
				}
			}
		},
	}
	gohatCmd.AddCommand(containsCommand)

	var objectBinary bool
	var objectCommand = &cobra.Command{
		Use:   "object",
		Short: "Dump the contents of an object",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			if len(args) != 2 {
				fmt.Println("object <heap file> <address>")
				os.Exit(1)
			}

			addr, _ := strconv.ParseUint(args[1], 16, 64)
			object := heapFile.Object(addr)
			if object == nil {
				fmt.Println("Could not find object")
				return
			}

			if objectBinary {
				os.Stdout.Write([]byte(object.Content))
				return
			}

			fmt.Printf("%x %s %d %d\n", object.Address, object.Kind(), object.Size, len(object.Content))
			if object.Type != nil {
				fmt.Println(object.Type.Name)
			}

			if object.Type != nil && object.Type.Name == "string" {
				val := derefToString([]byte(object.Content), heapFile)
				if val != "" {
					fmt.Printf("Value: %s\n", val)
				}
			}
			fmt.Println("")
			fmt.Print(hexDump(object.Content))
			fmt.Print("\n\n")

			if object.Type != nil {
				fmt.Println("Field List:")
				var lastOffset uint64
				for idx, field := range object.Type.FieldList {
					if idx == len(object.Type.FieldList)-1 {
						data := []byte(object.Content)[lastOffset:]
						switch field.KindString() {
						case "Ptr   ":
							var ptraddr int64
							buf := bytes.NewReader(data)
							binary.Read(buf, binary.LittleEndian, &ptraddr)
							fmt.Printf("%s 0x%04x  %x\n", field.KindString(), field.Offset, ptraddr)
						case "String":
							// val := derefToString(data, heapFile)
							fmt.Printf("%s 0x%04x  \n", field.KindString(), field.Offset)
						default:
							fmt.Printf("%s 0x%04x  \n", field.KindString(), field.Offset)
						}
					} else {
						lastOffset = object.Type.FieldList[idx].Offset
						nextOffset := object.Type.FieldList[idx+1].Offset
						data := []byte(object.Content)[lastOffset:nextOffset]
						switch field.Kind {
						case heapfile.FieldPtr:
							var ptraddr int64
							buf := bytes.NewReader(data)
							binary.Read(buf, binary.LittleEndian, &ptraddr)
							fmt.Printf("%s 0x%04x  %x\n", field.KindString(), field.Offset, ptraddr)
						case heapfile.FieldStr:
							// val := derefToString(data, heapFile)
							fmt.Printf("%s 0x%04x  \n", field.KindString(), field.Offset)
						default:
							fmt.Printf("%s 0x%04x  \n", field.KindString(), field.Offset)
						}
					}
				}
			}

			fmt.Println("\nChildren")
			for _, child := range object.Children() {
				displayObjectShort(child)
			}
		},
	}
	objectCommand.Flags().BoolVarP(&objectBinary, "binary", "b", false, "Dump the binary contents of the object")
	gohatCmd.AddCommand(objectCommand)

	var objectsCommand = &cobra.Command{
		Use:   "objects",
		Short: "Dump a list of objects",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			objects := heapFile.Objects()
			for _, object := range objects {
				typeName := "<unknown>"
				if object.Type != nil {
					typeName = object.Type.Name
				}
				fmt.Printf("%x,%s,%s,%d\n", object.Address, typeName, object.Kind(), object.Size)
			}
		},
	}
	gohatCmd.AddCommand(objectsCommand)

	var paramsCommand = &cobra.Command{
		Use:   "params",
		Short: "Show the heap parameters",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			dumpParams := heapFile.DumpParams()
			if dumpParams.BigEndian {
				fmt.Println("Big Endian")
			} else {
				fmt.Println("Little Endian")
			}
			fmt.Println("Pointer Size:", dumpParams.PtrSize)
			fmt.Println("Channel Header Size:", dumpParams.ChHdrSize)
			fmt.Printf("Heap Starting Address %02x\n", dumpParams.StartAddress)
			fmt.Printf("Heap Ending Address: %02x\n", dumpParams.EndAddress)
			fmt.Println("Architecture:", dumpParams.Arch)
			fmt.Println("GOEXPERIMENT:", dumpParams.GoExperiment)
			fmt.Println("nCPU:", dumpParams.NCPU)
		},
	}
	gohatCmd.AddCommand(paramsCommand)

	var rootsCommand = &cobra.Command{
		Use:   "roots",
		Short: "dump other roots",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			for _, root := range heapFile.OtherRoots() {
				fmt.Printf("%x %s\n", root.Pointer, root.Description)
			}
		},
	}
	gohatCmd.AddCommand(rootsCommand)

	var fragmentCommand = &cobra.Command{
		Use:   "fragment",
		Short: "show unused address locations",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			totalFrag := uint64(0)

			objects := heapFile.Objects()
			addresses := make(uint64arr, 0, len(objects))
			for _, obj := range objects {
				addresses = append(addresses, obj.Address)
			}
			sort.Sort(addresses)

			firstAddr := addresses[0]
			lastAddr := addresses[len(addresses)-1]
			lastObject := heapFile.Object(lastAddr)

			for i := 0; i < len(addresses)-1; i++ {
				addr := addresses[i]
				nextAddr := addresses[i+1]
				object := heapFile.Object(addr)
				size := object.Size
				endAddr := addr + uint64(size)

				if endAddr != nextAddr {
					fragAmount := nextAddr - endAddr
					totalFrag += fragAmount
					fmt.Printf("%x - %x  (%d)\n", endAddr, nextAddr, fragAmount)
				}
			}

			// May be junk on the end
			params := heapFile.DumpParams()
			endAddr := lastAddr + uint64(lastObject.Size)
			endCruft := params.EndAddress - endAddr
			if endCruft > 0 {
				totalFrag += endCruft
				fmt.Printf("%x - %x  (%d)\n", endAddr, params.EndAddress, endCruft)
			}

			fmt.Printf("Total bytes fragmented between %x and %x: %d\n", firstAddr, params.EndAddress, totalFrag)
		},
	}
	gohatCmd.AddCommand(fragmentCommand)

	type sameObject struct {
		Object      *heapfile.Object
		SameContent bool
	}
	var sameCommand = &cobra.Command{
		Use:   "same",
		Short: "find objects that are the same in two heap files",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile1 := verifyHeapDumpFile(args)

			if len(args) != 2 {
				fmt.Println("same <heap file> <heap file>")
				os.Exit(1)
			}

			heapFile2, err := heapfile.New(args[1])
			if err != nil {
				fmt.Println("Error:", err)
				os.Exit(1)
			}

			heapObjects1 := heapFile1.Objects()

			same := make([]*sameObject, 0, len(heapObjects1))

			for _, obj := range heapObjects1 {
				if cmp := heapFile2.Object(obj.Address); cmp != nil {
					if cmp.TypeAddress == obj.TypeAddress &&
						cmp.Kind() == obj.Kind() &&
						cmp.Size == obj.Size {
						same = append(same, &sameObject{obj, cmp.Content == obj.Content})
					}
				}
			}

			for _, same := range same {
				object := same.Object
				typeName := "unknown"
				if object.Type != nil {
					typeName = object.Type.Name
				}

				fmt.Printf("%x,%s,%d,%v\n", object.Address, typeName, object.Size, same.SameContent)
			}
		},
	}
	gohatCmd.AddCommand(sameCommand)

	var frameChildren bool
	var stackFramesCommand = &cobra.Command{
		Use:   "stackframes",
		Short: "Dump the stack frames",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			for _, frame := range heapFile.StackFrames() {
				fmt.Printf("%x %s\n", frame.StackPointer, frame.Name)
				if frameChildren {
					for _, object := range frame.Objects() {
						fmt.Print("\t")
						displayObjectShort(object)
						for _, child := range object.Children() {
							fmt.Print("\t\t")
							displayObjectShort(child)
						}
					}
				}
			}
		},
	}
	stackFramesCommand.Flags().BoolVarP(&frameChildren, "children", "c", false, "Show the children of the stack frames")
	gohatCmd.AddCommand(stackFramesCommand)

	var garbageCommand = &cobra.Command{
		Use:   "garbage",
		Short: "Dump unreachable objects",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			trash := heapFile.Garbage()
			fmt.Printf("Found %d unreachable objects\n", len(trash))
			for _, object := range trash {
				displayObjectShort(object)
			}
		},
	}
	gohatCmd.AddCommand(garbageCommand)

	var serverAddress string
	var serverCommand = &cobra.Command{
		Use:   "server",
		Short: "run the web interface",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)
			s := newGohatServer(serverAddress, heapFile)
			s.Run()
		},
	}
	gohatCmd.AddCommand(serverCommand)
	serverCommand.Flags().StringVarP(&serverAddress, "addr", "a", ":5150", "Address to listen on (default :5150)")

	var typeCommand = &cobra.Command{
		Use:   "type",
		Short: "Dump information about a type",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			if len(args) != 2 {
				fmt.Println("type <heap file> <address>")
				os.Exit(1)
			}

			addr, _ := strconv.ParseUint(args[1], 16, 64)

			t := heapFile.Type(addr)
			fmt.Printf("%x %d %s\n", t.Address, len(t.FieldList), t.Name)
			for _, field := range t.FieldList {
				fmt.Printf("%s 0x%0.4x\n", field.KindString(), field.Offset)
			}
		},
	}
	gohatCmd.AddCommand(typeCommand)

	var typesCommand = &cobra.Command{
		Use:   "types",
		Short: "Dump all the types found in the heap",
		Run: func(cmd *cobra.Command, args []string) {
			heapFile := verifyHeapDumpFile(args)

			types := heapFile.Types()
			for _, t := range types {
				fmt.Printf("%x %d %s\n", t.Address, len(t.FieldList), t.Name)
			}
		},
	}
	gohatCmd.AddCommand(typesCommand)

	gohatCmd.Execute()
}