head	1.1;
access;
symbols;
locks; strict;
comment	@# @;


1.1
date	2001.07.19.20.06.36;	author dlr;	state Exp;
branches;
next	;


desc
@@


1.1
log
@Ported from Joist.
@
text
@/* ================================================================
 * Copyright (c) 2000 Collab.Net.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by Collab.Net (http://www.Collab.Net/)."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of Collab.Net.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL COLLAB.NET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of Collab.Net.
 */


package org.tigris.helm.tool;

import java.io.*;
import java.util.*;
import java.text.*;
import java.lang.Math;

import org.apache.turbine.util.Log;

/**
 * Tracks hits on the web site.
 *
 * @@author C. Ludewig, CJ Software, Mill Valley, CA
 * @@author <a href="mailto:davidp@@dataweb-systems.com">David C. Pellegrini</a>
 * @@version $Revision: 1.8 $
 *
 * Copyright (c) 1999 dataweb systems, inc.
 * Copyright (c) 1999-2001 CollabNet, Inc.
 */
public class WebStats extends Thread 
{
    private static final boolean DEBUG = false;

    /**
     * The singleton instance of <code>WebStats</code>.
     */
    private static WebStats theInstance = null;

    // Configurable settings
    private long	interval;
    private int		batchSize;
    private int		historySize;
    private String	logFile;

    // Private state
    private int		count = 0;
    private int		batchCount;
    private int[]	countArray;
    private int		newest;
    private int		oldest;
    private boolean	running = false;
    private PrintWriter trafficLog = null;
    private DateFormat  dateFormat = null;
    
    /**
     * Default constructor
     *
     * @@param	interval    The number of seconds between samples.
     * @@param	batchSize   The number of samples to include per record in the 
     *                      log file.
     * @@param	historySize The number of past samples to retain as history.
     * @@param	logFile	log File to write to, or <code>null</code>.
     *
     * @@exception IOException
     */
	private WebStats(int interval, int batchSize, int historySize,
                     String logFile)
        throws IOException
    {
        super("WebStats"); // give the thread a name
        if (DEBUG) Log.debug("WebStats: constructor starting");
        this.interval = interval * 1000;	// milliseconds
        this.batchSize = batchSize;
        if (historySize >= batchSize)
            this.historySize = historySize;
        else
            this.historySize = batchSize;

        // bootstrap the circular buffer algorithm
        countArray = new int[this.historySize];
        newest = 0;
        oldest = 0;
        batchCount = 0;

        this.logFile = logFile + "." + Math.abs(new Random().nextInt());
        openLogFile();
        dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT);

        // give it slightly less than normal priority
        // so it interferes less with page serving
        //[davidp] bump it slightly higher so it runs regularly.
        //setPriority(Thread.NORM_PRIORITY+1);
        if (DEBUG) Log.debug("WebStats: constructor nearly done");
        start(); // let's get the background thread going
        if (DEBUG) Log.debug("WebStats: constructor done");
    }

    /**
     *  Returns the singleton instance of WebStats
     * @@return	This WebStats instance
     * @@exception	IOException
     */
	public static WebStats getInstance()
        throws IOException
    {
        if (theInstance == null)
        {
            // Create an instance with default values
            getInstance(10, 6, 360, "/usr/local/apache/logs/traffic.log");
        }
        return theInstance;
    }

    /**
     * Configures and returns the singleton instance of WebStats <p>
     * <b>FIXME: This implementation of the singleton factory has been
     * proven to NOT WORK.</b>
     *
     * @@param	interval    the   number of seconds between samples
     * @@param	batchSize   the   number of samples to include per record in the log file
     * @@param	historySize the number of past samples to retain as history
     * @@param	logFile	log file to write to, or null
     * @@return	The WebStats instance
     * @@exception IOException
     */
	public static WebStats getInstance(int interval, int batchSize,
                                       int historySize, String logFile)
        throws IOException
    {
        if (theInstance == null)
        {
            synchronized (WebStats.class)
            {
                if (theInstance == null)  // if it's _still_ null ;-)
                {
                    theInstance =
                        new WebStats(interval, batchSize, historySize, logFile);
                }
            }
        }
        return theInstance;
    }

    /**
     *
     */
	protected void finalize()
    {
        running = false;
        closeLogFile();
    }

    /**
     *  Adds a hit for the current time interval.
     */
	//[davidp: try unsynched] public synchronized void
	public void addHit()
    {
        count++;
    }

    /**
     * Closes the log file.
     */
	protected void closeLogFile()
    {
        trafficLog.close();
    }

    /**
     * Opens the log file for writing.
     *
     * @@exception	IOException
     */
	protected void openLogFile()
        throws IOException
    {
        trafficLog =
            new PrintWriter(new BufferedWriter(new FileWriter(logFile)));
    }

    /** 
     * Saves hit records to the trafficLog file every
     * <i>interval</i> seconds.
     */
	public void run()
    {
        if (DEBUG) Log.debug("WebStats.run: Starting");
        if (DEBUG) Log.debug("WebStats.run: running=" + (running? "true": "false"));
        running = true;
        while (running)
        {
            try
            {
                sleepAndIgnore(interval);
                recordIntervalCount();
                saveRecord();
            }
            catch (Exception e)
            {
                System.err.println("Can't record web stats: " + e);
            }
        }
        if (DEBUG) Log.debug("WebStats.run: Ending");
    }

    /**
     *  Records the current hit count in the history.
     */
	protected void recordIntervalCount()
    {
        int lastCount;
        // save and synchronize so hits aren't lost
        // (see addHit())
        //[davidp: don't bother] synchronized (this)
        {
            lastCount = count;
            count = 0; // reset for next interval
        }

        // Record the latest count in the history array
        if (++newest == historySize) newest = 0;
        if (newest == oldest) oldest++;
        if (oldest == historySize) oldest = 0;
        countArray[newest] = lastCount;
    }


    /**
     *  Records a hit record to the trafficLog file.
     * @@exception	IOException
     */
	protected void saveRecord()
        throws IOException
    {
        if (trafficLog != null)
        {
            if (++batchCount >= batchSize)
            {
                batchCount = 0;
                StringBuffer record = new StringBuffer();
                record.append(dateFormat.format(new Date())).append(": ");
                int index = newest;
                int[] recArray = new int[batchSize];

                for (int recIndex = 0; recIndex < batchSize; recIndex++)
                {
                    recArray[recIndex] = countArray[index];
                    if (--index < 0) index = historySize -1;
                }

                for (index = batchSize - 1; index >= 0; index--)
                {
                    record.append("\t").append(recArray[index]);
                }
                trafficLog.println(record);
                trafficLog.flush();
            }
        }
    }

    /**
     *  Calls standard sleep, but ignores the exception.
     * @@param	ms  A variable of type long
     */
	protected void sleepAndIgnore(long ms)
    {
        try { sleep(ms); }
        catch (InterruptedException e) {}
    }

    /**
     *  Shuts down the hit counter thread.
     */
	public void stopRunning()
    {
        running = false;
    }
}
@
