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