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();
+ }
+}