changed live hover checks from various classpath and property checks towards JMX bean lookup, and turned the check into parallel checks

This commit is contained in:
Martin Lippert
2019-02-11 11:32:18 +01:00
parent 6ef4d7837a
commit fd445778b3
7 changed files with 72 additions and 93 deletions

View File

@@ -78,6 +78,8 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
private String jmxMbeanActuatorDomain;
private Set<ObjectName> nonBootLiveMBeanNames;
private Boolean hasJmxBeans;
private int retryCount;
private LiveBeansModel cachedBeansModel;
private String cachedBeansModelMD5;
@@ -105,12 +107,6 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
@Override
public abstract String getProcessName() throws Exception;
@Override
public abstract boolean isSpringBootApp();
@Override
public abstract boolean isSpringApp();
private final MemoizingDisposableSupplier<JMXConnector> jmxConnector = new MemoizingDisposableSupplier<JMXConnector>(
//creating jmx connector:
@@ -158,6 +154,11 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
jmxConnector.dispose();
}
public boolean containsSystemProperty(Object key) throws Exception {
Properties props = getSystemProperties();
return props.containsKey(key);
}
@Override
public List<String> getActiveProfiles() {
try {
@@ -228,7 +229,63 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
}
@Override
public boolean providesNonBootLiveBeans() {
public boolean hasUsefulJmxBeans() {
if (hasJmxBeans == null) {
try {
if (containsSystemProperty("sts4.languageserver.name")) {
logger.info("language server process found -- " + this.getProcessID() + " = " + getProcessName());
hasJmxBeans = Boolean.FALSE;
}
else {
logger.info("check for spring jmx beans (retry no. " + retryCount + ") -- " + this.getProcessID() + " = " + getProcessName());
boolean jmxBeansFound = containsSpringJmxBeans();
if (jmxBeansFound) {
hasJmxBeans = Boolean.TRUE;
logger.info("spring jmx beans found -- " + this.getProcessID() + " = " + getProcessName());
}
else if (retryCount == 3) {
hasJmxBeans = Boolean.FALSE;
logger.info("no spring jmx beans found after trying 4 times -- " + this.getProcessID() + " = " + getProcessName());
}
else {
retryCount++;
}
}
}
catch (Exception e) {
if (retryCount == 3) {
hasJmxBeans = Boolean.FALSE;
try {
logger.info("no spring jmx beans found after trying 4 times -- " + this.getProcessID() + " = " + getProcessName());
} catch (Exception e1) {
logger.info("no spring jmx beans found after trying 4 times -- " + this.getProcessID() + " = (process name unknown)");
}
}
else {
retryCount++;
}
}
}
return hasJmxBeans != null ? hasJmxBeans : false;
}
private boolean containsSpringJmxBeans() throws Exception {
return withTimeout(() -> withJmxConnector(jmxConnector -> {
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
QueryExp queryExp = Query.or(Query.or(Query.isInstanceOf(Query.value("org.springframework.boot.actuate.endpoint.jmx.EndpointMBean")),
Query.isInstanceOf(Query.value("org.springframework.boot.actuate.endpoint.jmx.DataEndpointMBean"))),
Query.isInstanceOf(Query.value("org.springframework.context.support.LiveBeansView")));
Set<ObjectName> names = connection.queryNames(null, queryExp);
return names != null && names.size() > 0;
}));
}
protected boolean providesNonBootLiveBeans() {
return getNonBootSpringLiveMBeans().size() > 0;
}

View File

@@ -37,9 +37,6 @@ public class LocalSpringBootApp extends AbstractSpringBootApp {
private static final String LOCAL_CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress";
private Boolean isSpringBootApp;
private Boolean isSpringApp;
private static LocalSpringBootAppCache cache = new LocalSpringBootAppCache();
public static Collection<SpringBootApp> getAllRunningJavaApps() throws Exception {
@@ -47,7 +44,7 @@ public class LocalSpringBootApp extends AbstractSpringBootApp {
}
public static Collection<SpringBootApp> getAllRunningSpringApps() throws Exception {
return getAllRunningJavaApps().stream().filter(app -> app.isSpringBootApp() || app.isSpringApp()).collect(CollectorUtil.toImmutableList());
return getAllRunningJavaApps().parallelStream().filter(app -> app.hasUsefulJmxBeans()).collect(CollectorUtil.toImmutableList());
}
public LocalSpringBootApp(VirtualMachineDescriptor vmd) throws AttachNotSupportedException, IOException {
@@ -92,57 +89,6 @@ public class LocalSpringBootApp extends AbstractSpringBootApp {
return firstSpace < 0 ? rawName : rawName.substring(0, firstSpace);
}
@Override
public boolean isSpringApp() {
if (isSpringApp == null) {
try {
isSpringApp = !containsSystemProperty("sts4.languageserver.name")
&& (
isSpringAppClasspath() ||
providesNonBootLiveBeans()
);
} catch (Exception e) {
//Couldn't determine if the VM is a spring boot app. Could be it already died. Or could be its not accessible (yet).
// We will ignore the exception, pretend its not a boot app (most likely isn't) but DO NOT CACHE this result
// so it will be retried again on the next polling loop.
return false;
}
}
return isSpringApp;
}
private boolean isSpringAppClasspath() throws Exception {
return contains(getClasspath(), "spring-core");
}
@Override
public boolean isSpringBootApp() {
if (isSpringBootApp == null) {
try {
isSpringBootApp = !containsSystemProperty("sts4.languageserver.name")
&& (
isSpringBootAppClasspath() ||
isSpringBootAppSysprops()
);
} catch (Exception e) {
//Couldn't determine if the VM is a spring boot app. Could be it already died. Or could be its not accessible (yet).
// We will ignore the exception, pretend its not a boot app (most likely isn't) but DO NOT CACHE this result
// so it will be retried again on the next polling loop.
return false;
}
}
return isSpringBootApp;
}
private boolean isSpringBootAppSysprops() throws Exception {
Properties sysprops = getSystemProperties();
return "org.springframework.boot.loader".equals(sysprops.getProperty("java.protocol.handler.pkgs"));
}
private boolean isSpringBootAppClasspath() throws Exception {
return contains(getClasspath(), "spring-boot");
}
@Override
public Properties getSystemProperties() throws Exception {
try {
@@ -153,11 +99,6 @@ public class LocalSpringBootApp extends AbstractSpringBootApp {
}
}
public boolean containsSystemProperty(Object key) throws Exception {
Properties props = getSystemProperties();
return props.containsKey(key);
}
protected boolean contains(String[] cpElements, String element) {
for (String cpElement : cpElements) {
if (cpElement.contains(element)) {

View File

@@ -62,23 +62,6 @@ public class RemoteSpringBootApp extends AbstractSpringBootApp {
});
}
@Override
public boolean isSpringBootApp() {
return isSpringApp();
}
@Override
public boolean isSpringApp() {
//For now, let's assume that, if its not a boot app, then we won't create a RemoteSpringBootApp instance for it.
//The check that is here really only determines whether there's a process reachable at the remote jmx url.
return getProcessID() != null;
}
@Override
public boolean providesNonBootLiveBeans() {
return false;
}
@Override
public String getProcessID() {
try {

View File

@@ -30,14 +30,12 @@ public interface SpringBootApp extends Disposable {
String getPort() throws Exception;
String getContextPath() throws Exception;
boolean isSpringBootApp();
boolean isSpringApp();
boolean hasUsefulJmxBeans();
String getEnvironment() throws Exception;
Collection<RequestMapping> getRequestMappings() throws Exception;
LiveBeansModel getBeans();
boolean providesNonBootLiveBeans();
List<String> getActiveProfiles();
Optional<List<LiveConditional>> getLiveConditionals() throws Exception;

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 2019 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -20,7 +20,7 @@ public class SpringBootAppCLI {
public static void main(String[] args) throws Exception {
Collection<SpringBootApp> allRunningJavaApps = LocalSpringBootApp.getAllRunningJavaApps();
for (SpringBootApp app : allRunningJavaApps) {
if (app.isSpringBootApp()) {
if (app.hasUsefulJmxBeans()) {
printBootAppDetails(app);
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* Copyright (c) 2018, 2019 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -96,7 +96,7 @@ public class RemoteRunningAppsProvider implements RunningAppProvider {
@Override
public synchronized Collection<SpringBootApp> getAllRunningSpringApps() throws Exception {
return remoteAppInstances.values().stream().filter(SpringBootApp::isSpringBootApp).collect(CollectorUtil.toImmutableList());
return remoteAppInstances.values().stream().filter(SpringBootApp::hasUsefulJmxBeans).collect(CollectorUtil.toImmutableList());
}
synchronized void handleSettings(Settings settings) {

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2018 Pivotal, Inc.
* Copyright (c) 2017, 2019 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -109,7 +109,7 @@ public class MockRunningAppProvider {
}
public MockAppBuilder isSpringBootApp(boolean isBoot) throws Exception {
when(app.isSpringBootApp()).thenReturn(isBoot);
when(app.hasUsefulJmxBeans()).thenReturn(isBoot);
return this;
}