1919
2020package org .elasticsearch .tools .launchers ;
2121
22+ import java .io .BufferedReader ;
23+ import java .io .IOException ;
24+ import java .io .InputStream ;
25+ import java .io .InputStreamReader ;
26+ import java .nio .charset .StandardCharsets ;
27+ import java .nio .file .Path ;
2228import java .util .ArrayList ;
2329import java .util .HashMap ;
2430import java .util .List ;
2531import java .util .Locale ;
2632import java .util .Map ;
33+ import java .util .Optional ;
2734import java .util .regex .Matcher ;
2835import java .util .regex .Pattern ;
36+ import java .util .stream .Collectors ;
37+ import java .util .stream .Stream ;
2938
3039/**
3140 * Tunes Elasticsearch JVM settings based on inspection of provided JVM options.
3241 */
3342final 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-
4043
4144 private JvmErgonomics () {
4245 throw new AssertionError ("No instances intended" );
@@ -48,48 +51,73 @@ private JvmErgonomics() {
4851 * @param userDefinedJvmOptions A list of JVM options that have been defined by the user.
4952 * @return A list of additional JVM options to set.
5053 */
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- }
54+ static List <String > choose (final List <String > userDefinedJvmOptions ) throws InterruptedException , IOException {
55+ final List <String > ergonomicChoices = new ArrayList <>();
56+ final Map <String , Optional <String >> finalJvmOptions = finalJvmOptions (userDefinedJvmOptions );
57+ final long heapSize = extractHeapSize (finalJvmOptions );
58+ final Map <String , String > systemProperties = extractSystemProperties (userDefinedJvmOptions );
59+ if (systemProperties .containsKey ("io.netty.allocator.type" ) == false ) {
60+ if (heapSize <= 1 << 30 ) {
61+ ergonomicChoices .add ("-Dio.netty.allocator.type=unpooled" );
62+ } else {
63+ ergonomicChoices .add ("-Dio.netty.allocator.type=pooled" );
6264 }
6365 }
6466 return ergonomicChoices ;
6567 }
6668
67- private static final Pattern MAX_HEAP_SIZE = Pattern .compile ("^(-Xmx|-XX:MaxHeapSize=)(?<size>\\ d+)(?<unit>\\ w)?$" );
69+ private static final Pattern OPTION =
70+ Pattern .compile ("^\\ s*\\ S+\\ s+(?<flag>\\ S+)\\ s+:?=\\ s+(?<value>\\ S+)?\\ s+\\ {[^}]+?\\ }\\ s+\\ {[^}]+}" );
6871
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- }
72+ static Map <String , Optional <String >> finalJvmOptions (
73+ final List <String > userDefinedJvmOptions ) throws InterruptedException , IOException {
74+ return flagsFinal (userDefinedJvmOptions ).stream ()
75+ .map (OPTION ::matcher ).filter (Matcher ::matches )
76+ .collect (Collectors .toUnmodifiableMap (m -> m .group ("flag" ), m -> Optional .ofNullable (m .group ("value" ))));
77+ }
78+
79+ private static List <String > flagsFinal (final List <String > userDefinedJvmOptions ) throws InterruptedException , IOException {
80+ /*
81+ * To deduce the final set of JVM options that Elasticsearch is going to start with, we start a separate Java process with the JVM
82+ * options that we would pass on the command line. For this Java process we will add two additional flags, -XX:+PrintFlagsFinal and
83+ * -version. This causes the Java process that we start to parse the JVM options into their final values, display them on standard
84+ * output, print the version to standard error, and then exit. The JVM itself never bootstraps, and therefore this process is
85+ * lightweight. By doing this, we get the JVM options parsed exactly as the JVM that we are going to execute would parse them
86+ * without having to implement our own JVM option parsing logic.
87+ */
88+ final String java = Path .of (System .getProperty ("java.home" ), "bin" , "java" ).toString ();
89+ final List <String > command =
90+ Stream .of (Stream .of (java ), userDefinedJvmOptions .stream (), Stream .of ("-XX:+PrintFlagsFinal" ), Stream .of ("-version" ))
91+ .reduce (Stream ::concat )
92+ .get ()
93+ .collect (Collectors .toUnmodifiableList ());
94+ final Process process = new ProcessBuilder ().command (command ).start ();
95+ final List <String > output = readLinesFromInputStream (process .getInputStream ());
96+ final List <String > error = readLinesFromInputStream (process .getErrorStream ());
97+ final int status = process .waitFor ();
98+ if (status != 0 ) {
99+ final String message = String .format (
100+ Locale .ROOT ,
101+ "starting java failed with [%d]\n output:\n %s\n error:\n %s" ,
102+ status ,
103+ String .join ("\n " , output ),
104+ String .join ("\n " , error ));
105+ throw new RuntimeException (message );
106+ } else {
107+ return output ;
108+ }
109+ }
110+
111+ private static List <String > readLinesFromInputStream (final InputStream is ) throws IOException {
112+ try (InputStreamReader isr = new InputStreamReader (is , StandardCharsets .UTF_8 );
113+ BufferedReader br = new BufferedReader (isr )) {
114+ return br .lines ().collect (Collectors .toUnmodifiableList ());
91115 }
92- return null ;
116+ }
117+
118+ // package private for testing
119+ static Long extractHeapSize (final Map <String , Optional <String >> finalJvmOptions ) {
120+ return Long .parseLong (finalJvmOptions .get ("MaxHeapSize" ).get ());
93121 }
94122
95123 private static final Pattern SYSTEM_PROPERTY = Pattern .compile ("^-D(?<key>[\\ w+].*?)=(?<value>.*)$" );
@@ -105,4 +133,5 @@ static Map<String, String> extractSystemProperties(List<String> userDefinedJvmOp
105133 }
106134 return systemProperties ;
107135 }
136+
108137}
0 commit comments