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}