Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 7a43fe1

Browse files
Merge pull request #178 from Trivadis/feature/issue-177-improve-logging-native-image
Feature/issue 177 - Improve logging capabilities of standalone image and native image creation
2 parents 2be8977 + af680c3 commit 7a43fe1

File tree

11 files changed

+904
-159
lines changed

11 files changed

+904
-159
lines changed

settings/sql_developer/trivadis_custom_format.arbori

Lines changed: 85 additions & 74 deletions
Large diffs are not rendered by default.

sqlcl/format.js

Lines changed: 65 additions & 48 deletions
Large diffs are not rendered by default.

standalone/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ hs_err_pid*
3131
.project
3232
.classpath
3333
**/.settings
34+
.vscode
3435

3536
# IntelliJ
3637
**/.idea

standalone/README.md

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,66 @@
22

33
## Introduction
44

5-
This Maven project produces a standalone command line executable `tvdformat.jar` for the SQLcl script [`format.js`](../sqlcl/format.js). Optionally it produces also a GraalVM native image `tvdformat`.
5+
This Maven project produces a standalone command line executable `tvdformat.jar` for the SQLcl script [`format.js`](../sqlcl/format.js). Optionally it produces also a GraalVM [native image](https://www.graalvm.org/reference-manual/native-image/) `tvdformat`.
66

7-
The startup time of standalone JAR file and the native image are identical since the image still requires a JDK to execute. However, it is faster than running `format.js` from SQLcl.
7+
The startup time of standalone JAR file and the native image are similar since the image still requires a JDK to execute. However, it is faster than running `format.js` from SQLcl.
88

99
This project contains JUnit tests for
1010

1111
- the SQLDev/SQLcl formatter settings `trivadis_advanced_format.xml` and `trivadis_custom_format.arbori`
1212
- the SQLcl script `format.js`
1313
- the SQLcl command `tvdformat`
14-
- the standalone executable `tvdformat`
14+
- the standalone exectable `tvdformat`
1515

16-
The project requires a JDK 17, but it produces a Java 8 JAR file. A GraalVM JDK is required only if you want to produce a native image.
16+
The project requires a JDK 17, but it produces a Java 8 executable JAR file. A GraalVM JDK is required only if you want to build a [native image](https://www.graalvm.org/reference-manual/native-image/).
17+
18+
## Running the Standalone Formatter
19+
20+
### Configure Logging
21+
22+
Optionally, you can define the following environment variables:
23+
24+
Variable | Description
25+
-------- | -----------
26+
`TVDFORMAT_LOGGING_CONF_FILE` | Path to a [java.util.logging](https://docs.oracle.com/en/java/javase/17/core/java-logging-overview.html#GUID-B83B652C-17EA-48D9-93D2-563AE1FF8EDA) configuration file. Fully qualified or relative paths are supported. [This file](src/test/resources/logging.conf) is used for tests.
27+
`TVDFORMAT_DEBUG` | `true` enables Arbori debug messages.
28+
`TVDFORMAT_TIMING` |`true` enables Arbori query/callback timing messages.
29+
30+
### Executable JAR
31+
32+
The `tvdformat.jar` is a shaded, executable JAR. This means it contains all dependend Java classes. However, it still needs a JDK 8 or higher.
33+
34+
To run it, open a terminal window and type
35+
36+
```
37+
java -jar tvdformat.jar
38+
```
39+
40+
The parameters are the same as for the [SQLcl command `tvdformat`](../sqlcl/README.md#register-script-formatjs-as-sqlcl-command-tvdformat). Except for formatting the SQLcl buffer, of course.
41+
42+
### Native Image
43+
44+
A native image is a platform specific executable. The following images can be produced with a GraalVM JDK 17:
45+
46+
OS | amd64)? | aarch64? | Requires JDK 8+? | No JDK required?
47+
------- | ------- | -------- | ---------------- | ----------------
48+
macOS | yes | no | yes | yes
49+
Linux | yes | yes | yes | yes
50+
Windows | yes | no | yes | yes
51+
52+
Currently there is no way to produce an ARM based (aarch64) native image for macOS and Windows. This reduces the possible combinations from 12 to 8 native images. Native images are not part of a release. You have to build them yourself as described [below](#how-to-build).
53+
54+
Native images produced with the `--force-fallback` option have a size of around 14 MB. They require a JDK 8+ at runtime and are considered stable.
55+
56+
Native images produced with the `--no-fallback` option have a size of around 500 MB. They do not require a JDK at runtime. Due to the absence of automatic tests, these images are considered experimental.
57+
58+
To run a native image open a terminal window and type
59+
60+
```
61+
./tvdformat
62+
```
63+
64+
The parameters are the same as for the [executable JAR](#executable-jar).
1765

1866
## How to Build
1967

@@ -25,12 +73,33 @@ The project requires a JDK 17, but it produces a Java 8 JAR file. A GraalVM JDK
2573
6. Clone the plsql-formatter-settings repository
2674
7. Open a terminal window in the plsql-formatter-settings root folder and type
2775

28-
cd standalone
76+
```
77+
cd standalone
78+
```
79+
80+
8. Run Maven build by the following command
81+
82+
```
83+
mvn -Dsqlcl.libdir=/usr/local/bin/sqlcl/lib clean package
84+
```
85+
86+
Amend the parameter `sqlcl.libdir` to match the path of the lib directory of your SQLcl installation. This folder is used to reference libraries such as `dbtools-common.jar` which contains the formatter and its dependencies. These libraries are not available in public Maven repositories.
87+
88+
You can define the following optional parameters:
2989
30-
6. Run maven build by the following command
90+
| Parameter | Value | Meaning |
91+
| -------------------------- | ------- | ------- |
92+
| `skip.native` | `true` | Do not produce a native image (default) |
93+
| | `false` | Produce a native image |
94+
| `native.image.fallback` | `no` | Produce a native image of about 14 MB which requires a JDK at runtime (default) |
95+
| | `force` | Produce a native image of about 500 MB that runs without JDK |
96+
| `skipTests` | `true` | Run tests (default) |
97+
| | `false` | Do not run tests |
98+
| `disable.logging` | `true` | Disable logging message during test run (default) |
99+
| | `false` | Enable logging message during test run |
31100
32-
mvn -Dsqlcl.libdir=/usr/local/bin/sqlcl/lib -Dskip.native=false clean package
101+
Here's a fully qualified example to produce a native image:
33102
34-
Amend the parameter `sqlcl.libdir` to match the path of the lib directory of you SQLcl installation. This folder is used to reference the `dbtools-common.jar` library (containing the formatter and its dependencies) which is not available in public Maven repositories.
35-
36-
When you specifiy the parameter `-Dskip.native=false` a native image for your platform is created. When you pass the value `true` (default) then no native image is produced.
103+
```
104+
mvn -Dsqlcl.libdir=/usr/local/bin/sqlcl/lib -Dskip.native=false -Dnative.image.fallback=no -DskipTests=true -Ddisable.logging=true clean package
105+
```

standalone/pom.xml

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
<graalvm.version>21.3.0</graalvm.version>
1818
<graalvm.native.version>21.2.0</graalvm.native.version>
1919
<skip.native>true</skip.native>
20+
<!-- relevant when skip.native is false, valid: "no", "force" -->
21+
<native.image.fallback>force</native.image.fallback>
22+
<disable.logging>true</disable.logging>
2023
</properties>
2124
<dependencies>
2225
<!-- GraalVM JavaScript engine used for testing format.js as if executed via SQLcl -->
@@ -90,6 +93,17 @@
9093
<version>5.8.2</version>
9194
<scope>test</scope>
9295
</dependency>
96+
<!-- For native image with no-fallback option, used during build phase only -->
97+
<dependency>
98+
<groupId>org.reflections</groupId>
99+
<artifactId>reflections</artifactId>
100+
<version>0.10.2</version>
101+
</dependency>
102+
<dependency>
103+
<groupId>org.slf4j</groupId>
104+
<artifactId>slf4j-jdk14</artifactId>
105+
<version>1.7.32</version>
106+
</dependency>
93107
</dependencies>
94108

95109
<!-- Reporting -->
@@ -179,6 +193,7 @@
179193
<exclude>META-INF/MANIFEST.MF</exclude>
180194
<exclude>module-info.class</exclude>
181195
<exclude>META-INF/versions/**</exclude>
196+
<exclude>META-INF/native-image/org.graalvm.*/**</exclude>
182197
</excludes>
183198
</filter>
184199
<filter>
@@ -220,17 +235,27 @@
220235
<skip>${skip.native}</skip>
221236
<imageName>${project.artifactId}</imageName>
222237
<mainClass>com.trivadis.plsql.formatter.TvdFormat</mainClass>
223-
<!-- Creating a native image with "-language:js -no-fallback -H:ReflectionConfigurationFiles=..."
224-
was e dead end. There were various issues, such as
225-
- different behavior between JS/Java Strings and their methods
226-
- laborious identification of classes used via reflection
227-
- long build times (~280 seconds)
228-
- very large image size (437MB)
229-
Therefore, creating a native image which requires a JDK was much simpler.
230-
The drawback is a slower startup time. However, loading 437MB is not fast ether, at least the first time.
238+
<!-- There are basically two options to create a native image
239+
240+
a) with "force-fallback" option (stable)
241+
The resulting image is relatively small (14 MB) and works.
242+
The executable requires a JDK 8 or higher. The distribution does not matter.
243+
244+
b) with "no-fallback" option (experimental)
245+
The resulting image is large (almost 500 MB).
246+
The executable does not require a JDK.
247+
Must be build for every target platform/architecture combination.
248+
It should work with the default Arbori program and the trivadis_custom_format.arbori.
249+
However, runtime errors are possible when using unconfigured Java classes.
231250
-->
232251
<buildArgs>
233-
-H:IncludeResources=.* --force-fallback
252+
-H:IncludeResources=.*
253+
-H:DynamicProxyConfigurationFiles=${project.basedir}/src/main/resources/META-INF/native-image/${project.groupId}/${project.artifactId}/proxy-config.json
254+
-H:ReflectionConfigurationFiles=${project.basedir}/src/main/resources/META-INF/native-image/${project.groupId}/${project.artifactId}/reflect-config.json
255+
-H:+ReportExceptionStackTraces
256+
--language:js
257+
--features=com.trivadis.plsql.formatter.RuntimeReflectionRegistrationFeature
258+
--${native.image.fallback}-fallback
234259
</buildArgs>
235260
</configuration>
236261
</plugin>
@@ -243,6 +268,9 @@
243268
<includes>
244269
<include>**/*.java</include>
245270
</includes>
271+
<systemPropertyVariables>
272+
<disable.logging>${disable.logging}</disable.logging>
273+
</systemPropertyVariables>
246274
</configuration>
247275
</plugin>
248276
</plugins>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.trivadis.plsql.formatter;
2+
3+
import org.graalvm.nativeimage.hosted.Feature;
4+
import org.graalvm.nativeimage.hosted.RuntimeReflection;
5+
import org.reflections.Reflections;
6+
import org.reflections.scanners.Scanners;
7+
8+
import java.lang.reflect.Constructor;
9+
import java.lang.reflect.Field;
10+
import java.lang.reflect.Method;
11+
import java.util.Set;
12+
13+
@SuppressWarnings("unused")
14+
public class RuntimeReflectionRegistrationFeature implements Feature {
15+
private static final String[] SKIP_CLASS_NAMES = {
16+
"oracle.dbtools.util.Closeables",
17+
};
18+
19+
private static void register(String packageName, boolean includeSubPackages, ClassLoader classLoader) {
20+
Reflections reflections = new Reflections(packageName);
21+
Set<String> allClassNames = reflections.getAll(Scanners.SubTypes);
22+
// allClassNames contains also inner classes
23+
for (String className : allClassNames) {
24+
if (className.startsWith((packageName))) {
25+
if (includeSubPackages || (!className.substring(packageName.length() + 1).contains("."))) {
26+
if (validClass(className)) {
27+
registerClass(className, classLoader);
28+
}
29+
}
30+
}
31+
}
32+
}
33+
34+
private static boolean validClass(String className) {
35+
for (String skipClassName : SKIP_CLASS_NAMES) {
36+
if (className.startsWith(skipClassName)) {
37+
return false;
38+
}
39+
}
40+
return true;
41+
}
42+
43+
private static void registerClass(String className, ClassLoader classLoader) {
44+
try {
45+
Class<?> clazz = Class.forName(className, false, classLoader);
46+
// calling getClass() on a clazz throws an Exception when not found on the classpath
47+
RuntimeReflection.register(clazz.getClass());
48+
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
49+
RuntimeReflection.register(constructor);
50+
}
51+
for (Method method : clazz.getDeclaredMethods()) {
52+
RuntimeReflection.register((method));
53+
}
54+
for (Field field : clazz.getDeclaredFields()) {
55+
RuntimeReflection.register(field);
56+
}
57+
} catch (Throwable t) {
58+
// ignore
59+
}
60+
}
61+
62+
public void beforeAnalysis(BeforeAnalysisAccess access) {
63+
ClassLoader classLoader = access.getApplicationClassLoader();
64+
// register all classes in a package
65+
register("oracle.dbtools.app", true, classLoader);
66+
register("oracle.dbtools.arbori", true, classLoader);
67+
register("oracle.dbtools.parser", true, classLoader);
68+
register("oracle.dbtools.raptor", false, classLoader);
69+
register("oracle.dbtools.util", true, classLoader);
70+
}
71+
}

standalone/src/main/java/com/trivadis/plsql/formatter/TvdFormat.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package com.trivadis.plsql.formatter;
22

33
import com.oracle.truffle.js.scriptengine.GraalJSScriptEngine;
4+
import oracle.dbtools.arbori.Program;
45
import org.graalvm.polyglot.Context;
6+
import org.graalvm.polyglot.HostAccess;
57

68
import javax.script.*;
79
import java.io.*;
810
import java.net.URL;
9-
import java.util.function.Predicate;
1011
import java.util.logging.LogManager;
1112

1213
public class TvdFormat {
@@ -16,8 +17,8 @@ public class TvdFormat {
1617
TvdFormat() {
1718
scriptEngine = GraalJSScriptEngine.create(null,
1819
Context.newBuilder("js")
19-
.option("js.nashorn-compat", "true")
20-
.allowAllAccess(true));
20+
.allowHostAccess(HostAccess.ALL)
21+
.allowHostClassLookup(s -> true));
2122
ctx = new ScriptRunnerContext();
2223
ctx.setOutputStream(System.out);
2324
scriptEngine.getContext().setAttribute("ctx", ctx, ScriptContext.ENGINE_SCOPE);
@@ -35,9 +36,33 @@ public void run(String[] arguments) throws IOException, ScriptException {
3536
}
3637

3738
public static void main(String[] args) throws IOException, ScriptException {
39+
// configure logging
3840
LogManager.getLogManager().reset();
41+
String loggingConfFile = System.getenv("TVDFORMAT_LOGGING_CONF_FILE");
42+
if (loggingConfFile != null) {
43+
// enable logging according java.util.logging configuration file
44+
try {
45+
LogManager.getLogManager().readConfiguration(new FileInputStream(loggingConfFile));
46+
} catch (FileNotFoundException e) {
47+
System.out.println("\nWarning: The file '" + loggingConfFile +
48+
"' does not exist. Please update the environment variable TVDFORMAT_LOGGING_CONF_FILE.\n");
49+
}
50+
}
51+
// enable Arbori program debug
52+
String debug = System.getenv("TVDFORMAT_DEBUG");
53+
if (debug != null && debug.trim().equalsIgnoreCase("true")) {
54+
Program.debug = true;
55+
}
56+
// enable Arbori program timing
57+
String timing = System.getenv("TVDFORMAT_TIMING");
58+
if (timing != null && timing.trim().equalsIgnoreCase("true")) {
59+
Program.timing = true;
60+
}
61+
// amend usage help in format.js for standalone tvdformat
3962
System.setProperty("tvdformat.standalone", "true");
63+
// format.js is compiled at runtime with a GraalVM JDK but interpreted with other JDKs
4064
System.setProperty("polyglot.engine.WarnInterpreterOnly", "false");
65+
// run formatter with command line parameters
4166
TvdFormat formatter = new TvdFormat();
4267
formatter.run(args);
4368
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
[],
3+
["java.util.function.Predicate"]
4+
]

0 commit comments

Comments
 (0)