/*
 * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.svm.core;

import static com.oracle.svm.core.Containers.Options.PreferContainerQuotaForCPUCount;
import static com.oracle.svm.core.Containers.Options.UseContainerSupport;

import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport;

import com.oracle.svm.core.annotate.AutomaticFeature;
import com.oracle.svm.core.jdk.Jvm;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.option.RuntimeOptionKey;
import com.oracle.svm.core.util.VMError;

Provides container awareness to the rest of the VM. The implementation is based on the Container Metrics API from JDK 15.
/** * Provides container awareness to the rest of the VM. * * The implementation is based on the Container Metrics API from JDK 15. */
public class Containers { public static class Options { @Option(help = "Enable detection and runtime container configuration support.")// public static final HostedOptionKey<Boolean> UseContainerSupport = new HostedOptionKey<>(true); @Option(help = "Calculate the container CPU availability based on the value of quotas (if set), when true. " + "Otherwise, use the CPU shares value, provided it is less than quota.")// public static final RuntimeOptionKey<Boolean> PreferContainerQuotaForCPUCount = new RuntimeOptionKey<>(true); }
Sentinel used when the value is unknown.
/** Sentinel used when the value is unknown. */
public static final int UNKNOWN = -1; /*- * PER_CPU_SHARES has been set to 1024 because CPU shares' quota * is commonly used in cloud frameworks like Kubernetes[1], * AWS[2] and Mesos[3] in a similar way. They spawn containers with * --cpu-shares option values scaled by PER_CPU_SHARES. Thus, we do * the inverse for determining the number of possible available * CPUs to the JVM inside a container. See JDK-8216366. * * [1] https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu * In particular: * When using Docker: * The spec.containers[].resources.requests.cpu is converted to its core value, which is potentially * fractional, and multiplied by 1024. The greater of this number or 2 is used as the value of the * --cpu-shares flag in the docker run command. * [2] https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html * [3] https://github.com/apache/mesos/blob/3478e344fb77d931f6122980c6e94cd3913c441d/src/docker/docker.cpp#L648 * https://github.com/apache/mesos/blob/3478e344fb77d931f6122980c6e94cd3913c441d/src/slave/containerizer/mesos/isolators/cgroups/constants.hpp#L30 */ private static final int PER_CPU_SHARES = 1024;
Calculates an appropriate number of active processors for the VM to use. The calculation is based on these two inputs:
  • cpu quota & cpu period
  • cpu shares
Returns:number of CPUs
/** * Calculates an appropriate number of active processors for the VM to use. The calculation is * based on these two inputs: * <ul> * <li>cpu quota & cpu period * <li>cpu shares * </ul> * * @return number of CPUs */
public static int activeProcessorCount() { /*- * Algorithm (adapted from `src/hotspot/os/linux/cgroupSubsystem_linux.cpp`): * * If user specified a quota (quota != -1), calculate the number of * required CPUs by dividing quota by period. * * If shares are in effect (shares != -1), calculate the number * of CPUs required for the shares by dividing the share value * by PER_CPU_SHARES. * * All results of division are rounded up to the next whole number. * * If neither shares nor quotas have been specified, return the * number of active processors in the system. * * If both shares and quotas have been specified, the results are * based on the flag PreferContainerQuotaForCPUCount. If true, * return the quota value. If false return the smallest value * between shares and quotas. * * If shares and/or quotas have been specified, the resulting number * returned will never exceed the number of active processors. */ int cpuCount = Jvm.JVM_ActiveProcessorCount(); int limitCount = cpuCount; if (UseContainerSupport.getValue() && Platform.includedIn(Platform.LINUX.class)) { ContainerInfo info = new ContainerInfo(); if (info.isContainerized()) { long quota = info.getCpuQuota(); long period = info.getCpuPeriod(); long shares = info.getCpuShares(); int quotaCount = 0; if (quota > -1 && period > 0) { quotaCount = (int) Math.ceil(((double) quota) / period); } int shareCount = 0; if (shares > -1) { shareCount = (int) Math.ceil(((double) shares) / PER_CPU_SHARES); } if (quotaCount != 0 && shareCount != 0) { /* Both shares and quotas are specified. */ if (PreferContainerQuotaForCPUCount.getValue()) { limitCount = quotaCount; } else { limitCount = Math.min(quotaCount, shareCount); } } else if (quotaCount != 0) { limitCount = quotaCount; } else if (shareCount != 0) { limitCount = shareCount; } } } return Math.min(cpuCount, limitCount); }
Returns the limit of available memory for this process.
Returns:memory limit in bytes or UNKNOWN
/** * Returns the limit of available memory for this process. * * @return memory limit in bytes or {@link Containers#UNKNOWN} */
public static long memoryLimitInBytes() { if (UseContainerSupport.getValue() && Platform.includedIn(Platform.LINUX.class)) { ContainerInfo info = new ContainerInfo(); if (info.isContainerized()) { long memoryLimit = info.getMemoryLimit(); if (memoryLimit > 0) { return memoryLimit; } } } return UNKNOWN; } }
A simple wrapper around the Container Metrics API that abstracts over the used JDK.
/** A simple wrapper around the Container Metrics API that abstracts over the used JDK. */
@SuppressWarnings("static-method") final class ContainerInfo { private static final String ERROR_MSG = "JDK " + JavaVersionUtil.JAVA_SPEC + " specific overlay is missing."; boolean isContainerized() { throw VMError.shouldNotReachHere(ERROR_MSG); } long getCpuQuota() { throw VMError.shouldNotReachHere(ERROR_MSG); } long getCpuPeriod() { throw VMError.shouldNotReachHere(ERROR_MSG); } long getCpuShares() { throw VMError.shouldNotReachHere(ERROR_MSG); } long getMemoryLimit() { throw VMError.shouldNotReachHere(ERROR_MSG); } } @AutomaticFeature @Platforms(Platform.LINUX.class) class ContainersFeature implements Feature { @Override public void duringSetup(DuringSetupAccess access) { RuntimeClassInitializationSupport classInitSupport = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); classInitSupport.initializeAtRunTime("com.oracle.svm.core.containers.cgroupv1.CgroupV1Subsystem", "for cgroup support"); classInitSupport.initializeAtRunTime("com.oracle.svm.core.containers.cgroupv2.CgroupV2Subsystem", "for cgroup support"); } }