001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.monitoring;
018    
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.HashSet;
023    import java.util.Iterator;
024    import java.util.Properties;
025    import java.util.Set;
026    import java.util.TreeMap;
027    
028    import javax.annotation.PostConstruct;
029    import javax.annotation.Resource;
030    import javax.annotation.security.PermitAll;
031    import javax.annotation.security.RolesAllowed;
032    import javax.ejb.Local;
033    import javax.ejb.Remote;
034    import javax.ejb.Stateless;
035    import javax.ejb.Timeout;
036    import javax.ejb.Timer;
037    import javax.ejb.TimerService;
038    import javax.management.Attribute;
039    import javax.management.MBeanServer;
040    import javax.management.MBeanServerFactory;
041    import javax.management.ObjectName;
042    import javax.management.j2ee.Management;
043    import javax.management.j2ee.ManagementHome;
044    import javax.management.j2ee.statistics.CountStatistic;
045    import javax.management.j2ee.statistics.RangeStatistic;
046    import javax.management.j2ee.statistics.Statistic;
047    import javax.management.j2ee.statistics.Stats;
048    import javax.management.j2ee.statistics.TimeStatistic;
049    import javax.naming.Context;
050    import javax.naming.InitialContext;
051    import javax.sql.DataSource;
052    
053    import org.apache.commons.logging.Log;
054    import org.apache.commons.logging.LogFactory;
055    import org.apache.geronimo.monitoring.snapshot.SnapshotConfigXMLBuilder;
056    import org.apache.geronimo.monitoring.snapshot.SnapshotDBHelper;
057    import org.apache.geronimo.monitoring.snapshot.SnapshotProcessor;
058    
059    /**
060     * This is a Stateful Session Bean that will be the bottleneck for the communication
061     * between the management node and the data in the server node.
062     */
063    @Stateless(name="ejb/mgmt/MRC")
064    @Remote(MasterRemoteControlRemote.class)
065    @Local(MasterRemoteControlLocal.class)
066    @PermitAll
067    public class MasterRemoteControl {
068        private static Log log = LogFactory.getLog(MasterRemoteControl.class);
069        
070        // constants
071        private static final String GERONIMO_DEFAULT_DOMAIN = "geronimo";
072        private static final Long DEFAULT_DURATION = new Long(300000);
073        private static final int DEFAULT_RETENTION = 30; // 30 days
074        private static final String DURATION = "duration";
075        private static final String RETENTION = "retention";
076    
077        // mbean server to talk to other components
078        private static MBeanServer mbServer = null;
079        
080        // mangement ejb - use this to do the monitoring
081        private static Management mejb = null;
082        
083        // credentials for snapshot processor
084        private static String username = null;
085        private static String password = null;
086        private static int port = -1;
087    
088        // inject Data Sources
089        @Resource(name="jdbc/ActiveDS") private DataSource activeDS;
090        @Resource(name="jdbc/ArchiveDS") private DataSource archiveDS;
091        
092        // inject a TimerService
093        @Resource private TimerService timer;
094        
095        private SnapshotDBHelper snapshotDBHelper;
096    
097        public MasterRemoteControl() {
098            
099        }
100    
101        @PostConstruct
102        private void init() {
103            // set up SnaphotDBHelper with the necessary data sources
104            // Note: do not put this in the constructor...datasources are not injected by then
105            snapshotDBHelper = new SnapshotDBHelper(activeDS, archiveDS);
106        }
107    
108        /**
109         * Retrieves and instance of the MEJB and starts the snapshot process
110         */
111        @RolesAllowed("mejbuser")
112        public void setUpMEJB(String username, String password) {
113            // instantiate the MEJB, which will be our gateway to communicate to MBeans
114            try {
115                Properties p = new Properties();
116                p.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
117                InitialContext ctx = new InitialContext(p);
118    
119                ManagementHome mejbHome = (ManagementHome)ctx.lookup("ejb/mgmt/MEJBRemoteHome");
120                mejb = mejbHome.create();
121                
122                // save credentials
123                this.username = username;
124                this.password = password;
125                this.port = port;
126    
127            } catch(Exception e) {
128                log.error(e.getMessage(), e);
129            }
130        }
131        
132        /**
133         * Looks up the JSR-77 statistics associated with this object name.
134         * 
135         * @param objectName
136         * @return HashMap
137         * @throws Exception
138         */
139        @RolesAllowed("mejbuser")
140        public static HashMap<String, Long> getStats(String objectName) throws Exception {
141            HashMap<String, Long> statsMap = new HashMap<String, Long>();
142            Stats stats = (Stats)mejb.getAttribute(new ObjectName(objectName), "stats");
143            String[] sttsName = stats.getStatisticNames();
144            Statistic[] stts = stats.getStatistics();
145            for(int i = 0; i < sttsName.length; i++) {
146                Statistic aStat = stats.getStatistic(sttsName[i]);
147                if(aStat instanceof RangeStatistic) {
148                    Long current = new Long(((RangeStatistic)aStat).getCurrent());
149                    Long high = new Long(((RangeStatistic)aStat).getHighWaterMark());
150                    Long low = new Long(((RangeStatistic)aStat).getLowWaterMark());
151                    statsMap.put(stts[i].getName() + " Current", current);
152                    statsMap.put(stts[i].getName() + " Max", high);
153                    statsMap.put(stts[i].getName() + " Min", low);
154                } else if(aStat instanceof CountStatistic) {
155                    Long current = new Long(((CountStatistic)aStat).getCount());
156                    statsMap.put(stts[i].getName(), current);
157                } else if(aStat instanceof TimeStatistic) {
158                    Long count = new Long(((TimeStatistic)aStat).getCount());
159                    Long max = new Long(((TimeStatistic)aStat).getMaxTime());
160                    Long min = new Long(((TimeStatistic)aStat).getMinTime());
161                    Long total = new Long(((TimeStatistic)aStat).getTotalTime());
162                    statsMap.put(stts[i].getName() + " Count", count);
163                    statsMap.put(stts[i].getName() + " MaxTime", max);
164                    statsMap.put(stts[i].getName() + " MinTime", min);
165                    statsMap.put(stts[i].getName() + " TotalTime", total);
166                } else {
167                    // for the time being, only numbers should be returned
168                }
169            }
170            return statsMap;
171        }
172        
173        /**
174         * Changes the objectName's attrName's value to attrValue
175         * 
176         * @param objectName
177         * @param attrName
178         * @param attrValue
179         * @throws Exception
180         */
181        @RolesAllowed("mejbadmin")
182        public void setAttribute(String objectName, String attrName, Object attrValue) throws Exception {
183            Attribute attr = new Attribute(attrName, attrValue);
184            mejb.setAttribute(new ObjectName(objectName), attr);
185        }
186        
187        // This method is called by the EJB container upon Timer expiration.
188        @Timeout
189        @PermitAll
190        public void handleTimeout(Timer theTimer) {
191            SnapshotProcessor.takeSnapshot(this.username, this.password);
192            
193            // get the duration of theTimer
194            long duration = Long.parseLong((String)theTimer.getInfo());
195            // if the duration is different than the one in the snapshot-config.xml
196            // we need to get rid of this timer and start a new one with the 
197            // correct duration.
198            if(duration != getSnapshotDuration().longValue()) {
199                Collection<Timer> timers = timer.getTimers();
200                for(Iterator<Timer> it = timers.iterator(); it.hasNext(); ) {
201                    // cancel all timers
202                    it.next().cancel();
203                }
204                // start a new one
205                long newDuration = getSnapshotDuration().longValue();
206                timer.createTimer(newDuration, newDuration, "" + newDuration);
207            }
208        }
209        
210        /**
211         * Begins the snapshot process given the time interval between snapshots
212         *
213         * Precondition:
214         *          interval is given in milli seconds
215         * 
216         * @param interval
217         */
218        @RolesAllowed("mejbuser")
219        public boolean startSnapshot(Long interval) {
220            // get the saved/default retention period
221            String retentionStr = null;
222            try {
223                retentionStr = SnapshotConfigXMLBuilder.getAttributeValue("retention");
224            } catch(Exception e){
225                // happens when there is not an instance of "retention" in the config
226                // which is okay.
227            }
228            int retention;
229            if(retentionStr == null) {
230                retention = DEFAULT_RETENTION;
231            } else {
232                retention = Integer.parseInt(retentionStr);
233            }
234            return startSnapshot(interval, retention);
235        }
236        
237        @RolesAllowed("mejbuser")
238        public boolean startSnapshot(Long interval, int retention) {
239            Collection<Timer> timers = timer.getTimers();
240            if(timers.size() == 0) {
241                saveDuration(interval.longValue());
242                saveRetention(retention);
243                timer.createTimer(0, interval.longValue(), "" + interval.longValue());
244                log.info("Created timer successfully.");
245                return true;
246            } else {
247                log.warn("There is already a snapshot timer running...");
248                return false;
249            }
250        }
251        
252        /**
253         * Stops the snapshot thread
254         */
255        @RolesAllowed("mejbuser")
256        public boolean stopSnapshot() {
257            Collection<Timer> timers = timer.getTimers();
258            // stop all timers
259            boolean cancelled = false;
260            for(Iterator<Timer> it = timers.iterator(); it.hasNext(); ) {
261                Timer t = it.next();
262                t.cancel();
263                cancelled = true;
264                log.info("Stopped snapshot timer...");
265            }
266            return cancelled;
267        }
268        
269        /**
270         * Fetches the data stored from the snapshot thread and returns
271         * it in a ArrayList with each element being a HashMap of the attribute
272         * mapping to the statistic. All stats will be the average of 
273         *          1 - n, n+1 - 2n, ..., cn+1 - c(n+1)
274         *
275         * Grabs 'numberOfSnapshots' snapshots. Grabs one snapshot per
276         * 'everyNthsnapshot'
277         * 
278         * @param numberOfSnapshot
279         * @param everyNthSnapshot
280         * @return ArrayList
281         */ 
282        @RolesAllowed("mejbuser")
283        public ArrayList<HashMap<String, HashMap<String, Object>>> fetchSnapshotData(Integer numberOfSnapshot, Integer everyNthSnapshot) {
284            return (ArrayList<HashMap<String, HashMap<String, Object>>>)snapshotDBHelper.fetchData(numberOfSnapshot, everyNthSnapshot);
285        }
286        
287        /**
288         * Fetches the max amount for each statistic stored from the snapshot thread
289         * and returns it in a HashMap
290         * 
291         * @param numberOfSnapshot
292         * @return HashMap
293         */
294        @RolesAllowed("mejbuser")
295        public HashMap<String, HashMap<String, Long>> fetchMaxSnapshotData(Integer numberOfSnapshot) {
296            return (HashMap<String, HashMap<String, Long>>)snapshotDBHelper.fetchMaxSnapshotData(numberOfSnapshot);
297        }
298    
299        /**
300         * Fetches the min amount for each statistic stored from the snapshot thread
301         * and returns it in a HashMap
302         * 
303         * @param numberOfSnapshot
304         * @return HashMap
305         */
306        @RolesAllowed("mejbuser")
307        public HashMap<String, HashMap<String, Long>> fetchMinSnapshotData(Integer numberOfSnapshot) {
308            return (HashMap<String, HashMap<String, Long>>)snapshotDBHelper.fetchMinSnapshotData(numberOfSnapshot);
309        }
310        
311        /**
312         * Gets the elapsed time in milliseconds between each snapshot.
313         * 
314         * @return Long
315         */
316        @RolesAllowed("mejbuser")
317        public Long getSnapshotDuration() {
318            // return what is stored in the snapshot-config.xml or default value
319            try {
320                String returnedDuration = SnapshotConfigXMLBuilder.getAttributeValue( DURATION );
321                return Long.parseLong( returnedDuration );
322            } catch(Exception e) {
323                return DEFAULT_DURATION; // the default
324            }
325        }
326        
327        /**
328         * Sets the elapsed time in milliseconds between each snapshot.
329         * The duration will be read in each time the handleTimeout()
330         * is called. So the change will be seen when the next
331         * handleTimeout() is called.
332         * 
333         * @param snapshotDuration
334         */
335        @RolesAllowed("mejbuser")
336        public void setSnapshotDuration(Long snapshotDuration) {
337            saveDuration(snapshotDuration);
338        }
339        
340        @RolesAllowed("mejbuser")
341        public void setSnapshotRetention(int retention) {
342            saveRetention(retention);
343        }
344        
345        @RolesAllowed("mejbuser")
346        public String getSnapshotRetention() {
347            try {
348                return SnapshotConfigXMLBuilder.getAttributeValue( RETENTION );
349            } catch(Exception e) {
350                return "" + DEFAULT_RETENTION; // the default
351            }
352        }
353        
354        @RolesAllowed("mejbuser")
355        public Long getSnapshotCount() {
356            return snapshotDBHelper.getSnapshotCount();
357        }
358        
359        /**
360         * Fetches all mbean names that provide JSR-77 statistics
361         * 
362         * @return A set containing all mbean names of mbeans that provide
363         * statistics
364         */
365        @RolesAllowed("mejbuser")
366        public Set<String> getStatisticsProviderMBeanNames() {
367            return MBeanHelper.getStatsProvidersMBeans( getAllMBeanNames() );
368        }
369        
370        /**
371         * Fetches all mbean names
372         * 
373         * @return A set containing all mbean names
374         */
375        @RolesAllowed("mejbuser")
376        public Set<String> getAllMBeanNames() {
377            try {
378                Set<ObjectName> names = (Set<ObjectName>)mejb.queryNames(null, null);
379                Set<String> strNames = new HashSet<String>();
380                for(Iterator<ObjectName> it = names.iterator(); it.hasNext(); ) {
381                    strNames.add(it.next().getCanonicalName());
382                }
383                return strNames;
384            } catch(Exception e) {
385                log.error(e.getMessage(), e);
386                return new HashSet<String>();
387            }
388        }
389        
390        private void saveDuration(long duration) {
391            SnapshotConfigXMLBuilder.saveDuration(duration);
392        }
393        
394        private void saveRetention(int retention) {
395            SnapshotConfigXMLBuilder.saveRetention(retention);
396        }
397        
398        /**
399         * Adds a record of the mbean via its name to take snapshots of. As a result
400         * the mbeanName will be written to snapshot-config.xml
401         * 
402         * @param mbeanName
403         */
404        @RolesAllowed("mejbuser")
405        public boolean addMBeanForSnapshot(String mbeanName) {
406            return SnapshotConfigXMLBuilder.addMBeanName(mbeanName);
407        }
408    
409        /**
410         * Removes a record of the mbean via its name to take snapshots of. As a result
411         * the mbeanName will be removed from snapshot-config.xml
412         * 
413         * @param mbeanName
414         */
415        @RolesAllowed("mejbuser")
416        public boolean removeMBeanForSnapshot(String mbeanName) {
417            return SnapshotConfigXMLBuilder.removeMBeanName(mbeanName);
418        }
419        
420        /**
421         * @return A map: mbeanName --> ArrayList of statistic attributes for that mbean
422         */
423        @RolesAllowed("mejbuser")
424        public HashMap<String, ArrayList<String>> getAllSnapshotStatAttributes() {
425            HashMap<String, ArrayList<String>> snapshotAttributes = new HashMap<String, ArrayList<String>>();
426            Set<String> mbeans = getTrackedMBeans();
427            // for each mbean name
428            for(Iterator<String> it = mbeans.iterator(); it.hasNext(); ) {
429                ArrayList<String> mbeanStatsList = new ArrayList<String>();
430                String mbeanName = it.next();
431                try {
432                    Stats stats = (Stats)mejb.getAttribute(new ObjectName(mbeanName), "stats");
433                    String[] sttsName = stats.getStatisticNames();
434                    Statistic[] stts = stats.getStatistics();
435                    for(int i = 0; i < sttsName.length; i++) {
436                        Statistic aStat = stats.getStatistic(sttsName[i]);
437                        if(aStat instanceof RangeStatistic) {
438                            mbeanStatsList.add(stts[i].getName() + " Current");
439                            mbeanStatsList.add(stts[i].getName() + " Max");
440                            mbeanStatsList.add(stts[i].getName() + " Min");
441                        } else if(aStat instanceof CountStatistic) {
442                            mbeanStatsList.add(stts[i].getName());
443                        } else if(aStat instanceof TimeStatistic) {
444                            mbeanStatsList.add(stts[i].getName() + " Count");
445                            mbeanStatsList.add(stts[i].getName() + " MaxTime");
446                            mbeanStatsList.add(stts[i].getName() + " MinTime");
447                            mbeanStatsList.add(stts[i].getName() + " TotalTime");
448                        } else {
449                            // for the time being, only numbers should be returned
450                        }
451                    }
452                } catch (Exception e) {
453                    log.error(e.getMessage(), e);
454                }
455                // save attributes to the returning list
456                snapshotAttributes.put(mbeanName, mbeanStatsList);
457            }
458            return snapshotAttributes;
459        }
460    
461        /**
462         * @return Returns true if snapshot is running.
463         */
464        @RolesAllowed("mejbuser")
465        public boolean isSnapshotRunning() {
466            Collection<Timer> timers = timer.getTimers();
467            // if there are timers there is something running to collect snapshots
468            if(timers.size() > 0) {
469                return true;
470            } else {
471                return false;
472            }
473        }
474        
475        /**
476         * @param name - object name of the mbean
477         * @param operationName - method within the class
478         * @param params - parameters for the method
479         * @param signature - types for the parameters
480         * @return Invokes the method of a class defined.
481         */
482        @RolesAllowed("mejbadmin")
483        public Object invoke(ObjectName name, String operationName, Object[] params, String[] signature) throws Exception {
484            return mejb.invoke(name, operationName, params, signature);
485        }
486        
487        /**
488         * @param mbeanName
489         * @param statsName
490         * @param numberOfSnapshots
491         * @param everyNthSnapshot
492         * @return HashMap which maps from a snapshot_time --> value of the mbean.statsName at that time
493         */
494        @RolesAllowed("mejbuser")
495        public TreeMap<Long, Long> getSpecificStatistics(   String mbeanName,
496                                                            String statsName, 
497                                                            int numberOfSnapshots, 
498                                                            int everyNthSnapshot,
499                                                            boolean showArchived) {
500            return snapshotDBHelper.getSpecificStatistics(mbeanName, statsName, numberOfSnapshots, everyNthSnapshot, showArchived);
501        }
502        
503        /**
504         * @return A set of all mbeans being tracked from the db
505         */
506        @RolesAllowed("mejbuser")
507        public Set<String> getTrackedMBeans() {
508            ArrayList<String> mbeans = (ArrayList<String>)SnapshotConfigXMLBuilder.getMBeanNames();
509            Set<String> set = new HashSet<String>();
510            for(int i = 0; i < mbeans.size(); i++) {
511                set.add(mbeans.get(i));
512            }
513            return set;
514        }
515    }