All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.openhft.chronicle.core.OS Maven / Gradle / Ivy

There is a newer version: 2.27ea1
Show newest version
/*
 * Copyright 2016-2020 chronicle.software
 *
 *       https://chronicle.software
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.openhft.chronicle.core;

import net.openhft.chronicle.core.internal.Bootstrap;
import net.openhft.chronicle.core.util.ClassLocal;
import net.openhft.chronicle.core.util.ThrowingFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.nio.ch.FileChannelImpl;

import javax.naming.TimeLimitExceededException;
import java.io.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.FileChannel;
import java.security.SecureRandom;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import static java.lang.management.ManagementFactory.getRuntimeMXBean;
import static net.openhft.chronicle.core.util.Longs.*;

/**
 * Low level access to OS class. The OS class provides utility methods related to the operating system.
 */
public final class OS {
    @Deprecated(/* to be removed in x.26, use getUserDir() */)
    public static final String USER_DIR = Jvm.getProperty("user.dir");

    public static final String TMP = findTmp();

    public static final String USER_HOME = Jvm.getProperty("user.home");
    public static final Exception TIME_LIMIT = new TimeLimitExceededException();
    public static final int SAFE_PAGE_SIZE = 64 << 10;
    static final ClassLocal MAP0_MH = ClassLocal.withInitial(c -> {
        try {
            Method map0;
            if (Jvm.isJava20Plus()) {
                Class dispatcherClass = OS.isWindows() ? findClass("sun.nio.ch.FileDispatcherImpl") : findClass("sun.nio.ch.UnixFileDispatcherImpl");
                map0 = Jvm.getMethod(dispatcherClass, "map0", FileDescriptor.class, int.class, long.class, long.class, boolean.class);
            } else if (Jvm.isJava19Plus()) {
                map0 = Jvm.getMethod(c, "map0", FileDescriptor.class, int.class, long.class, long.class, boolean.class);
            } else if (Jvm.isJava14Plus()) {
                map0 = Jvm.getMethod(c, "map0", int.class, long.class, long.class, boolean.class);
            } else {
                map0 = Jvm.getMethod(c, "map0", int.class, long.class, long.class);
            }
            return MethodHandles.lookup().unreflect(map0);
        } catch (IllegalAccessException e) {
            throw new AssertionError(e);
        }
    });
    private static final Field FD_FIELD = Jvm.getField(FileChannelImpl.class, "fd");
    private static final String TARGET = findTarget();
    private static final String USER_NAME = Jvm.getProperty("user.name");
    private static final int MAP_RO = 0;
    private static final int MAP_RW = 1;
    private static final int MAP_PV = 2;
    private static final boolean IS64BIT = is64Bit0();
    private static final AtomicInteger PROCESS_ID = new AtomicInteger();
    private static final AtomicLong memoryMapped = new AtomicLong();
    private static final MethodHandle UNMAPP0_MH;
    private static final MethodHandle READ0_MH;
    private static final MethodHandle WRITE0_MH;
    private static final MethodHandle WRITE0_MH2;
    private static final String PROC_SELF = "/proc/self";
    private static final String PROC_SYS_KERNEL_PID_MAX = "/proc/sys/kernel/pid_max";
    private static int pageSize; // avoid circular initialisation
    private static int mapAlignment;

    static {
        // make sure it is initialised first.
        Jvm.debug();
        try {
            Method unmap0;
            if (Jvm.isJava20Plus()) {
                Class dispatcherClass = OS.isWindows() ? findClass("sun.nio.ch.FileDispatcherImpl") : findClass("sun.nio.ch.UnixFileDispatcherImpl");
                unmap0 = Jvm.getMethod(dispatcherClass, "unmap0", long.class, long.class);
            } else {
                unmap0 = Jvm.getMethod(FileChannelImpl.class, "unmap0", long.class, long.class);
            }
            UNMAPP0_MH = MethodHandles.lookup().unreflect(unmap0);

            Class fdi = Class.forName("sun.nio.ch.FileDispatcherImpl");
            Method read0 = Jvm.getMethod(fdi, "read0", FileDescriptor.class, long.class, int.class);
            READ0_MH = MethodHandles.lookup().unreflect(read0);

            final WriteZero wz = new WriteZero(fdi);
            WRITE0_MH = wz.write0Mh;
            WRITE0_MH2 = wz.write0Mh2;

            TIME_LIMIT.setStackTrace(new StackTraceElement[0]);

        } catch (IllegalAccessException | ClassNotFoundException e) {
            throw new AssertionError(e);
        }
    }

    // Suppresses default constructor, ensuring non-instantiability.
    private OS() {
    }

    private static Class findClass(String name) {
        try {
            Class clazz = Thread.currentThread().getContextClassLoader().loadClass(name);
            return clazz;
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("Not found: " + name, e);
        }
    }

    /**
     * Checks if the operating system supports sparse files
     *
     * @return if the OS supports sparse files
     */
    public static boolean isSparseFileSupported() {
        return OS.isLinux() && OS.is64Bit();
    }

    /**
     * Finds the temporary directory path.
     *
     * @return the path of the temporary directory
     */
    static String findTmp() {
        return asRelativePath(findTmp0());
    }

    private static String findTmp0() {
        String target = Jvm.getProperty("project.build.directory");
        if (target != null) {
            final File tmp = new File(target, "tmp");
            tmp.mkdir();
            return tmp.getPath();
        }
        final String tmp = Jvm.getProperty("java.io.tmpdir");
        if (tmp != null
                && new File(tmp).isDirectory()
                && new File(tmp).canWrite())
            return tmp;
        new File("tmp").mkdirs();
        return "tmp";
    }

    @NotNull
    private static String findTarget() {
        String path = findTarget0();
        return asRelativePath(path);
    }

    @NotNull
    private static String asRelativePath(String path) {
        String userDir = new File(USER_DIR).getAbsolutePath();
        if (!userDir.endsWith(File.separator))
            userDir += File.separator;
        if (path.startsWith(userDir)) {
            if (path.equals(userDir))
                return ".";
            return path.substring(userDir.length());
        }
        return path;
    }

    private static String findTarget0() {
        String target = Jvm.getProperty("project.build.directory");
        if (target != null)
            return target;
        for (File dir = new File(Jvm.getProperty("user.dir")); dir != null; dir = dir.getParentFile()) {
            @NotNull File mavenTarget = new File(dir, "target");
            if (mavenTarget.exists())
                return mavenTarget.getAbsolutePath();
            @NotNull File gradleTarget = new File(dir, "build");
            if (gradleTarget.exists())
                return gradleTarget.getAbsolutePath();
        }
        final File dir = new File(Jvm.getProperty("java.io.tmpdir"), "target");
        dir.mkdirs();
        return dir.getPath();
    }

    /**
     * Finds a directory with the specified suffix in the class path.
     *
     * @param suffix the suffix of the directory
     * @return the path of the found directory
     * @throws FileNotFoundException if no directory with the specified suffix is found
     */
    @NotNull
    public static String findDir(@NotNull String suffix) throws FileNotFoundException {
        String[] split = Jvm.getProperty("java.class.path").split(File.pathSeparator);
        for (@NotNull String s : split) {
            if (s.endsWith(suffix) && new File(s).isDirectory())
                return s;
        }
        throw new FileNotFoundException(suffix);
    }

    /**
     * Search a list of directories to find a path which is the last element.
     *
     * @param path of directories to use if found, the last path is always appended.
     * @return the resulting File path.
     */
    @NotNull
    public static File findFile(@NotNull String... path) {
        @NotNull File dir = new File(".").getAbsoluteFile();
        for (int i = 0; i < path.length - 1; i++) {
            @NotNull File dir2 = new File(dir, path[i]);
            if (dir2.isDirectory())
                dir = dir2;
        }
        return new File(dir, path[path.length - 1]);
    }

    /**
     * Returns the host name of the current machine.
     *
     * @return the host name
     */
    public static String getHostName() {
        return HostnameHolder.HOST_NAME;
    }

    /**
     * Returns the IP address of the current machine.
     *
     * @return the IP address
     */
    public static String getIPAddress() {
        return IPAddressHolder.IP_ADDRESS;
    }

    /**
     * Returns the username of the current user.
     *
     * @return the username
     */
    public static String getUserName() {
        return USER_NAME;
    }

    /**
     * Returns the target of the operating system.
     *
     * @return the target
     */
    public static String getTarget() {
        return TARGET;
    }

    /**
     * Returns the temporary directory path.
     *
     * @return the temporary directory path
     */
    public static String getTmp() {
        return TMP;
    }

    /**
     * @return native memory accessor class
     */
    @NotNull
    public static Memory memory() {
        return UnsafeMemory.INSTANCE;
    }

    /**
     * Align the size to page boundary
     *
     * @param size     the size to align
     * @param pageSize
     * @return aligned size
     * @see #pageSize()
     */
    public static long pageAlign(long size, int pageSize) {
        final long mask = pageSize - 1L;
        return (size + mask) & ~mask;
    }

    /**
     * Align the size to page boundary
     *
     * @param size the size to align
     * @return aligned size
     * @see #pageSize()
     */
    public static long pageAlign(long size) {
        return pageAlign(size, pageSize());
    }

    /**
     * @return size of pages
     * @see #pageAlign(long)
     */
    public static int pageSize() {
        if (pageSize == 0)
            pageSize = memory().pageSize();
        return pageSize;
    }

    /**
     * Returns default OS page size.
     */
    public static int defaultOsPageSize() {
        // Windows 10 produces this error for alignment of less than 64K
        // java.io.IOException: The base address or the file offset specified does not have the proper alignment
        // c.f. https://docs.microsoft.com/en-us/windows/win32/memory/creating-a-view-within-a-file
        return isWindows() ? SAFE_PAGE_SIZE : pageSize();
    }
    /**
     * Aligns the specified offset for a memory-mapped file based on the operating system's page size.
     * Memory mapping typically requires that the offset be aligned to the operating system's page size.
     * This method calculates the nearest higher offset that meets this alignment requirement.
     *
     * 

For example, if the operating system's page size is 4096 bytes and an offset of 6000 is specified, * this method would return 8192, as 8192 is the next multiple of 4096 greater than 6000. * * @param offset the offset to be aligned. It must be non-negative. * @return the aligned offset. * @throws IllegalArgumentException if offset is negative. * @see #mapAlignment() */ public static long mapAlign(long offset) { return mapAlign(offset, defaultOsPageSize()); } /** * Aligns the specified offset for a memory-mapped file to the nearest higher multiple of the given pageAlignment. * This method calculates the nearest higher offset that meets this alignment requirement. * *

For example, if the provided alignment is 2M bytes and an offset of 2.5M is specified, * this method would return 4M, as 4M is the next multiple of 2M greater than 2.5M. * * @param offset the offset to be aligned. It must be non-negative. * @param pageAlignment the alignment size, in bytes. This should typically be the operating system's page size or a multiple thereof. * @return the aligned offset. * @throws IllegalArgumentException if offset is negative or pageAlignment is non-positive. */ public static long mapAlign(long offset, int pageAlignment) { requireNonNegative(offset); require(positive(), pageAlignment); return (offset + pageAlignment - 1) / pageAlignment * pageAlignment; } /** * Returns the alignment of offsets in file, from which memory mapping could start, based on OS. * * @return granularity of an offset in a file * @see #mapAlign(long) */ public static long mapAlignment() { if (mapAlignment == 0) mapAlignment = defaultOsPageSize(); return mapAlignment; } /** * @return is the JVM 64-bit */ public static boolean is64Bit() { return IS64BIT; } private static boolean is64Bit0() { String systemProp; systemProp = Jvm.getProperty("com.ibm.vm.bitmode"); if (systemProp != null) { return "64".equals(systemProp); } systemProp = Jvm.getProperty("sun.arch.data.model"); if (systemProp != null) { return "64".equals(systemProp); } systemProp = Jvm.getProperty("java.vm.version"); return systemProp != null && systemProp.contains("_64"); } /** * Returns the process ID of the current running process. * *

* Note: Getting the process ID may be slow if the reserve DNS is not set up correctly. * * * @return the process ID */ public static int getProcessId() { // getting the process id is slow if the reserve DNS is not setup correctly. // which is frustrating since we don't actually use the hostname. int id = PROCESS_ID.get(); if (id == 0) { id = getProcessId0(); PROCESS_ID.set(id); } return id; } /** * Returns the process ID of the current running process. * * @return the process ID */ static int getProcessId0() { @Nullable String pid = null; @NotNull final File self = new File(PROC_SELF); try { if (self.exists()) pid = self.getCanonicalFile().getName(); } catch (IOException ignored) { // ignored } if (pid == null) pid = getRuntimeMXBean().getName().split("@", 0)[0]; if (pid != null) { try { return Integer.parseInt(pid); } catch (NumberFormatException e) { // ignored } } final int minPid = 2; final int rpid = minPid + new SecureRandom().nextInt((1 << 16) - minPid); Jvm.warn().on(OS.class, "Unable to determine PID, picked a random number=" + rpid); return rpid; } /** * Returns if this JVM runs on the Windows operating system. * * @return if runs on Windows */ public static boolean isWindows() { return Bootstrap.IS_WIN; } /** * Returns if this JVM runs on the MacOS operating system. * * @return if runs on MacOS */ public static boolean isMacOSX() { return Bootstrap.IS_MAC; } /** * Returns if this JVM runs on the Linux operating system. * * @return if runs on Linux */ public static boolean isLinux() { return Bootstrap.IS_LINUX; } /** * @return the maximum PID. */ public static long getPidMax() { if (isLinux()) { @NotNull File file = new File(PROC_SYS_KERNEL_PID_MAX); if (file.canRead()) try { try (Scanner scanner = new Scanner(file)) { return Maths.nextPower2(scanner.nextLong(), 1); } } catch (FileNotFoundException e) { Jvm.debug().on(OS.class, e); } } else if (isMacOSX()) { return 1L << 24; } // the default. return Bootstrap.IS_WIN10 ? 1L << 32 : 1L << 16; } /** * Map a region of a file into memory. * * @param fileChannel to map * @param mode of access * @param start offset within a file * @param size of region to map. * @return the address of the memory mapping. * @throws IOException if the mapping fails * @throws IllegalArgumentException if the arguments are not valid */ public static long map(@NotNull FileChannel fileChannel, FileChannel.MapMode mode, long start, long size, int pageSize) throws IOException, IllegalArgumentException { if (isWindows() && size > 4L << 30) throw new IllegalArgumentException("Mapping more than 4096 MiB is unusable on Windows, size = " + (size >> 20) + " MiB"); final long address = map0(fileChannel, imodeFor(mode), mapAlign(start, pageSize), pageAlign(size, pageSize)); final long threshold = Math.min(64 * size, 32L << 40); if (isLinux() && (address > 0 && address < threshold) && Jvm.is64bit()) { double ratio = (double) threshold / address; final long durationMs = Math.max(5000, (long) (250 * ratio * ratio * ratio)); System.err.println("Running low on virtual memory, pausing " + durationMs + " ms, address: " + Long.toUnsignedString(address, 16)); Jvm.pause(durationMs); } return address; } public static long map(@NotNull FileChannel fileChannel, FileChannel.MapMode mode, long start, long size) throws IOException, IllegalArgumentException { return map(fileChannel, mode, start, size, (int) mapAlignment()); } private static long invokeFileChannelMap0(@NotNull MethodHandle map0, @NotNull FileChannel fileChannel, int imode, long start, long size, @NotNull ThrowingFunction errorHandler) throws IOException { try { // For now, access is assumed to be non-synchronous // TODO - Support passing/deducing synchronous flag externally if (Jvm.isJava20Plus()) { final FileDescriptor fd = (FileDescriptor) FD_FIELD.get(fileChannel); return (long) map0.invokeExact(fd, imode, start, size, false); } else if (Jvm.isJava19Plus()) { final FileDescriptor fd = (FileDescriptor) FD_FIELD.get(fileChannel); return (long) map0.invokeExact((FileChannelImpl) fileChannel, fd, imode, start, size, false); } else if (Jvm.isJava14Plus()) return (long) map0.invokeExact((FileChannelImpl) fileChannel, imode, start, size, false); else return (long) map0.invokeExact((FileChannelImpl) fileChannel, imode, start, size); } catch (IllegalAccessException e) { throw new AssertionError("Method map0 is not accessible", e); } catch (OutOfMemoryError oom) { return errorHandler.apply(oom); } catch (IOException ioe) { throw ioe; } catch (Throwable e) { throw new IOException(e); } } static long map0(@NotNull FileChannel fileChannel, int imode, long start, long size) throws IOException { MethodHandle map0 = MAP0_MH.get(fileChannel.getClass()); final long address = invokeFileChannelMap0(map0, fileChannel, imode, start, size, oome1 -> { System.gc(); Jvm.pause(100); return invokeFileChannelMap0(map0, fileChannel, imode, start, size, oome2 -> { throw new IOException("Map failed", oome2); }); }); memoryMapped.addAndGet(size); return address; } /** * Unmap a region of memory. * * @param address of the start of the mapping. * @param size of the region mapped. * @throws IOException if the unmap fails. */ public static void unmap(long address, long size, int pageSize) throws IOException { try { final long size2 = pageAlign(size, pageSize); // n must be used here final int n = (int) UNMAPP0_MH.invokeExact(address, size2); memoryMapped.addAndGet(-size2); } catch (Throwable e) { throw asAnIOException(e); } } public static void unmap(long address, long size) throws IOException { unmap(address, size, (int) mapAlignment()); } /** * Returns the number of bytes that is memory mapped. * * @return bytes memory mapped */ public static long memoryMapped() { return memoryMapped.get(); } @NotNull private static IOException asAnIOException(Throwable e) { if (e instanceof InvocationTargetException) e = e.getCause(); if (e instanceof IOException) return (IOException) e; return new IOException(e); } static int imodeFor(FileChannel.MapMode mode) { int imode = -1; if (FileChannel.MapMode.READ_ONLY.equals(mode)) imode = MAP_RO; else if (FileChannel.MapMode.READ_WRITE.equals(mode)) imode = MAP_RW; else if (FileChannel.MapMode.PRIVATE.equals(mode)) imode = MAP_PV; assert (imode >= 0); return imode; } /** * Get the space actually used by a file. * * @param filename to get the actual size of * @return size in bytes. */ public static long spaceUsed(@NotNull String filename) { return spaceUsed(new File(filename)); } private static long spaceUsed(@NotNull File file) { if (!isWindows()) { try { final String du = run("du", "-ks", file.getAbsolutePath()); return Long.parseLong(du.substring(0, du.indexOf('\t'))); } catch (@NotNull IOException | NumberFormatException e) { Jvm.warn().on(OS.class, e); } } return file.length(); } private static String run(String... cmds) throws IOException { @NotNull ProcessBuilder pb = new ProcessBuilder(cmds); pb.redirectErrorStream(true); Process process = pb.start(); @NotNull StringWriter sw = new StringWriter(); char @NotNull [] chars = new char[1024]; try (@NotNull Reader r = new InputStreamReader(process.getInputStream())) { for (int len; (len = r.read(chars)) > 0; ) { sw.write(chars, 0, len); } } return sw.toString(); } /** * Returns the current working directory of the user. * * @return the user's current working directory */ public static String userDir() { return USER_DIR; } public static int read0(FileDescriptor fd, long address, int len) throws IOException { try { return (int) READ0_MH.invokeExact(fd, address, len); } catch (IOException ioe) { throw ioe; } catch (Throwable e) { throw new IOException(e); } } public static int write0(FileDescriptor fd, long address, int len) throws IOException { try { if (WRITE0_MH2 == null) return (int) WRITE0_MH.invokeExact(fd, address, len); else return (int) WRITE0_MH2.invokeExact(fd, address, len, false); } catch (IOException ioe) { throw ioe; } catch (Throwable e) { throw new IOException(e); } } private static boolean isSet(String s) { return !(s == null || s.isEmpty()); } private static final class WriteZero { private MethodHandle write0Mh = null; private MethodHandle write0Mh2 = null; public WriteZero(final Class fdi) throws IllegalAccessException { try { Method write0 = Jvm.getMethod(fdi, "write0", FileDescriptor.class, long.class, int.class); write0Mh = MethodHandles.lookup().unreflect(write0); } catch (AssertionError ae) { Method write0 = Jvm.getMethod(fdi, "write0", FileDescriptor.class, long.class, int.class, boolean.class); write0Mh2 = MethodHandles.lookup().unreflect(write0); } } } @Deprecated(/* to be moved in x.26 to an internal package of Chronicle-Bytes */) public static final class Unmapper implements Runnable { private final long size; private final int pageSize; private volatile long address; public Unmapper(long address, long size, int pageSize) throws IllegalStateException { assert (address != 0); this.address = address; this.size = size; this.pageSize = pageSize; } @Override public void run() { if (address == 0) return; try { unmap(address, size, pageSize); address = 0; } catch (@NotNull IOException e) { Jvm.warn().on(OS.class, "Error on unmap and release", e); } } } static class IPAddressHolder { public static final String NO_ADDRESS = "0.0.0.0"; static final String IP_ADDRESS = getIPAddress0(); static String getIPAddress0() { String addr = getIpAddressByLocalHost(); if (isSet(addr)) return addr; addr = getIpAddressByDatagram(); if (isSet(addr)) return addr; addr = getIpAddressBySocket(); if (isSet(addr)) return addr; return NO_ADDRESS; } static boolean isSet(String s) { return !(s == null || s.isEmpty() || s.equals(NO_ADDRESS)); } static String getIpAddressByLocalHost() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (Throwable e) { return ""; } } static String getIpAddressBySocket() { try { try (Socket socket = new Socket()) { socket.connect(new InetSocketAddress("google.com", 80)); return socket.getLocalAddress().getHostAddress(); } } catch (Throwable e) { return ""; } } static String getIpAddressByDatagram() { try { try (final DatagramSocket socket = new DatagramSocket()) { socket.connect(InetAddress.getByName("8.8.8.8"), 10002); return socket.getLocalAddress().getHostAddress(); } } catch (Throwable e) { return null; } } } static class HostnameHolder { static final String HOST_NAME = getHostName0(); private static String getHostName0() { if (isWindows()) { String computerName = System.getenv().get("COMPUTERNAME"); if (isSet(computerName)) return computerName.toLowerCase(); } try { return InetAddress.getLocalHost().getHostName(); } catch (Throwable e) { try { return execHostname(); } catch (IOException ioe) { return "localhost"; } } } static String execHostname() throws IOException { try (BufferedReader br = new BufferedReader( new InputStreamReader( Runtime.getRuntime().exec("hostname") .getInputStream()))) { return br.readLine(); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy