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