11package io .prometheus .jmx ;
22
33import io .prometheus .client .Collector ;
4+ import io .prometheus .client .Counter ;
45import java .io .IOException ;
56import java .io .PrintWriter ;
6- import java .io .Reader ;
7+ import java .io .File ;
8+ import java .io .FileReader ;
79import java .io .StringWriter ;
810import java .util .ArrayList ;
911import java .util .Iterator ;
2527import static java .lang .String .format ;
2628
2729public 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 {
0 commit comments