This is an automated email from the ASF dual-hosted git repository.

joerghoh pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-metrics.git


The following commit(s) were added to refs/heads/master by this push:
     new 6c205fc  SLING-12411 Add JMX notification listener to include metrics 
from the late starter services (#8)
6c205fc is described below

commit 6c205fc6b86061a1c12173086fa71a8330bba183
Author: infoscale <[email protected]>
AuthorDate: Thu Aug 22 16:50:02 2024 +0100

    SLING-12411 Add JMX notification listener to include metrics from the late 
starter services (#8)
    
    * Add JMX notification listener to include metrics from the late starter 
services
    
    Co-authored-by: Pramod Hirole <[email protected]>
---
 .../metrics/internal/JmxExporterFactory.java       | 54 ++++++++++++++++++++--
 .../metrics/internal/JmxExporterFactoryTest.java   | 24 ++++++++--
 .../internal/JmxNotificationListenerTest.java      | 52 +++++++++++++++++++++
 3 files changed, 124 insertions(+), 6 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/commons/metrics/internal/JmxExporterFactory.java
 
b/src/main/java/org/apache/sling/commons/metrics/internal/JmxExporterFactory.java
index 94a18f4..db06254 100644
--- 
a/src/main/java/org/apache/sling/commons/metrics/internal/JmxExporterFactory.java
+++ 
b/src/main/java/org/apache/sling/commons/metrics/internal/JmxExporterFactory.java
@@ -19,7 +19,10 @@
 package org.apache.sling.commons.metrics.internal;
 
 import java.lang.management.ManagementFactory;
+import javax.management.MBeanServerNotification;
+import javax.management.NotificationListener;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Hashtable;
 import java.util.List;
@@ -33,7 +36,9 @@ import javax.management.MBeanAttributeInfo;
 import javax.management.MBeanException;
 import javax.management.MBeanInfo;
 import javax.management.MBeanServer;
+import javax.management.MBeanServerDelegate;
 import javax.management.MalformedObjectNameException;
+import javax.management.Notification;
 import javax.management.ObjectName;
 import javax.management.ReflectionException;
 
@@ -70,6 +75,8 @@ public class JmxExporterFactory {
         @AttributeDefinition
         String webconsole_configurationFactory_nameHint() default "Pattern: 
{objectnames}"; //NOSONAR
     }
+
+    String[] patterns;
     
     
     private static final Logger LOG = 
LoggerFactory.getLogger(JmxExporterFactory.class);
@@ -78,14 +85,55 @@ public class JmxExporterFactory {
     MetricsService metrics;
     
     MBeanServer server;
-    
+    /**
+     * This listener is registered to the MBeanServerDelegate to listen for 
MBean registrations.
+     * When an MBean is registered, the listener checks if the objectname 
matches the configured patterns,
+     * and if so, registers all attributes of the MBean as metrics if they are 
not registered already.
+     */
+    NotificationListener listener = new NotificationListener() {
+        /**
+         * The handler is invoked for every notification,
+         * however gauges are only registered if they do not exist yet.
+         * See {@link #MetricsService#gauge(String, Supplier)} and {@link 
#MetricsServiceImpl#getOrAddGauge(String, Supplier)}
+         */
+        public void handleNotification(Notification notification, Object 
handback) {
+            if 
(MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(notification.getType()))
 {
+                ObjectName objectname = null;
+                try {
+                    if(notification instanceof MBeanServerNotification) {
+                        MBeanServerNotification mbeanNotification = 
(MBeanServerNotification) notification;
+                        objectname = mbeanNotification.getMBeanName();
+                        LOG.debug("JMX Notification : match {} with pattern = 
{}", objectname, Arrays.asList(patterns));
+                        for (String pattern : patterns) {
+                            if (objectname.toString().matches(pattern)) {
+                                LOG.debug("JMX Notification : register metrics 
for MBean: {}", objectname);
+                                registerMBeanProperties(objectname);
+                                break;
+                            }
+                        }
+                    } else {
+                        LOG.debug("JMX Notification : Cannot handle 
notification, because it's not a MBeanServerNotification ({})", notification);
+                    }
+                } catch (InstanceNotFoundException | ReflectionException | 
IntrospectionException e) {
+                    LOG.error("JMX Notification : Cannot register metrics for 
objectname = {}", objectname, e);
+                }
+            }
+        }
+    };
+
     @Activate
     @Modified
     public void activate(Config config) {
         server = ManagementFactory.getPlatformMBeanServer();
-        registerMetrics(config.objectnames());
+        patterns = config.objectnames();
+        try {
+            server.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, 
listener, null, null);
+        } catch (InstanceNotFoundException e) {
+            LOG.error("Cannot add notification listener to 
MBeanServerDelegate", e);
+        }
+        registerMetrics(patterns);
     }
-    
+
 
     /**
      * Register all applicable metrics for an objectname pattern
diff --git 
a/src/test/java/org/apache/sling/commons/metrics/internal/JmxExporterFactoryTest.java
 
b/src/test/java/org/apache/sling/commons/metrics/internal/JmxExporterFactoryTest.java
index ca080d7..b1a5f15 100644
--- 
a/src/test/java/org/apache/sling/commons/metrics/internal/JmxExporterFactoryTest.java
+++ 
b/src/test/java/org/apache/sling/commons/metrics/internal/JmxExporterFactoryTest.java
@@ -33,11 +33,13 @@ import javax.management.InstanceAlreadyExistsException;
 import javax.management.InstanceNotFoundException;
 import javax.management.MBeanRegistrationException;
 import javax.management.MBeanServer;
+import javax.management.MBeanServerDelegate;
 import javax.management.MalformedObjectNameException;
 import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.NotificationListener;
 import javax.management.ObjectName;
 
-import org.apache.sling.commons.metrics.Gauge;
 import org.apache.sling.commons.metrics.MetricsService;
 import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
 import org.junit.After;
@@ -99,14 +101,17 @@ public class JmxExporterFactoryTest {
     private static final Double STATIC_DOUBLE = 1.0;
     
     MetricsService metrics;
-    
+    NotificationListener listener;
     SimpleBean mbeans[] = { new SimpleBean(0,0L), new SimpleBean(1,1L), new 
SimpleBean(2,2L)};
     
     
     @Before
-    public void setup() throws MalformedObjectNameException, 
InstanceAlreadyExistsException, MBeanRegistrationException, 
NotCompliantMBeanException {
+    public void setup() throws MalformedObjectNameException, 
InstanceAlreadyExistsException, MBeanRegistrationException, 
NotCompliantMBeanException, InstanceNotFoundException {
         MBeanServer server = ManagementFactory.getPlatformMBeanServer();
 
+        listener = Mockito.mock(NotificationListener.class);
+        server.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, 
listener, null, null);
+
         server.registerMBean(mbeans[0],new ObjectName(OBJECT_NAME_0));        
         server.registerMBean(mbeans[1],new ObjectName(OBJECT_NAME_1));
         server.registerMBean(mbeans[2],new ObjectName(OBJECT_NAME_2));
@@ -170,6 +175,9 @@ public class JmxExporterFactoryTest {
         
         // verify that no metrics for MBean2 have been registered
         Mockito.verify(metrics, 
never()).gauge(Mockito.eq(EXPECTED_2_INT_NAME), intSupplierCaptor.capture());
+
+        Mockito.verify(listener, 
Mockito.times(3)).handleNotification(Mockito.any(Notification.class), 
Mockito.any());
+
         
     }
     
@@ -190,6 +198,16 @@ public class JmxExporterFactoryTest {
         context.registerInjectActivateService(exporter, props);
         Mockito.verifyNoInteractions(metrics);
     }
+
+    @Test
+    public void checkNotificationListener() throws Exception {
+        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+        ObjectName test = new ObjectName("com.example:type=TestMBean");
+        server.registerMBean(new SimpleBean(1, 1L), test);
+        Mockito.verify(listener, 
Mockito.times(4)).handleNotification(Mockito.any(Notification.class), 
Mockito.any());
+    }
+
+  
     
     
     static class SimpleBean implements SimpleBeanMBean {
diff --git 
a/src/test/java/org/apache/sling/commons/metrics/internal/JmxNotificationListenerTest.java
 
b/src/test/java/org/apache/sling/commons/metrics/internal/JmxNotificationListenerTest.java
new file mode 100644
index 0000000..5795a0a
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/commons/metrics/internal/JmxNotificationListenerTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.commons.metrics.internal;
+
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerNotification;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class JmxNotificationListenerTest {
+
+    JmxExporterFactory exporter  = new JmxExporterFactory();    
+    NotificationListener listener = exporter.listener;
+
+    @Test
+    public void testHandleNotification() throws Exception {
+        exporter.patterns = new String[] { "test:type=Test" };
+        exporter.server = Mockito.mock(MBeanServer.class);
+        MBeanInfo m = Mockito.mock(MBeanInfo.class);
+        MBeanServerNotification notification = 
Mockito.mock(MBeanServerNotification.class);
+        
Mockito.when(notification.getType()).thenReturn("JMX.mbean.registered");
+        ObjectName objectName = new ObjectName("test:type=Test");
+        Mockito.when(notification.getMBeanName()).thenReturn(objectName);
+        
Mockito.when(exporter.server.getMBeanInfo(Mockito.eq(objectName))).thenReturn(m);
+        Mockito.when(m.getAttributes()).thenReturn(new 
javax.management.MBeanAttributeInfo[0]);
+        listener.handleNotification(notification, null);
+
+        //Assert that MBeanInfo attribute methid is called
+        Mockito.verify(m, Mockito.times(1)).getAttributes();
+    }
+}

Reply via email to