001    package org.apache.jcs;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.io.Serializable;
023    import java.util.ArrayList;
024    import java.util.List;
025    
026    import junit.framework.TestCase;
027    
028    import org.apache.commons.logging.Log;
029    import org.apache.commons.logging.LogFactory;
030    import org.apache.jcs.engine.stats.behavior.IStatElement;
031    import org.apache.jcs.engine.stats.behavior.IStats;
032    
033    /**
034     * This is based on a test that was posted to the user's list:
035     * <p>
036     * http://www.opensubscriber.com/message/jcs-users@jakarta.apache.org/2435965.html
037     */
038    public class JCSThrashTest
039        extends TestCase
040    {
041        /** The logger. */
042        private static final Log LOG = LogFactory.getLog( JCSThrashTest.class.getName() );
043    
044        /**
045         * the cache instance
046         */
047        protected JCS<String, Serializable> jcs;
048    
049        /**
050         * @param args
051         */
052        public static void main( String[] args )
053        {
054            junit.textui.TestRunner.run( JCSThrashTest.class );
055        }
056    
057        /**
058         * @param arg0
059         */
060        public JCSThrashTest( String arg0 )
061        {
062            super( arg0 );
063        }
064    
065        /**
066         * Sets up the test
067         * @throws Exception
068         */
069        @Override
070        protected void setUp()
071            throws Exception
072        {
073            super.setUp();
074            JCS.setConfigFilename( "/TestThrash.ccf" );
075            jcs = JCS.getInstance( "testcache" );
076        }
077    
078        /**
079         * @throws Exception
080         */
081        @Override
082        protected void tearDown()
083            throws Exception
084        {
085            super.tearDown();
086            jcs.clear();
087            jcs.dispose();
088        }
089    
090        /**
091         * Tests adding an entry.
092         * @throws Exception
093         */
094        public void testPut()
095            throws Exception
096        {
097            final String value = "value";
098            final String key = "key";
099    
100            // Make sure the element is not found
101            assertEquals( 0, getListSize() );
102    
103            assertNull( jcs.get( key ) );
104    
105            jcs.put( key, value );
106    
107            // Get the element
108            LOG.info( "jcs.getStats(): " + jcs.getStatistics() );
109            assertEquals( 1, getListSize() );
110            assertNotNull( jcs.get( key ) );
111            assertEquals( value, jcs.get( key ) );
112        }
113    
114        /**
115         * Test elements can be removed from the store
116         * @throws Exception
117         */
118        public void testRemove()
119            throws Exception
120        {
121            jcs.put( "key1", "value1" );
122            assertEquals( 1, getListSize() );
123    
124            jcs.remove( "key1" );
125            assertEquals( 0, getListSize() );
126    
127            jcs.put( "key2", "value2" );
128            jcs.put( "key3", "value3" );
129            assertEquals( 2, getListSize() );
130    
131            jcs.remove( "key2" );
132            assertEquals( 1, getListSize() );
133    
134            // Try to remove an object that is not there in the store
135            jcs.remove( "key4" );
136            assertEquals( 1, getListSize() );
137        }
138    
139        /**
140         * This does a bunch of work and then verifies that the memory has not grown by much. Most of
141         * the time the amount of memory used after the test is less.
142         * @throws Exception
143         */
144        public void testForMemoryLeaks()
145            throws Exception
146        {
147            long differenceMemoryCache = thrashCache();
148            LOG.info( "Memory Difference is: " + differenceMemoryCache );
149            assertTrue( differenceMemoryCache < 500000 );
150    
151            //LOG.info( "Memory Used is: " + measureMemoryUse() );
152        }
153    
154        /**
155         * @return time
156         * @throws Exception
157         */
158        protected long thrashCache()
159            throws Exception
160        {
161            long startingSize = measureMemoryUse();
162            LOG.info( "Memory Used is: " + startingSize );
163    
164            final String value = "value";
165            final String key = "key";
166    
167            // Add the entry
168            jcs.put( key, value );
169    
170            // Create 15 threads that read the keys;
171            final List<Executable> executables = new ArrayList<Executable>();
172            for ( int i = 0; i < 15; i++ )
173            {
174                final JCSThrashTest.Executable executable = new JCSThrashTest.Executable()
175                {
176                    public void execute()
177                        throws Exception
178                    {
179                        for ( int i = 0; i < 500; i++ )
180                        {
181                            final String key = "key" + i;
182                            jcs.get( key );
183                        }
184                        jcs.get( "key" );
185                    }
186                };
187                executables.add( executable );
188            }
189    
190            // Create 15 threads that are insert 500 keys with large byte[] as
191            // values
192            for ( int i = 0; i < 15; i++ )
193            {
194                final JCSThrashTest.Executable executable = new JCSThrashTest.Executable()
195                {
196                    public void execute()
197                        throws Exception
198                    {
199    
200                        // Add a bunch of entries
201                        for ( int i = 0; i < 500; i++ )
202                        {
203                            // Use a random length value
204                            final String key = "key" + i;
205                            byte[] value = new byte[10000];
206                            jcs.put( key, value );
207                        }
208                    }
209                };
210                executables.add( executable );
211            }
212    
213            runThreads( executables );
214            jcs.clear();
215    
216            long finishingSize = measureMemoryUse();
217            LOG.info( "Memory Used is: " + finishingSize );
218            return finishingSize - startingSize;
219        }
220    
221        /**
222         * Runs a set of threads, for a fixed amount of time.
223         * <p>
224         * @param executables
225         * @throws Exception
226         */
227        protected void runThreads( final List<Executable> executables )
228            throws Exception
229        {
230    
231            final long endTime = System.currentTimeMillis() + 10000;
232            final Throwable[] errors = new Throwable[1];
233    
234            // Spin up the threads
235            final Thread[] threads = new Thread[executables.size()];
236            for ( int i = 0; i < threads.length; i++ )
237            {
238                final JCSThrashTest.Executable executable = executables.get( i );
239                threads[i] = new Thread()
240                {
241                    @Override
242                    public void run()
243                    {
244                        try
245                        {
246                            // Run the thread until the given end time
247                            while ( System.currentTimeMillis() < endTime )
248                            {
249                                executable.execute();
250                            }
251                        }
252                        catch ( Throwable t )
253                        {
254                            // Hang on to any errors
255                            errors[0] = t;
256                        }
257                    }
258                };
259                threads[i].start();
260            }
261    
262            // Wait for the threads to finish
263            for ( int i = 0; i < threads.length; i++ )
264            {
265                threads[i].join();
266            }
267    
268            // Throw any error that happened
269            if ( errors[0] != null )
270            {
271                throw new Exception( "Test thread failed.", errors[0] );
272            }
273        }
274    
275        /**
276         * Measure memory used by the VM.
277         * <p>
278         * @return bytes
279         * @throws InterruptedException
280         */
281        protected long measureMemoryUse()
282            throws InterruptedException
283        {
284            System.gc();
285            Thread.sleep( 3000 );
286            System.gc();
287            return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
288        }
289    
290        /**
291         * A runnable, that can throw an exception.
292         */
293        protected interface Executable
294        {
295            /**
296             * Executes this object.
297             * @throws Exception
298             */
299            void execute()
300                throws Exception;
301        }
302    
303        /**
304         * @return size
305         */
306        private int getListSize()
307        {
308            final String listSize = "List Size";
309            final String lruMemoryCache = "LRU Memory Cache";
310            String result = "0";
311            IStats istats[] = jcs.getStatistics().getAuxiliaryCacheStats();
312            for ( int i = 0; i < istats.length; i++ )
313            {
314                IStatElement statElements[] = istats[i].getStatElements();
315                if ( lruMemoryCache.equals( istats[i].getTypeName() ) )
316                {
317                    for ( int j = 0; j < statElements.length; j++ )
318                    {
319                        if ( listSize.equals( statElements[j].getName() ) )
320                        {
321                            result = statElements[j].getData();
322                        }
323                    }
324                }
325            }
326            return Integer.parseInt( result );
327        }
328    }