Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
gluon-bot committed Sep 1, 2023
2 parents 4061b7a + 63eaafa commit 27867e2
Show file tree
Hide file tree
Showing 20 changed files with 161 additions and 238 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public final class AMD64BitSwapOp extends AMD64LIRInstruction {
public static final LIRInstructionClass<AMD64BitSwapOp> TYPE = LIRInstructionClass.create(AMD64BitSwapOp.class);

@Def({REG}) protected Value dstValue;
@Use({REG}) protected Value srcValue;
@Alive({REG}) protected Value srcValue;

@Temp({REG}) protected Value rtmpValue;
@Temp({REG, ILLEGAL}) protected Value rtmp2Value;
Expand Down
6 changes: 3 additions & 3 deletions docs/reference-manual/native-image/BuildOutput.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ The memory limit and number of threads used by the build process.
More precisely, the memory limit of the Java heap, so actual memory consumption can be even higher.
Please check the [peak RSS](#glossary-peak-rss) reported at the end of the build to understand how much memory was actually used.
By default, the process will only use _available_ memory: memory that the operating system can make available without swapping out memory used by other processes.
Therefore, consider freeing up memory if your build process is slow, for example, by closing applications that you do not need.
Note that, by default, the build process will not use more than 32GB available memory.
By default, the build process tries to only use free memory (to avoid memory pressure on the build machine), and never more than 32GB of memory.
If less than 8GB of memory are free, the build process falls back to use 85% of total memory.
Therefore, consider freeing up memory if your machine is slow during a build, for example, by closing applications that you do not need.
By default, the build process uses all available CPU cores to maximize speed.
Use the `--parallelism` option to set the number of threads explicitly (for example, `--parallelism=4`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,114 +7,128 @@ permalink: /reference-manual/native-image/guides/containerise-native-executable-

# Containerise a Native Executable and Run in a Docker Container

Docker containers provide the flexibility of development environments to match a production environment, to help isolate your application, and to minimize overhead. For self-contained executables, generated with GraalVM Native Image, containers are an obvious deployment scenario.
Docker containers provide the flexibility of development environments to match a production environment, to help isolate your application, and to minimize overhead.
For self-contained executables, generated with GraalVM Native Image, containers are an obvious deployment choice.

To support container-based development, there are several GraalVM container images available, depending on the platform, the architecture, the Java version, and the edition:

- Oracle GraalVM container images can be found in the [Oracle Container Registry](https://container-registry.oracle.com/ords/f?p=113:10::::::)
- GraalVM Community Edition container images can be found in the [GitHub Container Registry](https://github.com/orgs/graalvm/packages)
- Oracle GraalVM container images, available in [Oracle Container Registry (OCR)](https://container-registry.oracle.com) under the [GraalVM Free Terms and Conditions (GFTC) license](https://www.oracle.com/downloads/licenses/graal-free-license.html).
- GraalVM Community Edition container images published in the [GitHub Container Registry](https://github.com/orgs/graalvm/packages).

This guide shows how to containerise a Java application with Docker on macOS.
You will use `ghcr.io/graalvm/jdk:ol8-java17` which is a size compact GraalVM Community container image with the GraalVM JDK pre-installed.
The Dockerfile will be provided.
This guide shows how to containerise a native executable for your Java application.
You will use a GraalVM container image with Native Image to compile a Java application ahead-of-time into a native executable.

### Prerequisites
### Sample Application

- Docker-API compatible container runtime like [Rancher Desktop](https://docs.rancherdesktop.io/getting-started/installation/) or [Docker](https://www.docker.io/gettingstarted/) installed to run MySQL and to run tests using [Testcontainers](https://www.testcontainers.org).
This guide uses the [Spring Boot 3 Native Image Microservice example](https://github.com/graalvm/graalvm-demos/blob/master/spring-native-image/README.md).
The example is a minimal REST-based API application, built on top of Spring Boot 3.
If you call the HTTP endpoint `/jibber`, it will return some nonsense verse generated in the style of the Jabberwocky poem, by Lewis Carroll.

## Note on a Sample Application
## Prerequisites

For the demo you will use the [Spring Boot 3 Native Image Microservice example](https://github.com/graalvm/graalvm-demos/blob/master/spring-native-image/README.md).
1. Download and install the latest Oracle GraalVM from [Downloads](https://www.graalvm.org/downloads/).
The easiest option is to use [SDKMAN!](https://sdkman.io). Run the following command to install Oracle GraalVM for JDK 17:

1. Make sure you have installed a GraalVM JDK.
The easiest way to get started is with [SDKMAN!](https://sdkman.io/jdks#graal).
For other installation options, visit the [Downloads section](https://www.graalvm.org/downloads/).
```bash
sdk install java 17.0.8-graal
```

2. Install and run a Docker-API compatible container runtime such as [Rancher Desktop](https://docs.rancherdesktop.io/getting-started/installation/), [Docker](https://www.docker.io/gettingstarted/), or [Podman](https://podman.io/docs/installation).

2. Clone the [GraalVM Demos repository](https://github.com/graalvm/graalvm-demos) and enter the application directory:
3. Clone the GraalVM Demos repository:

```shell
git clone https://github.com/graalvm/graalvm-demos
```

4. Change directory to the demo directory:

```shell
cd spring-native-image
```

3. Build a native executable and run the application:
## Build and Run as a Native Executable

With the built-in support for GraalVM Native Image in Spring Boot 3, it has become much easier to compile a Spring Boot 3 application into a native executable.

1. Build a native executable:

```shell
mvn -Pnative native:compile
./mvnw native:compile -Pnative
```
The `-Pnative` profile is used to turn on building a native executable with Maven.

This will create a binary executable `target/benchmark-jibber`. Start it to see the application running:

The `-Pnative` profile is used to generate a native executable for your platform.
This will generate a native executable called _benchmark-jibber_ in the _target_ directory.

2. Run the native executable and put it into the background by appending `&`:

```shell
./target/benchmark-jibber &
```

3. Call the endpoint using `curl`:

```shell
curl http://localhost:8080/jibber
fg
```

Now that you have a native executable version of the sample application (`target/jibber`) and seen it working, you can proceed to the next steps.
You should get a random nonsense verse.

4. Bring the application to the foreground using `fg`, and then enter `<CTRL-c>` to terminate the application.

## Containerise the Native Executable

## Containerise a Native Executable
The generated native executable is platform-dependent.

The output of a native executable is platform-dependent.
If you use a Mac or Windows, to build a Docker image containing your native executable, you build a native executable **within** a Docker container - so you need a container with a JDK distribution.
If you are a Linux user, you can just pass a native executable to Docker and use the simplest slim or distroless container, depending on static libraries your application is linked against.
For example:
1. Containerise the native executable using the following command:

```
FROM gcr.io/distroless/base
ARG APP_FILE
EXPOSE 8080
COPY target/${APP_FILE} app
ENTRYPOINT ["/jibber"]
```
- On Linux, containerise the native executable generated in the previous step using the following command:

```shell
docker build -f Dockerfiles/Dockerfile.native --build-arg APP_FILE=benchmark-jibber -t jibber-benchmark:native.0.0.1-SNAPSHOT .
```

For user's convenience, Dockerfiles are provided with the sample application.
- On MacOS, Windows, or Linux, use multistage Docker builds to build a native executable inside a container, and package the native executable in a lightweight container image:

```shell
docker build -f Dockerfiles/Dockerfile -t jibber-benchmark:native.0.0.1-SNAPSHOT .
```

2. Run the application:

1. From application root folder, run this command to create a native executable within a container and then build a Docker image containing that native executable:
```shell
docker build -f Dockerfiles/Dockerfile \
--build-arg APP_FILE=./target/jibber \
-t localhost/jibber:native.01 .
docker run --rm --name native -p 8080:8080 jibber-benchmark:native.0.0.1-SNAPSHOT
```
It will take several minutes to set up Maven in the container and do rest of the job.

2. Query Docker to look at your newly built image:
3. From a new terminal window, call the endpoint using `curl`:

```shell
docker images | head -n2
curl http://localhost:8080/jibber
```
You should see a new image listed.

3. Run the image as follows:
It should generate a random nonsense verse.

4. To stop the application, first get the container id using `docker ps`, and then run:

```shell
docker run --rm --name native -d -p 8080:8080 localhost/jibber:native.01
docker rm -f <container_id>
```
4. Then call the endpoint using the `curl` command in the same console window:

5. To delete the container images, first get the image id using `docker images`, and then run:

```shell
curl http://localhost:8080/jibber
docker rmi -f <image_1_id> <image_n_id>
```
You should receive a nonsense verse in the style of the poem Jabberwocky.
You can take a look at how long the application took to startup by looking at the logs:
```shell
docker logs <CONTAINER ID>
```
You can also query Docker to get the size of the produced container:
```
docker images localhost/jibber:native.01
```
The difference will be more visible if you build a Docker image of the same Spring Boot application containing a JAR file instead of a native executable, and compare images startup times and file sizes.

On Linux, you can shrink your container size even more.
With GraalVM Native Image you have the ability to build a statically linked native executable by packaging the native executable directly into an empty Docker image, also known as a scratch container. Continue to [Build a Static or Mostly-Static Native Executable guide](build-static-and-mostly-static-executable.md) to learn more.

## Summary

In this guide, you saw how to use GraalVM container images to containerize a native executable for your Java application.

With GraalVM Native Image you can build a statically linked native executable by packaging the native executable directly into tiny containers such as scratch or distroless images.
Continue to [Build a Static or Mostly-Static Native Executable guide](build-static-and-mostly-static-executable.md) to learn more.

### Related Documentation

* [GraalVM Native Image, Spring and Containerisation](https://luna.oracle.com/lab/fdfd090d-e52c-4481-a8de-dccecdca7d68)
* [GraalVM Community Images](https://github.com/graalvm/container/)
* [Build a Static or Mostly-Static Native Executable](build-static-and-mostly-static-executable.md)
* [Build a Static or Mostly-Static Native Executable](build-static-and-mostly-static-executable.md)
* <a href="https://docs.oracle.com/en/graalvm/jdk/17/docs/getting-started/container-images/" target="_blank">Oracle GraalVM Container Images</a>
* <a href="https://luna.oracle.com/lab/fdfd090d-e52c-4481-a8de-dccecdca7d68" target="_blank">Hands-on Lab: GraalVM Native Image, Spring and Containerisation</a>
1 change: 1 addition & 0 deletions substratevm/mx.substratevm/mx_substratevm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,7 @@ def _native_image_launcher_extra_jvm_args():
] + svm_experimental_options([
'-H:IncludeResources=com/oracle/svm/driver/launcher/.*',
'-H:-ParseRuntimeOptions',
f'-R:MaxHeapSize={256 * 1024 * 1024}',
])

additional_ni_dependencies = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
Path filePath = debugTypeInfo.filePath();
addTypeEntry(idType, typeName, fileName, filePath, byteSize, typeKind);
}));
debugInfoProvider.recordActivity();

/* Now we can cross reference static and instance field details. */
debugInfoProvider.typeInfoProvider().forEach(debugTypeInfo -> debugTypeInfo.debugContext((debugContext) -> {
Expand All @@ -310,6 +311,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
TypeEntry typeEntry = (idType != null ? lookupTypeEntry(idType) : lookupHeaderType());
typeEntry.addDebugInfo(this, debugTypeInfo, debugContext);
}));
debugInfoProvider.recordActivity();

debugInfoProvider.codeInfoProvider().forEach(debugCodeInfo -> debugCodeInfo.debugContext((debugContext) -> {
/*
Expand All @@ -335,6 +337,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
*/
EconomicMap<DebugLocationInfo, SubRange> subRangeIndex = EconomicMap.create();
debugCodeInfo.locationInfoProvider().forEach(debugLocationInfo -> addSubrange(debugLocationInfo, primaryRange, classEntry, subRangeIndex, debugContext));
debugInfoProvider.recordActivity();
}));

debugInfoProvider.dataInfoProvider().forEach(debugDataInfo -> debugDataInfo.debugContext((debugContext) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,4 +444,6 @@ enum Type {
Stream<DebugDataInfo> dataInfoProvider();

Path getCachePath();

void recordActivity();
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.stream.Stream;

import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.options.OptionStability;
import org.graalvm.compiler.options.OptionType;

import com.oracle.svm.core.option.BundleMember;
Expand All @@ -49,7 +50,7 @@
public final class ConfigurationFiles {

public static final class Options {
@Option(help = "Directories directly containing configuration files for dynamic features at runtime.", type = OptionType.User)//
@Option(help = "Directories directly containing configuration files for dynamic features at runtime.", type = OptionType.User, stability = OptionStability.STABLE)//
@BundleMember(role = BundleMember.Role.Input)//
static final HostedOptionKey<LocatableMultiOptionValue.Paths> ConfigurationFileDirectories = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,14 @@
*/
package com.oracle.svm.core.jdk.management;

import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.TargetClass;

import sun.management.VMManagement;

@TargetClass(className = "com.sun.management.internal.OperatingSystemImpl")
final class Target_com_sun_management_internal_OperatingSystemImpl {
@Substitute
@Alias
Target_com_sun_management_internal_OperatingSystemImpl(@SuppressWarnings("unused") VMManagement vm) {
/* Workaround until we enable container support (GR-37365). */
SubstrateUtil.cast(this, Target_sun_management_BaseOperatingSystemImpl.class).loadavg = new double[1];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
@SuppressWarnings("static-method")
@TargetClass(className = "sun.management.BaseOperatingSystemImpl")
final class Target_sun_management_BaseOperatingSystemImpl {
@Alias double[] loadavg;

@Alias
Target_sun_management_BaseOperatingSystemImpl(@SuppressWarnings("unused") VMManagement vm) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ public static JfrManager get() {
public RuntimeSupport.Hook startupHook() {
return isFirstIsolate -> {
parseFlightRecorderLogging(SubstrateOptions.FlightRecorderLogging.getValue());
periodicEventSetup();
if (isJFREnabled()) {
periodicEventSetup();
initRecording();
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
import com.oracle.svm.core.threadlocal.FastThreadLocalLong;
import com.oracle.svm.core.util.TimeUtils;

import static com.oracle.svm.core.thread.PlatformThreads.fromVMThread;

public class ThreadCPULoadEvent {
private static final FastThreadLocalLong cpuTimeTL = FastThreadLocalFactory.createLong("ThreadCPULoadEvent.cpuTimeTL");
private static final FastThreadLocalLong userTimeTL = FastThreadLocalFactory.createLong("ThreadCPULoadEvent.userTimeTL");
Expand Down Expand Up @@ -119,7 +121,7 @@ public static void emit(IsolateThread isolateThread) {

JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ThreadCPULoad);
JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks());
JfrNativeEventWriter.putEventThread(data);
JfrNativeEventWriter.putThread(data, fromVMThread(isolateThread));
JfrNativeEventWriter.putFloat(data, userTime / totalAvailableTime);
JfrNativeEventWriter.putFloat(data, systemTime / totalAvailableTime);
JfrNativeEventWriter.endSmallEvent(data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ public static String optionName(String name) {
}
}

public static String valueSeparatorToString(char valueSeparator) {
return valueSeparator != APIOption.NO_SEPARATOR ? Character.toString(valueSeparator) : "";
}

public static String groupName(APIOptionGroup group) {
if (group.name() == null || group.name().isEmpty()) {
VMError.shouldNotReachHere("Invalid APIOptionGroup.name() for " + group.getClass().getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ public static String commandArgument(OptionKey<?> option, String value, String a
apiOptionWithValue = optionName;
} else {
/* Option with custom value. Use form with valueSeparator */
apiOptionWithValue = optionName + apiOption.valueSeparator()[0] + value;
String valueSeparator = APIOption.Utils.valueSeparatorToString(apiOption.valueSeparator()[0]);
apiOptionWithValue = optionName + valueSeparator + value;
}
}
} else if (apiOption.fixedValue()[0].equals(value)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,7 @@ String translateOption(ArgumentQueue argQueue) {
whitespaceSeparated = true;
break found;
} else {
boolean withSeparator = valueSeparator != APIOption.NO_SEPARATOR;
String separatorString = withSeparator ? Character.toString(valueSeparator) : "";
String optionNameWithSeparator = optionName + separatorString;
String optionNameWithSeparator = optionName + APIOption.Utils.valueSeparatorToString(valueSeparator);
if (headArg.startsWith(optionNameWithSeparator)) {
option = optionInfo;
int length = optionNameWithSeparator.length();
Expand Down
Loading

0 comments on commit 27867e2

Please sign in to comment.