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 }