Skip to content

Commit 2aefb72

Browse files
Choose JVM options ergonomically
With this commit we add the possibility to define further JVM options (and system properties) based on the current environment. As a proof of concept, it chooses Netty's allocator ergonomically based on the maximum defined heap size. We switch to the unpooled allocator at 1GB heap size (value determined experimentally, see #30684 for more details). We are also explicit about the choice of the allocator in either case. Relates #30684
1 parent e7a7b96 commit 2aefb72

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.tools.launchers;
21+
22+
import java.util.ArrayList;
23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Locale;
26+
import java.util.Map;
27+
import java.util.regex.Matcher;
28+
import java.util.regex.Pattern;
29+
30+
/**
31+
* Tunes Elasticsearch JVM settings based on inspection of provided JVM options.
32+
*/
33+
final class JvmErgonomics {
34+
private static final long KB = 1024L;
35+
36+
private static final long MB = 1024L * 1024L;
37+
38+
private static final long GB = 1024L * 1024L * 1024L;
39+
40+
41+
private JvmErgonomics() {
42+
throw new AssertionError("No instances intended");
43+
}
44+
45+
/**
46+
* Chooses additional JVM options for Elasticsearch.
47+
*
48+
* @param userDefinedJvmOptions A list of JVM options that have been defined by the user.
49+
* @return A list of additional JVM options to set.
50+
*/
51+
static List<String> choose(List<String> userDefinedJvmOptions) {
52+
List<String> ergonomicChoices = new ArrayList<>();
53+
Long heapSize = extractHeapSize(userDefinedJvmOptions);
54+
Map<String, String> systemProperties = extractSystemProperties(userDefinedJvmOptions);
55+
if (heapSize != null) {
56+
if (systemProperties.containsKey("io.netty.allocator.type") == false) {
57+
if (heapSize <= 1 * GB) {
58+
ergonomicChoices.add("-Dio.netty.allocator.type=unpooled");
59+
} else {
60+
ergonomicChoices.add("-Dio.netty.allocator.type=pooled");
61+
}
62+
}
63+
}
64+
return ergonomicChoices;
65+
}
66+
67+
private static final Pattern MAX_HEAP_SIZE = Pattern.compile("^(-Xmx|-XX:MaxHeapSize=)(?<size>\\d+)(?<unit>\\w)?$");
68+
69+
// package private for testing
70+
static Long extractHeapSize(List<String> userDefinedJvmOptions) {
71+
for (String jvmOption : userDefinedJvmOptions) {
72+
final Matcher matcher = MAX_HEAP_SIZE.matcher(jvmOption);
73+
if (matcher.matches()) {
74+
final long size = Long.parseLong(matcher.group("size"));
75+
final String unit = matcher.group("unit");
76+
if (unit == null) {
77+
return size;
78+
} else {
79+
switch (unit.toLowerCase(Locale.ROOT)) {
80+
case "k":
81+
return size * KB;
82+
case "m":
83+
return size * MB;
84+
case "g":
85+
return size * GB;
86+
default:
87+
throw new IllegalArgumentException("Unknown unit [" + unit + "] for max heap size in [" + jvmOption + "]");
88+
}
89+
}
90+
}
91+
}
92+
return null;
93+
}
94+
95+
private static final Pattern SYSTEM_PROPERTY = Pattern.compile("^-D(?<key>[\\w+].*?)=(?<value>.*)$");
96+
97+
// package private for testing
98+
static Map<String, String> extractSystemProperties(List<String> userDefinedJvmOptions) {
99+
Map<String, String> systemProperties = new HashMap<>();
100+
for (String jvmOption : userDefinedJvmOptions) {
101+
final Matcher matcher = SYSTEM_PROPERTY.matcher(jvmOption);
102+
if (matcher.matches()) {
103+
systemProperties.put(matcher.group("key"), matcher.group("value"));
104+
}
105+
}
106+
return systemProperties;
107+
}
108+
}

distribution/tools/launchers/src/main/java/org/elasticsearch/tools/launchers/JvmOptionsParser.java

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ public void accept(final int lineNumber, final String line) {
7878
}
7979

8080
if (invalidLines.isEmpty()) {
81+
List<String> ergonomicJvmOptions = JvmErgonomics.choose(jvmOptions);
82+
jvmOptions.addAll(ergonomicJvmOptions);
8183
final String spaceDelimitedJvmOptions = spaceDelimitJvmOptions(jvmOptions);
8284
Launchers.outPrintln(spaceDelimitedJvmOptions);
8385
Launchers.exit(0);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.tools.launchers;
21+
22+
import java.util.Arrays;
23+
import java.util.Collections;
24+
import java.util.HashMap;
25+
import java.util.List;
26+
import java.util.Map;
27+
28+
import static org.junit.Assert.assertEquals;
29+
import static org.junit.Assert.assertNull;
30+
import static org.junit.Assert.assertTrue;
31+
import static org.junit.Assert.fail;
32+
33+
public class JvmErgonomicsTests extends LaunchersTestCase {
34+
public void testExtractValidHeapSize() {
35+
assertEquals(Long.valueOf(1024), JvmErgonomics.extractHeapSize(Collections.singletonList("-Xmx1024")));
36+
assertEquals(Long.valueOf(2L * 1024 * 1024 * 1024), JvmErgonomics.extractHeapSize(Collections.singletonList("-Xmx2g")));
37+
assertEquals(Long.valueOf(32 * 1024 * 1024), JvmErgonomics.extractHeapSize(Collections.singletonList("-Xmx32M")));
38+
assertEquals(Long.valueOf(32 * 1024 * 1024), JvmErgonomics.extractHeapSize(Collections.singletonList("-XX:MaxHeapSize=32M")));
39+
}
40+
41+
public void testExtractInvalidHeapSize() {
42+
try {
43+
JvmErgonomics.extractHeapSize(Collections.singletonList("-Xmx2T"));
44+
fail("Expected IllegalArgumentException to be raised");
45+
} catch (IllegalArgumentException expected) {
46+
assertEquals("Unknown unit [T] for max heap size in [-Xmx2T]", expected.getMessage());
47+
}
48+
}
49+
50+
public void testExtractNoHeapSize() {
51+
assertNull("No spaces allowed", JvmErgonomics.extractHeapSize(Collections.singletonList("-Xmx 1024")));
52+
assertNull("JVM option is not present", JvmErgonomics.extractHeapSize(Collections.singletonList("")));
53+
assertNull("Multiple JVM options per line", JvmErgonomics.extractHeapSize(Collections.singletonList("-Xms2g -Xmx2g")));
54+
}
55+
56+
public void testExtractSystemProperties() {
57+
Map<String, String> expectedSystemProperties = new HashMap<>();
58+
expectedSystemProperties.put("file.encoding", "UTF-8");
59+
expectedSystemProperties.put("kv.setting", "ABC=DEF");
60+
61+
Map<String, String> parsedSystemProperties = JvmErgonomics.extractSystemProperties(
62+
Arrays.asList("-Dfile.encoding=UTF-8", "-Dkv.setting=ABC=DEF"));
63+
64+
assertEquals(expectedSystemProperties, parsedSystemProperties);
65+
}
66+
67+
public void testExtractNoSystemProperties() {
68+
Map<String, String> parsedSystemProperties = JvmErgonomics.extractSystemProperties(Arrays.asList("-Xms1024M", "-Xmx1024M"));
69+
assertTrue(parsedSystemProperties.isEmpty());
70+
}
71+
72+
public void testLittleMemoryErgonomicChoices() {
73+
String smallHeap = randomFrom(Arrays.asList("64M", "512M", "1024M", "1G"));
74+
List<String> expectedChoices = Collections.singletonList("-Dio.netty.allocator.type=unpooled");
75+
assertEquals(expectedChoices, JvmErgonomics.choose(Arrays.asList("-Xms" + smallHeap, "-Xmx" + smallHeap)));
76+
}
77+
78+
public void testPlentyMemoryErgonomicChoices() {
79+
String largeHeap = randomFrom(Arrays.asList("1025M", "2048M", "2G", "8G"));
80+
List<String> expectedChoices = Collections.singletonList("-Dio.netty.allocator.type=pooled");
81+
assertEquals(expectedChoices, JvmErgonomics.choose(Arrays.asList("-Xms" + largeHeap, "-Xmx" + largeHeap)));
82+
}
83+
}

0 commit comments

Comments
 (0)