@@ -9,7 +9,6 @@ package main
99import (
1010 "flag"
1111 "fmt"
12- "log"
1312 "os"
1413 "sort"
1514 "strings"
@@ -68,165 +67,10 @@ func NewFuncStat(name, file string, line int) *FuncStat {
6867 }
6968}
7069
71- func main () {
72- flag .Parse ()
73- if flag .NArg () < 1 {
74- log .Fatal ("Usage: memcheck <pprof_file>" )
75- }
76-
77- filename := flag .Arg (0 )
78- f , err := os .Open (filename )
79- if err != nil {
80- log .Fatalf ("Failed to open file: %v" , err )
81- }
82- defer func () {
83- _ = f .Close ()
84- }()
85-
86- p , err := profile .Parse (f )
87- if err != nil {
88- log .Fatalf ("Failed to parse profile: %v" , err )
89- }
90-
91- // 1. Identify Metrics
92- idxAllocSpace , idxAllocObj := - 1 , - 1
93- idxInUseSpace , idxInUseObj := - 1 , - 1
94-
95- for i , st := range p .SampleType {
96- switch st .Type {
97- case "alloc_space" , "alloc_bytes" :
98- idxAllocSpace = i
99- case "alloc_objects" , "alloc_count" :
100- idxAllocObj = i
101- case "inuse_space" , "inuse_bytes" :
102- idxInUseSpace = i
103- case "inuse_objects" , "inuse_count" :
104- idxInUseObj = i
105- }
106- }
107-
108- if idxAllocSpace == - 1 {
109- log .Fatal ("Profile missing 'alloc_space'. Ensure this is a heap profile." )
110- }
111-
112- // 2. Aggregate Data
113- stats := make (map [string ]* FuncStat )
114- lineStats := make (map [string ]* LineStat )
115- labelStats := make (map [string ]* LabelStat )
116- var totalAllocBytes , totalInUseBytes int64
117- var topStacks []StackRecord
118-
119- for _ , s := range p .Sample {
120- allocBytes := s .Value [idxAllocSpace ]
121- allocObj := s .Value [idxAllocObj ]
122- inUseBytes := s .Value [idxInUseSpace ]
123- inUseObj := s .Value [idxInUseObj ]
124-
125- totalAllocBytes += allocBytes
126- totalInUseBytes += inUseBytes
127-
128- // A. Function Analysis
129- seen := make (map [string ]bool )
130- if len (s .Location ) > 0 {
131- leafLoc := s .Location [0 ]
132- if len (leafLoc .Line ) > 0 {
133- fn := leafLoc .Line [0 ].Function
134- lineNo := int (leafLoc .Line [0 ].Line )
135-
136- if fn != nil {
137- key := funcKey (fn )
138- if _ , ok := stats [key ]; ! ok {
139- stats [key ] = NewFuncStat (fn .Name , fn .Filename , lineNo )
140- }
141- stats [key ].FlatAllocBytes += allocBytes
142- stats [key ].FlatAllocObj += allocObj
143- stats [key ].FlatInUseBytes += inUseBytes
144- stats [key ].FlatInUseObj += inUseObj
145-
146- if len (s .Location ) > 1 {
147- parentLoc := s .Location [1 ]
148- if len (parentLoc .Line ) > 0 {
149- pFn := parentLoc .Line [0 ].Function
150- if pFn != nil {
151- stats [key ].Callers [pFn .Name ] += allocBytes
152- }
153- }
154- }
155-
156- // Line Stat
157- lineKey := fmt .Sprintf ("%s:%d" , fn .Filename , lineNo )
158- if _ , ok := lineStats [lineKey ]; ! ok {
159- lineStats [lineKey ] = & LineStat {File : fn .Filename , Line : lineNo , Function : fn .Name }
160- }
161- lineStats [lineKey ].AllocBytes += allocBytes
162- }
163- }
164- }
165-
166- // B. Stack Trace Collection (Cumulative & Tree)
167- var currentStack []string
168- for _ , loc := range s .Location {
169- for _ , line := range loc .Line {
170- fn := line .Function
171- if fn == nil {
172- continue
173- }
174- currentStack = append (currentStack , fn .Name )
175-
176- key := funcKey (fn )
177- if seen [key ] {
178- continue
179- }
180- seen [key ] = true
181-
182- if _ , ok := stats [key ]; ! ok {
183- stats [key ] = NewFuncStat (fn .Name , fn .Filename , int (line .Line ))
184- }
185- stats [key ].CumAllocBytes += allocBytes
186- }
187- }
188-
189- // C. Label Analysis
190- for key , values := range s .Label {
191- for _ , val := range values {
192- labelID := fmt .Sprintf ("%s:%s" , key , val )
193- if _ , ok := labelStats [labelID ]; ! ok {
194- labelStats [labelID ] = & LabelStat {Name : labelID }
195- }
196- labelStats [labelID ].AllocBytes += allocBytes
197- labelStats [labelID ].InUseBytes += inUseBytes
198- }
199- }
200-
201- if allocBytes > 0 {
202- topStacks = append (topStacks , StackRecord {Stack : currentStack , Bytes : allocBytes })
203- }
204- }
205-
206- // 3. Sorting
207- var statList []* FuncStat
208- for _ , s := range stats {
209- statList = append (statList , s )
210- }
211- var labelList []* LabelStat
212- for _ , s := range labelStats {
213- labelList = append (labelList , s )
214- }
215- sort .Slice (labelList , func (i , j int ) bool { return labelList [i ].AllocBytes > labelList [j ].AllocBytes })
216- var lineList []* LineStat
217- for _ , s := range lineStats {
218- lineList = append (lineList , s )
219- }
220- sort .Slice (lineList , func (i , j int ) bool { return lineList [i ].AllocBytes > lineList [j ].AllocBytes })
221-
222- // 5. Generate Report
223- printUnifiedReport (statList , labelList , lineList , topStacks , totalAllocBytes , totalInUseBytes )
224- }
225-
22670func printUnifiedReport (stats []* FuncStat , labels []* LabelStat , lines []* LineStat , stacks []StackRecord , totalAlloc , totalInUse int64 ) {
22771 w := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , 0 )
22872
229- writef (w , "\n ================ ULTIMATE GO MEMORY ANALYZER ================\n " )
73+ writef (w , "\n ================ MEMORY ANALYZER ================\n " )
23074 writef (w , "Total Allocated: %s | Total In-Use: %s\n \n " , formatBytes (totalAlloc ), formatBytes (totalInUse ))
23175
23276 detectAntiPatterns (stats , w )
@@ -585,3 +429,158 @@ func writef(w *tabwriter.Writer, format string, a ...interface{}) {
585429func writeLine (w * tabwriter.Writer , s string ) {
586430 _ , _ = fmt .Fprintln (w , s )
587431}
432+
433+ func main () {
434+ flag .Parse ()
435+ if flag .NArg () < 1 {
436+ log .Fatal ("Usage: memcheck <pprof_file>" )
437+ }
438+
439+ filename := flag .Arg (0 )
440+ f , err := os .Open (filename )
441+ if err != nil {
442+ log .Fatalf ("Failed to open file: %v" , err )
443+ }
444+ defer func () {
445+ _ = f .Close ()
446+ }()
447+
448+ p , err := profile .Parse (f )
449+ if err != nil {
450+ log .Fatalf ("Failed to parse profile: %v" , err )
451+ }
452+
453+ // 1. Identify Metrics
454+ idxAllocSpace , idxAllocObj := - 1 , - 1
455+ idxInUseSpace , idxInUseObj := - 1 , - 1
456+
457+ for i , st := range p .SampleType {
458+ switch st .Type {
459+ case "alloc_space" , "alloc_bytes" :
460+ idxAllocSpace = i
461+ case "alloc_objects" , "alloc_count" :
462+ idxAllocObj = i
463+ case "inuse_space" , "inuse_bytes" :
464+ idxInUseSpace = i
465+ case "inuse_objects" , "inuse_count" :
466+ idxInUseObj = i
467+ }
468+ }
469+
470+ if idxAllocSpace == - 1 {
471+ log .Fatal ("Profile missing 'alloc_space'. Ensure this is a heap profile." )
472+ }
473+
474+ // 2. Aggregate Data
475+ stats := make (map [string ]* FuncStat )
476+ lineStats := make (map [string ]* LineStat )
477+ labelStats := make (map [string ]* LabelStat )
478+ var totalAllocBytes , totalInUseBytes int64
479+ var topStacks []StackRecord
480+
481+ for _ , s := range p .Sample {
482+ allocBytes := s .Value [idxAllocSpace ]
483+ allocObj := s .Value [idxAllocObj ]
484+ inUseBytes := s .Value [idxInUseSpace ]
485+ inUseObj := s .Value [idxInUseObj ]
486+
487+ totalAllocBytes += allocBytes
488+ totalInUseBytes += inUseBytes
489+
490+ // A. Function Analysis
491+ seen := make (map [string ]bool )
492+ if len (s .Location ) > 0 {
493+ leafLoc := s .Location [0 ]
494+ if len (leafLoc .Line ) > 0 {
495+ fn := leafLoc .Line [0 ].Function
496+ lineNo := int (leafLoc .Line [0 ].Line )
497+
498+ if fn != nil {
499+ key := funcKey (fn )
500+ if _ , ok := stats [key ]; ! ok {
501+ stats [key ] = NewFuncStat (fn .Name , fn .Filename , lineNo )
502+ }
503+ stats [key ].FlatAllocBytes += allocBytes
504+ stats [key ].FlatAllocObj += allocObj
505+ stats [key ].FlatInUseBytes += inUseBytes
506+ stats [key ].FlatInUseObj += inUseObj
507+
508+ if len (s .Location ) > 1 {
509+ parentLoc := s .Location [1 ]
510+ if len (parentLoc .Line ) > 0 {
511+ pFn := parentLoc .Line [0 ].Function
512+ if pFn != nil {
513+ stats [key ].Callers [pFn .Name ] += allocBytes
514+ }
515+ }
516+ }
517+
518+ // Line Stat
519+ lineKey := fmt .Sprintf ("%s:%d" , fn .Filename , lineNo )
520+ if _ , ok := lineStats [lineKey ]; ! ok {
521+ lineStats [lineKey ] = & LineStat {File : fn .Filename , Line : lineNo , Function : fn .Name }
522+ }
523+ lineStats [lineKey ].AllocBytes += allocBytes
524+ }
525+ }
526+ }
527+
528+ // B. Stack Trace Collection (Cumulative & Tree)
529+ var currentStack []string
530+ for _ , loc := range s .Location {
531+ for _ , line := range loc .Line {
532+ fn := line .Function
533+ if fn == nil {
534+ continue
535+ }
536+ currentStack = append (currentStack , fn .Name )
537+
538+ key := funcKey (fn )
539+ if seen [key ] {
540+ continue
541+ }
542+ seen [key ] = true
543+
544+ if _ , ok := stats [key ]; ! ok {
545+ stats [key ] = NewFuncStat (fn .Name , fn .Filename , int (line .Line ))
546+ }
547+ stats [key ].CumAllocBytes += allocBytes
548+ }
549+ }
550+
551+ // C. Label Analysis
552+ for key , values := range s .Label {
553+ for _ , val := range values {
554+ labelID := fmt .Sprintf ("%s:%s" , key , val )
555+ if _ , ok := labelStats [labelID ]; ! ok {
556+ labelStats [labelID ] = & LabelStat {Name : labelID }
557+ }
558+ labelStats [labelID ].AllocBytes += allocBytes
559+ labelStats [labelID ].InUseBytes += inUseBytes
560+ }
561+ }
562+
563+ if allocBytes > 0 {
564+ topStacks = append (topStacks , StackRecord {Stack : currentStack , Bytes : allocBytes })
565+ }
566+ }
567+
568+ // 3. Sorting
569+ var statList []* FuncStat
570+ for _ , s := range stats {
571+ statList = append (statList , s )
572+ }
573+ var labelList []* LabelStat
574+ for _ , s := range labelStats {
575+ labelList = append (labelList , s )
576+ }
577+ sort .Slice (labelList , func (i , j int ) bool { return labelList [i ].AllocBytes > labelList [j ].AllocBytes })
578+ var lineList []* LineStat
579+ for _ , s := range lineStats {
580+ lineList = append (lineList , s )
581+ }
582+ sort .Slice (lineList , func (i , j int ) bool { return lineList [i ].AllocBytes > lineList [j ].AllocBytes })
583+
584+ // 5. Generate Report
585+ printUnifiedReport (statList , labelList , lineList , topStacks , totalAllocBytes , totalInUseBytes )
586+ }
0 commit comments