| 
 | 1 | +/*  | 
 | 2 | + * Copyright (c) 2025 Contributors to the Eclipse Foundation  | 
 | 3 | + *  | 
 | 4 | + * This program and the accompanying materials are made available under the  | 
 | 5 | + * terms of the Eclipse Public License v. 2.0, which is available at  | 
 | 6 | + * http://www.eclipse.org/legal/epl-2.0.  | 
 | 7 | + *  | 
 | 8 | + * This Source Code may also be made available under the following Secondary  | 
 | 9 | + * Licenses when the conditions for such availability set forth in the  | 
 | 10 | + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,  | 
 | 11 | + * version 2 with the GNU Classpath Exception, which is available at  | 
 | 12 | + * https://www.gnu.org/software/classpath/license.html.  | 
 | 13 | + *  | 
 | 14 | + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0  | 
 | 15 | + */  | 
 | 16 | +package org.glassfish.tests.embedded.runnable;  | 
 | 17 | + | 
 | 18 | +import java.io.File;  | 
 | 19 | +import java.io.IOException;  | 
 | 20 | +import java.util.ArrayList;  | 
 | 21 | +import java.util.List;  | 
 | 22 | +import java.util.Optional;  | 
 | 23 | +import java.util.Set;  | 
 | 24 | +import java.util.concurrent.TimeUnit;  | 
 | 25 | +import java.util.logging.Logger;  | 
 | 26 | + | 
 | 27 | +import javax.management.MBeanServerConnection;  | 
 | 28 | +import javax.management.ObjectName;  | 
 | 29 | +import javax.management.remote.JMXConnector;  | 
 | 30 | +import javax.management.remote.JMXConnectorFactory;  | 
 | 31 | +import javax.management.remote.JMXServiceURL;  | 
 | 32 | + | 
 | 33 | +import org.glassfish.tests.embedded.runnable.TestArgumentProviders.GfEmbeddedJarNameProvider;  | 
 | 34 | +import org.junit.jupiter.params.ParameterizedTest;  | 
 | 35 | +import org.junit.jupiter.params.provider.ArgumentsSource;  | 
 | 36 | + | 
 | 37 | +import static org.junit.jupiter.api.Assertions.assertNotNull;  | 
 | 38 | +import static org.junit.jupiter.api.Assertions.assertTrue;  | 
 | 39 | + | 
 | 40 | +/**  | 
 | 41 | + * @author Ondro Mihalyi  | 
 | 42 | + */  | 
 | 43 | +public class MonitoringTest {  | 
 | 44 | + | 
 | 45 | +    private static final Logger LOG = Logger.getLogger(MonitoringTest.class.getName());  | 
 | 46 | +    private static final int JMX_PORT = 8686;  | 
 | 47 | +    private static final int WAIT_SECONDS = 30;  | 
 | 48 | + | 
 | 49 | +    @ParameterizedTest  | 
 | 50 | +    @ArgumentsSource(GfEmbeddedJarNameProvider.class)  | 
 | 51 | +    void testJmxMonitoringWithFlashlightAgent(String gfEmbeddedJarName) throws Exception {  | 
 | 52 | +        Process gfEmbeddedProcess = null;  | 
 | 53 | +        JMXConnector jmxConnector = null;  | 
 | 54 | +        try {  | 
 | 55 | +            gfEmbeddedProcess = startGlassFishWithJmx(gfEmbeddedJarName);  | 
 | 56 | + | 
 | 57 | +            jmxConnector = connectToJmx();  | 
 | 58 | +            MBeanServerConnection mbsc = jmxConnector.getMBeanServerConnection();  | 
 | 59 | + | 
 | 60 | +            bootAmxAndWait(mbsc);  | 
 | 61 | + | 
 | 62 | +            verifyMonitoringMBeans(mbsc);  | 
 | 63 | + | 
 | 64 | +        } finally {  | 
 | 65 | +            cleanup(jmxConnector, gfEmbeddedProcess);  | 
 | 66 | +        }  | 
 | 67 | +    }  | 
 | 68 | + | 
 | 69 | +    private File createMonitoringPropertiesFile() throws Exception {  | 
 | 70 | +        File propertiesFile = File.createTempFile("monitoring", ".properties");  | 
 | 71 | +        java.nio.file.Files.write(propertiesFile.toPath(), List.of(  | 
 | 72 | +                "configs.config.server-config.monitoring-service.module-monitoring-levels.http-service=HIGH",  | 
 | 73 | +                "configs.config.server-config.monitoring-service.module-monitoring-levels.thread-pool=HIGH"  | 
 | 74 | +        ));  | 
 | 75 | +        return propertiesFile;  | 
 | 76 | +    }  | 
 | 77 | + | 
 | 78 | +    private Process startGlassFishWithJmx(String gfEmbeddedJarName) throws IOException {  | 
 | 79 | +        List<String> arguments = new ArrayList<>();  | 
 | 80 | +        arguments.add(ProcessHandle.current().info().command().get());  | 
 | 81 | +        arguments.addAll(List.of(  | 
 | 82 | +                "-javaagent:flashlight-agent.jar",  | 
 | 83 | +                "-Dcom.sun.management.jmxremote",  | 
 | 84 | +                "-Dcom.sun.management.jmxremote.port=" + JMX_PORT,  | 
 | 85 | +                "-Dcom.sun.management.jmxremote.authenticate=false",  | 
 | 86 | +                "-Dcom.sun.management.jmxremote.ssl=false",  | 
 | 87 | +                "-jar", gfEmbeddedJarName,  | 
 | 88 | +                "--noPort",  | 
 | 89 | +                "enable-monitoring --modules http-service"  | 
 | 90 | +        ));  | 
 | 91 | + | 
 | 92 | +        return new ProcessBuilder()  | 
 | 93 | +                .redirectOutput(ProcessBuilder.Redirect.PIPE)  | 
 | 94 | +                .redirectError(ProcessBuilder.Redirect.PIPE)  | 
 | 95 | +                .command(arguments)  | 
 | 96 | +                .start();  | 
 | 97 | +    }  | 
 | 98 | + | 
 | 99 | +    private JMXConnector connectToJmx() throws Exception {  | 
 | 100 | +        JMXServiceURL serviceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + JMX_PORT + "/jmxrmi");  | 
 | 101 | + | 
 | 102 | +        for (int i = 0; i < WAIT_SECONDS * 2; i++) {  | 
 | 103 | +            try {  | 
 | 104 | +                return JMXConnectorFactory.connect(serviceURL, null);  | 
 | 105 | +            } catch (Exception e) {  | 
 | 106 | +                Thread.sleep(500);  | 
 | 107 | +            }  | 
 | 108 | +        }  | 
 | 109 | +        throw new IllegalStateException("Could not connect to JMX in " + WAIT_SECONDS + " seconds");  | 
 | 110 | +    }  | 
 | 111 | + | 
 | 112 | +    private void bootAmxAndWait(MBeanServerConnection mbsc) throws Exception {  | 
 | 113 | +        ObjectName bootAMXObjectName = new ObjectName("amx-support:type=boot-amx");  | 
 | 114 | + | 
 | 115 | +        for (int i = 0; i < WAIT_SECONDS * 2; i++) {  | 
 | 116 | +            if (mbsc.isRegistered(bootAMXObjectName)) {  | 
 | 117 | +                break;  | 
 | 118 | +            }  | 
 | 119 | +            Thread.sleep(500);  | 
 | 120 | +        }  | 
 | 121 | + | 
 | 122 | + | 
 | 123 | +        assertTrue(mbsc.isRegistered(bootAMXObjectName), "bootAMX is registered");  | 
 | 124 | + | 
 | 125 | +        mbsc.invoke(bootAMXObjectName, "bootAMX", null, null);  | 
 | 126 | + | 
 | 127 | +        // Wait for AMX runtime to be available  | 
 | 128 | +        for (int i = 0; i < WAIT_SECONDS * 2; i++) {  | 
 | 129 | +            Set<ObjectName> runtimeBeans = mbsc.queryNames(new ObjectName("amx:pp=/,type=runtime"), null);  | 
 | 130 | +            if (!runtimeBeans.isEmpty()) {  | 
 | 131 | +                return;  | 
 | 132 | +            }  | 
 | 133 | +            Thread.sleep(500);  | 
 | 134 | +        }  | 
 | 135 | +        throw new IllegalStateException("AMX runtime not available after " + WAIT_SECONDS + " seconds");  | 
 | 136 | +    }  | 
 | 137 | + | 
 | 138 | +    private void verifyMonitoringMBeans(MBeanServerConnection mbsc) throws Exception {  | 
 | 139 | +        Set<ObjectName> requestBeans = mbsc.queryNames(new ObjectName("amx:type=request-mon,*"), null);  | 
 | 140 | +        assertTrue(!requestBeans.isEmpty(), "Request monitoring MBean should be present");  | 
 | 141 | + | 
 | 142 | +        // Verify we can read monitoring data  | 
 | 143 | +        ObjectName requestBean = requestBeans.iterator().next();  | 
 | 144 | +        assertNotNull(mbsc.getAttribute(requestBean, "countrequests"), "Should be able to read request count");  | 
 | 145 | +    }  | 
 | 146 | + | 
 | 147 | +    private void cleanup(JMXConnector jmxConnector, Process process) throws InterruptedException {  | 
 | 148 | +        if (jmxConnector != null) try { jmxConnector.close(); } catch (Exception ignored) {}  | 
 | 149 | +        if (process != null && process.isAlive()) {  | 
 | 150 | +            process.destroyForcibly();  | 
 | 151 | +            process.waitFor(5, TimeUnit.SECONDS);  | 
 | 152 | +        }  | 
 | 153 | +    }  | 
 | 154 | + | 
 | 155 | +    private void cleanupFiles(File... files) {  | 
 | 156 | +        for (File file : files) {  | 
 | 157 | +            Optional.ofNullable(file).ifPresent(File::delete);  | 
 | 158 | +        }  | 
 | 159 | +    }  | 
 | 160 | +}  | 
0 commit comments