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