1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.spark.network.util; 19 20 import java.io.Closeable; 21 import java.io.File; 22 import java.io.IOException; 23 import java.nio.ByteBuffer; 24 import java.nio.charset.StandardCharsets; 25 import java.util.concurrent.TimeUnit; 26 import java.util.regex.Matcher; 27 import java.util.regex.Pattern; 28 29 import com.google.common.base.Preconditions; 30 import com.google.common.collect.ImmutableMap; 31 import io.netty.buffer.Unpooled; 32 import org.apache.commons.lang3.SystemUtils; 33 import org.slf4j.Logger; 34 import org.slf4j.LoggerFactory; 35 36 /** 37 * General utilities available in the network package. Many of these are sourced from Spark's 38 * own Utils, just accessible within this package. 39 */ 40 public class JavaUtils { 41 private static final Logger logger = LoggerFactory.getLogger(JavaUtils.class); 42 43 /** 44 * Define a default value for driver memory here since this value is referenced across the code 45 * base and nearly all files already use Utils.scala 46 */ 47 public static final long DEFAULT_DRIVER_MEM_MB = 1024; 48 49 /** Closes the given object, ignoring IOExceptions. */ closeQuietly(Closeable closeable)50 public static void closeQuietly(Closeable closeable) { 51 try { 52 if (closeable != null) { 53 closeable.close(); 54 } 55 } catch (IOException e) { 56 logger.error("IOException should not have been thrown.", e); 57 } 58 } 59 60 /** Returns a hash consistent with Spark's Utils.nonNegativeHash(). */ nonNegativeHash(Object obj)61 public static int nonNegativeHash(Object obj) { 62 if (obj == null) { return 0; } 63 int hash = obj.hashCode(); 64 return hash != Integer.MIN_VALUE ? Math.abs(hash) : 0; 65 } 66 67 /** 68 * Convert the given string to a byte buffer. The resulting buffer can be 69 * converted back to the same string through {@link #bytesToString(ByteBuffer)}. 70 */ stringToBytes(String s)71 public static ByteBuffer stringToBytes(String s) { 72 return Unpooled.wrappedBuffer(s.getBytes(StandardCharsets.UTF_8)).nioBuffer(); 73 } 74 75 /** 76 * Convert the given byte buffer to a string. The resulting string can be 77 * converted back to the same byte buffer through {@link #stringToBytes(String)}. 78 */ bytesToString(ByteBuffer b)79 public static String bytesToString(ByteBuffer b) { 80 return Unpooled.wrappedBuffer(b).toString(StandardCharsets.UTF_8); 81 } 82 83 /** 84 * Delete a file or directory and its contents recursively. 85 * Don't follow directories if they are symlinks. 86 * 87 * @param file Input file / dir to be deleted 88 * @throws IOException if deletion is unsuccessful 89 */ deleteRecursively(File file)90 public static void deleteRecursively(File file) throws IOException { 91 if (file == null) { return; } 92 93 // On Unix systems, use operating system command to run faster 94 // If that does not work out, fallback to the Java IO way 95 if (SystemUtils.IS_OS_UNIX) { 96 try { 97 deleteRecursivelyUsingUnixNative(file); 98 return; 99 } catch (IOException e) { 100 logger.warn("Attempt to delete using native Unix OS command failed for path = {}. " + 101 "Falling back to Java IO way", file.getAbsolutePath(), e); 102 } 103 } 104 105 deleteRecursivelyUsingJavaIO(file); 106 } 107 deleteRecursivelyUsingJavaIO(File file)108 private static void deleteRecursivelyUsingJavaIO(File file) throws IOException { 109 if (file.isDirectory() && !isSymlink(file)) { 110 IOException savedIOException = null; 111 for (File child : listFilesSafely(file)) { 112 try { 113 deleteRecursively(child); 114 } catch (IOException e) { 115 // In case of multiple exceptions, only last one will be thrown 116 savedIOException = e; 117 } 118 } 119 if (savedIOException != null) { 120 throw savedIOException; 121 } 122 } 123 124 boolean deleted = file.delete(); 125 // Delete can also fail if the file simply did not exist. 126 if (!deleted && file.exists()) { 127 throw new IOException("Failed to delete: " + file.getAbsolutePath()); 128 } 129 } 130 deleteRecursivelyUsingUnixNative(File file)131 private static void deleteRecursivelyUsingUnixNative(File file) throws IOException { 132 ProcessBuilder builder = new ProcessBuilder("rm", "-rf", file.getAbsolutePath()); 133 Process process = null; 134 int exitCode = -1; 135 136 try { 137 // In order to avoid deadlocks, consume the stdout (and stderr) of the process 138 builder.redirectErrorStream(true); 139 builder.redirectOutput(new File("/dev/null")); 140 141 process = builder.start(); 142 143 exitCode = process.waitFor(); 144 } catch (Exception e) { 145 throw new IOException("Failed to delete: " + file.getAbsolutePath(), e); 146 } finally { 147 if (process != null) { 148 process.destroy(); 149 } 150 } 151 152 if (exitCode != 0 || file.exists()) { 153 throw new IOException("Failed to delete: " + file.getAbsolutePath()); 154 } 155 } 156 listFilesSafely(File file)157 private static File[] listFilesSafely(File file) throws IOException { 158 if (file.exists()) { 159 File[] files = file.listFiles(); 160 if (files == null) { 161 throw new IOException("Failed to list files for dir: " + file); 162 } 163 return files; 164 } else { 165 return new File[0]; 166 } 167 } 168 isSymlink(File file)169 private static boolean isSymlink(File file) throws IOException { 170 Preconditions.checkNotNull(file); 171 File fileInCanonicalDir = null; 172 if (file.getParent() == null) { 173 fileInCanonicalDir = file; 174 } else { 175 fileInCanonicalDir = new File(file.getParentFile().getCanonicalFile(), file.getName()); 176 } 177 return !fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile()); 178 } 179 180 private static final ImmutableMap<String, TimeUnit> timeSuffixes = 181 ImmutableMap.<String, TimeUnit>builder() 182 .put("us", TimeUnit.MICROSECONDS) 183 .put("ms", TimeUnit.MILLISECONDS) 184 .put("s", TimeUnit.SECONDS) 185 .put("m", TimeUnit.MINUTES) 186 .put("min", TimeUnit.MINUTES) 187 .put("h", TimeUnit.HOURS) 188 .put("d", TimeUnit.DAYS) 189 .build(); 190 191 private static final ImmutableMap<String, ByteUnit> byteSuffixes = 192 ImmutableMap.<String, ByteUnit>builder() 193 .put("b", ByteUnit.BYTE) 194 .put("k", ByteUnit.KiB) 195 .put("kb", ByteUnit.KiB) 196 .put("m", ByteUnit.MiB) 197 .put("mb", ByteUnit.MiB) 198 .put("g", ByteUnit.GiB) 199 .put("gb", ByteUnit.GiB) 200 .put("t", ByteUnit.TiB) 201 .put("tb", ByteUnit.TiB) 202 .put("p", ByteUnit.PiB) 203 .put("pb", ByteUnit.PiB) 204 .build(); 205 206 /** 207 * Convert a passed time string (e.g. 50s, 100ms, or 250us) to a time count in the given unit. 208 * The unit is also considered the default if the given string does not specify a unit. 209 */ timeStringAs(String str, TimeUnit unit)210 public static long timeStringAs(String str, TimeUnit unit) { 211 String lower = str.toLowerCase().trim(); 212 213 try { 214 Matcher m = Pattern.compile("(-?[0-9]+)([a-z]+)?").matcher(lower); 215 if (!m.matches()) { 216 throw new NumberFormatException("Failed to parse time string: " + str); 217 } 218 219 long val = Long.parseLong(m.group(1)); 220 String suffix = m.group(2); 221 222 // Check for invalid suffixes 223 if (suffix != null && !timeSuffixes.containsKey(suffix)) { 224 throw new NumberFormatException("Invalid suffix: \"" + suffix + "\""); 225 } 226 227 // If suffix is valid use that, otherwise none was provided and use the default passed 228 return unit.convert(val, suffix != null ? timeSuffixes.get(suffix) : unit); 229 } catch (NumberFormatException e) { 230 String timeError = "Time must be specified as seconds (s), " + 231 "milliseconds (ms), microseconds (us), minutes (m or min), hour (h), or day (d). " + 232 "E.g. 50s, 100ms, or 250us."; 233 234 throw new NumberFormatException(timeError + "\n" + e.getMessage()); 235 } 236 } 237 238 /** 239 * Convert a time parameter such as (50s, 100ms, or 250us) to milliseconds for internal use. If 240 * no suffix is provided, the passed number is assumed to be in ms. 241 */ timeStringAsMs(String str)242 public static long timeStringAsMs(String str) { 243 return timeStringAs(str, TimeUnit.MILLISECONDS); 244 } 245 246 /** 247 * Convert a time parameter such as (50s, 100ms, or 250us) to seconds for internal use. If 248 * no suffix is provided, the passed number is assumed to be in seconds. 249 */ timeStringAsSec(String str)250 public static long timeStringAsSec(String str) { 251 return timeStringAs(str, TimeUnit.SECONDS); 252 } 253 254 /** 255 * Convert a passed byte string (e.g. 50b, 100kb, or 250mb) to the given. If no suffix is 256 * provided, a direct conversion to the provided unit is attempted. 257 */ byteStringAs(String str, ByteUnit unit)258 public static long byteStringAs(String str, ByteUnit unit) { 259 String lower = str.toLowerCase().trim(); 260 261 try { 262 Matcher m = Pattern.compile("([0-9]+)([a-z]+)?").matcher(lower); 263 Matcher fractionMatcher = Pattern.compile("([0-9]+\\.[0-9]+)([a-z]+)?").matcher(lower); 264 265 if (m.matches()) { 266 long val = Long.parseLong(m.group(1)); 267 String suffix = m.group(2); 268 269 // Check for invalid suffixes 270 if (suffix != null && !byteSuffixes.containsKey(suffix)) { 271 throw new NumberFormatException("Invalid suffix: \"" + suffix + "\""); 272 } 273 274 // If suffix is valid use that, otherwise none was provided and use the default passed 275 return unit.convertFrom(val, suffix != null ? byteSuffixes.get(suffix) : unit); 276 } else if (fractionMatcher.matches()) { 277 throw new NumberFormatException("Fractional values are not supported. Input was: " 278 + fractionMatcher.group(1)); 279 } else { 280 throw new NumberFormatException("Failed to parse byte string: " + str); 281 } 282 283 } catch (NumberFormatException e) { 284 String byteError = "Size must be specified as bytes (b), " + 285 "kibibytes (k), mebibytes (m), gibibytes (g), tebibytes (t), or pebibytes(p). " + 286 "E.g. 50b, 100k, or 250m."; 287 288 throw new NumberFormatException(byteError + "\n" + e.getMessage()); 289 } 290 } 291 292 /** 293 * Convert a passed byte string (e.g. 50b, 100k, or 250m) to bytes for 294 * internal use. 295 * 296 * If no suffix is provided, the passed number is assumed to be in bytes. 297 */ byteStringAsBytes(String str)298 public static long byteStringAsBytes(String str) { 299 return byteStringAs(str, ByteUnit.BYTE); 300 } 301 302 /** 303 * Convert a passed byte string (e.g. 50b, 100k, or 250m) to kibibytes for 304 * internal use. 305 * 306 * If no suffix is provided, the passed number is assumed to be in kibibytes. 307 */ byteStringAsKb(String str)308 public static long byteStringAsKb(String str) { 309 return byteStringAs(str, ByteUnit.KiB); 310 } 311 312 /** 313 * Convert a passed byte string (e.g. 50b, 100k, or 250m) to mebibytes for 314 * internal use. 315 * 316 * If no suffix is provided, the passed number is assumed to be in mebibytes. 317 */ byteStringAsMb(String str)318 public static long byteStringAsMb(String str) { 319 return byteStringAs(str, ByteUnit.MiB); 320 } 321 322 /** 323 * Convert a passed byte string (e.g. 50b, 100k, or 250m) to gibibytes for 324 * internal use. 325 * 326 * If no suffix is provided, the passed number is assumed to be in gibibytes. 327 */ byteStringAsGb(String str)328 public static long byteStringAsGb(String str) { 329 return byteStringAs(str, ByteUnit.GiB); 330 } 331 332 /** 333 * Returns a byte array with the buffer's contents, trying to avoid copying the data if 334 * possible. 335 */ bufferToArray(ByteBuffer buffer)336 public static byte[] bufferToArray(ByteBuffer buffer) { 337 if (buffer.hasArray() && buffer.arrayOffset() == 0 && 338 buffer.array().length == buffer.remaining()) { 339 return buffer.array(); 340 } else { 341 byte[] bytes = new byte[buffer.remaining()]; 342 buffer.get(bytes); 343 return bytes; 344 } 345 } 346 347 } 348