package leak import ( "fmt" "io" "os" "time" ) // NewFinder returns a new *Finder instance, populated with the three given file // paths. func NewFinder(filePath1, filePath2, filePath3 string) *Finder { return &Finder{ FilePaths: [3]string{filePath1, filePath2, filePath3}, } } // Finder helps with finding a memory leak across three different memory dumps // from a Ruby process. type Finder struct { FilePaths [3]string Dumps [3]*Dump Leaks []*string Verbose bool VerboseWriter io.Writer } // Process will will load and process each of the dump files. func (s *Finder) Process() error { for i, filePath := range s.FilePaths { start := time.Now() s.verbose(fmt.Sprintf("Parsing %s", filePath)) dump := NewDump(filePath) err := dump.Process() if err != nil { return err } s.Dumps[i] = dump elapsed := time.Now().Sub(start) s.verbose(fmt.Sprintf( "Parsed %d objects in %.6f seconds", len(dump.Index), elapsed.Seconds(), )) } return nil } // WriteLeakedAddresses prints the memory addresses in hex (0x...) format for // all objects which are likely to be leaked memory. func (s *Finder) WriteLeakedAddresses(w io.Writer) { s.verbose("\nLeaked Addresses:") s.Dumps[1].WriteEntryAddresses(w, s.FindLeaks()) } // WriteLeakedObjects prints the full JSON blobs for all objects which are // likely to be memory leaks. func (s *Finder) WriteLeakedObjects(w io.Writer) error { s.verbose("\nLeaked Objects:") return s.Dumps[1].WriteEntryJSON(w, s.FindLeaks()) } // FindLeaks finds potential memory leaks by removing all objects in heap dump // #1 from heap dump #2, and then also removing all entries from heap dump #2 // which are not present in heap dump #3. func (s *Finder) FindLeaks() []*string { if s.Leaks != nil { return s.Leaks } mapA := map[string]bool{} mapC := map[string]bool{} for _, x := range s.Dumps[0].Index { mapA[*x] = true } for _, x := range s.Dumps[2].Index { mapC[*x] = true } for _, x := range s.Dumps[1].Index { _, okA := mapA[*x] _, okC := mapC[*x] if !okA && okC { s.Leaks = append(s.Leaks, x) } } return s.Leaks } func (s *Finder) log(msg string) { if s.Verbose { fmt.Println(msg) } } func (s *Finder) verbose(msg string) { if s.Verbose { w := s.VerboseWriter if w == nil { w = os.Stderr } fmt.Fprintln(w, msg) } }