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


1.2
date	2001.07.19.20.17.54;	author dlr;	state Exp;
branches;
next	1.1;

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


desc
@@


1.2
log
@Changed properties keys (also changed in
sandbox/conf/zone.properties.d/00zone.properties.in).
@
text
@/* ================================================================
 * Copyright (c) 2000 CollabNet.  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 CollabNet (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 COLLABNET 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 CollabNet.
 */
package org.tigris.helm.tool;

import org.apache.turbine.util.Log;

/**
 * Write state of the memory usage message to the webmacro log periodically.
 * 
 * &laquo;Immutable -- Read-Only.&raquo;
 * <p>
 * Spawn a thread that wakes up periodically to write memory use.   Copied
 * the way HelmProperties implements the singleton pattern.  
 * <p>
 * Set thread to run in daemon mode.  The JVM will not wait on its completion
 * if it's a daemon thread.  If only JVM threads running in a JVM, it exits.
 * 
 * @@author <a href="mailto:stack@@collab.net">St.Ack</a>
 * @@version $Id: MemorySentinel.java,v 1.1 2001/07/19 20:12:28 dlr Exp $
 * @@see HelmProperties
 */
public class MemorySentinel extends Thread
{
    /**
     * Default sleep period between logging.
     * One minute in milliseconds.
     */
    private final static long DEFAULT_SLEEP = ( ( 1000 ) * ( 60 ) );
    
    /**
     * Period to wait between printings of memory usage.
     * In milliseconds.
     */
    private long period = DEFAULT_SLEEP;
    
    /**
     * Run in daemon mode.
     * If daemon mode, we can run for ever and JVM won't wait on our completion
     * before exiting.
     */
    private boolean daemon = Boolean.FALSE.booleanValue( );
    
    /**
     * Should we log?
     */
    private boolean on = Boolean.FALSE.booleanValue( );
        
    /**
     * Keep here the single thisInstance of this class. 
     * We only want one of these per heap.
     */
    private static MemorySentinel thisInstance = null;
    
    /**
     * This thread's name.
     */
    private final static String THREAD_NAME = "Memory Sentinel";
    
    /**
     * Key to pull whether or not to log.
     */
    private final static String ON_KEY
        = "org.tigris.helm.tool.memorysentinel.on";
    
    /**
     * Key to pull period between loggings
     */
    private final static String PERIOD_KEY 
        = "org.tigris.helm.tool.memorysentinel.period";

    /**
     * Key to pull whether we should run in daemon mode.
     */
    private final static String DAEMON_KEY 
        = "org.tigris.helm.tool.memorysentinel.daemon";

    /**
     * Bytes in a megabyte define.
     * Make it a double otherwise we get integer math (no decimals).
     */
    private final static double MEGABYTE = ( 1024 * 1024 );
    
    /**
     * Only want one thisInstance per heap so make this 
     * singleton class.  Reads properties using HelmProperties class.
     *
     * @@exception NullPointerException If we can't get a HelmProperties
     * instance.
     * @@exception IllegalArgumentException If the period we get is 
     * negative or greater than once every 24 hours.
     */
    private MemorySentinel()
        throws NullPointerException, IllegalArgumentException
    {
        HelmProperties properties = HelmProperties.getInstance( );
        if( properties == null )
            throw new NullPointerException( "HelmProperties is null" );
            
        Boolean onSetting = new Boolean( properties.getProperty( ON_KEY ) );
        on = onSetting.booleanValue( );
        
        Boolean daemonSetting 
            = new Boolean( properties.getProperty( DAEMON_KEY ) );
        daemon = daemonSetting.booleanValue( );
        
        period = Long.parseLong( properties.getProperty( PERIOD_KEY ) );
        if( ( period < 0 ) || ( period > ( 1000 * 60 * 60 * 24 ) ) )
            throw new IllegalArgumentException( "Period negative or too big: "
                + Long.toString( period ) );
                
        Log.debug( "on: " 
                    + ( ( on )
                        ? Boolean.TRUE.toString( )
                        : Boolean.FALSE.toString( ) )
                    + ", daemon: "
                    + ( ( daemon )
                        ? Boolean.TRUE.toString( )
                        : Boolean.FALSE.toString( ) )
                    + ", period between logs: "
                    + Long.toString( period )
                    + " milliseconds.");
    }
    
    
    /**
     * The first call to getInstance starts the memory sentinel thread.
     *
     * Safe to call multiple times.  Only one thisInstance possible per heap.
     * Reads properties that tell it interval to run at and whether to run
     * as daemon thread.  Same property file may tell us not to start the 
     * logging thread.
     *
     * @@return The single thisInstance of this class.
     * 
     * @@exception IllegalThreadStateException
     * @@exception SecurityException
     * @@exception NullPointerException If we can't get a HelmProperties
     * instance.
     * @@exception IllegalArgumentException If the period we get is 
     * negative or greater than once every 24 hours.
     */
    public static MemorySentinel getInstance()
        throws IllegalThreadStateException, 
                SecurityException,
                NullPointerException,
                IllegalArgumentException
    {
        if( thisInstance == null )
        {
            synchronized( MemorySentinel.class ) // Get class lock.
            {
                // Test again for null in case we've been preempted between
                // the first test for null and obtaining our class lock
                //
                if( thisInstance == null )
                {
                    // Set a thread name and set priority to high.  It's 
                    // important we run -- especially when JVM is trashing.
                    //
                    thisInstance = new MemorySentinel( );
                    thisInstance.setName( THREAD_NAME );
                    thisInstance.setPriority( Thread.MAX_PRIORITY ); 
                    if( thisInstance.daemon )
                        thisInstance.setDaemon( Boolean.TRUE.booleanValue( ) );
                    if( thisInstance.on )
                        thisInstance.start( );
                }
            }
        }
        
        return thisInstance;
    }
    
    /**
     * Print total and free memory to webmacro log on an interval.
     * 
     * Should be no problems w/ synchronization since there is only one
     * instance of this class per jvm.  Do most of the work in the run method
     * itself for then i do not need class data members to hold values between
     * sleeps.  Can keep it all storage local to this method.
     * 
     */
    public void run()
    {
        Runtime runtime = Runtime.getRuntime( );
        StringBuffer buffer = new StringBuffer( );
        long total = runtime.totalMemory( ); // Start w/ sensible values
        long free = runtime.freeMemory( ); // Start w/ sensible values
        long previousTotal = total;
        long previousFree = free; // Previous free memory.
         
        while( Boolean.TRUE.booleanValue( ) )
        {
            previousTotal = total; 
            previousFree = free;
            total = runtime.totalMemory( );
            free = runtime.freeMemory( );
            
            buffer.setLength( 0 );
            buffer.append( "Free: " );
            buffer.append( format( free, previousFree ) );
            buffer.append( ", Total: " );
            buffer.append( format( total, previousTotal ) );
            buffer.append( "." );
            Log.debug( buffer.toString( ) );

            try
            {
                sleep( period );
            }
            
            catch( InterruptedException exception )
            {
                Log.debug( exception.toString( ) );
                break; // Finish
            }
        }
    }

    /**
     * Format memory for logging.
     * Conveniance method. Format passed values for logging as follows:
     * <pre>
     *  MEMORY_IN_BYTES MEMORY_IN_MEGS (+/-DELTA%)
     * </pre>
     * @@param inMemory Memory in bytes
     * @@param inPreviousMemory Previous memory value in bytes.
     * @@return Formatted string for logging.
     */
    private String format( long inMemory, long inPreviousMemory )
    {
        StringBuffer buffer = new StringBuffer( strMegabytes( inMemory ) );
        buffer.append( "M (" );
        buffer.append( strPercentageDelta( inMemory, inPreviousMemory ) );
        buffer.append( ")" );
        return buffer.toString( );  
    }
    
    /**
     * Return the passed in long of bytes as a pretty string of megabytes.
     * @@param inBytes
     * @@return Megabytes as string w/ a precision of two decimal places
     */
    private String strMegabytes( long inBytes )
    {
        java.math.BigDecimal megs 
            = new java.math.BigDecimal( inBytes / this.MEGABYTE );
        megs = megs.setScale( 2, java.math.BigDecimal.ROUND_UP );
        
        return megs.toString( );
    }
    
    /**
     * Calculate the percentage delta between two values.
     * Returns result as a string suitable for logging.
     * Format returned is +/-DELTA%
     * @@param inNew
     * @@param inOld
     * @@return Return difference between the new and old values as a percentage.
     * @@exception java.lang.ArithmeticException 
     */
     private String strPercentageDelta( long inNew, long inOld )
        throws java.lang.ArithmeticException
     {
        int delta = percentageDelta( inNew, inOld );
        StringBuffer strBuffer = new StringBuffer( );
        if( delta > 0 )
            strBuffer.append( '+' );
        if( delta != 0 )
            strBuffer.append( Integer.toString( delta ) ); 
        else
            strBuffer.append( "0" );
        strBuffer.append( '%' );
        
        return strBuffer.toString( );
     }
     
    /**
     * Calculate percentage delta between two values.
     * @@param inNew
     * @@param inOld
     * @@return Return delta between the new and old values as a percentage.
     * @@exception java.lang.ArithmeticException 
     */
     private int percentageDelta( long inNew, long inOld )
        throws java.lang.ArithmeticException
     {
        double reusedDouble = inNew - inOld;
        return ( int )( ( reusedDouble / inOld ) * 100 );
     }
    
    /**
     * Main method. 
     *
     * Used to test this class.  Supply at least the PERIOD_KEY
     * and the ON_KEY properties as system properties on the command
     * line.  Here's an example of how to run this main method:
     * <pre>
     *  % java -cp ./classes:/home/stack/software/webmacro/webmacro.jar -Dorg.tigris.helm.tool.memorysentinel.period=1000 -Dorg.tigris.helm.tool.memorysentinel.on=true org.tigris.helm.tool.MemorySentinel
     * </pre>
     * 
     * @@param args List of command line arguments. Ignored.
     */
    public static void main( String[] args )    
    {
        HelmProperties properties = HelmProperties.getInstance( );
        properties.init( System.getProperties( ) );
        getInstance( );
        
        // Just keep allocating memory so i can watch the memory
        // logging change values
        //
        while( Boolean.TRUE.booleanValue( ) )
        {
            StringBuffer strBuffer = new StringBuffer( );
            
            for( int i = 0; i < 10000; i++ )
            {
                strBuffer.append( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" );
            }
        }
    }
}
@


1.1
log
@Ported from Joist.
@
text
@d62 1
a62 1
 * @@version $Id: MemorySentinel.java,v 1.5 2001/05/25 20:45:10 dlr Exp $
d105 2
a106 1
    private final static String ON_KEY = "org.joist.util.memorysentinel.on";
d112 1
a112 1
        = "org.joist.util.memorysentinel.period";
d118 1
a118 1
        = "org.joist.util.memorysentinel.daemon";
d342 1
a342 1
     *  % java -cp ./classes:/home/stack/software/webmacro/webmacro.jar -Dorg.joist.util.memorysentinel.period=1000 -Dorg.joist.util.memorysentinel.on=true org.tigris.helm.tool.MemorySentinel
@

