DiagnosticsOutputDirectory.java revision 13304:5e9c41536bd2
1/*
2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.graalvm.compiler.debug;
24
25import java.io.File;
26import java.io.FileOutputStream;
27import java.io.IOException;
28import java.lang.management.ManagementFactory;
29import java.nio.file.FileVisitResult;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.nio.file.Paths;
33import java.nio.file.SimpleFileVisitor;
34import java.nio.file.attribute.BasicFileAttributes;
35import java.util.ArrayList;
36import java.util.Collections;
37import java.util.List;
38import java.util.zip.Deflater;
39import java.util.zip.ZipEntry;
40import java.util.zip.ZipOutputStream;
41
42import org.graalvm.compiler.options.OptionValues;
43
44/**
45 * Manages a directory into which diagnostics such crash reports and dumps should be written. The
46 * directory is archived and deleted when {@link #close()} is called.
47 */
48public class DiagnosticsOutputDirectory {
49
50    /**
51     * Use an illegal file name to denote that {@link #close()} has been called.
52     */
53    private static final String CLOSED = "\u0000";
54
55    public DiagnosticsOutputDirectory(OptionValues options) {
56        this.options = options;
57    }
58
59    private final OptionValues options;
60
61    private String path;
62
63    /**
64     * Gets the path to the output directory managed by this object, creating if it doesn't exist
65     * and has not been deleted.
66     *
67     * @returns the directory or {@code null} if the could not be created or has been deleted
68     */
69    public String getPath() {
70        return getPath(true);
71    }
72
73    /**
74     * Gets a unique identifier for this execution such as a process ID.
75     */
76    protected String getExecutionID() {
77        String runtimeName = ManagementFactory.getRuntimeMXBean().getName();
78        try {
79            int index = runtimeName.indexOf('@');
80            if (index != -1) {
81                long pid = Long.parseLong(runtimeName.substring(0, index));
82                return Long.toString(pid);
83            }
84        } catch (NumberFormatException e) {
85        }
86        return runtimeName;
87    }
88
89    private synchronized String getPath(boolean createIfNull) {
90        if (path == null && createIfNull) {
91            path = createPath();
92            File dir = new File(path).getAbsoluteFile();
93            if (!dir.exists()) {
94                dir.mkdirs();
95                if (!dir.exists()) {
96                    TTY.println("Warning: could not create Graal diagnostic directory " + dir);
97                    return null;
98                }
99            }
100        }
101        return CLOSED.equals(path) ? null : path;
102    }
103
104    /**
105     * Gets the path of the directory to be created.
106     *
107     * Subclasses can override this to determine how the path name is created.
108     *
109     * @return the path to be created
110     */
111    protected String createPath() {
112        Path baseDir;
113        try {
114            baseDir = DebugOptions.getDumpDirectory(options);
115        } catch (IOException e) {
116            // Default to current directory if there was a problem creating the
117            // directory specified by the DumpPath option.
118            baseDir = Paths.get(".");
119        }
120        return baseDir.resolve("graal_diagnostics_" + getExecutionID()).toAbsolutePath().toString();
121    }
122
123    /**
124     * Archives and deletes this directory if it exists.
125     */
126    public void close() {
127        archiveAndDelete();
128    }
129
130    /**
131     * Archives and deletes the {@linkplain #getPath() output directory} if it exists.
132     */
133    private synchronized void archiveAndDelete() {
134        String outDir = getPath(false);
135        if (outDir != null) {
136            // Notify other threads calling getPath() that the directory is deleted.
137            // This attempts to mitigate other threads writing to the directory
138            // while it is being archived and deleted.
139            path = CLOSED;
140
141            Path dir = Paths.get(outDir);
142            if (dir.toFile().exists()) {
143                File zip = new File(outDir + ".zip").getAbsoluteFile();
144                List<Path> toDelete = new ArrayList<>();
145                try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zip))) {
146                    zos.setLevel(Deflater.BEST_COMPRESSION);
147                    Files.walkFileTree(dir, Collections.emptySet(), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
148                        @Override
149                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
150                            if (attrs.isRegularFile()) {
151                                ZipEntry ze = new ZipEntry(file.toString());
152                                zos.putNextEntry(ze);
153                                zos.write(Files.readAllBytes(file));
154                                zos.closeEntry();
155                            }
156                            toDelete.add(file);
157                            return FileVisitResult.CONTINUE;
158                        }
159
160                        @Override
161                        public FileVisitResult postVisitDirectory(Path d, IOException exc) throws IOException {
162                            toDelete.add(d);
163                            return FileVisitResult.CONTINUE;
164                        }
165                    });
166                    // Keep this in sync with the catch_files in common.hocon
167                    TTY.println("Graal diagnostic output saved in %s", zip);
168                } catch (IOException e) {
169                    TTY.printf("IO error archiving %s:%n%s. The directory will not be deleted and must be " +
170                                    "manually removed once the VM exits.%n", dir, e);
171                    toDelete.clear();
172                }
173                if (!toDelete.isEmpty()) {
174                    IOException lastDeletionError = null;
175                    for (Path p : toDelete) {
176                        try {
177                            Files.delete(p);
178                        } catch (IOException e) {
179                            lastDeletionError = e;
180                        }
181                    }
182                    if (lastDeletionError != null) {
183                        TTY.printf("IO error deleting %s:%n%s. This is most likely due to a compilation on " +
184                                        "another thread holding a handle to a file within this directory. " +
185                                        "Please delete the directory manually once the VM exits.%n", dir, lastDeletionError);
186                    }
187                }
188            }
189        }
190    }
191}
192