DiagnosticsOutputDirectory.java revision 13431:fa2686ded3a7
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.nio.file.FileVisitResult;
29import java.nio.file.Files;
30import java.nio.file.Path;
31import java.nio.file.Paths;
32import java.nio.file.SimpleFileVisitor;
33import java.nio.file.attribute.BasicFileAttributes;
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.List;
37import java.util.zip.Deflater;
38import java.util.zip.ZipEntry;
39import java.util.zip.ZipOutputStream;
40
41import org.graalvm.compiler.options.OptionValues;
42
43/**
44 * Manages a directory into which diagnostics such crash reports and dumps should be written. The
45 * directory is archived and deleted when {@link #close()} is called.
46 */
47public class DiagnosticsOutputDirectory {
48
49    /**
50     * Use an illegal file name to denote that {@link #close()} has been called.
51     */
52    private static final String CLOSED = "\u0000";
53
54    public DiagnosticsOutputDirectory(OptionValues options) {
55        this.options = options;
56    }
57
58    private final OptionValues options;
59
60    private String path;
61
62    /**
63     * Gets the path to the output directory managed by this object, creating if it doesn't exist
64     * and has not been deleted.
65     *
66     * @returns the directory or {@code null} if the could not be created or has been deleted
67     */
68    public String getPath() {
69        return getPath(true);
70    }
71
72    /**
73     * Gets a unique identifier for this execution such as a process ID.
74     */
75    protected String getExecutionID() {
76        try {
77            String runtimeName = java.lang.management.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        } catch (LinkageError err) {
88            return String.valueOf(org.graalvm.compiler.debug.PathUtilities.getGlobalTimeStamp());
89        }
90    }
91
92    private synchronized String getPath(boolean createIfNull) {
93        if (path == null && createIfNull) {
94            path = createPath();
95            File dir = new File(path).getAbsoluteFile();
96            if (!dir.exists()) {
97                dir.mkdirs();
98                if (!dir.exists()) {
99                    TTY.println("Warning: could not create Graal diagnostic directory " + dir);
100                    return null;
101                }
102            }
103        }
104        return CLOSED.equals(path) ? null : path;
105    }
106
107    /**
108     * Gets the path of the directory to be created.
109     *
110     * Subclasses can override this to determine how the path name is created.
111     *
112     * @return the path to be created
113     */
114    protected String createPath() {
115        Path baseDir;
116        try {
117            baseDir = DebugOptions.getDumpDirectory(options);
118        } catch (IOException e) {
119            // Default to current directory if there was a problem creating the
120            // directory specified by the DumpPath option.
121            baseDir = Paths.get(".");
122        }
123        return baseDir.resolve("graal_diagnostics_" + getExecutionID()).toAbsolutePath().toString();
124    }
125
126    /**
127     * Archives and deletes this directory if it exists.
128     */
129    public void close() {
130        archiveAndDelete();
131    }
132
133    /**
134     * Archives and deletes the {@linkplain #getPath() output directory} if it exists.
135     */
136    private synchronized void archiveAndDelete() {
137        String outDir = getPath(false);
138        if (outDir != null) {
139            // Notify other threads calling getPath() that the directory is deleted.
140            // This attempts to mitigate other threads writing to the directory
141            // while it is being archived and deleted.
142            path = CLOSED;
143
144            Path dir = Paths.get(outDir);
145            if (dir.toFile().exists()) {
146                File zip = new File(outDir + ".zip").getAbsoluteFile();
147                List<Path> toDelete = new ArrayList<>();
148                try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zip))) {
149                    zos.setLevel(Deflater.BEST_COMPRESSION);
150                    Files.walkFileTree(dir, Collections.emptySet(), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
151                        @Override
152                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
153                            if (attrs.isRegularFile()) {
154                                String name = dir.relativize(file).toString();
155                                ZipEntry ze = new ZipEntry(name);
156                                zos.putNextEntry(ze);
157                                zos.write(Files.readAllBytes(file));
158                                zos.closeEntry();
159                            }
160                            toDelete.add(file);
161                            return FileVisitResult.CONTINUE;
162                        }
163
164                        @Override
165                        public FileVisitResult postVisitDirectory(Path d, IOException exc) throws IOException {
166                            toDelete.add(d);
167                            return FileVisitResult.CONTINUE;
168                        }
169                    });
170                    // Keep this in sync with the catch_files in common.hocon
171                    TTY.println("Graal diagnostic output saved in %s", zip);
172                } catch (IOException e) {
173                    TTY.printf("IO error archiving %s:%n%s. The directory will not be deleted and must be " +
174                                    "manually removed once the VM exits.%n", dir, e);
175                    toDelete.clear();
176                }
177                if (!toDelete.isEmpty()) {
178                    IOException lastDeletionError = null;
179                    for (Path p : toDelete) {
180                        try {
181                            Files.delete(p);
182                        } catch (IOException e) {
183                            lastDeletionError = e;
184                        }
185                    }
186                    if (lastDeletionError != null) {
187                        TTY.printf("IO error deleting %s:%n%s. This is most likely due to a compilation on " +
188                                        "another thread holding a handle to a file within this directory. " +
189                                        "Please delete the directory manually once the VM exits.%n", dir, lastDeletionError);
190                    }
191                }
192            }
193        }
194    }
195}
196