1 /*
   2  * Copyright (c) 2004, 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 
  26 package sun.jvmstat.perfdata.monitor.protocol.rmi;
  27 
  28 import sun.jvmstat.monitor.*;
  29 import sun.jvmstat.monitor.event.*;
  30 import sun.jvmstat.monitor.remote.*;
  31 import sun.jvmstat.perfdata.monitor.*;
  32 import java.util.*;
  33 import java.net.*;
  34 import java.io.*;
  35 import java.rmi.*;
  36 import java.util.HashMap;
  37 
  38 /**
  39  * Concrete implementation of the MonitoredHost interface for the
  40  * <em>rmi</em> protocol of the HotSpot PerfData monitoring implementation.
  41  *
  42  * @author Brian Doherty
  43  * @since 1.5
  44  */
  45 public class MonitoredHostProvider extends MonitoredHost {
  46     private static final String serverName = "/JStatRemoteHost";
  47     private static final int DEFAULT_POLLING_INTERVAL = 1000;
  48 
  49     private ArrayList<HostListener> listeners;
  50     private NotifierTask task;
  51     private HashSet<Integer> activeVms;
  52     private RemoteVmManager vmManager;
  53     private RemoteHost remoteHost;
  54     private Timer timer;
  55 
  56     /**
  57      * Create a MonitoredHostProvider instance using the given HostIdentifier.
  58      *
  59      * @param hostId the host identifier for this MonitoredHost
  60      * @throws MonitorException Thrown on any error encountered while
  61      *                          communicating with the remote host.
  62      */
  63     public MonitoredHostProvider(HostIdentifier hostId)
  64            throws MonitorException {
  65         this.hostId = hostId;
  66         this.listeners = new ArrayList<HostListener>();
  67         this.interval = DEFAULT_POLLING_INTERVAL;
  68         this.activeVms = new HashSet<Integer>();
  69 
  70         String rmiName;
  71         String sn = serverName;
  72         String path = hostId.getPath();
  73 
  74         if ((path != null) && (path.length() > 0)) {
  75             sn = path;
  76         }
  77 
  78         if (hostId.getPort() != -1) {
  79             rmiName = "rmi://" + hostId.getHost() + ":" + hostId.getPort() + sn;
  80         } else {
  81             rmiName = "rmi://" + hostId.getHost() + sn;
  82         }
  83 
  84         try {
  85             remoteHost = (RemoteHost)Naming.lookup(rmiName);
  86 
  87         } catch (RemoteException e) {
  88             /*
  89              * rmi registry not available
  90              *
  91              * Access control exceptions, where the rmi server refuses a
  92              * connection based on policy file configuration, come through
  93              * here on the client side. Unfortunately, the RemoteException
  94              * doesn't contain enough information to determine the true cause
  95              * of the exception. So, we have to output a rather generic message.
  96              */
  97             String message = "RMI Registry not available at "
  98                              + hostId.getHost();
  99 
 100             if (hostId.getPort() == -1) {
 101                 message = message + ":"
 102                           + java.rmi.registry.Registry.REGISTRY_PORT;
 103             } else {
 104                 message = message + ":" + hostId.getPort();
 105             }
 106 
 107             if (e.getMessage() != null) {
 108                 throw new MonitorException(message + "\n" + e.getMessage(), e);
 109             } else {
 110                 throw new MonitorException(message, e);
 111             }
 112 
 113         } catch (NotBoundException e) {
 114             // no server with given name
 115             String message = e.getMessage();
 116             if (message == null) message = rmiName;
 117             throw new MonitorException("RMI Server " + message
 118                                        + " not available", e);
 119         } catch (MalformedURLException e) {
 120             // this is a programming problem
 121             e.printStackTrace();
 122             throw new IllegalArgumentException("Malformed URL: " + rmiName);
 123         }
 124         this.vmManager = new RemoteVmManager(remoteHost);
 125         this.timer = new Timer(true);
 126     }
 127 
 128     /**
 129      * {@inheritDoc}
 130      */
 131     public MonitoredVm getMonitoredVm(VmIdentifier vmid)
 132                        throws MonitorException {
 133         return getMonitoredVm(vmid, DEFAULT_POLLING_INTERVAL);
 134     }
 135 
 136     /**
 137      * {@inheritDoc}
 138      */
 139     public MonitoredVm getMonitoredVm(VmIdentifier vmid, int interval)
 140                        throws MonitorException {
 141         VmIdentifier nvmid = null;
 142         try {
 143             nvmid = hostId.resolve(vmid);
 144             RemoteVm rvm = remoteHost.attachVm(vmid.getLocalVmId(),
 145                                                vmid.getMode());
 146             RemoteMonitoredVm rmvm = new RemoteMonitoredVm(rvm, nvmid, timer,
 147                                                            interval);
 148             rmvm.attach();
 149             return rmvm;
 150 
 151         } catch (RemoteException e) {
 152             throw new MonitorException("Remote Exception attaching to "
 153                                        + nvmid.toString(), e);
 154         } catch (URISyntaxException e) {
 155             /*
 156              * the VmIdentifier is expected to be a valid and should resolve
 157              * easonably against the host identifier. A URISyntaxException
 158              * here is most likely a programming error.
 159              */
 160             throw new IllegalArgumentException("Malformed URI: "
 161                                                + vmid.toString(), e);
 162         }
 163     }
 164 
 165     /**
 166      * {@inheritDoc}
 167      */
 168     public void detach(MonitoredVm vm) throws MonitorException {
 169         RemoteMonitoredVm rmvm = (RemoteMonitoredVm)vm;
 170         rmvm.detach();
 171         try {
 172             remoteHost.detachVm(rmvm.getRemoteVm());
 173 
 174         } catch (RemoteException e) {
 175             throw new MonitorException("Remote Exception detaching from "
 176                                        + vm.getVmIdentifier().toString(), e);
 177         }
 178     }
 179 
 180     /**
 181      * {@inheritDoc}
 182      */
 183     public void addHostListener(HostListener listener) {
 184         synchronized(listeners) {
 185             listeners.add(listener);
 186             if (task == null) {
 187                 task = new NotifierTask();
 188                 timer.schedule(task, 0, interval);
 189             }
 190         }
 191     }
 192 
 193     /**
 194      * {@inheritDoc}
 195      */
 196     public void removeHostListener(HostListener listener) {
 197         /*
 198          * XXX: if a disconnect method is added, make sure it calls
 199          * this method to unregister this object from the watcher. otherwise,
 200          * an unused MonitoredHostProvider instance may go uncollected.
 201          */
 202         synchronized(listeners) {
 203             listeners.remove(listener);
 204             if (listeners.isEmpty() && (task != null)) {
 205                 task.cancel();
 206                 task = null;
 207             }
 208         }
 209     }
 210 
 211     public void setInterval(int newInterval) {
 212         synchronized(listeners) {
 213             if (newInterval == interval) {
 214                 return;
 215             }
 216 
 217             int oldInterval = interval;
 218             super.setInterval(newInterval);
 219 
 220             if (task != null) {
 221                 task.cancel();
 222                 NotifierTask oldTask = task;
 223                 task = new NotifierTask();
 224                 CountedTimerTaskUtils.reschedule(timer, oldTask, task,
 225                                                  oldInterval, newInterval);
 226             }
 227         }
 228     }
 229 
 230     /**
 231      * {@inheritDoc}
 232      */
 233     public Set<Integer> activeVms() throws MonitorException {
 234         return vmManager.activeVms();
 235     }
 236 
 237     /**
 238      * Fire VmStatusChangeEvent events to HostListener objects
 239      *
 240      * @param active Set of Integer objects containing the local
 241      *               Vm Identifiers of the active JVMs
 242      * @param started Set of Integer objects containing the local
 243      *                Vm Identifiers of new JVMs started since last
 244      *                interval.
 245      * @param terminated Set of Integer objects containing the local
 246      *                   Vm Identifiers of terminated JVMs since last
 247      *                   interval.
 248      */
 249     @SuppressWarnings("unchecked") // Cast of result of clone
 250     private void fireVmStatusChangedEvents(Set<Integer> active, Set<Integer> started,
 251                                            Set<Integer> terminated) {
 252         ArrayList<HostListener> registered = null;
 253         VmStatusChangeEvent ev = null;
 254 
 255         synchronized(listeners) {
 256             registered = (ArrayList)listeners.clone();
 257         }
 258 
 259         for (Iterator<HostListener> i = registered.iterator(); i.hasNext(); /* empty */) {
 260             HostListener l = i.next();
 261             if (ev == null) {
 262                 ev = new VmStatusChangeEvent(this, active, started, terminated);
 263             }
 264             l.vmStatusChanged(ev);
 265         }
 266     }
 267 
 268     /**
 269      * Fire hostDisconnectEvent events.
 270      */
 271     @SuppressWarnings("unchecked") // Cast of result of clone
 272     void fireDisconnectedEvents() {
 273         ArrayList<HostListener> registered = null;
 274         HostEvent ev = null;
 275 
 276         synchronized(listeners) {
 277             registered = (ArrayList)listeners.clone();
 278         }
 279 
 280         for (Iterator<HostListener> i = registered.iterator(); i.hasNext(); /* empty */) {
 281             HostListener l = i.next();
 282             if (ev == null) {
 283                 ev = new HostEvent(this);
 284             }
 285             l.disconnected(ev);
 286         }
 287     }
 288 
 289     /**
 290      * class to poll the remote machine and generate local event notifications.
 291      */
 292     private class NotifierTask extends CountedTimerTask {
 293         public void run() {
 294             super.run();
 295 
 296             // save the last set of active JVMs
 297             Set<Integer> lastActiveVms = activeVms;
 298 
 299             try {
 300                 // get the current set of active JVMs
 301                 activeVms = (HashSet<Integer>)vmManager.activeVms();
 302 
 303             } catch (MonitorException e) {
 304                 // XXX: use logging api
 305                 System.err.println("MonitoredHostProvider: polling task "
 306                                    + "caught MonitorException:");
 307                 e.printStackTrace();
 308 
 309                 // mark the HostManager as errored and notify listeners
 310                 setLastException(e);
 311                 fireDisconnectedEvents();
 312             }
 313 
 314             if (activeVms.isEmpty()) {
 315                 return;
 316             }
 317 
 318             Set<Integer> startedVms = new HashSet<>();
 319             Set<Integer> terminatedVms = new HashSet<>();
 320 
 321             for (Iterator<Integer> i = activeVms.iterator(); i.hasNext(); /* empty */ ) {
 322                 Integer vmid = i.next();
 323                 if (!lastActiveVms.contains(vmid)) {
 324                     // a new file has been detected, add to set
 325                     startedVms.add(vmid);
 326                 }
 327             }
 328 
 329             for (Iterator<Integer> i = lastActiveVms.iterator(); i.hasNext();
 330                     /* empty */ ) {
 331                 Integer o = i.next();
 332                 if (!activeVms.contains(o)) {
 333                     // JVM has terminated, remove it from the active list
 334                     terminatedVms.add(o);
 335                 }
 336             }
 337 
 338             if (!startedVms.isEmpty() || !terminatedVms.isEmpty()) {
 339                 fireVmStatusChangedEvents(activeVms, startedVms, terminatedVms);
 340             }
 341         }
 342     }
 343 }