001package io.prometheus.jmx; 002 003import io.prometheus.client.Collector; 004import io.prometheus.client.Counter; 005import org.yaml.snakeyaml.Yaml; 006 007import javax.management.MalformedObjectNameException; 008import javax.management.ObjectName; 009import java.io.File; 010import java.io.FileReader; 011import java.io.IOException; 012import java.io.InputStream; 013import java.io.PrintWriter; 014import java.io.StringWriter; 015import java.util.ArrayList; 016import java.util.HashMap; 017import java.util.HashSet; 018import java.util.Iterator; 019import java.util.LinkedHashMap; 020import java.util.LinkedList; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.TreeMap; 025import java.util.logging.Level; 026import java.util.logging.Logger; 027import java.util.regex.Matcher; 028import java.util.regex.Pattern; 029 030import static java.lang.String.format; 031 032public class JmxCollector extends Collector implements Collector.Describable { 033 034 public enum Mode { 035 AGENT, 036 STANDALONE 037 } 038 039 private final Mode mode; 040 041 static final Counter configReloadSuccess = Counter.build() 042 .name("jmx_config_reload_success_total") 043 .help("Number of times configuration have successfully been reloaded.").register(); 044 045 static final Counter configReloadFailure = Counter.build() 046 .name("jmx_config_reload_failure_total") 047 .help("Number of times configuration have failed to be reloaded.").register(); 048 049 private static final Logger LOGGER = Logger.getLogger(JmxCollector.class.getName()); 050 051 static class Rule { 052 Pattern pattern; 053 String name; 054 String value; 055 Double valueFactor = 1.0; 056 String help; 057 boolean attrNameSnakeCase; 058 boolean cache = false; 059 Type type = Type.UNKNOWN; 060 ArrayList<String> labelNames; 061 ArrayList<String> labelValues; 062 } 063 064 private static class Config { 065 Integer startDelaySeconds = 0; 066 String jmxUrl = ""; 067 String username = ""; 068 String password = ""; 069 boolean ssl = false; 070 boolean lowercaseOutputName; 071 boolean lowercaseOutputLabelNames; 072 List<ObjectName> whitelistObjectNames = new ArrayList<ObjectName>(); 073 List<ObjectName> blacklistObjectNames = new ArrayList<ObjectName>(); 074 List<Rule> rules = new ArrayList<Rule>(); 075 long lastUpdate = 0L; 076 077 MatchedRulesCache rulesCache; 078 } 079 080 private Config config; 081 private File configFile; 082 private long createTimeNanoSecs = System.nanoTime(); 083 084 private final JmxMBeanPropertyCache jmxMBeanPropertyCache = new JmxMBeanPropertyCache(); 085 086 public JmxCollector(File in) throws IOException, MalformedObjectNameException { 087 this(in, null); 088 } 089 090 public JmxCollector(File in, Mode mode) throws IOException, MalformedObjectNameException { 091 configFile = in; 092 this.mode = mode; 093 config = loadConfig((Map<String, Object>)new Yaml().load(new FileReader(in))); 094 config.lastUpdate = configFile.lastModified(); 095 exitOnConfigError(); 096 } 097 098 public JmxCollector(String yamlConfig) throws MalformedObjectNameException { 099 config = loadConfig((Map<String, Object>)new Yaml().load(yamlConfig)); 100 mode = null; 101 } 102 103 public JmxCollector(InputStream inputStream) throws MalformedObjectNameException { 104 config = loadConfig((Map<String, Object>)new Yaml().load(inputStream)); 105 mode = null; 106 } 107 108 private void exitOnConfigError() { 109 if (mode == Mode.AGENT && !config.jmxUrl.isEmpty()) { 110 LOGGER.severe("Configuration error: When running jmx_exporter as a Java agent, you must not configure 'jmxUrl' or 'hostPort' because you don't want to monitor a remote JVM."); 111 System.exit(-1); 112 } 113 if (mode == Mode.STANDALONE && config.jmxUrl.isEmpty()) { 114 LOGGER.severe("Configuration error: When running jmx_exporter in standalone mode (using jmx_prometheus_httpserver-*.jar) you must configure 'jmxUrl' or 'hostPort'."); 115 System.exit(-1); 116 } 117 } 118 119 private void reloadConfig() { 120 try { 121 FileReader fr = new FileReader(configFile); 122 123 try { 124 Map<String, Object> newYamlConfig = (Map<String, Object>)new Yaml().load(fr); 125 config = loadConfig(newYamlConfig); 126 config.lastUpdate = configFile.lastModified(); 127 configReloadSuccess.inc(); 128 } catch (Exception e) { 129 LOGGER.severe("Configuration reload failed: " + e.toString()); 130 configReloadFailure.inc(); 131 } finally { 132 fr.close(); 133 } 134 135 } catch (IOException e) { 136 LOGGER.severe("Configuration reload failed: " + e.toString()); 137 configReloadFailure.inc(); 138 } 139 } 140 141 private synchronized Config getLatestConfig() { 142 if (configFile != null) { 143 long mtime = configFile.lastModified(); 144 if (mtime > config.lastUpdate) { 145 LOGGER.fine("Configuration file changed, reloading..."); 146 reloadConfig(); 147 } 148 } 149 exitOnConfigError(); 150 return config; 151 } 152 153 private Config loadConfig(Map<String, Object> yamlConfig) throws MalformedObjectNameException { 154 Config cfg = new Config(); 155 156 if (yamlConfig == null) { // Yaml config empty, set config to empty map. 157 yamlConfig = new HashMap<String, Object>(); 158 } 159 160 if (yamlConfig.containsKey("startDelaySeconds")) { 161 try { 162 cfg.startDelaySeconds = (Integer) yamlConfig.get("startDelaySeconds"); 163 } catch (NumberFormatException e) { 164 throw new IllegalArgumentException("Invalid number provided for startDelaySeconds", e); 165 } 166 } 167 if (yamlConfig.containsKey("hostPort")) { 168 if (yamlConfig.containsKey("jmxUrl")) { 169 throw new IllegalArgumentException("At most one of hostPort and jmxUrl must be provided"); 170 } 171 cfg.jmxUrl ="service:jmx:rmi:///jndi/rmi://" + (String)yamlConfig.get("hostPort") + "/jmxrmi"; 172 } else if (yamlConfig.containsKey("jmxUrl")) { 173 cfg.jmxUrl = (String)yamlConfig.get("jmxUrl"); 174 } 175 176 if (yamlConfig.containsKey("username")) { 177 cfg.username = (String)yamlConfig.get("username"); 178 } 179 180 if (yamlConfig.containsKey("password")) { 181 cfg.password = (String)yamlConfig.get("password"); 182 } 183 184 if (yamlConfig.containsKey("ssl")) { 185 cfg.ssl = (Boolean)yamlConfig.get("ssl"); 186 } 187 188 if (yamlConfig.containsKey("lowercaseOutputName")) { 189 cfg.lowercaseOutputName = (Boolean)yamlConfig.get("lowercaseOutputName"); 190 } 191 192 if (yamlConfig.containsKey("lowercaseOutputLabelNames")) { 193 cfg.lowercaseOutputLabelNames = (Boolean)yamlConfig.get("lowercaseOutputLabelNames"); 194 } 195 196 if (yamlConfig.containsKey("whitelistObjectNames")) { 197 List<Object> names = (List<Object>) yamlConfig.get("whitelistObjectNames"); 198 for(Object name : names) { 199 cfg.whitelistObjectNames.add(new ObjectName((String)name)); 200 } 201 } else { 202 cfg.whitelistObjectNames.add(null); 203 } 204 205 if (yamlConfig.containsKey("blacklistObjectNames")) { 206 List<Object> names = (List<Object>) yamlConfig.get("blacklistObjectNames"); 207 for (Object name : names) { 208 cfg.blacklistObjectNames.add(new ObjectName((String)name)); 209 } 210 } 211 212 if (yamlConfig.containsKey("rules")) { 213 List<Map<String,Object>> configRules = (List<Map<String,Object>>) yamlConfig.get("rules"); 214 for (Map<String, Object> ruleObject : configRules) { 215 Map<String, Object> yamlRule = ruleObject; 216 Rule rule = new Rule(); 217 cfg.rules.add(rule); 218 if (yamlRule.containsKey("pattern")) { 219 rule.pattern = Pattern.compile("^.*(?:" + (String)yamlRule.get("pattern") + ").*$"); 220 } 221 if (yamlRule.containsKey("name")) { 222 rule.name = (String)yamlRule.get("name"); 223 } 224 if (yamlRule.containsKey("value")) { 225 rule.value = String.valueOf(yamlRule.get("value")); 226 } 227 if (yamlRule.containsKey("valueFactor")) { 228 String valueFactor = String.valueOf(yamlRule.get("valueFactor")); 229 try { 230 rule.valueFactor = Double.valueOf(valueFactor); 231 } catch (NumberFormatException e) { 232 // use default value 233 } 234 } 235 if (yamlRule.containsKey("attrNameSnakeCase")) { 236 rule.attrNameSnakeCase = (Boolean)yamlRule.get("attrNameSnakeCase"); 237 } 238 if (yamlRule.containsKey("cache")) { 239 rule.cache = (Boolean)yamlRule.get("cache"); 240 } 241 if (yamlRule.containsKey("type")) { 242 String t = (String)yamlRule.get("type"); 243 // Gracefully handle switch to OM data model. 244 if ("UNTYPED".equals(t)) { 245 t = "UNKNOWN"; 246 } 247 rule.type = Type.valueOf(t); 248 } 249 if (yamlRule.containsKey("help")) { 250 rule.help = (String)yamlRule.get("help"); 251 } 252 if (yamlRule.containsKey("labels")) { 253 TreeMap labels = new TreeMap((Map<String, Object>)yamlRule.get("labels")); 254 rule.labelNames = new ArrayList<String>(); 255 rule.labelValues = new ArrayList<String>(); 256 for (Map.Entry<String, Object> entry : (Set<Map.Entry<String, Object>>)labels.entrySet()) { 257 rule.labelNames.add(entry.getKey()); 258 rule.labelValues.add((String)entry.getValue()); 259 } 260 } 261 262 // Validation. 263 if ((rule.labelNames != null || rule.help != null) && rule.name == null) { 264 throw new IllegalArgumentException("Must provide name, if help or labels are given: " + yamlRule); 265 } 266 if (rule.name != null && rule.pattern == null) { 267 throw new IllegalArgumentException("Must provide pattern, if name is given: " + yamlRule); 268 } 269 } 270 } else { 271 // Default to a single default rule. 272 cfg.rules.add(new Rule()); 273 } 274 275 cfg.rulesCache = new MatchedRulesCache(cfg.rules); 276 277 return cfg; 278 279 } 280 281 static String toSnakeAndLowerCase(String attrName) { 282 if (attrName == null || attrName.isEmpty()) { 283 return attrName; 284 } 285 char firstChar = attrName.subSequence(0, 1).charAt(0); 286 boolean prevCharIsUpperCaseOrUnderscore = Character.isUpperCase(firstChar) || firstChar == '_'; 287 StringBuilder resultBuilder = new StringBuilder(attrName.length()).append(Character.toLowerCase(firstChar)); 288 for (char attrChar : attrName.substring(1).toCharArray()) { 289 boolean charIsUpperCase = Character.isUpperCase(attrChar); 290 if (!prevCharIsUpperCaseOrUnderscore && charIsUpperCase) { 291 resultBuilder.append("_"); 292 } 293 resultBuilder.append(Character.toLowerCase(attrChar)); 294 prevCharIsUpperCaseOrUnderscore = charIsUpperCase || attrChar == '_'; 295 } 296 return resultBuilder.toString(); 297 } 298 299 /** 300 * Change invalid chars to underscore, and merge underscores. 301 * @param name Input string 302 * @return 303 */ 304 static String safeName(String name) { 305 if (name == null) { 306 return null; 307 } 308 boolean prevCharIsUnderscore = false; 309 StringBuilder safeNameBuilder = new StringBuilder(name.length()); 310 if (!name.isEmpty() && Character.isDigit(name.charAt(0))) { 311 // prevent a numeric prefix. 312 safeNameBuilder.append("_"); 313 } 314 for (char nameChar : name.toCharArray()) { 315 boolean isUnsafeChar = !JmxCollector.isLegalCharacter(nameChar); 316 if ((isUnsafeChar || nameChar == '_')) { 317 if (prevCharIsUnderscore) { 318 continue; 319 } else { 320 safeNameBuilder.append("_"); 321 prevCharIsUnderscore = true; 322 } 323 } else { 324 safeNameBuilder.append(nameChar); 325 prevCharIsUnderscore = false; 326 } 327 } 328 329 return safeNameBuilder.toString(); 330 } 331 332 private static boolean isLegalCharacter(char input) { 333 return ((input == ':') || 334 (input == '_') || 335 (input >= 'a' && input <= 'z') || 336 (input >= 'A' && input <= 'Z') || 337 (input >= '0' && input <= '9')); 338 } 339 340 /** 341 * A sample is uniquely identified by its name, labelNames and labelValues 342 */ 343 private static class SampleKey { 344 private final String name; 345 private final List<String> labelNames; 346 private final List<String> labelValues; 347 348 private SampleKey(String name, List<String> labelNames, List<String> labelValues) { 349 this.name = name; 350 this.labelNames = labelNames; 351 this.labelValues = labelValues; 352 } 353 354 private static SampleKey of(MetricFamilySamples.Sample sample) { 355 return new SampleKey(sample.name, sample.labelNames, sample.labelValues); 356 } 357 358 @Override 359 public boolean equals(Object o) { 360 if (this == o) return true; 361 if (o == null || getClass() != o.getClass()) return false; 362 363 SampleKey sampleKey = (SampleKey) o; 364 365 if (name != null ? !name.equals(sampleKey.name) : sampleKey.name != null) return false; 366 if (labelValues != null ? !labelValues.equals(sampleKey.labelValues) : sampleKey.labelValues != null) return false; 367 return labelNames != null ? labelNames.equals(sampleKey.labelNames) : sampleKey.labelNames == null; 368 } 369 370 @Override 371 public int hashCode() { 372 int result = name != null ? name.hashCode() : 0; 373 result = 31 * result + (labelNames != null ? labelNames.hashCode() : 0); 374 result = 31 * result + (labelValues != null ? labelValues.hashCode() : 0); 375 return result; 376 } 377 378 } 379 380 static class Receiver implements JmxScraper.MBeanReceiver { 381 Map<String, MetricFamilySamples> metricFamilySamplesMap = 382 new HashMap<String, MetricFamilySamples>(); 383 Set<SampleKey> sampleKeys = new HashSet<SampleKey>(); 384 385 Config config; 386 MatchedRulesCache.StalenessTracker stalenessTracker; 387 388 private static final char SEP = '_'; 389 390 Receiver(Config config, MatchedRulesCache.StalenessTracker stalenessTracker) { 391 this.config = config; 392 this.stalenessTracker = stalenessTracker; 393 } 394 395 // [] and () are special in regexes, so swtich to <>. 396 private String angleBrackets(String s) { 397 return "<" + s.substring(1, s.length() - 1) + ">"; 398 } 399 400 void addSample(MetricFamilySamples.Sample sample, Type type, String help) { 401 MetricFamilySamples mfs = metricFamilySamplesMap.get(sample.name); 402 if (mfs == null) { 403 // JmxScraper.MBeanReceiver is only called from one thread, 404 // so there's no race here. 405 mfs = new MetricFamilySamples(sample.name, type, help, new ArrayList<MetricFamilySamples.Sample>()); 406 metricFamilySamplesMap.put(sample.name, mfs); 407 } 408 SampleKey sampleKey = SampleKey.of(sample); 409 boolean exists = sampleKeys.contains(sampleKey); 410 if (exists) { 411 if (LOGGER.isLoggable(Level.FINE)) { 412 String labels = "{"; 413 for (int i = 0; i < sample.labelNames.size(); i++) { 414 labels += sample.labelNames.get(i) + "=" + sample.labelValues.get(i) + ","; 415 } 416 labels += "}"; 417 LOGGER.fine("Metric " + sample.name + labels + " was created multiple times. Keeping the first occurrence. Dropping the others."); 418 } 419 } else { 420 mfs.samples.add(sample); 421 sampleKeys.add(sampleKey); 422 } 423 } 424 425 // Add the matched rule to the cached rules and tag it as not stale 426 // if the rule is configured to be cached 427 private void addToCache(final Rule rule, final String cacheKey, final MatchedRule matchedRule) { 428 if (rule.cache) { 429 config.rulesCache.put(rule, cacheKey, matchedRule); 430 stalenessTracker.add(rule, cacheKey); 431 } 432 } 433 434 private MatchedRule defaultExport( 435 String matchName, 436 String domain, 437 LinkedHashMap<String, String> beanProperties, 438 LinkedList<String> attrKeys, 439 String attrName, 440 String help, 441 Double value, 442 double valueFactor, 443 Type type) { 444 StringBuilder name = new StringBuilder(); 445 name.append(domain); 446 if (beanProperties.size() > 0) { 447 name.append(SEP); 448 name.append(beanProperties.values().iterator().next()); 449 } 450 for (String k : attrKeys) { 451 name.append(SEP); 452 name.append(k); 453 } 454 name.append(SEP); 455 name.append(attrName); 456 String fullname = safeName(name.toString()); 457 458 if (config.lowercaseOutputName) { 459 fullname = fullname.toLowerCase(); 460 } 461 462 List<String> labelNames = new ArrayList<String>(); 463 List<String> labelValues = new ArrayList<String>(); 464 if (beanProperties.size() > 1) { 465 Iterator<Map.Entry<String, String>> iter = beanProperties.entrySet().iterator(); 466 // Skip the first one, it's been used in the name. 467 iter.next(); 468 while (iter.hasNext()) { 469 Map.Entry<String, String> entry = iter.next(); 470 String labelName = safeName(entry.getKey()); 471 if (config.lowercaseOutputLabelNames) { 472 labelName = labelName.toLowerCase(); 473 } 474 labelNames.add(labelName); 475 labelValues.add(entry.getValue()); 476 } 477 } 478 479 return new MatchedRule(fullname, matchName, type, help, labelNames, labelValues, value, valueFactor); 480 } 481 482 public void recordBean( 483 String domain, 484 LinkedHashMap<String, String> beanProperties, 485 LinkedList<String> attrKeys, 486 String attrName, 487 String attrType, 488 String attrDescription, 489 Object beanValue) { 490 491 String beanName = domain + angleBrackets(beanProperties.toString()) + angleBrackets(attrKeys.toString()); 492 493 // Build the HELP string from the bean metadata. 494 String help = domain + ":name=" + beanProperties.get("name") + ",type=" + beanProperties.get("type") + ",attribute=" + attrName; 495 // Add the attrDescription to the HELP if it exists and is useful. 496 if (attrDescription != null && !attrDescription.equals(attrName)) { 497 help = attrDescription + " " + help; 498 } 499 500 String attrNameSnakeCase = toSnakeAndLowerCase(attrName); 501 502 MatchedRule matchedRule = MatchedRule.unmatched(); 503 504 for (Rule rule : config.rules) { 505 // Rules with bean values cannot be properly cached (only the value from the first scrape will be cached). 506 // If caching for the rule is enabled, replace the value with a dummy <cache> to avoid caching different values at different times. 507 Object matchBeanValue = rule.cache ? "<cache>" : beanValue; 508 509 String matchName = beanName + (rule.attrNameSnakeCase ? attrNameSnakeCase : attrName) + ": " + matchBeanValue; 510 511 if (rule.cache) { 512 MatchedRule cachedRule = config.rulesCache.get(rule, matchName); 513 if (cachedRule != null) { 514 stalenessTracker.add(rule, matchName); 515 if (cachedRule.isMatched()) { 516 matchedRule = cachedRule; 517 break; 518 } 519 520 // The bean was cached earlier, but did not match the current rule. 521 // Skip it to avoid matching against the same pattern again 522 continue; 523 } 524 } 525 526 Matcher matcher = null; 527 if (rule.pattern != null) { 528 matcher = rule.pattern.matcher(matchName); 529 if (!matcher.matches()) { 530 addToCache(rule, matchName, MatchedRule.unmatched()); 531 continue; 532 } 533 } 534 535 Double value = null; 536 if (rule.value != null && !rule.value.isEmpty()) { 537 String val = matcher.replaceAll(rule.value); 538 try { 539 value = Double.valueOf(val); 540 } catch (NumberFormatException e) { 541 LOGGER.fine("Unable to parse configured value '" + val + "' to number for bean: " + beanName + attrName + ": " + beanValue); 542 return; 543 } 544 } 545 546 // If there's no name provided, use default export format. 547 if (rule.name == null) { 548 matchedRule = defaultExport(matchName, domain, beanProperties, attrKeys, rule.attrNameSnakeCase ? attrNameSnakeCase : attrName, help, value, rule.valueFactor, rule.type); 549 addToCache(rule, matchName, matchedRule); 550 break; 551 } 552 553 // Matcher is set below here due to validation in the constructor. 554 String name = safeName(matcher.replaceAll(rule.name)); 555 if (name.isEmpty()) { 556 return; 557 } 558 if (config.lowercaseOutputName) { 559 name = name.toLowerCase(); 560 } 561 562 // Set the help. 563 if (rule.help != null) { 564 help = matcher.replaceAll(rule.help); 565 } 566 567 // Set the labels. 568 ArrayList<String> labelNames = new ArrayList<String>(); 569 ArrayList<String> labelValues = new ArrayList<String>(); 570 if (rule.labelNames != null) { 571 for (int i = 0; i < rule.labelNames.size(); i++) { 572 final String unsafeLabelName = rule.labelNames.get(i); 573 final String labelValReplacement = rule.labelValues.get(i); 574 try { 575 String labelName = safeName(matcher.replaceAll(unsafeLabelName)); 576 String labelValue = matcher.replaceAll(labelValReplacement); 577 if (config.lowercaseOutputLabelNames) { 578 labelName = labelName.toLowerCase(); 579 } 580 if (!labelName.isEmpty() && !labelValue.isEmpty()) { 581 labelNames.add(labelName); 582 labelValues.add(labelValue); 583 } 584 } catch (Exception e) { 585 throw new RuntimeException( 586 format("Matcher '%s' unable to use: '%s' value: '%s'", matcher, unsafeLabelName, labelValReplacement), e); 587 } 588 } 589 } 590 591 matchedRule = new MatchedRule(name, matchName, rule.type, help, labelNames, labelValues, value, rule.valueFactor); 592 addToCache(rule, matchName, matchedRule); 593 break; 594 } 595 596 if (matchedRule.isUnmatched()) { 597 return; 598 } 599 600 Number value; 601 if (matchedRule.value != null) { 602 beanValue = matchedRule.value; 603 } 604 605 if (beanValue instanceof Number) { 606 value = ((Number) beanValue).doubleValue() * matchedRule.valueFactor; 607 } else if (beanValue instanceof Boolean) { 608 value = (Boolean) beanValue ? 1 : 0; 609 } else { 610 LOGGER.fine("Ignoring unsupported bean: " + beanName + attrName + ": " + beanValue); 611 return; 612 } 613 614 // Add to samples. 615 LOGGER.fine("add metric sample: " + matchedRule.name + " " + matchedRule.labelNames + " " + matchedRule.labelValues + " " + value.doubleValue()); 616 addSample(new MetricFamilySamples.Sample(matchedRule.name, matchedRule.labelNames, matchedRule.labelValues, value.doubleValue()), matchedRule.type, matchedRule.help); 617 } 618 619 } 620 621 public List<MetricFamilySamples> collect() { 622 // Take a reference to the current config and collect with this one 623 // (to avoid race conditions in case another thread reloads the config in the meantime) 624 Config config = getLatestConfig(); 625 626 MatchedRulesCache.StalenessTracker stalenessTracker = new MatchedRulesCache.StalenessTracker(); 627 Receiver receiver = new Receiver(config, stalenessTracker); 628 JmxScraper scraper = new JmxScraper(config.jmxUrl, config.username, config.password, config.ssl, 629 config.whitelistObjectNames, config.blacklistObjectNames, receiver, jmxMBeanPropertyCache); 630 long start = System.nanoTime(); 631 double error = 0; 632 if ((config.startDelaySeconds > 0) && 633 ((start - createTimeNanoSecs) / 1000000000L < config.startDelaySeconds)) { 634 throw new IllegalStateException("JMXCollector waiting for startDelaySeconds"); 635 } 636 try { 637 scraper.doScrape(); 638 } catch (Exception e) { 639 error = 1; 640 StringWriter sw = new StringWriter(); 641 e.printStackTrace(new PrintWriter(sw)); 642 LOGGER.severe("JMX scrape failed: " + sw.toString()); 643 } 644 config.rulesCache.evictStaleEntries(stalenessTracker); 645 646 List<MetricFamilySamples> mfsList = new ArrayList<MetricFamilySamples>(); 647 mfsList.addAll(receiver.metricFamilySamplesMap.values()); 648 List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>(); 649 samples.add(new MetricFamilySamples.Sample( 650 "jmx_scrape_duration_seconds", new ArrayList<String>(), new ArrayList<String>(), (System.nanoTime() - start) / 1.0E9)); 651 mfsList.add(new MetricFamilySamples("jmx_scrape_duration_seconds", Type.GAUGE, "Time this JMX scrape took, in seconds.", samples)); 652 653 samples = new ArrayList<MetricFamilySamples.Sample>(); 654 samples.add(new MetricFamilySamples.Sample( 655 "jmx_scrape_error", new ArrayList<String>(), new ArrayList<String>(), error)); 656 mfsList.add(new MetricFamilySamples("jmx_scrape_error", Type.GAUGE, "Non-zero if this scrape failed.", samples)); 657 samples = new ArrayList<MetricFamilySamples.Sample>(); 658 samples.add(new MetricFamilySamples.Sample( 659 "jmx_scrape_cached_beans", new ArrayList<String>(), new ArrayList<String>(), stalenessTracker.cachedCount())); 660 mfsList.add(new MetricFamilySamples("jmx_scrape_cached_beans", Type.GAUGE, "Number of beans with their matching rule cached", samples)); 661 return mfsList; 662 } 663 664 public List<MetricFamilySamples> describe() { 665 List<MetricFamilySamples> sampleFamilies = new ArrayList<MetricFamilySamples>(); 666 sampleFamilies.add(new MetricFamilySamples("jmx_scrape_duration_seconds", Type.GAUGE, "Time this JMX scrape took, in seconds.", new ArrayList<MetricFamilySamples.Sample>())); 667 sampleFamilies.add(new MetricFamilySamples("jmx_scrape_error", Type.GAUGE, "Non-zero if this scrape failed.", new ArrayList<MetricFamilySamples.Sample>())); 668 sampleFamilies.add(new MetricFamilySamples("jmx_scrape_cached_beans", Type.GAUGE, "Number of beans with their matching rule cached", new ArrayList<MetricFamilySamples.Sample>())); 669 return sampleFamilies; 670 } 671 672 /** 673 * Convenience function to run standalone. 674 */ 675 public static void main(String[] args) throws Exception { 676 String hostPort = ""; 677 if (args.length > 0) { 678 hostPort = args[0]; 679 } 680 JmxCollector jc = new JmxCollector(("{" 681 + "`hostPort`: `" + hostPort + "`," 682 + "}").replace('`', '"')); 683 for(MetricFamilySamples mfs : jc.collect()) { 684 System.out.println(mfs); 685 } 686 } 687}