@@ -43,6 +43,25 @@ var compressedHeapExtensions = []string{
4343const hdOut = "heap_dump.out"
4444const hdZip = "heap_dump.zip"
4545
46+ type HeapDumpFile struct {
47+ SrcHeapDumpPath string
48+ SrcHeapDumpFile * os.File
49+
50+ DstCompressedPath string
51+ DstCompressedFile * os.File
52+ }
53+
54+ func (h * HeapDumpFile ) IsSrcHeapDumpAlreadyCompressed () bool {
55+ ext := strings .TrimPrefix (filepath .Ext (h .SrcHeapDumpPath ), "." )
56+
57+ for _ , compressedExt := range compressedHeapExtensions {
58+ if ext == compressedExt {
59+ return true
60+ }
61+ }
62+ return false
63+ }
64+
4665type HeapDump struct {
4766 Capture
4867 JavaHome string
@@ -61,84 +80,120 @@ func NewHeapDump(javaHome string, pid int, hdPath string, dump bool) *HeapDump {
6180 }
6281}
6382
64- // getDestHeapDumpFilename returns the appropriate filename based on compression status
65- func (t * HeapDump ) getDestHeapDumpFilename (isCompressed bool ) string {
66- if isCompressed {
67- return hdZip
68- }
69- return hdOut
70- }
71-
72- // Run executes the heap dump capture process and uploads the captured file
73- // to the specified endpoint.
7483func (t * HeapDump ) Run () (Result , error ) {
75- var hd * os.File
76- var err error
77- var isCompressed bool
78- var contentEncoding string
84+ heapDumpFile := & HeapDumpFile {}
7985
80- // Try pre-captured heap dump first
8186 if len (t .hdPath ) > 0 {
82- isCompressed , contentEncoding = isCompressedHeapFile (t .hdPath )
83- if isCompressed {
84- logger .Log ("detected pre-compressed heap dump file: %s with encoding: %s" , t .hdPath , contentEncoding )
87+ heapDumpFile .SrcHeapDumpPath = t .hdPath
88+
89+ hd , err := os .Open (heapDumpFile .SrcHeapDumpPath )
90+
91+ // Fallback, try to open the file in the Docker container
92+ if err != nil && runtime .GOOS == "linux" {
93+ logger .Log ("failed to open hdPath(%s) err: %s. Trying to open in the Docker container..." , t .hdPath , err .Error ())
94+ hd , err = os .Open (filepath .Join ("/proc" , strconv .Itoa (t .Pid ), "root" , t .hdPath ))
8595 }
8696
87- hd , err = t .getPreCapturedDumpFile (isCompressed )
8897 if err != nil {
98+ logger .Log ("failed to open hdPath(%s) err: %s" , t .hdPath , err .Error ())
8999 return Result {
90- Msg : fmt .Sprintf ("capture heap dump failed : %s" , err .Error ()),
100+ Msg : fmt .Sprintf ("failed to open heap dump file : %s" , err .Error ()),
91101 Ok : false ,
92- }, nil
102+ }, err
93103 }
104+
105+ heapDumpFile .SrcHeapDumpFile = hd
106+
107+ // Ensure the source heap dump file is closed when the function exits
108+ defer func () {
109+ if err := hd .Close (); err != nil && ! errors .Is (err , os .ErrClosed ) {
110+ logger .Log ("failed to close source heap dump file: %s" , err .Error ())
111+ }
112+ }()
94113 } else if t .Pid > 0 && t .dump {
95- var actualDumpPath string
96- // Then try capturing a new heap dump
97- hd , actualDumpPath , err = t .captureDumpFile ()
114+ hd , actualDumpPath , err := t .captureDumpFile ()
98115 if err != nil {
99116 return Result {
100117 Msg : fmt .Sprintf ("capture heap dump failed: %s" , err .Error ()),
101118 Ok : false ,
102119 }, nil
103120 }
104121
105- // Cleanup the actual dump file after we're done with it
106- if actualDumpPath != "" {
107- defer func () {
108- err := os .Remove (actualDumpPath )
109- if err != nil {
110- logger .Trace ().Err (err ).Str ("file" , actualDumpPath ).Msg ("failed to rm hd file" )
111- }
112- }()
113- }
122+ heapDumpFile .SrcHeapDumpPath = actualDumpPath
123+ heapDumpFile .SrcHeapDumpFile = hd
124+
125+ // Ensure the captured heap dump file is closed when the function exits
126+ defer func () {
127+ if err := hd .Close (); err != nil && ! errors .Is (err , os .ErrClosed ) {
128+ logger .Log ("failed to close captured heap dump file: %s" , err .Error ())
129+ }
130+ }()
131+
132+ // because this code creates the file, it's responsible for cleaning it up
133+ defer func () {
134+ if removeErr := os .Remove (actualDumpPath ); removeErr != nil {
135+ logger .Log ("failed to rm hd file %s cause err: %s" , actualDumpPath , removeErr .Error ())
136+ }
137+ }()
114138 }
115139
116- if hd == nil {
140+ if heapDumpFile . SrcHeapDumpFile == nil {
117141 return Result {Msg : "skipped heap dump" }, nil
118142 }
119143
120- tempFilename := t .getDestHeapDumpFilename (isCompressed )
144+ if heapDumpFile .IsSrcHeapDumpAlreadyCompressed () {
145+ logger .Log ("copying heap dump data %s" , t .hdPath )
146+
147+ srcExtension := strings .TrimPrefix (filepath .Ext (heapDumpFile .SrcHeapDumpPath ), "." )
148+ heapDumpFile .DstCompressedPath = "heap_dump." + srcExtension
121149
122- defer func () {
123- err := hd .Close ()
124- if err != nil && ! errors .Is (err , os .ErrClosed ) {
125- logger .Log ("failed to close hd file %s cause err: %s" , tempFilename , err .Error ())
150+ dstCompressedFile , err := os .OpenFile (heapDumpFile .DstCompressedPath , os .O_RDWR | os .O_CREATE | os .O_TRUNC , 0644 )
151+ if err != nil {
152+ return Result {
153+ Msg : fmt .Sprintf ("failed creating heap dump in current working directory: %s" , err .Error ()),
154+ Ok : false ,
155+ }, nil
126156 }
127- }()
128157
129- var fileToUpload * os.File
130- var uploadContentEncoding string
158+ // Ensure the file is closed when the function exits
159+ defer func () {
160+ if err := dstCompressedFile .Close (); err != nil && ! errors .Is (err , os .ErrClosed ) {
161+ logger .Log ("failed to close destination compressed file: %s" , err .Error ())
162+ }
163+ }()
164+
165+ _ , err = io .Copy (dstCompressedFile , heapDumpFile .SrcHeapDumpFile )
166+ if err != nil {
167+ return Result {
168+ Msg : fmt .Sprintf ("failed copying heap dump data: %s" , err .Error ()),
169+ Ok : false ,
170+ }, nil
171+ }
131172
132- if isCompressed {
133- // If the file is already compressed, use it directly without re-compressing
134- logger .Log ("file is already compressed, skipping compression step" )
135- fileToUpload = hd
136- uploadContentEncoding = contentEncoding
173+ // Sync the file to ensure all data is written to disk
174+ err = dstCompressedFile .Sync ()
175+ if err != nil {
176+ return Result {
177+ Msg : fmt .Sprintf ("failed syncing heap dump file: %s" , err .Error ()),
178+ Ok : false ,
179+ }, nil
180+ }
181+
182+ // Reset file position to beginning for reading during upload
183+ _ , err = dstCompressedFile .Seek (0 , 0 )
184+ if err != nil {
185+ return Result {
186+ Msg : fmt .Sprintf ("failed seeking to beginning of heap dump file: %s" , err .Error ()),
187+ Ok : false ,
188+ }, nil
189+ }
190+
191+ heapDumpFile .DstCompressedFile = dstCompressedFile
192+ logger .Log ("copied heap dump data %s to %s" , t .hdPath , heapDumpFile .DstCompressedPath )
137193 } else {
138- // For uncompressed files, compress them
139194 logger .Log ("captured heap dump data, zipping..." )
140195
141- zipfile , err := t .CreateZipFile (hd )
196+ zipfile , err := t .CreateZipFile (heapDumpFile . SrcHeapDumpFile )
142197 if err != nil {
143198 return Result {
144199 Msg : fmt .Sprintf ("capture heap dump failed: %s" , err .Error ()),
@@ -152,65 +207,16 @@ func (t *HeapDump) Run() (Result, error) {
152207 }
153208 }()
154209
155- defer func () {
156- err = os .Remove (tempFilename )
157- if err != nil {
158- logger .Log ("failed to rm hd file %s cause err: %s" , tempFilename , err .Error ())
159- }
160- }()
161-
162- fileToUpload = zipfile
163- uploadContentEncoding = "zip"
210+ heapDumpFile .DstCompressedPath = hdZip
211+ heapDumpFile .DstCompressedFile = zipfile
164212 }
165213
166- result := t .UploadCapturedFile (fileToUpload , uploadContentEncoding )
214+ // Upload heapDumpFile.DstCompressedFile to the endpoint
215+ dstCompressedExtension := strings .TrimPrefix (filepath .Ext (heapDumpFile .DstCompressedPath ), "." )
216+ result := t .UploadCapturedFile (heapDumpFile .DstCompressedFile , dstCompressedExtension )
167217 return result , nil
168218}
169219
170- // getPreCapturedDumpFile handles the case when a heap dump is pre-captured (using the hdPath field)
171- func (t * HeapDump ) getPreCapturedDumpFile (isCompressed bool ) (* os.File , error ) {
172- hdf , err := os .Open (t .hdPath )
173-
174- // Fallback, try to open the file in the Docker container
175- if err != nil && runtime .GOOS == "linux" {
176- logger .Log ("failed to open hdPath(%s) err: %s. Trying to open in the Docker container..." , t .hdPath , err .Error ())
177- hdf , err = os .Open (filepath .Join ("/proc" , strconv .Itoa (t .Pid ), "root" , t .hdPath ))
178- }
179-
180- if err != nil {
181- logger .Log ("failed to open hdPath(%s) err: %s" , t .hdPath , err .Error ())
182- return nil , err
183- }
184-
185- logger .Log ("copying heap dump data %s" , t .hdPath )
186-
187- defer func () {
188- err := hdf .Close ()
189- if err != nil {
190- logger .Log ("failed to close hd file %s cause err: %s" , t .hdPath , err .Error ())
191- }
192- }()
193-
194- filename := t .getDestHeapDumpFilename (isCompressed )
195- hd , err := os .Create (filename )
196- if err != nil {
197- return nil , err
198- }
199-
200- _ , err = io .Copy (hd , hdf )
201- if err != nil {
202- return nil , err
203- }
204-
205- _ , err = hd .Seek (0 , 0 )
206- if err != nil {
207- return nil , err
208- }
209-
210- logger .Log ("copied heap dump data %s to %s" , t .hdPath , filename )
211- return hd , nil
212- }
213-
214220// captureDumpFile handles the case when a heap dump needs to be captured (using the Pid field)
215221// and returns both the file handle and the actual dump path
216222func (t * HeapDump ) captureDumpFile () (* os.File , string , error ) {
@@ -255,25 +261,44 @@ func (t *HeapDump) CreateZipFile(hd *os.File) (*os.File, error) {
255261 return nil , fmt .Errorf ("failed to create zip file: %w" , err )
256262 }
257263
258- writer := zip .NewWriter (bufio .NewWriter (zipfile ))
264+ bufferedWriter := bufio .NewWriter (zipfile )
265+ writer := zip .NewWriter (bufferedWriter )
259266 out , err := writer .Create (hdOut )
260267 if err != nil {
268+ zipfile .Close ()
261269 return nil , fmt .Errorf ("failed to create zip file: %w" , err )
262270 }
263271
264272 _ , err = io .Copy (out , hd )
265273 if err != nil {
274+ zipfile .Close ()
266275 return nil , fmt .Errorf ("failed to zip heap dump file: %w" , err )
267276 }
268277
278+ // Close zip writer first to write central directory records
269279 err = writer .Close ()
270280 if err != nil {
271- return nil , fmt .Errorf ("failed to finish zipping heap dump file: %w" , err )
281+ zipfile .Close ()
282+ return nil , fmt .Errorf ("failed to close zip writer: %w" , err )
283+ }
284+
285+ // Flush the buffered writer to ensure all data is written to the file
286+ err = bufferedWriter .Flush ()
287+ if err != nil {
288+ zipfile .Close ()
289+ return nil , fmt .Errorf ("failed to flush zip file buffer: %w" , err )
290+ }
291+
292+ // Close the file to ensure all data is synced to disk
293+ err = zipfile .Close ()
294+ if err != nil {
295+ return nil , fmt .Errorf ("failed to close zip file: %w" , err )
272296 }
273297
274- e := zipfile .Sync ()
275- if e != nil && ! errors .Is (e , os .ErrClosed ) {
276- logger .Log ("failed to sync file %s" , e )
298+ // Reopen the file for reading to pass to the upload function
299+ zipfile , err = os .Open (hdZip )
300+ if err != nil {
301+ return nil , fmt .Errorf ("failed to reopen zip file for upload: %w" , err )
277302 }
278303
279304 return zipfile , nil
@@ -288,19 +313,6 @@ func (t *HeapDump) UploadCapturedFile(file *os.File, contentEncoding string) Res
288313 }
289314}
290315
291- // isCompressedHeapFile checks if the given file is a compressed heap dump file
292- // and returns the extension as the MIME type if it is.
293- func isCompressedHeapFile (filePath string ) (bool , string ) {
294- ext := strings .TrimPrefix (filepath .Ext (filePath ), "." )
295-
296- for _ , compressedExt := range compressedHeapExtensions {
297- if ext == compressedExt {
298- return true , ext
299- }
300- }
301- return false , ext
302- }
303-
304316// heapDump runs the JDK tool (jcmd, jattach, etc) to capture the heap dump to the requested file.
305317// The returned actualDumpPath is the actual file name written to is returned.
306318// In IBM JDK, this may not be the same as the requested filename for several reasons:
0 commit comments