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