/
JVMOptionsTuner.java
258 lines (222 loc) · 10.7 KB
/
JVMOptionsTuner.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
* Copyright 2017 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.netflix.priam.tuner;
import com.netflix.priam.config.IConfiguration;
import java.io.File;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is to tune the jvm.options file introduced in Cassandra 3.x to pass JVM parameters to
* Cassandra. It supports configuring GC type (CMS/G1GC) where it automatically activates default
* properties as provided in jvm.options file. Note that this will not "add" any GC options.
*
* <p>Created by aagrawal on 8/23/17.
*/
public class JVMOptionsTuner {
private static final Logger logger = LoggerFactory.getLogger(JVMOptionsTuner.class);
protected final IConfiguration config;
@Inject
public JVMOptionsTuner(IConfiguration config) {
this.config = config;
}
/**
* Update the JVM options file and save to a file for cassandra by updating/removing JVM options
* {@link IConfiguration#getJVMExcludeSet()} and {@link IConfiguration#getJVMUpsertSet()},
* configuring GC {@link IConfiguration#getGCType()}etc.
*
* @param outputFile File name with which this configured JVM options should be written.
* @throws Exception when encountered with invalid configured GC type. {@link
* IConfiguration#getGCType()}
*/
public void updateAndSaveJVMOptions(final String outputFile, final String versionOutputFile)
throws Exception {
Map<String, List<String>> Options = updateJVMOptions();
if (logger.isInfoEnabled()) {
StringBuffer buffer = new StringBuffer("\n");
Options.get("configuredJVMOptions")
.stream()
.forEach(line -> buffer.append(line).append("\n"));
logger.info("Updating jvm-server.options with following values: " + buffer.toString());
}
// Verify we can write to output file and it is not directory.
File file = new File(outputFile);
if (file.exists() && !file.canWrite()) {
throw new Exception("Not enough permissions to write to file: " + outputFile);
}
// Write jvm-server.options back to override defaults.
Files.write(new File(outputFile).toPath(), Options.get("configuredJVMOptions"));
// Update jdk version specific options
if (logger.isInfoEnabled()) {
StringBuffer buffer = new StringBuffer("\n");
Options.get("configuredJVMVersionOptions")
.stream()
.forEach(line -> buffer.append(line).append("\n"));
logger.info("Updating jvm8-server.options with following values: " + buffer.toString());
}
// Verify we can write to version specific output file and it is not directory.
File versionFile = new File(versionOutputFile);
if (versionFile.exists() && !versionFile.canWrite()) {
throw new Exception("Not enough permissions to write to file: " + versionOutputFile);
}
// Write jvm8-server.options back to override version specific defaults.
Files.write(
new File(versionOutputFile).toPath(), Options.get("configuredJVMVersionOptions"));
}
/**
* Update the JVM options file for cassandra by updating/removing JVM options {@link
* IConfiguration#getJVMExcludeSet()} and {@link IConfiguration#getJVMUpsertSet()}, configuring
* GC {@link IConfiguration#getGCType()}etc.
*
* @return List of Configuration as String after reading the configuration from jvm.options
* @throws Exception when encountered with invalid configured GC type. {@link
* IConfiguration#getGCType()}
*/
protected Map<String, List<String>> updateJVMOptions() throws Exception {
File jvmOptionsFile = new File(config.getJVMOptionsFileLocation());
validate(jvmOptionsFile);
File jvmVersionOptionsFile = new File(config.getJVMVersionOptionsFileLocation());
validate(jvmVersionOptionsFile);
final GCType configuredGC = config.getGCType();
final Map<String, JVMOption> excludeSet =
JVMOptionsTuner.parseJVMOptions(config.getJVMExcludeSet());
// Make a copy of upsertSet, so we can delete the entries as we process them.
Map<String, JVMOption> upsertSet =
JVMOptionsTuner.parseJVMOptions(config.getJVMUpsertSet());
// Don't use streams for processing as upsertSet jvm options needs to be removed if we find
// them
// already in jvm-server.options file.
List<String> optionsFromFile =
Files.lines(jvmOptionsFile.toPath()).collect(Collectors.toList());
List<String> configuredOptions = new LinkedList<>();
for (String line : optionsFromFile) {
configuredOptions.add(
updateConfigurationValue(line, configuredGC, upsertSet, excludeSet));
}
// Process the list for version specific options file.
List<String> optionsFromVersionFile =
Files.lines(jvmVersionOptionsFile.toPath()).collect(Collectors.toList());
List<String> configuredVersionOptions = new LinkedList<>();
for (String line : optionsFromVersionFile) {
configuredVersionOptions.add(
updateConfigurationValue(line, configuredGC, upsertSet, excludeSet));
}
// Add all the upserts(inserts only left) from config to generic options file.
if (upsertSet != null && !upsertSet.isEmpty()) {
configuredOptions.add("#################");
configuredOptions.add("# USER PROVIDED CUSTOM JVM CONFIGURATIONS #");
configuredOptions.add("#################");
configuredOptions.addAll(
upsertSet
.values()
.stream()
.map(JVMOption::toJVMOptionString)
.collect(Collectors.toList()));
}
final String injectSet = config.getJVMInjectSet();
if (injectSet != null && !injectSet.trim().isEmpty()) configuredOptions.add(injectSet);
HashMap<String, List<String>> options = new HashMap<String, List<String>>() {};
options.put("configuredJVMOptions", configuredOptions);
options.put("configuredJVMVersionOptions", configuredVersionOptions);
return options;
}
private void setHeapSetting(String configuredValue, JVMOption option) {
if (!StringUtils.isEmpty(configuredValue))
option.setCommented(false).setValue(configuredValue);
}
/**
* @param line a line as read from jvm.options file.
* @param configuredGC GCType configured by user for Cassandra.
* @param upsertSet configured upsert set of JVM properties as provided by user for Cassandra.
* @param excludeSet configured exclude set of JVM properties as provided by user for Cassandra.
* @return the "comment" as is, if not a valid JVM option. Else, a string representation of JVM
* option
*/
private String updateConfigurationValue(
final String line,
GCType configuredGC,
Map<String, JVMOption> upsertSet,
Map<String, JVMOption> excludeSet) {
JVMOption option = JVMOption.parse(line);
if (option == null) return line;
// Is parameter for heap setting.
if (option.isHeapJVMOption()) {
String configuredValue;
switch (option.getJvmOption()) {
// Special handling for heap new size ("Xmn")
case "-Xmn":
configuredValue = config.getHeapNewSize();
break;
// Set min and max heap size to same value
default:
configuredValue = config.getHeapSize();
break;
}
setHeapSetting(configuredValue, option);
}
// We don't want Xmn with G1GC, allow the GC to determine optimal young gen
if (option.getJvmOption().equals("-Xmn") && configuredGC == GCType.G1GC)
option.setCommented(true);
// Is parameter for GC.
GCType gcType = GCTuner.getGCType(option);
if (gcType != null) {
option.setCommented(gcType != configuredGC);
}
// See if option is in upsert list.
if (upsertSet != null && upsertSet.containsKey(option.getJvmOption())) {
JVMOption configuration = upsertSet.get(option.getJvmOption());
option.setCommented(false);
option.setValue(configuration.getValue());
upsertSet.remove(option.getJvmOption());
}
// See if option is in exclude list.
if (excludeSet != null && excludeSet.containsKey(option.getJvmOption()))
option.setCommented(true);
return option.toJVMOptionString();
}
private void validate(File jvmOptionsFile) throws Exception {
if (!jvmOptionsFile.exists())
throw new Exception(
"JVM Option File does not exist: " + jvmOptionsFile.getAbsolutePath());
if (jvmOptionsFile.isDirectory())
throw new Exception(
"JVM Option File is a directory: " + jvmOptionsFile.getAbsolutePath());
if (!jvmOptionsFile.canRead() || !jvmOptionsFile.canWrite())
throw new Exception(
"JVM Option File does not have right permission: "
+ jvmOptionsFile.getAbsolutePath());
}
/**
* Util function to parse comma separated list of jvm options to a Map (jvmOptionName,
* JVMOption). It will ignore anything which is not a valid JVM option.
*
* @param property comma separated list of JVM options.
* @return Map of (jvmOptionName, JVMOption).
*/
public static final Map<String, JVMOption> parseJVMOptions(String property) {
if (StringUtils.isEmpty(property)) return null;
return new HashSet<>(Arrays.asList(property.split(",")))
.stream()
.map(JVMOption::parse)
.filter(Objects::nonNull)
.collect(Collectors.toMap(JVMOption::getJvmOption, jvmOption -> jvmOption));
}
}