Skip to content

Commit 0a3437b

Browse files
Redesign cache for better performance (#1163)
Signed-off-by: Rafał Sumisławski <rafal.sumislawski@coralogix.com>
1 parent 345259a commit 0a3437b

File tree

2 files changed

+233
-213
lines changed

2 files changed

+233
-213
lines changed

collector/src/main/java/io/prometheus/jmx/JmxCollector.java

Lines changed: 165 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static java.util.logging.Level.FINE;
2121
import static java.util.logging.Level.SEVERE;
2222

23+
import io.prometheus.jmx.MatchedRulesCache.CacheKey;
2324
import io.prometheus.jmx.logger.Logger;
2425
import io.prometheus.jmx.logger.LoggerFactory;
2526
import io.prometheus.metrics.core.metrics.Counter;
@@ -475,7 +476,15 @@ private Config loadConfig(Map<String, Object> yamlConfig) throws MalformedObject
475476
cfg.rules.add(new Rule());
476477
}
477478

478-
cfg.rulesCache = new MatchedRulesCache(cfg.rules);
479+
boolean hasCachedRules = false;
480+
for (Rule rule : cfg.rules) {
481+
hasCachedRules |= rule.cache;
482+
}
483+
484+
// Avoid all costs related to maintaining the cache if there are no cached rules
485+
if (hasCachedRules) {
486+
cfg.rulesCache = new MatchedRulesCache();
487+
}
479488
cfg.objectNameAttributeFilter = ObjectNameAttributeFilter.create(yamlConfig);
480489

481490
return cfg;
@@ -563,12 +572,10 @@ private String angleBrackets(String s) {
563572
}
564573

565574
// Add the matched rule to the cached rules and tag it as not stale
566-
// if the rule is configured to be cached
567-
private void addToCache(
568-
final Rule rule, final String cacheKey, final MatchedRule matchedRule) {
569-
if (rule.cache) {
570-
config.rulesCache.put(rule, cacheKey, matchedRule);
571-
stalenessTracker.add(rule, cacheKey);
575+
private void addToCache(final CacheKey cacheKey, final MatchedRule matchedRule) {
576+
if (config.rulesCache != null && cacheKey != null) {
577+
config.rulesCache.put(cacheKey, matchedRule);
578+
stalenessTracker.markAsFresh(cacheKey);
572579
}
573580
}
574581

@@ -634,161 +641,173 @@ public void recordBean(
634641
String attrDescription,
635642
Object beanValue) {
636643

637-
String beanName =
638-
domain
639-
+ angleBrackets(beanProperties.toString())
640-
+ angleBrackets(attrKeys.toString());
641-
642-
// Build the HELP string from the bean metadata.
643-
String help =
644-
domain
645-
+ ":name="
646-
+ beanProperties.get("name")
647-
+ ",type="
648-
+ beanProperties.get("type")
649-
+ ",attribute="
650-
+ attrName;
651-
// Add the attrDescription to the HELP if it exists and is useful.
652-
if (attrDescription != null && !attrDescription.equals(attrName)) {
653-
help = attrDescription + " " + help;
654-
}
655-
656644
MatchedRule matchedRule = MatchedRule.unmatched();
657645

658-
for (Rule rule : config.rules) {
659-
// Rules with bean values cannot be properly cached (only the value from the first
660-
// scrape will be cached).
661-
// If caching for the rule is enabled, replace the value with a dummy <cache> to
662-
// avoid caching different values at different times.
663-
Object matchBeanValue = rule.cache ? "<cache>" : beanValue;
646+
CacheKey cacheKey = null;
647+
MatchedRule cachedRule = null;
664648

665-
String attributeName;
666-
if (rule.attrNameSnakeCase) {
667-
attributeName = toSnakeAndLowerCase(attrName);
668-
} else {
669-
attributeName = attrName;
649+
if (config.rulesCache != null) {
650+
cacheKey = new CacheKey(domain, beanProperties, attrKeys, attrName);
651+
cachedRule = config.rulesCache.get(cacheKey);
652+
if (cachedRule != null) {
653+
stalenessTracker.markAsFresh(cacheKey);
654+
matchedRule = cachedRule;
670655
}
656+
}
671657

672-
String matchName = (beanName + attributeName + ": " + matchBeanValue).intern();
658+
if (matchedRule.isUnmatched()) {
673659

674-
if (rule.cache) {
675-
MatchedRule cachedRule = config.rulesCache.get(rule, matchName);
676-
if (cachedRule != null) {
677-
stalenessTracker.add(rule, matchName);
678-
if (cachedRule.isMatched()) {
679-
matchedRule = cachedRule;
680-
break;
681-
}
660+
String beanName =
661+
domain
662+
+ angleBrackets(beanProperties.toString())
663+
+ angleBrackets(attrKeys.toString());
664+
665+
// Build the HELP string from the bean metadata.
666+
String help =
667+
domain
668+
+ ":name="
669+
+ beanProperties.get("name")
670+
+ ",type="
671+
+ beanProperties.get("type")
672+
+ ",attribute="
673+
+ attrName;
674+
// Add the attrDescription to the HELP if it exists and is useful.
675+
if (attrDescription != null && !attrDescription.equals(attrName)) {
676+
help = attrDescription + " " + help;
677+
}
682678

683-
// The bean was cached earlier, but did not match the current rule.
684-
// Skip it to avoid matching against the same pattern again
679+
for (Rule rule : config.rules) {
680+
681+
// If we cache that rule, and we found a cache entry for this bean/attribute,
682+
// then what's left to do is to check all uncached rules
683+
if (rule.cache && cachedRule != null) {
685684
continue;
686685
}
687-
}
688686

689-
Matcher matcher = null;
690-
if (rule.pattern != null) {
691-
matcher = rule.pattern.matcher(matchName);
692-
if (!matcher.matches()) {
693-
addToCache(rule, matchName, MatchedRule.unmatched());
694-
continue;
687+
// Rules with bean values cannot be properly cached (only the value from the
688+
// first
689+
// scrape will be cached).
690+
// If caching for the rule is enabled, replace the value with a dummy <cache> to
691+
// avoid caching different values at different times.
692+
Object matchBeanValue = rule.cache ? "<cache>" : beanValue;
693+
694+
String attributeName;
695+
if (rule.attrNameSnakeCase) {
696+
attributeName = toSnakeAndLowerCase(attrName);
697+
} else {
698+
attributeName = attrName;
695699
}
696-
}
697700

698-
Double value = null;
699-
if (rule.value != null && !rule.value.isEmpty()) {
700-
String val = matcher.replaceAll(rule.value);
701-
try {
702-
value = Double.valueOf(val);
703-
} catch (NumberFormatException e) {
704-
LOGGER.log(
705-
FINE,
706-
"Unable to parse configured value '%s' to number for bean: %s%s:"
707-
+ " %s",
708-
val,
709-
beanName,
710-
attrName,
711-
beanValue);
712-
return;
701+
String matchName = beanName + attributeName + ": " + matchBeanValue;
702+
703+
Matcher matcher = null;
704+
if (rule.pattern != null) {
705+
matcher = rule.pattern.matcher(matchName);
706+
if (!matcher.matches()) {
707+
continue;
708+
}
713709
}
714-
}
715710

716-
// If there's no name provided, use default export format.
717-
if (rule.name == null) {
718-
matchedRule =
719-
defaultExport(
720-
matchName,
721-
domain,
722-
beanProperties,
723-
attrKeys,
724-
attributeName,
725-
help,
726-
value,
727-
rule.valueFactor,
728-
rule.type,
729-
attributesAsLabelsWithValues);
730-
addToCache(rule, matchName, matchedRule);
731-
break;
732-
}
711+
Double value = null;
712+
if (rule.value != null && !rule.value.isEmpty()) {
713+
String val = matcher.replaceAll(rule.value);
714+
try {
715+
value = Double.valueOf(val);
716+
} catch (NumberFormatException e) {
717+
LOGGER.log(
718+
FINE,
719+
"Unable to parse configured value '%s' to number for bean:"
720+
+ " %s%s: %s",
721+
val,
722+
beanName,
723+
attrName,
724+
beanValue);
725+
return;
726+
}
727+
}
733728

734-
// Matcher is set below here due to validation in the constructor.
735-
String name = safeName(matcher.replaceAll(rule.name));
736-
if (name.isEmpty()) {
737-
return;
738-
}
739-
if (config.lowercaseOutputName) {
740-
name = name.toLowerCase();
741-
}
729+
// If there's no name provided, use default export format.
730+
if (rule.name == null) {
731+
matchedRule =
732+
defaultExport(
733+
matchName,
734+
domain,
735+
beanProperties,
736+
attrKeys,
737+
attributeName,
738+
help,
739+
value,
740+
rule.valueFactor,
741+
rule.type,
742+
attributesAsLabelsWithValues);
743+
if (rule.cache) {
744+
addToCache(cacheKey, matchedRule);
745+
}
746+
break;
747+
}
742748

743-
// Set the help.
744-
if (rule.help != null) {
745-
help = matcher.replaceAll(rule.help);
746-
}
749+
// Matcher is set below here due to validation in the constructor.
750+
String name = safeName(matcher.replaceAll(rule.name));
751+
if (name.isEmpty()) {
752+
return;
753+
}
754+
if (config.lowercaseOutputName) {
755+
name = name.toLowerCase();
756+
}
747757

748-
// Set the labels.
749-
ArrayList<String> labelNames = new ArrayList<>();
750-
ArrayList<String> labelValues = new ArrayList<>();
751-
addAttributesAsLabelsWithValuesToLabels(
752-
config, attributesAsLabelsWithValues, labelNames, labelValues);
753-
if (rule.labelNames != null) {
754-
for (int i = 0; i < rule.labelNames.size(); i++) {
755-
final String unsafeLabelName = rule.labelNames.get(i);
756-
final String labelValReplacement = rule.labelValues.get(i);
757-
try {
758-
String labelName = safeName(matcher.replaceAll(unsafeLabelName));
759-
String labelValue = matcher.replaceAll(labelValReplacement);
760-
if (config.lowercaseOutputLabelNames) {
761-
labelName = labelName.toLowerCase();
762-
}
763-
if (!labelName.isEmpty() && !labelValue.isEmpty()) {
764-
labelNames.add(labelName);
765-
labelValues.add(labelValue);
758+
// Set the help.
759+
if (rule.help != null) {
760+
help = matcher.replaceAll(rule.help);
761+
}
762+
763+
// Set the labels.
764+
ArrayList<String> labelNames = new ArrayList<>();
765+
ArrayList<String> labelValues = new ArrayList<>();
766+
addAttributesAsLabelsWithValuesToLabels(
767+
config, attributesAsLabelsWithValues, labelNames, labelValues);
768+
if (rule.labelNames != null) {
769+
for (int i = 0; i < rule.labelNames.size(); i++) {
770+
final String unsafeLabelName = rule.labelNames.get(i);
771+
final String labelValReplacement = rule.labelValues.get(i);
772+
try {
773+
String labelName = safeName(matcher.replaceAll(unsafeLabelName));
774+
String labelValue = matcher.replaceAll(labelValReplacement);
775+
if (config.lowercaseOutputLabelNames) {
776+
labelName = labelName.toLowerCase();
777+
}
778+
if (!labelName.isEmpty() && !labelValue.isEmpty()) {
779+
labelNames.add(labelName);
780+
labelValues.add(labelValue);
781+
}
782+
} catch (Exception e) {
783+
throw new RuntimeException(
784+
format(
785+
"Matcher '%s' unable to use: '%s' value: '%s'",
786+
matcher, unsafeLabelName, labelValReplacement),
787+
e);
766788
}
767-
} catch (Exception e) {
768-
throw new RuntimeException(
769-
format(
770-
"Matcher '%s' unable to use: '%s' value: '%s'",
771-
matcher, unsafeLabelName, labelValReplacement),
772-
e);
773789
}
774790
}
775-
}
776791

777-
matchedRule =
778-
new MatchedRule(
779-
name,
780-
matchName,
781-
rule.type,
782-
help,
783-
labelNames,
784-
labelValues,
785-
value,
786-
rule.valueFactor);
787-
addToCache(rule, matchName, matchedRule);
788-
break;
792+
matchedRule =
793+
new MatchedRule(
794+
name,
795+
matchName,
796+
rule.type,
797+
help,
798+
labelNames,
799+
labelValues,
800+
value,
801+
rule.valueFactor);
802+
if (rule.cache) {
803+
addToCache(cacheKey, matchedRule);
804+
}
805+
break;
806+
}
789807
}
790808

791809
if (matchedRule.isUnmatched()) {
810+
addToCache(cacheKey, matchedRule);
792811
return;
793812
}
794813

@@ -804,8 +823,10 @@ public void recordBean(
804823
} else {
805824
LOGGER.log(
806825
FINE,
807-
"Ignoring unsupported bean: %s%s: %s ",
808-
beanName,
826+
"Ignoring unsupported bean: %s%s%s%s: %s ",
827+
domain,
828+
angleBrackets(beanProperties.toString()),
829+
angleBrackets(attrKeys.toString()),
809830
attrName,
810831
beanValue);
811832
return;
@@ -880,11 +901,13 @@ public MetricSnapshots collect() {
880901
LOGGER.log(SEVERE, "JMX scrape failed: %s", sw);
881902
}
882903

883-
config.rulesCache.evictStaleEntries(stalenessTracker);
904+
if (config.rulesCache != null) {
905+
config.rulesCache.evictStaleEntries(stalenessTracker);
906+
}
884907

885908
jmxScrapeDurationSeconds.set((System.nanoTime() - start) / 1.0E9);
886909
jmxScrapeError.set(error);
887-
jmxScrapeCachedBeans.set(stalenessTracker.cachedCount());
910+
jmxScrapeCachedBeans.set(stalenessTracker.freshCount());
888911

889912
return MatchedRuleToMetricSnapshotsConverter.convert(receiver.matchedRules);
890913
}

0 commit comments

Comments
 (0)