Skip to content

Commit 123b0b7

Browse files
icetrainbrian-brazil
authored andcommitted
Reload configuration when file changes (#90)
* Reload configuration when file changes * Use config file mtime for lastupdate * Fix spacing * Add instrumentation for configuration reload * Fix potential resource leak in config reload
1 parent 818fd6c commit 123b0b7

File tree

3 files changed

+108
-62
lines changed

3 files changed

+108
-62
lines changed

collector/src/main/java/io/prometheus/jmx/JmxCollector.java

Lines changed: 104 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package io.prometheus.jmx;
22

33
import io.prometheus.client.Collector;
4+
import io.prometheus.client.Counter;
45
import java.io.IOException;
56
import java.io.PrintWriter;
6-
import java.io.Reader;
7+
import java.io.File;
8+
import java.io.FileReader;
79
import java.io.StringWriter;
810
import java.util.ArrayList;
911
import java.util.Iterator;
@@ -25,6 +27,14 @@
2527
import static java.lang.String.format;
2628

2729
public class JmxCollector extends Collector {
30+
static final Counter configReloadSuccess = Counter.build()
31+
.name("jmx_config_reload_success_total")
32+
.help("Number of times configuration have successfully been reloaded.").register();
33+
34+
static final Counter configReloadFailure = Counter.build()
35+
.name("jmx_config_reload_failure_total")
36+
.help("Number of times configuration have failed to be reloaded.").register();
37+
2838
private static final Logger LOGGER = Logger.getLogger(JmxCollector.class.getName());
2939

3040
private static class Rule {
@@ -39,83 +49,109 @@ private static class Rule {
3949
ArrayList<String> labelValues;
4050
}
4151

42-
String jmxUrl;
43-
String username;
44-
String password;
45-
46-
boolean lowercaseOutputName;
47-
boolean lowercaseOutputLabelNames;
48-
List<ObjectName> whitelistObjectNames = new ArrayList<ObjectName>();
49-
List<ObjectName> blacklistObjectNames = new ArrayList<ObjectName>();
50-
ArrayList<Rule> rules = new ArrayList<Rule>();
52+
private static class Config {
53+
String jmxUrl = "";
54+
String username = "";
55+
String password = "";
56+
boolean lowercaseOutputName;
57+
boolean lowercaseOutputLabelNames;
58+
List<ObjectName> whitelistObjectNames = new ArrayList<ObjectName>();
59+
List<ObjectName> blacklistObjectNames = new ArrayList<ObjectName>();
60+
ArrayList<Rule> rules = new ArrayList<Rule>();
61+
long lastUpdate = 0L;
62+
}
63+
64+
private Config config;
65+
private File configFile;
5166

5267
private static final Pattern snakeCasePattern = Pattern.compile("([a-z0-9])([A-Z])");
5368

54-
public JmxCollector(Reader in) throws IOException, MalformedObjectNameException {
55-
this((Map<String, Object>)new Yaml().load(in));
69+
public JmxCollector(File in) throws IOException, MalformedObjectNameException {
70+
configFile = in;
71+
config = loadConfig((Map<String, Object>)new Yaml().load(new FileReader(in)));
72+
config.lastUpdate = configFile.lastModified();
5673
}
74+
5775
public JmxCollector(String yamlConfig) throws MalformedObjectNameException {
58-
this((Map<String, Object>)new Yaml().load(yamlConfig));
76+
config = loadConfig((Map<String, Object>)new Yaml().load(yamlConfig));
5977
}
60-
private JmxCollector(Map<String, Object> config) throws MalformedObjectNameException {
61-
if(config == null) { //Yaml config empty, set config to empty map.
62-
config = new HashMap<String, Object>();
78+
79+
private void reloadConfig() {
80+
try {
81+
FileReader fr = new FileReader(configFile);
82+
83+
try {
84+
Map<String, Object> newYamlConfig = (Map<String, Object>)new Yaml().load(fr);
85+
config = loadConfig(newYamlConfig);
86+
config.lastUpdate = configFile.lastModified();
87+
configReloadSuccess.inc();
88+
} catch (Exception e) {
89+
LOGGER.severe("Configuration reload failed: " + e.toString());
90+
configReloadFailure.inc();
91+
} finally {
92+
fr.close();
6393
}
6494

65-
if (config.containsKey("hostPort")) {
66-
if (config.containsKey("jmxUrl")) {
67-
throw new IllegalArgumentException("At most one of hostPort and jmxUrl must be provided");
68-
}
69-
jmxUrl ="service:jmx:rmi:///jndi/rmi://" + (String)config.get("hostPort") + "/jmxrmi";
70-
} else if (config.containsKey("jmxUrl")) {
71-
jmxUrl = (String)config.get("jmxUrl");
72-
} else {
73-
// Default to local JVM
74-
jmxUrl = "";
95+
} catch (IOException e) {
96+
LOGGER.severe("Configuration reload failed: " + e.toString());
97+
configReloadFailure.inc();
98+
}
99+
}
100+
101+
private Config loadConfig(Map<String, Object> yamlConfig) throws MalformedObjectNameException {
102+
Config cfg = new Config();
103+
104+
if (yamlConfig == null) { // Yaml config empty, set config to empty map.
105+
yamlConfig = new HashMap<String, Object>();
75106
}
76107

77-
if (config.containsKey("username")) {
78-
username = (String)config.get("username");
79-
} else {
80-
// Any username.
81-
username = "";
108+
if (yamlConfig.containsKey("hostPort")) {
109+
if (yamlConfig.containsKey("jmxUrl")) {
110+
throw new IllegalArgumentException("At most one of hostPort and jmxUrl must be provided");
82111
}
112+
cfg.jmxUrl ="service:jmx:rmi:///jndi/rmi://" + (String)yamlConfig.get("hostPort") + "/jmxrmi";
113+
} else if (yamlConfig.containsKey("jmxUrl")) {
114+
cfg.jmxUrl = (String)yamlConfig.get("jmxUrl");
115+
}
116+
117+
if (yamlConfig.containsKey("username")) {
118+
cfg.username = (String)yamlConfig.get("username");
119+
}
83120

84-
if (config.containsKey("password")) {
85-
password = (String)config.get("password");
86-
} else {
87-
// Empty password.
88-
password = "";
89-
}
121+
if (yamlConfig.containsKey("password")) {
122+
cfg.password = (String)yamlConfig.get("password");
123+
}
90124

91-
if (config.containsKey("lowercaseOutputName")) {
92-
lowercaseOutputName = (Boolean)config.get("lowercaseOutputName");
125+
if (yamlConfig.containsKey("lowercaseOutputName")) {
126+
cfg.lowercaseOutputName = (Boolean)yamlConfig.get("lowercaseOutputName");
93127
}
94-
if (config.containsKey("lowercaseOutputLabelNames")) {
95-
lowercaseOutputLabelNames = (Boolean)config.get("lowercaseOutputLabelNames");
128+
129+
if (yamlConfig.containsKey("lowercaseOutputLabelNames")) {
130+
cfg.lowercaseOutputLabelNames = (Boolean)yamlConfig.get("lowercaseOutputLabelNames");
96131
}
97132

98-
if (config.containsKey("whitelistObjectNames")) {
99-
List<Object> names = (List<Object>) config.get("whitelistObjectNames");
133+
if (yamlConfig.containsKey("whitelistObjectNames")) {
134+
List<Object> names = (List<Object>) yamlConfig.get("whitelistObjectNames");
100135
for(Object name : names) {
101-
whitelistObjectNames.add(new ObjectName((String)name));
136+
cfg.whitelistObjectNames.add(new ObjectName((String)name));
102137
}
103138
} else {
104-
whitelistObjectNames.add(null);
139+
cfg.whitelistObjectNames.add(null);
105140
}
106-
if (config.containsKey("blacklistObjectNames")) {
107-
List<Object> names = (List<Object>) config.get("blacklistObjectNames");
141+
142+
if (yamlConfig.containsKey("blacklistObjectNames")) {
143+
List<Object> names = (List<Object>) yamlConfig.get("blacklistObjectNames");
108144
for (Object name : names) {
109-
blacklistObjectNames.add(new ObjectName((String)name));
145+
cfg.blacklistObjectNames.add(new ObjectName((String)name));
110146
}
111147
}
112148

113-
if (config.containsKey("rules")) {
114-
List<Map<String,Object>> configRules = (List<Map<String,Object>>) config.get("rules");
149+
if (yamlConfig.containsKey("rules")) {
150+
List<Map<String,Object>> configRules = (List<Map<String,Object>>) yamlConfig.get("rules");
115151
for (Map<String, Object> ruleObject : configRules) {
116152
Map<String, Object> yamlRule = ruleObject;
117153
Rule rule = new Rule();
118-
rules.add(rule);
154+
cfg.rules.add(rule);
119155
if (yamlRule.containsKey("pattern")) {
120156
rule.pattern = Pattern.compile("^.*" + (String)yamlRule.get("pattern") + ".*$");
121157
}
@@ -160,9 +196,11 @@ private JmxCollector(Map<String, Object> config) throws MalformedObjectNameExcep
160196
}
161197
} else {
162198
// Default to a single default rule.
163-
rules.add(new Rule());
199+
cfg.rules.add(new Rule());
164200
}
165201

202+
return cfg;
203+
166204
}
167205

168206
class Receiver implements JmxScraper.MBeanReceiver {
@@ -217,7 +255,7 @@ private void defaultExport(
217255
name.append(attrName);
218256
String fullname = safeName(name.toString());
219257

220-
if (lowercaseOutputName) {
258+
if (config.lowercaseOutputName) {
221259
fullname = fullname.toLowerCase();
222260
}
223261

@@ -230,7 +268,7 @@ private void defaultExport(
230268
while (iter.hasNext()) {
231269
Map.Entry<String, String> entry = iter.next();
232270
String labelName = safeName(entry.getKey());
233-
if (lowercaseOutputLabelNames) {
271+
if (config.lowercaseOutputLabelNames) {
234272
labelName = labelName.toLowerCase();
235273
}
236274
labelNames.add(labelName);
@@ -256,7 +294,7 @@ public void recordBean(
256294
String help = attrDescription + " (" + beanName + attrName + ")";
257295
String attrNameSnakeCase = snakeCasePattern.matcher(attrName).replaceAll("$1_$2").toLowerCase();
258296

259-
for (Rule rule : rules) {
297+
for (Rule rule : config.rules) {
260298
Matcher matcher = null;
261299
String matchName = beanName + (rule.attrNameSnakeCase ? attrNameSnakeCase : attrName);
262300
if (rule.pattern != null) {
@@ -297,7 +335,7 @@ public void recordBean(
297335
if (name.isEmpty()) {
298336
return;
299337
}
300-
if (lowercaseOutputName) {
338+
if (config.lowercaseOutputName) {
301339
name = name.toLowerCase();
302340
}
303341

@@ -316,7 +354,7 @@ public void recordBean(
316354
try {
317355
String labelName = safeName(matcher.replaceAll(unsafeLabelName));
318356
String labelValue = matcher.replaceAll(labelValReplacement);
319-
if (lowercaseOutputLabelNames) {
357+
if (config.lowercaseOutputLabelNames) {
320358
labelName = labelName.toLowerCase();
321359
}
322360
if (!labelName.isEmpty() && !labelValue.isEmpty()) {
@@ -340,8 +378,16 @@ public void recordBean(
340378
}
341379

342380
public List<MetricFamilySamples> collect() {
381+
if (configFile != null) {
382+
long mtime = configFile.lastModified();
383+
if (mtime > config.lastUpdate) {
384+
LOGGER.fine("Configuration file changed, reloading...");
385+
reloadConfig();
386+
}
387+
}
388+
343389
Receiver receiver = new Receiver();
344-
JmxScraper scraper = new JmxScraper(jmxUrl, username, password, whitelistObjectNames, blacklistObjectNames, receiver);
390+
JmxScraper scraper = new JmxScraper(config.jmxUrl, config.username, config.password, config.whitelistObjectNames, config.blacklistObjectNames, receiver);
345391
long start = System.nanoTime();
346392
double error = 0;
347393
try {

jmx_prometheus_httpserver/src/main/java/io/prometheus/jmx/WebServer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.prometheus.jmx;
22

33
import io.prometheus.client.exporter.MetricsServlet;
4-
import java.io.FileReader;
4+
import java.io.File;
55
import org.eclipse.jetty.server.Server;
66
import org.eclipse.jetty.servlet.ServletContextHandler;
77
import org.eclipse.jetty.servlet.ServletHolder;
@@ -12,7 +12,7 @@ public static void main(String[] args) throws Exception {
1212
System.err.println("Usage: WebServer <port> <yaml configuration file>");
1313
System.exit(1);
1414
}
15-
JmxCollector jc = new JmxCollector(new FileReader(args[1])).register();
15+
JmxCollector jc = new JmxCollector(new File(args[1])).register();
1616

1717
int port = Integer.parseInt(args[0]);
1818
Server server = new Server(port);

jmx_prometheus_javaagent/src/main/java/io/prometheus/jmx/JavaAgent.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import io.prometheus.client.exporter.MetricsServlet;
44
import io.prometheus.client.hotspot.DefaultExports;
55
import java.lang.instrument.Instrumentation;
6-
import java.io.FileReader;
6+
import java.io.File;
77
import java.net.InetSocketAddress;
88
import org.eclipse.jetty.server.Server;
99
import org.eclipse.jetty.servlet.ServletContextHandler;
@@ -34,7 +34,7 @@ public static void premain(String agentArgument, Instrumentation instrumentation
3434
file = args[1];
3535
}
3636

37-
new JmxCollector(new FileReader(file)).register();
37+
new JmxCollector(new File(file)).register();
3838
DefaultExports.initialize();
3939

4040
server = new Server(socket);

0 commit comments

Comments
 (0)