From 32fae25bbc120fce2759de7eeb7a7a309d518753 Mon Sep 17 00:00:00 2001 From: Max Melentyev Date: Wed, 23 Oct 2024 09:48:39 -0400 Subject: [PATCH] Refresh mbeans cache when they are registered/unregistered Signed-off-by: Max Melentyev --- .../java/io/prometheus/jmx/JmxScraper.java | 90 ++++++++++++++----- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/collector/src/main/java/io/prometheus/jmx/JmxScraper.java b/collector/src/main/java/io/prometheus/jmx/JmxScraper.java index e93b39e8..0dac2391 100644 --- a/collector/src/main/java/io/prometheus/jmx/JmxScraper.java +++ b/collector/src/main/java/io/prometheus/jmx/JmxScraper.java @@ -29,6 +29,7 @@ import javax.management.openmbean.CompositeType; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularType; +import javax.management.relation.MBeanServerNotificationFilter; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; @@ -62,9 +63,23 @@ void recordBean( // Values cached per connection. private MBeanServerConnection _beanConn; - private Set mBeanNames; - private ObjectNameAttributeFilter objectNameAttributeFilter; - private JmxMBeanPropertyCache jmxMBeanPropertyCache; + private Cache cache; + private boolean cacheIsStale = false; + + private class Cache { + private final Set mBeanNames; + private final ObjectNameAttributeFilter objectNameAttributeFilter; + private final JmxMBeanPropertyCache jmxMBeanPropertyCache; + + private Cache( + Set mBeanNames, + ObjectNameAttributeFilter objectNameAttributeFilter, + JmxMBeanPropertyCache jmxMBeanPropertyCache) { + this.mBeanNames = mBeanNames; + this.objectNameAttributeFilter = objectNameAttributeFilter; + this.jmxMBeanPropertyCache = jmxMBeanPropertyCache; + } + } public JmxScraper( String jmxUrl, @@ -119,9 +134,41 @@ private MBeanServerConnection connectToMBeanServer() throws Exception { return jmxc.getMBeanServerConnection(); } - private void loadMBeanNames(MBeanServerConnection beanConn) throws Exception { + private synchronized MBeanServerConnection getMBeanServerConnection() throws Exception { + if (_beanConn == null) { + cacheIsStale = true; + _beanConn = connectToMBeanServer(); + // Subscribe to MBeans register/unregister events to invalidate cache + MBeanServerNotificationFilter filter = new MBeanServerNotificationFilter(); + filter.enableAllObjectNames(); + _beanConn.addNotificationListener( + MBeanServerDelegate.DELEGATE_NAME, + (notification, handback) -> { + String type = notification.getType(); + if (MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(type) + || MBeanServerNotification.UNREGISTRATION_NOTIFICATION.equals( + type)) { + LOGGER.log(FINE, "Marking cache as stale due to %s", type); + // Mark cache as stale instead of refreshing it immediately + // to debounce multiple notifications. + synchronized (this) { + cacheIsStale = true; + } + } + }, + filter, + null); + } + if (cacheIsStale) { + cache = fetchCache(_beanConn); + cacheIsStale = false; + } + return _beanConn; + } + + private Cache fetchCache(MBeanServerConnection beanConn) throws Exception { // Query MBean names, see #89 for reasons queryMBeans() is used instead of queryNames() - mBeanNames = new HashSet<>(); + Set mBeanNames = new HashSet<>(); for (ObjectName name : includeObjectNames) { for (ObjectInstance instance : beanConn.queryMBeans(name, null)) { mBeanNames.add(instance.getObjectName()); @@ -134,10 +181,10 @@ private void loadMBeanNames(MBeanServerConnection beanConn) throws Exception { } } - this.jmxMBeanPropertyCache = new JmxMBeanPropertyCache(mBeanNames); + ObjectNameAttributeFilter attributeFilter = defaultObjectNameAttributeFilter.dup(); + attributeFilter.onlyKeepMBeans(mBeanNames); - this.objectNameAttributeFilter = defaultObjectNameAttributeFilter.dup(); - objectNameAttributeFilter.onlyKeepMBeans(mBeanNames); + return new Cache(mBeanNames, attributeFilter, new JmxMBeanPropertyCache(mBeanNames)); } /** @@ -145,16 +192,13 @@ private void loadMBeanNames(MBeanServerConnection beanConn) throws Exception { * *

Values are passed to the receiver in a single thread. */ - public void doScrape(MBeanReceiver receiver) throws Exception { - synchronized (this) { - if (_beanConn == null) { - _beanConn = connectToMBeanServer(); - loadMBeanNames(_beanConn); - } - } - MBeanServerConnection beanConn = _beanConn; + public synchronized void doScrape(MBeanReceiver receiver) throws Exception { + // Method is synchronized to avoid multiple scrapes running concurrently + // and let one of them refresh the cache in the middle of the scrape. - for (ObjectName objectName : mBeanNames) { + MBeanServerConnection beanConn = getMBeanServerConnection(); + + for (ObjectName objectName : cache.mBeanNames) { long start = System.nanoTime(); scrapeBean(receiver, beanConn, objectName); LOGGER.log(FINE, "TIME: %d ns for %s", System.nanoTime() - start, objectName); @@ -162,9 +206,7 @@ public void doScrape(MBeanReceiver receiver) throws Exception { } private void scrapeBean( - MBeanReceiver receiver, - MBeanServerConnection beanConn, - ObjectName mBeanName) { + MBeanReceiver receiver, MBeanServerConnection beanConn, ObjectName mBeanName) { MBeanInfo mBeanInfo; try { @@ -186,7 +228,7 @@ private void scrapeBean( continue; } - if (objectNameAttributeFilter.exclude(mBeanName, mBeanAttributeInfo.getName())) { + if (cache.objectNameAttributeFilter.exclude(mBeanName, mBeanAttributeInfo.getName())) { continue; } @@ -249,7 +291,7 @@ private void scrapeBean( receiver, mBeanName, mBeanDomain, - jmxMBeanPropertyCache.getKeyPropertyList(mBeanName), + cache.jmxMBeanPropertyCache.getKeyPropertyList(mBeanName), new LinkedList<>(), mBeanAttributeInfo.getName(), mBeanAttributeInfo.getType(), @@ -289,7 +331,7 @@ private void processAttributesOneByOne( receiver, mbeanName, mbeanName.getDomain(), - jmxMBeanPropertyCache.getKeyPropertyList(mbeanName), + cache.jmxMBeanPropertyCache.getKeyPropertyList(mbeanName), new LinkedList<>(), attr.getName(), attr.getType(), @@ -447,7 +489,7 @@ private void processBeanValue( attrDescription, value.toString()); } else { - objectNameAttributeFilter.add(objectName, attrName); + cache.objectNameAttributeFilter.add(objectName, attrName); LOGGER.log(FINE, "%s%s scrape: %s not exported", domain, beanProperties, attrType); } }