Skip to content

Commit 6616c5f

Browse files
committed
Enabled manual control of a JMX connection (Connect & Disconnect actions) and enable/disable automatic connection (Connect Automatically action)
1 parent 631e16d commit 6616c5f

File tree

9 files changed

+326
-112
lines changed

9 files changed

+326
-112
lines changed

visualvm/jmx/nbproject/project.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<compile-dependency/>
2222
<run-dependency>
2323
<release-version>2</release-version>
24-
<specification-version>2.0</specification-version>
24+
<specification-version>2.1</specification-version>
2525
</run-dependency>
2626
</dependency>
2727
<dependency>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package org.graalvm.visualvm.jmx.impl;
26+
27+
import java.awt.event.ActionEvent;
28+
import javax.swing.JCheckBoxMenuItem;
29+
import javax.swing.JMenuItem;
30+
import org.graalvm.visualvm.core.VisualVM;
31+
import org.graalvm.visualvm.core.ui.actions.ActionUtils;
32+
import org.graalvm.visualvm.core.ui.actions.SingleDataSourceAction;
33+
import org.openide.awt.Mnemonics;
34+
import org.openide.util.NbBundle;
35+
import org.openide.util.actions.Presenter;
36+
37+
/**
38+
*
39+
* @author Jiri Sedlacek
40+
*/
41+
public final class AutoConnectAction extends SingleDataSourceAction<JmxApplication> implements Presenter.Popup {
42+
43+
private static AutoConnectAction INSTANCE;
44+
45+
private boolean currentAutoConnect;
46+
47+
48+
public static synchronized AutoConnectAction instance() {
49+
if (INSTANCE == null) INSTANCE = new AutoConnectAction();
50+
return INSTANCE;
51+
}
52+
53+
54+
@Override
55+
protected void actionPerformed(final JmxApplication app, ActionEvent actionEvent) {
56+
final boolean autoConnect = currentAutoConnect;
57+
VisualVM.getInstance().runTask(new Runnable() {
58+
public void run() {
59+
if (autoConnect) app.disableHeartbeat();
60+
else app.enableHeartbeat();
61+
}
62+
});
63+
}
64+
65+
@Override
66+
protected boolean isEnabled(JmxApplication app) {
67+
return true;
68+
}
69+
70+
71+
@Override
72+
public JMenuItem getPopupPresenter() {
73+
JmxApplication app = ActionUtils.getSelectedDataSource(getScope());
74+
currentAutoConnect = !app.isHeartbeatDisabled();
75+
76+
JMenuItem presenter = new JCheckBoxMenuItem(this);
77+
Mnemonics.setLocalizedText(presenter, NbBundle.getMessage(ConnectDisconnectAction.class, "LBL_AutoConnect")); // NOI18N
78+
presenter.setSelected(currentAutoConnect);
79+
80+
return presenter;
81+
}
82+
83+
84+
private AutoConnectAction() {
85+
super(JmxApplication.class);
86+
}
87+
88+
}

visualvm/jmx/src/org/graalvm/visualvm/jmx/impl/Bundle.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,7 @@ Retry_Insecure_SSL=&Do not require SSL for this connection
7272

7373
LBL_RememberAction=&Remember selected action
7474
TTP_RememberAction=Select to perform the action automatically on subsequent sessions. Can be reset using Tools | Options | General | Reset Do Not Show Again confirmations.
75+
76+
LBL_Connect=&Connect
77+
LBL_Disconnect=&Disconnect
78+
LBL_AutoConnect=Connect A&utomatically
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package org.graalvm.visualvm.jmx.impl;
26+
27+
import java.awt.event.ActionEvent;
28+
import javax.swing.JMenuItem;
29+
import org.graalvm.visualvm.core.VisualVM;
30+
import org.graalvm.visualvm.core.datasupport.Stateful;
31+
import org.graalvm.visualvm.core.ui.actions.ActionUtils;
32+
import org.graalvm.visualvm.core.ui.actions.SingleDataSourceAction;
33+
import org.openide.awt.Mnemonics;
34+
import org.openide.util.NbBundle;
35+
import org.openide.util.actions.Presenter;
36+
37+
/**
38+
*
39+
* @author Jiri Sedlacek
40+
*/
41+
public final class ConnectDisconnectAction extends SingleDataSourceAction<JmxApplication> implements Presenter.Popup {
42+
43+
private static ConnectDisconnectAction INSTANCE;
44+
45+
private int currentState = Stateful.STATE_UNKNOWN;
46+
private boolean currentAutoConnect;
47+
48+
49+
public static synchronized ConnectDisconnectAction instance() {
50+
if (INSTANCE == null) INSTANCE = new ConnectDisconnectAction();
51+
return INSTANCE;
52+
}
53+
54+
55+
@Override
56+
protected void actionPerformed(final JmxApplication app, ActionEvent actionEvent) {
57+
final int state = currentState;
58+
VisualVM.getInstance().runTask(new Runnable() {
59+
public void run() {
60+
if (state == Stateful.STATE_AVAILABLE) app.disconnect();
61+
else JmxHeartbeat.scheduleImmediately(app);
62+
}
63+
});
64+
}
65+
66+
@Override
67+
protected boolean isEnabled(JmxApplication app) {
68+
return true;
69+
}
70+
71+
@Override
72+
public JMenuItem getPopupPresenter() {
73+
JmxApplication app = ActionUtils.getSelectedDataSource(getScope());
74+
currentState = app.getState();
75+
currentAutoConnect = !app.isHeartbeatDisabled();
76+
77+
JMenuItem presenter = new JMenuItem(this);
78+
if (currentState == Stateful.STATE_AVAILABLE) Mnemonics.setLocalizedText(presenter, NbBundle.getMessage(ConnectDisconnectAction.class, "LBL_Disconnect")); // NOI18N
79+
else Mnemonics.setLocalizedText(presenter, NbBundle.getMessage(ConnectDisconnectAction.class, "LBL_Connect")); // NOI18N
80+
presenter.setEnabled(currentState == Stateful.STATE_AVAILABLE || !currentAutoConnect);
81+
82+
return presenter;
83+
}
84+
85+
86+
private ConnectDisconnectAction() {
87+
super(JmxApplication.class);
88+
}
89+
90+
}

visualvm/jmx/src/org/graalvm/visualvm/jmx/impl/JmxApplication.java

Lines changed: 114 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525

2626
package org.graalvm.visualvm.jmx.impl;
2727

28+
import java.beans.PropertyChangeEvent;
29+
import java.beans.PropertyChangeListener;
30+
import java.io.IOException;
2831
import org.graalvm.visualvm.application.Application;
2932
import org.graalvm.visualvm.application.jvm.Jvm;
3033
import org.graalvm.visualvm.core.datasource.Storage;
@@ -38,7 +41,10 @@
3841
import org.graalvm.visualvm.tools.jmx.JvmMXBeans;
3942
import org.graalvm.visualvm.tools.jmx.JvmMXBeansFactory;
4043
import java.lang.management.RuntimeMXBean;
44+
import java.util.logging.Level;
45+
import java.util.logging.Logger;
4146
import javax.management.remote.JMXServiceURL;
47+
import org.graalvm.visualvm.application.jvm.JvmFactory;
4248

4349
/**
4450
* This type of application represents an application
@@ -49,15 +55,23 @@
4955
*/
5056
public final class JmxApplication extends Application {
5157

58+
private static final Logger LOGGER = Logger.getLogger(JmxApplication.class.getName());
59+
60+
private static final String PROPERTY_DISABLE_HEARTBEAT = "prop_disable_heartbeat"; // NOI18N
61+
5262
private int pid = UNKNOWN_PID;
5363
private final JMXServiceURL url;
54-
private EnvironmentProvider envProvider;
64+
private final EnvironmentProvider envProvider;
5565
private final Storage storage;
5666
// since getting JVM for the first time can take a long time
5767
// hard reference jvm from application so we are sure that it is not garbage collected
58-
public Jvm jvm;
59-
JmxModel jmxModel;
60-
ProxyClient client;
68+
private Jvm jvm;
69+
private JmxModel jmxModel;
70+
private ProxyClient client;
71+
72+
private PropertyChangeListener modelListener;
73+
74+
private final Object connectionLock = new Object();
6175

6276
// Note: storage may be null, in this case the JmxApplication isn't persistent
6377
// and creates a temporary storage just like any other regular Application
@@ -98,17 +112,6 @@ public int getPid() {
98112
}
99113

100114

101-
public void setStateImpl(int newState) {
102-
if (newState != Stateful.STATE_AVAILABLE) {
103-
pid = UNKNOWN_PID;
104-
jvm = null;
105-
jmxModel = null;
106-
client = null;
107-
}
108-
setState(newState);
109-
}
110-
111-
112115
public boolean supportsUserRemove() {
113116
return true;
114117
}
@@ -147,11 +150,103 @@ private static String createId(JMXServiceURL url, EnvironmentProvider envProvide
147150
return envId + "-" + urlId; // NOI18N
148151
}
149152

150-
ProxyClient getProxyClient() {
151-
return client;
153+
final ProxyClient getProxyClient() {
154+
synchronized (connectionLock) {
155+
return client;
156+
}
152157
}
158+
159+
160+
// Only to be called from JmxHeartbeat
161+
// Use JmxHeartbeat.scheduleImmediately(JmxApplication) from any other code!
162+
final boolean tryConnect() {
163+
synchronized (connectionLock) {
164+
if (isConnected()) return true;
165+
166+
try {
167+
ProxyClient newClient = new ProxyClient(this);
168+
newClient.connect();
169+
if (newClient.getConnectionState() == ConnectionState.CONNECTED) {
170+
client = newClient;
171+
172+
setStateImpl(Stateful.STATE_AVAILABLE);
173+
174+
jmxModel = JmxModelFactory.getJmxModelFor(this);
175+
jvm = JvmFactory.getJVMFor(this);
176+
177+
modelListener = new PropertyChangeListener() {
178+
public void propertyChange(PropertyChangeEvent evt) {
179+
if (evt.getNewValue() != ConnectionState.CONNECTED) {
180+
synchronized (connectionLock) {
181+
setStateImpl(Stateful.STATE_UNAVAILABLE);
182+
}
183+
}
184+
}
185+
};
186+
jmxModel.addPropertyChangeListener(modelListener);
187+
188+
return true;
189+
}
190+
} catch (IOException ex) {
191+
LOGGER.log(Level.FINE, "ProxyClient.connect", ex); // NOI18N
192+
}
153193

154-
void setClient(ProxyClient client) {
155-
this.client = client;
194+
return false;
195+
}
196+
}
197+
198+
final void disconnect() {
199+
disableHeartbeat();
200+
201+
ProxyClient _client;
202+
synchronized (connectionLock) {
203+
if (!isConnected()) return;
204+
_client = client;
205+
}
206+
207+
_client.disconnect(); // will invoke modelListener.propertyChange() -> ConnectionState.DISCONNECTED
156208
}
209+
210+
private boolean isConnected() { // must be called under connectionLock
211+
return client != null && client.getConnectionState() == ConnectionState.CONNECTED;
212+
}
213+
214+
215+
private void setStateImpl(int newState) { // must be called under connectionLock
216+
if (newState != Stateful.STATE_AVAILABLE) {
217+
pid = UNKNOWN_PID;
218+
jvm = null;
219+
if (jmxModel != null && modelListener != null) jmxModel.removePropertyChangeListener(modelListener);
220+
jmxModel = null;
221+
client = null;
222+
if (supportsHeartbeat(this)) JmxHeartbeat.scheduleLazily(this);
223+
}
224+
225+
setState(newState);
226+
}
227+
228+
229+
final void enableHeartbeat() {
230+
getStorage().clearCustomProperty(PROPERTY_DISABLE_HEARTBEAT);
231+
if (supportsHeartbeat(this)) {
232+
synchronized (connectionLock) {
233+
if (isConnected()) return;
234+
}
235+
JmxHeartbeat.scheduleImmediately(this);
236+
}
237+
}
238+
239+
final void disableHeartbeat() {
240+
getStorage().setCustomProperty(PROPERTY_DISABLE_HEARTBEAT, Boolean.TRUE.toString());
241+
}
242+
243+
final boolean isHeartbeatDisabled() {
244+
return Boolean.TRUE.toString().equals(getStorage().getCustomProperty(PROPERTY_DISABLE_HEARTBEAT));
245+
}
246+
247+
248+
static boolean supportsHeartbeat(JmxApplication app) {
249+
return !app.isRemoved() && !app.isHeartbeatDisabled();
250+
}
251+
157252
}

0 commit comments

Comments
 (0)