FileDocCategorySizeDatePackage
ErrorStatistics.javaAPI DocGlassfish v2 API14711Tue Jul 31 14:38:10 BST 2007com.sun.enterprise.server.logging.stats

ErrorStatistics.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * ErrorStatistics.java
 * $Id: ErrorStatistics.java,v 1.12 2007/07/31 21:38:09 jielin Exp $
 * $Date: 2007/07/31 21:38:09 $
 * $Revision: 1.12 $
 */

package com.sun.enterprise.server.logging.stats;

import java.util.Map;
import java.util.List;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;

import com.sun.logging.LogDomains;
import com.sun.appserv.management.ext.logging.LogAnalyzer;
import com.sun.enterprise.server.ServerContext;
import com.sun.enterprise.server.ApplicationServer;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.serverbeans.ServerBeansFactory;
import com.sun.enterprise.config.serverbeans.LogService;

/**
 * This keeps track of the error statistics (log levels SEVERE and WARNING).
 *
 * @author Ram Jeyaraman
 */
public class ErrorStatistics {
    
    // Static constants, classes and methods
    
    private static final int MIN_INTERVALS = 5;
    private static final int MAX_INTERVALS = 500;
    private static final long DEFAULT_INTERVAL = 3600*1000; // (1 hour) msecs
     
    private static final ErrorStatistics singleton = new ErrorStatistics();    
    
    public static ErrorStatistics singleton() {
        return singleton;
    }
    
    public static void registerStartupTime() {
        singleton(); // Loads the class which sets up the startup time.
    }
    
    private static class ErrorCount {
        
        private int severeCount;
        private int warningCount;
        
        void setSevereCount(int count) {
            this.severeCount = count;
        }
        
        int getSevereCount() {
            return this.severeCount;
        }
        
        void setWarningCount(int count) {
            this.warningCount = count;
        }
        
        int getWarningCount() {
            return this.warningCount;
        }
    }
    
    // Instance variables and methods
    
    private long interval;
    private int numOfIntervals;
    private long startTimeStamp;
    private HashMap<Long,HashMap<String,ErrorCount>> intervalMap;
    
    ErrorStatistics() {

        interval = DEFAULT_INTERVAL;
        numOfIntervals = MIN_INTERVALS;
        intervalMap = new HashMap<Long,HashMap<String,ErrorCount>>();
        startTimeStamp = System.currentTimeMillis();
    }

    /**
     * Get the number of intevals, for which error statistics is maintained.
     *
     * @return number of intervals.
     */    
    public int getNumOfIntervals() {
        try {
            ServerContext sc = ApplicationServer.getServerContext();
            if (sc != null) {
                LogService ls = ServerBeansFactory.getConfigBean(sc.getConfigContext()).getLogService();
                numOfIntervals = Integer.parseInt(ls.getRetainErrorStatisticsForHours());
            }
        }catch(Exception n) {
            numOfIntervals = MIN_INTERVALS;
        }
        return numOfIntervals;
    }
    
    /**
     * Set the number of time intervals, for which statistics will be 
     * maintained in memory. For example, if the interval is 1 hour, and
     * number of intervals is 10, statistics will be maintained for the most
     * recent 10 hours.
     */
    public void setNumOfIntervals(int intervals) {
        if ((intervals < MIN_INTERVALS) || (intervals > MAX_INTERVALS)) {
            throw new IllegalArgumentException(
                    "Number of intervals must be between " + MIN_INTERVALS +
                    " and " + MAX_INTERVALS);
        }
        numOfIntervals = intervals;
    }
    
    /**
     * Set interval duration. The interval is the duration, in milli seconds,
     * between consecutive time samples. For example, if the interval is 1 hour,
     * statistical data collected over time, will be organized by the hour.
     */
    public void setIntervalDuration(long interval) {
        this.interval = interval;
    }
    
    /**
     * Get the interval duration.
     *
     * @return interval duration, in millsecs.
     */
    public long getIntervalDuration() {
        return this.interval;
    }
    
    /**
     * This method is not synchronized. The caller must ensure that
     * this method is not used in a concurrent fashion. Currently,
     * the only caller is publish() method of FileandSyslogHandler.
     */
    public void updateStatistics(LogRecord record) {
        
        // Get information from log record.
        
        long logTimeStamp = record.getMillis();
        String logModuleId = record.getLoggerName();
        if (logModuleId == null) {
            Logger logger = LogDomains.getLogger(LogDomains.CORE_LOGGER);
            logger.log(
                    Level.WARNING, "Update error statistics failed",
                    (new Throwable("Logger name is null.")).fillInStackTrace());
            return;
        }
        Level logLevel = record.getLevel();
        
        // Sanity check.
        /*
        boolean isAnErrorRecord =
            logLevel.equals(Level.SEVERE) || logLevel.equals(Level.WARNING);
        if (!isAnErrorRecord) {
            return; // no op.
        }
        */
        // Calculate the timestamp bucket for the log record.
        
        long hoursElapsed = (logTimeStamp-startTimeStamp)/interval;
        long timeStamp = startTimeStamp + hoursElapsed*interval;
        /*
        System.out.println((new java.text.SimpleDateFormat("HH:mm:ss")).
                                format(new java.util.Date(timeStamp)));
        */
        
        // Update the error statistics.
        
        HashMap<String,ErrorCount> moduleMap = intervalMap.get(timeStamp);
        if (moduleMap == null) {
            moduleMap = new  HashMap<String,ErrorCount>();
            trimHourMap(timeStamp); // removes stale entries            
            intervalMap.put(Long.valueOf(timeStamp), moduleMap);
        }
        
        ErrorCount errorCount = moduleMap.get(logModuleId);
        if (errorCount == null) {
            errorCount = new ErrorCount();
            moduleMap.put(logModuleId, errorCount);
        }

        if (logLevel.equals(Level.SEVERE)) {
            errorCount.setSevereCount(errorCount.getSevereCount()+1);
        } else { // Level == WARNING
            errorCount.setWarningCount(errorCount.getWarningCount()+1); 
        }
    }
    
    private void trimHourMap(long refTimeStamp) {
        Long[] timeStamps = intervalMap.keySet().toArray(new Long[0]);        
        for (int i = 0; i < timeStamps.length; i++) {
            long timeStamp = timeStamps[i];
            if ((refTimeStamp-timeStamp) >= interval*getNumOfIntervals()) {
                intervalMap.remove(timeStamp);
            }            
        }
    }
    
    // Methods for Log MBean.
    
    /**
     * @return a list of Map objects. Each map object contains
     * the tuple [TimeStamp, SevereCount, WarningCount].
     */
    public List<Map<String,Object>> getErrorInformation() {
        
        // Calculate the most recent timestamp bucket.
        
        long intervalsElapsed =
                (System.currentTimeMillis()-startTimeStamp)/interval;
        long recentTimeStamp = startTimeStamp + (intervalsElapsed*interval);
        
        // Gather the information for the past numOfIntervals.
        List<Map<String,Object>> results =
                new ArrayList<Map<String,Object>>();
        for (int i = 0; i < getNumOfIntervals(); i++) {
            long timeStamp = recentTimeStamp - interval*i;            
            HashMap<String,ErrorCount> moduleMap = intervalMap.get(timeStamp);
            int severeCount = 0, warningCount = 0;
            if (timeStamp<startTimeStamp) {
                severeCount  = -1;
                warningCount = -1;            
            }
            if (moduleMap != null) {
                for (ErrorCount error : moduleMap.values()) {
                    severeCount += error.getSevereCount();
                    warningCount += error.getWarningCount();
                }
            }
            HashMap<String,Object> entry = new HashMap<String,Object>();
            entry.put(LogAnalyzer.TIMESTAMP_KEY, timeStamp);
            entry.put(LogAnalyzer.SEVERE_COUNT_KEY, severeCount);
            entry.put(LogAnalyzer.WARNING_COUNT_KEY, warningCount);
            results.add(entry);
        }
        
        return results;
    }
       
    /**
     * @return a map. The key is module id (String) and the value is
     *         error count (Integer) for the specific module.
     */     
    public Map<String,Integer> getErrorDistribution(
            long timeStamp, Level level) {
                
        // Sanity check.

        if (!(level.equals(Level.SEVERE) || level.equals(Level.WARNING))) {
            throw new IllegalArgumentException("Log level: " + level);
        }
        
        // Gather the information.
        
        HashMap<String,ErrorCount> moduleMap = intervalMap.get(timeStamp);
        if (moduleMap == null) {
            return null;
        }
        
        Map<String,Integer> results = new HashMap<String,Integer>();
        for (String moduleId : moduleMap.keySet()) {
            ErrorCount errorCount = moduleMap.get(moduleId);
            if (level.equals(Level.SEVERE)) {
                results.put(moduleId, errorCount.getSevereCount());
            } else { // Level == WARNING
                results.put(moduleId, errorCount.getWarningCount());                
            }
        }
        
        return results;
    }
    
    // Unit test
    
    public static void main(String[] args) {

        // Create log records
        
        LogRecord srecord = new LogRecord(Level.SEVERE, "severe record");
        srecord.setMillis(System.currentTimeMillis());
        srecord.setLoggerName("com.wombat.smodule");
        
        LogRecord wrecord = new LogRecord(Level.WARNING, "warning record");
        wrecord.setMillis(System.currentTimeMillis());
        wrecord.setLoggerName("com.wombat.wmodule");
        
        // Update error statistics
        
        java.text.SimpleDateFormat sdf =
                new java.text.SimpleDateFormat("HH:mm:ss");
        
        ErrorStatistics stats = new ErrorStatistics();
        long interval = 1000; // 1 second.
        stats.setIntervalDuration(interval);
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(interval);
            } catch (InterruptedException e) {}            
            for (int j = 0; j < 2; j++) {
                srecord.setMillis(System.currentTimeMillis());
                stats.updateStatistics(srecord);
                wrecord.setMillis(System.currentTimeMillis());
                stats.updateStatistics(wrecord);
            }
            System.out.printf("Interval(%1$s): %2$s\n", i,
                sdf.format(new java.util.Date(System.currentTimeMillis())));
        }
        
        // Query error statistics
        
        System.out.println("\nTimeStamp\tSevere\tWarning");
        System.out.println("--------------------------------");
        List<Map<String,Object>> list = stats.getErrorInformation();
        for (Map<String,Object> item : list) {
            long timeStamp = (Long) item.get(LogAnalyzer.TIMESTAMP_KEY);
            int severeCount = (Integer) item.get(LogAnalyzer.SEVERE_COUNT_KEY);
            int warningCount = 
                    (Integer) item.get(LogAnalyzer.WARNING_COUNT_KEY);
            System.out.printf("%1$s\t%2$s\t%3$s\n",
                              sdf.format(new java.util.Date(timeStamp)),
                              severeCount, warningCount);
        }
        
        for (Map<String,Object> item : list) {
            long timeStamp = (Long) item.get(LogAnalyzer.TIMESTAMP_KEY);
            Map<String,Integer> map =
                    stats.getErrorDistribution(timeStamp, Level.SEVERE);
            System.out.printf("\nModuleId\tLevel.SEVERE\t(%1$s)\n",
                              sdf.format(new java.util.Date(timeStamp)));
            System.out.println("------------------------------------------");
            for (String moduleId : map.keySet()) {
                System.out.printf("%1$s\t%2$s\n", moduleId, map.get(moduleId));
            }
            map = stats.getErrorDistribution(timeStamp, Level.WARNING);
            System.out.printf("\nModuleId\tLevel.WARNING\t(%1$s)\n",
                              sdf.format(new java.util.Date(timeStamp)));
            System.out.println("------------------------------------------");
            for (String moduleId : map.keySet()) {
                System.out.printf("%1$s\t%2$s\n", moduleId, map.get(moduleId));
            }
        }        
    }
}