/* * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.visualvm.jvmstat.application; import com.sun.tools.visualvm.application.Application; import com.sun.tools.visualvm.application.jvm.JvmFactory; import com.sun.tools.visualvm.core.datasource.DataSourceRepository; import com.sun.tools.visualvm.core.datasource.descriptor.DataSourceDescriptorFactory; import com.sun.tools.visualvm.core.datasupport.DataChangeEvent; import com.sun.tools.visualvm.core.datasupport.DataChangeListener; import com.sun.tools.visualvm.core.datasupport.Stateful; import com.sun.tools.visualvm.core.options.GlobalPreferences; import com.sun.tools.visualvm.core.ui.DesktopUtils; import com.sun.tools.visualvm.host.Host; import com.sun.tools.visualvm.uisupport.HTMLLabel; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.net.URL; import java.rmi.ConnectException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; import javax.swing.Timer; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; import org.openide.util.Utilities; import sun.jvmstat.monitor.MonitorException; import sun.jvmstat.monitor.MonitoredHost; import sun.jvmstat.monitor.HostIdentifier; import sun.jvmstat.monitor.event.HostEvent; import sun.jvmstat.monitor.event.HostListener; import sun.jvmstat.monitor.event.VmStatusChangeEvent; /** * A provider for Applications discovered by jvmstat. * * @author Jiri Sedlacek * @author Tomas Hurka */ public class JvmstatApplicationProvider implements DataChangeListener<Host> { private static final Logger LOGGER = Logger.getLogger(JvmstatApplicationProvider.class.getName()); private static final RequestProcessor PROCESSOR = new RequestProcessor("JvmstatApplicationProvider Processor", 10); // NOI18N private static JvmstatApplicationProvider instance; private final Map<String, JvmstatApplication> applications = new HashMap(); private final Map<Host,Map<HostIdentifier,JvmstatConnection>> hostsListeners = new HashMap(); static synchronized JvmstatApplicationProvider sharedInstance() { if (instance == null) { instance = new JvmstatApplicationProvider(); } return instance; } public void dataChanged(DataChangeEvent<Host> event) { Set<Host> newHosts = event.getAdded(); for (final Host host : newHosts) { // run new host in request processor, since it will take // a long time that there is no jstatd running on new host // we do not want to block DataSource.EVENT_QUEUE for long time PROCESSOR.post(new Runnable() { public void run() { processNewHost(host); } }); } Set<Host> removedHosts = event.getRemoved(); for (final Host host : removedHosts) { // run removed host in request processor, since it can take // a long time and we do not want to block DataSource.EVENT_QUEUE // for long time PROCESSOR.post(new Runnable() { public void run() { processFinishedHost(host); } }); } } private void processNewHost(final Host host) { if (host == Host.UNKNOWN_HOST) return; Set<ConnectionDescriptor> descrs = HostPropertiesProvider.descriptorsForHost(host); registerJvmstatConnections(host, descrs); } private void registerJvmstatConnections(final Host host, final Set<ConnectionDescriptor> descrs) { for (ConnectionDescriptor desc : descrs) { int interval = (int)(desc.getRefreshRate()*1000); HostIdentifier hostId = desc.createHostIdentifier(host); registerJvmstatConnection(host,hostId,interval); } } private void processChangedJvmstatConnection(Host host, ConnectionDescriptor changedConnection) { HostIdentifier hostId = changedConnection.createHostIdentifier(host); MonitoredHost monitoredHost = getMonitoredHost(hostId); if (monitoredHost != null) { int interval = (int)(changedConnection.getRefreshRate()*1000); monitoredHost.setInterval(interval); } } private void processFinishedHost(final Host host) { if (host == Host.UNKNOWN_HOST) return; synchronized (hostsListeners) { Map<HostIdentifier,JvmstatConnection> hostListeners = hostsListeners.get(host); if (hostListeners != null) { for (JvmstatConnection listener : new ArrayList<JvmstatConnection>(hostListeners.values())) { processDisconnectedJvmstat(host, listener); } } } } private void processRemovedJvmstatConnection(final Host host, HostIdentifier hostId) { if (host == Host.UNKNOWN_HOST) return; synchronized (hostsListeners) { Map<HostIdentifier,JvmstatConnection> hostListeners = hostsListeners.get(host); if (hostListeners != null) { JvmstatConnection listener = hostListeners.get(hostId); if (listener != null) { processDisconnectedJvmstat(host, listener); } } } } private void processDisconnectedJvmstat(Host host, JvmstatConnection listener) { HostIdentifier hostId = listener.monitoredHost.getHostIdentifier(); try { listener.monitoredHost.removeHostListener(listener); } catch (MonitorException ex) {} unregisterHostListener(host,hostId); Set<JvmstatApplication> jvmstatApplications = host.getRepository().getDataSources(JvmstatApplication.class); Iterator<JvmstatApplication> appIt = jvmstatApplications.iterator(); while (appIt.hasNext()) { JvmstatApplication application = appIt.next(); if (application.getHostIdentifier().equals(hostId)) { application.setStateImpl(Stateful.STATE_UNAVAILABLE); } else { appIt.remove(); } } host.getRepository().removeDataSources(jvmstatApplications); } private void processNewApplicationsByPids(Host host, HostIdentifier hostId, Set<Integer> applicationPids) { Set<JvmstatApplication> newApplications = new HashSet(); for (int applicationPid : applicationPids) { // Do not provide instance for Application.CURRENT_APPLICATION if (Application.CURRENT_APPLICATION.getPid() == applicationPid && Host.LOCALHOST.equals(host)) { continue; } String appId = createId(host, applicationPid); JvmstatApplication application = new JvmstatApplication(host, hostId, appId, applicationPid); if (!applications.containsKey(appId)) { // precompute JVM application.jvm = JvmFactory.getJVMFor(application); applications.put(appId, application); newApplications.add(application); } } host.getRepository().addDataSources(newApplications); } private void processTerminatedApplicationsByPids(Host host, Set<Integer> applicationPids) { Set<JvmstatApplication> finishedApplications = new HashSet(); for (int applicationPid : applicationPids) { String appId = createId(host, applicationPid); if (applications.containsKey(appId)) { JvmstatApplication application = applications.get(appId); if (application != null) { finishedApplications.add(application); application.setStateImpl(Stateful.STATE_UNAVAILABLE); } applications.remove(appId); } } host.getRepository().removeDataSources(finishedApplications); } private void registerHostListener(Host host,HostIdentifier hostId,JvmstatConnection hostListener) { synchronized (hostsListeners) { Map<HostIdentifier,JvmstatConnection> hostListeners = hostsListeners.get(host); if (hostListeners == null) { hostListeners = new HashMap(); hostsListeners.put(host,hostListeners); } hostListeners.put(hostId,hostListener); } } private void unregisterHostListener(Host host,HostIdentifier hostId) { synchronized (hostsListeners) { Map<HostIdentifier,JvmstatConnection> hostListeners = hostsListeners.get(host); assert hostListeners != null; hostListeners.remove(hostId); } } private void registerJvmstatConnection(Host host, HostIdentifier hostId, int interval) { // Monitor the Host for new/finished Applications // NOTE: the code relies on the fact that the provider is the first listener registered in MonitoredHost of the Host // in which case the first obtained event contains all applications already running on the Host JvmstatConnection hostListener = null; // Get the MonitoredHost for Host final MonitoredHost monitoredHost = getMonitoredHost(hostId); if (monitoredHost == null) { // monitored host not available reschedule rescheduleProcessNewHost(host,hostId); return; } hostId = monitoredHost.getHostIdentifier(); monitoredHost.setInterval(interval); if (host == Host.LOCALHOST) checkForBrokenLocalJps(monitoredHost); try { // Fetch already running applications on the host processNewApplicationsByPids(host, hostId, monitoredHost.activeVms()); hostListener = new JvmstatConnection(host, monitoredHost); monitoredHost.addHostListener(hostListener); registerHostListener(host, hostId, hostListener); } catch (MonitorException e) { Throwable t = e.getCause(); monitoredHost.setLastException(e); if (!(t instanceof ConnectException)) { DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message( NbBundle.getMessage(JvmstatApplicationProvider.class, "MSG_Broken_Jvmstat", // NOI18N DataSourceDescriptorFactory.getDescriptor(host).getName()), NotifyDescriptor.ERROR_MESSAGE)); LOGGER.log(Level.INFO, "Jvmstat connection to " + host + " failed.", t); // NOI18N } else { rescheduleProcessNewHost(host,hostId); } } } private String createId(Host host, int pid) { return host.getHostName() + "-" + pid; } void removeFromMap(JvmstatApplication jvmstatApplication) { applications.remove(jvmstatApplication.getId()); } // TODO: reimplement to listen for Host.getState() == STATE_UNAVAILABLE // private void processAllTerminatedApplications(Host host) { // Set<JvmstatApplication> applicationsSet = host.getRepository().getDataSources(JvmstatApplication.class); // Set<JvmstatApplication> finishedApplications = new HashSet(); // // for (JvmstatApplication application : applicationsSet) // if (applications.containsKey(application.getPid())) { // applications.remove(application.getPid()); // finishedApplications.add(application); // } // // host.getRepository().removeDataSources(finishedApplications); // } // Checks broken jps according to https://netbeans.org/bugzilla/show_bug.cgi?id=115490 // Checks broken jps according to https://java.net/jira/browse/VISUALVM-311 private void checkForBrokenLocalJps(MonitoredHost monitoredHost) { try { if (monitoredHost.activeVms().size() != 0) { if (Utilities.isWindows()) { String perf = "hsperfdata_" + System.getProperty("user.name"); // NOI18N File perfCorrect = new File(System.getProperty("java.io.tmpdir"), perf); // NOI18N File perfCurrent = perfCorrect.getCanonicalFile(); // Resolves real capitalization if (!perfCorrect.getName().equals(perfCurrent.getName())) { String link = DesktopUtils.isBrowseAvailable() ? NbBundle.getMessage(JvmstatApplicationProvider.class, "MSG_Broken_Jps2_Link") // NOI18N : NbBundle.getMessage(JvmstatApplicationProvider.class, "MSG_Broken_Jps2_NoLink"); // NOI18N String message = NbBundle.getMessage(JvmstatApplicationProvider.class, "MSG_Broken_Jps2", link); // NOI18N notifyBrokenJps(message); } } return; } } catch (Exception e) { return; } String link = DesktopUtils.isBrowseAvailable() ? NbBundle.getMessage(JvmstatApplicationProvider.class, "MSG_Broken_Jps_Link") // NOI18N : NbBundle.getMessage(JvmstatApplicationProvider.class, "MSG_Broken_Jps_NoLink"); // NOI18N String message = NbBundle.getMessage(JvmstatApplicationProvider.class, "MSG_Broken_Jps", link); // NOI18N notifyBrokenJps(message); } private static void notifyBrokenJps(String message) { final HTMLLabel label = new HTMLLabel(message) { protected void showURL(URL url) { try { DesktopUtils.browse(url.toURI()); } catch (Exception e) {} } }; SwingUtilities.invokeLater(new Runnable() { public void run() { NotifyDescriptor nd = new NotifyDescriptor.Message(label, NotifyDescriptor.ERROR_MESSAGE); DialogDisplayer.getDefault().notify(nd); } }); } private MonitoredHost getLocalMonitoredHost() { try { return MonitoredHost.getMonitoredHost("localhost"); // NOI18N } catch (Exception ex) { throw new RuntimeException(ex); } } private MonitoredHost getMonitoredHost(HostIdentifier hostId) { try { return MonitoredHost.getMonitoredHost(hostId); } catch (MonitorException ex) { // NOTE: valid state, jstatd not running, Host will be scheduled for later MonitoredHost resolving // ErrorManager.getDefault().log(ErrorManager.WARNING,ex.getLocalizedMessage()); } return null; } public MonitoredHost getMonitoredHost(Application app) { JvmstatApplication japp; if (Application.CURRENT_APPLICATION.equals(app)) { return getLocalMonitoredHost(); } if (!(app instanceof JvmstatApplication)) { String appId = createId(app.getHost(),app.getPid()); japp = applications.get(appId); } else { japp = (JvmstatApplication) app; } if (japp != null) { return getMonitoredHost(japp.getHostIdentifier()); } return null; } private void rescheduleProcessNewHost(final Host host,final HostIdentifier hostId) { int timerInterval = GlobalPreferences.sharedInstance().getMonitoredHostPoll(); Timer timer = new Timer(timerInterval*1000, new ActionListener() { public void actionPerformed(ActionEvent e) { // do not block EQ - use request processor, processNewHost() can take a long time PROCESSOR.post(new Runnable() { public void run() { if (!host.isRemoved()) { Set<ConnectionDescriptor> descriptors = HostPropertiesProvider.descriptorsForHost(host); for (ConnectionDescriptor desc : descriptors) { if (hostId.equals(desc.createHostIdentifier(host))) { int interval = (int)(desc.getRefreshRate()*1000); registerJvmstatConnection(host,hostId,interval); } } } } }); } }); timer.setRepeats(false); timer.start(); } public static void register() { DataSourceRepository.sharedInstance().addDataChangeListener(sharedInstance(), Host.class); } public static MonitoredHost findMonitoredHost(Application app) { return sharedInstance().getMonitoredHost(app); } // Invoked from AWT thread void connectionsChanged(final Host host, final Set<ConnectionDescriptor> added, final Set<ConnectionDescriptor> removed, final Set<ConnectionDescriptor> changed) { PROCESSOR.post(new Runnable() { public void run() { registerJvmstatConnections(host,added); for (ConnectionDescriptor removedConnection : removed) { processRemovedJvmstatConnection(host, removedConnection.createHostIdentifier(host)); } for (ConnectionDescriptor changedConnection : changed) { processChangedJvmstatConnection(host, changedConnection); } } }); } private class JvmstatConnection implements HostListener { // Flag for determining first MonitoredHost event private boolean firstEvent = true; private Host host; private MonitoredHost monitoredHost; private JvmstatConnection(Host host, MonitoredHost mHost) { this.host = host; monitoredHost = mHost; } public void vmStatusChanged(final VmStatusChangeEvent e) { if (firstEvent) { if (LOGGER.isLoggable(Level.FINER)) { LOGGER.finer("Monitored Host (" + host.getHostName() + ") status changed - adding all active applications"); } firstEvent = false; processNewApplicationsByPids(host, monitoredHost.getHostIdentifier(), e.getActive()); } else { processNewApplicationsByPids(host, monitoredHost.getHostIdentifier(), e.getStarted()); processTerminatedApplicationsByPids(host, e.getTerminated()); } } public void disconnected(HostEvent e) { processDisconnectedJvmstat(host, this); rescheduleProcessNewHost(host,monitoredHost.getHostIdentifier()); } } }