1   package org.apache.jcs;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  import junit.framework.TestCase;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.jcs.engine.stats.behavior.IStatElement;
30  import org.apache.jcs.engine.stats.behavior.IStats;
31  
32  /***
33   * This is based on a test that was posted to the user's list:
34   *
35   * http://www.opensubscriber.com/message/jcs-users@jakarta.apache.org/2435965.html
36   *
37   */
38  public class JCSThrashTest
39      extends TestCase
40  {
41  
42      private static final Log LOG = LogFactory.getLog( JCSThrashTest.class.getName() );
43  
44      /***
45       * the cache instance
46       */
47      protected JCS jcs;
48  
49      /***
50       * @param args
51       */
52      public static void main( String[] args )
53      {
54          junit.textui.TestRunner.run( JCSThrashTest.class );
55      }
56  
57      /***
58       * @param arg0
59       */
60      public JCSThrashTest( String arg0 )
61      {
62          super( arg0 );
63      }
64  
65      protected void setUp()
66          throws Exception
67      {
68          super.setUp();
69          JCS.setConfigFilename( "/TestThrash.ccf" );
70          jcs = JCS.getInstance( "testcache" );
71      }
72  
73      protected void tearDown()
74          throws Exception
75      {
76          super.tearDown();
77          jcs.clear();
78          jcs.dispose();
79      }
80  
81      /***
82       * Tests adding an entry.
83       * @throws Exception
84       */
85      public void testPut()
86          throws Exception
87      {
88          final String value = "value";
89          final String key = "key";
90  
91          // Make sure the element is not found
92          assertEquals( 0, getListSize() );
93  
94          assertNull( jcs.get( key ) );
95  
96          jcs.put( key, value );
97  
98          // Get the element
99          LOG.info( "jcs.getStats(): " + jcs.getStatistics() );
100         assertEquals( 1, getListSize() );
101         assertNotNull( jcs.get( key ) );
102         assertEquals( value, jcs.get( key ) );
103     }
104 
105     /***
106      * Test elements can be removed from the store
107      * @throws Exception
108      */
109     public void testRemove()
110         throws Exception
111     {
112         jcs.put( "key1", "value1" );
113         assertEquals( 1, getListSize() );
114 
115         jcs.remove( "key1" );
116         assertEquals( 0, getListSize() );
117 
118         jcs.put( "key2", "value2" );
119         jcs.put( "key3", "value3" );
120         assertEquals( 2, getListSize() );
121 
122         jcs.remove( "key2" );
123         assertEquals( 1, getListSize() );
124 
125         // Try to remove an object that is not there in the store
126         jcs.remove( "key4" );
127         assertEquals( 1, getListSize() );
128     }
129 
130     /***
131      * This does a bunch of work and then verifies that the memory has not grown by much.
132      * Most of the time the amount of memory used after the test is less.
133      *
134      * @throws Exception
135      */
136     public void testForMemoryLeaks()
137         throws Exception
138     {
139         long differenceMemoryCache = thrashCache();
140         LOG.info( "Memory Difference is: " + differenceMemoryCache );
141         assertTrue( differenceMemoryCache < 500000 );
142 
143         //LOG.info( "Memory Used is: " + measureMemoryUse() );
144     }
145 
146     /***
147      * @return
148      * @throws Exception
149      */
150     protected long thrashCache()
151         throws Exception
152     {
153 
154         long startingSize = measureMemoryUse();
155         LOG.info( "Memory Used is: " + startingSize );
156 
157         final String value = "value";
158         final String key = "key";
159 
160         // Add the entry
161         jcs.put( key, value );
162 
163         // Create 15 threads that read the keys;
164         final List executables = new ArrayList();
165         for ( int i = 0; i < 15; i++ )
166         {
167             final JCSThrashTest.Executable executable = new JCSThrashTest.Executable()
168             {
169                 public void execute()
170                     throws Exception
171                 {
172                     for ( int i = 0; i < 500; i++ )
173                     {
174                         final String key = "key" + i;
175                         jcs.get( key );
176                     }
177                     jcs.get( "key" );
178                 }
179             };
180             executables.add( executable );
181         }
182 
183         // Create 15 threads that are insert 500 keys with large byte[] as
184         // values
185         for ( int i = 0; i < 15; i++ )
186         {
187             final JCSThrashTest.Executable executable = new JCSThrashTest.Executable()
188             {
189                 public void execute()
190                     throws Exception
191                 {
192 
193                     // Add a bunch of entries
194                     for ( int i = 0; i < 500; i++ )
195                     {
196                         // Use a random length value
197                         final String key = "key" + i;
198                         byte[] value = new byte[10000];
199                         jcs.put( key, value );
200                     }
201                 }
202             };
203             executables.add( executable );
204         }
205 
206         runThreads( executables );
207         jcs.clear();
208 
209         long finishingSize = measureMemoryUse();
210         LOG.info( "Memory Used is: " + finishingSize );
211         return finishingSize - startingSize;
212     }
213 
214     /***
215      * Runs a set of threads, for a fixed amount of time.
216      * @param executables
217      * @throws Exception
218      */
219     protected void runThreads( final List executables )
220         throws Exception
221     {
222 
223         final long endTime = System.currentTimeMillis() + 10000;
224         final Throwable[] errors = new Throwable[1];
225 
226         // Spin up the threads
227         final Thread[] threads = new Thread[executables.size()];
228         for ( int i = 0; i < threads.length; i++ )
229         {
230             final JCSThrashTest.Executable executable = (JCSThrashTest.Executable) executables.get( i );
231             threads[i] = new Thread()
232             {
233                 public void run()
234                 {
235                     try
236                     {
237                         // Run the thread until the given end time
238                         while ( System.currentTimeMillis() < endTime )
239                         {
240                             executable.execute();
241                         }
242                     }
243                     catch ( Throwable t )
244                     {
245                         // Hang on to any errors
246                         errors[0] = t;
247                     }
248                 }
249             };
250             threads[i].start();
251         }
252 
253         // Wait for the threads to finish
254         for ( int i = 0; i < threads.length; i++ )
255         {
256             threads[i].join();
257         }
258 
259         // Throw any error that happened
260         if ( errors[0] != null )
261         {
262             throw new Exception( "Test thread failed.", errors[0] );
263         }
264     }
265 
266     /***
267      * Measure memory used by the VM.
268      *
269      * @return
270      * @throws InterruptedException
271      */
272     protected long measureMemoryUse()
273         throws InterruptedException
274     {
275         System.gc();
276         Thread.sleep( 3000 );
277         System.gc();
278         return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
279     }
280 
281     /***
282      * A runnable, that can throw an exception.
283      */
284     protected interface Executable
285     {
286         /***
287          * Executes this object.
288          *
289          * @throws Exception
290          */
291         void execute()
292             throws Exception;
293     }
294 
295     /***
296      *
297      * @return
298      */
299     private int getListSize()
300     {
301         final String listSize = "List Size";
302         final String lruMemoryCache = "LRU Memory Cache";
303         String result = "0";
304         IStats istats[] = jcs.getStatistics().getAuxiliaryCacheStats();
305         for ( int i = 0; i < istats.length; i++ )
306         {
307             IStatElement statElements[] = istats[i].getStatElements();
308             if ( lruMemoryCache.equals( istats[i].getTypeName() ) )
309             {
310                 for ( int j = 0; j < statElements.length; j++ )
311                 {
312                     if ( listSize.equals( statElements[j].getName() ) )
313                     {
314                         result = statElements[j].getData();
315                     }
316                 }
317             }
318 
319         }
320         return Integer.parseInt( result );
321     }
322 
323 //    private int getMapSize()
324 //    {
325 //        final String listSize = "Map Size";
326 //        String result = "0";
327 //        IStatElement statElements[] = jcs.getStatistics().getStatElements();
328 //        for ( int i = 0; i < statElements.length; i++ )
329 //        {
330 //            if ( listSize.equals( statElements[i].getName() ) )
331 //            {
332 //                result = statElements[i].getData();
333 //            }
334 //        }
335 //        return Integer.parseInt( result );
336 //    }
337 }