1   /**
2    *  Copyright 2003-2007 Greg Luck
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache;
18  
19  import net.sf.ehcache.distribution.JVMUtil;
20  import net.sf.ehcache.store.LruMemoryStoreTest;
21  import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
22  import net.sf.ehcache.store.Store;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  import java.io.IOException;
27  import java.util.ArrayList;
28  import java.util.Date;
29  import java.util.List;
30  import java.util.Random;
31  
32  /**
33   * Other than policy differences, the Store implementations should work identically
34   *
35   * @author Greg Luck
36   * @version $Id: MemoryStoreTester.java 512 2007-07-10 09:18:45Z gregluck $
37   */
38  public class MemoryStoreTester extends AbstractCacheTest {
39  
40      private static final Log LOG = LogFactory.getLog(MemoryStoreTester.class.getName());
41  
42      /**
43       * The memory store that tests will be performed on
44       */
45      protected Store store;
46  
47  
48      /**
49       * The cache under test
50       */
51      protected Cache cache;
52  
53  
54      /**
55       * For automatic suite generators
56       */
57      public void testNoop() {
58          //noop
59      }
60  
61      /**
62       * setup test
63       */
64      protected void setUp() throws Exception {
65          manager = CacheManager.getInstance();
66      }
67  
68      /**
69       * teardown
70       */
71      protected void tearDown() throws Exception {
72          if (manager != null) {
73              manager.shutdown();
74          }
75      }
76  
77      /**
78       * Creates a cache with the given policy and adds it to the manager.
79       *
80       * @param evictionPolicy
81       * @throws CacheException
82       */
83      protected void createMemoryStore(MemoryStoreEvictionPolicy evictionPolicy) throws CacheException {
84          cache = new Cache("test", 1000, evictionPolicy, true, null, true, 60, 30, false, 60, null);
85          manager.addCache(cache);
86          store = cache.getMemoryStore();
87      }
88  
89      /**
90       * Creates a cache with the given policy and adds it to the manager.
91       *
92       * @param evictionPolicy
93       * @throws CacheException
94       */
95      protected void createMemoryStore(MemoryStoreEvictionPolicy evictionPolicy, int memoryStoreSize) throws CacheException {
96          manager.removeCache("test");
97          cache = new Cache("test", memoryStoreSize, evictionPolicy, true, null, true, 60, 30, false, 60, null);
98          manager.addCache(cache);
99          store = cache.getMemoryStore();
100     }
101 
102     /**
103      * Creates a store from the given configuration and cache within it.
104      *
105      * @param filePath
106      * @param cacheName
107      * @throws CacheException
108      */
109     protected void createMemoryStore(String filePath, String cacheName) throws CacheException {
110         manager.shutdown();
111         manager = CacheManager.create(filePath);
112         cache = manager.getCache(cacheName);
113         store = cache.getMemoryStore();
114     }
115 
116 
117     /**
118      * Test elements can be put in the store
119      */
120     protected void putTest() throws IOException {
121         Element element;
122 
123         assertEquals(0, store.getSize());
124 
125         element = new Element("key1", "value1");
126         store.put(element);
127         assertEquals(1, store.getSize());
128         assertEquals("value1", store.get("key1").getObjectValue());
129 
130         element = new Element("key2", "value2");
131         store.put(element);
132         assertEquals(2, store.getSize());
133         assertEquals("value2", store.get("key2").getObjectValue());
134 
135         for (int i = 0; i < 2000; i++) {
136             store.put(new Element("" + i, new Date()));
137         }
138 
139         assertEquals(4, store.getSize());
140         assertEquals(2002, cache.getSize());
141         assertEquals(1998, cache.getDiskStoreSize());
142     }
143 
144     /**
145      * Test elements can be removed from the store
146      */
147     protected void removeTest() throws IOException {
148         Element element;
149 
150         element = new Element("key1", "value1");
151         store.put(element);
152         assertEquals(1, store.getSize());
153 
154         store.remove("key1");
155         assertEquals(0, store.getSize());
156 
157         store.put(new Element("key2", "value2"));
158         store.put(new Element("key3", "value3"));
159         assertEquals(2, store.getSize());
160 
161         assertNotNull(store.remove("key2"));
162         assertEquals(1, store.getSize());
163 
164         // Try to remove an object that is not there in the store
165         assertNull(store.remove("key4"));
166         assertEquals(1, store.getSize());
167 
168         //check no NPE on key handling
169         assertNull(store.remove(null));
170 
171     }
172 
173 
174     /**
175      * Check no NPE on put
176      */
177     public void testNullPut() throws IOException {
178         store.put(null);
179     }
180 
181     /**
182      * Check no NPE on get
183      */
184     public void testNullGet() throws IOException {
185         assertNull(store.get(null));
186     }
187 
188     /**
189      * Check no NPE on remove
190      */
191     public void testNullRemove() throws IOException {
192         assertNull(store.remove(null));
193     }
194 
195     /**
196      * Tests looking up an entry that does not exist.
197      */
198     public void testGetUnknown() throws Exception {
199         final Element element = store.get("key");
200         assertNull(element);
201     }
202 
203     /**
204      * Tests adding an entry.
205      */
206     public void testPut() throws Exception {
207         final String value = "value";
208         final String key = "key";
209 
210         // Make sure the element is not found
211         assertEquals(0, store.getSize());
212         Element element = store.get(key);
213         assertNull(element);
214 
215         // Add the element
216         element = new Element(key, value);
217         store.put(element);
218 
219         // Get the element
220         assertEquals(1, store.getSize());
221         element = store.get(key);
222         assertNotNull(element);
223         assertEquals(value, element.getObjectValue());
224     }
225 
226     /**
227      * Tests removing an entry.
228      */
229     public void testRemove() throws Exception {
230         final String value = "value";
231         final String key = "key";
232 
233         // Add the entry
234 
235         Element element = new Element(key, value);
236         store.put(element);
237 
238         // Check the entry is there
239         assertEquals(1, store.getSize());
240         element = store.get(key);
241         assertNotNull(element);
242 
243         // Remove it
244         store.remove(key);
245 
246         // Check the entry is not there
247         assertEquals(0, store.getSize());
248         element = store.get(key);
249         assertNull(element);
250     }
251 
252     /**
253      * Tests removing all the entries.
254      */
255     public void testRemoveAll() throws Exception {
256         final String value = "value";
257         final String key = "key";
258 
259         // Add the entry
260         Element element = new Element(key, value);
261         store.put(element);
262 
263         // Check the entry is there
264         element = store.get(key);
265         assertNotNull(element);
266 
267         // Remove it
268         store.removeAll();
269 
270         // Check the entry is not there
271         assertEquals(0, store.getSize());
272         element = store.get(key);
273         assertNull(element);
274     }
275 
276     /**
277      * Tests bulk load.
278      */
279     public void testBulkLoad() throws Exception {
280         final Random random = new Random();
281         StopWatch stopWatch = new StopWatch();
282 
283         // Add a bunch of entries
284         for (int i = 0; i < 500; i++) {
285             // Use a random length value
286             final String key = "key" + i;
287             final String value = "value" + random.nextInt(1000);
288 
289             // Add an element, and make sure it is present
290             Element element = new Element(key, value);
291             store.put(element);
292             element = store.get(key);
293             assertNotNull(element);
294 
295             // Remove the element
296             store.remove(key);
297             element = store.get(key);
298             assertNull(element);
299 
300             element = new Element(key, value);
301             store.put(element);
302             element = store.get(key);
303             assertNotNull(element);
304         }
305         long time = stopWatch.getElapsedTime();
306         LOG.info("Time for Bulk Load: " + time);
307     }
308 
309     /**
310      * Benchmark to test speed.
311      */
312     public void testBenchmarkPutGetRemove() throws Exception {
313         final String key = "key";
314         byte[] value = new byte[500];
315         StopWatch stopWatch = new StopWatch();
316 
317         // Add a bunch of entries
318         for (int i = 0; i < 50000; i++) {
319             Element element = new Element(key, value);
320             store.put(element);
321             store.get(key + i);
322         }
323         for (int i = 0; i < 50000; i++) {
324             store.remove(key + i);
325         }
326         long time = stopWatch.getElapsedTime();
327         LOG.info("Time for benchmarkPutGetRemove: " + time);
328         assertTrue("Too slow. Time was " + time, time < 500);
329     }
330 
331     /**
332      * Benchmark to test speed.
333      */
334     public void testBenchmarkPutGet() throws Exception {
335         final String key = "key";
336         byte[] value = new byte[500];
337         StopWatch stopWatch = new StopWatch();
338 
339         // Add a bunch of entries
340         for (int i = 0; i < 50000; i++) {
341             Element element = new Element(key, value);
342             store.put(element);
343         }
344         for (int i = 0; i < 50000; i++) {
345             store.get(key + i);
346         }
347         long time = stopWatch.getElapsedTime();
348         LOG.info("Time for benchmarkPutGet: " + time);
349         assertTrue("Too slow. Time was " + time, time < 300);
350     }
351 
352 
353     /**
354      * Benchmark to test speed.
355      * Original implementation 12seconds
356      * This implementation 9 seconds
357      */
358     public void benchmarkPutGetSuryaTest(long allowedTime) throws Exception {
359         Random random = new Random();
360         byte[] value = new byte[500];
361         StopWatch stopWatch = new StopWatch();
362 
363         // Add a bunch of entries
364         for (int i = 0; i < 50000; i++) {
365             String key = "key" + i;
366 
367             Element element = new Element(key, value);
368             store.put(element);
369 
370             //Access each element random number of times, min:0 maximum:9
371             int accesses = random.nextInt(5);
372             for (int j = 0; j <= accesses; j++) {
373                 store.get(key);
374             }
375         }
376         long time = stopWatch.getElapsedTime();
377         LOG.info("Time for benchmarkPutGetSurya: " + time);
378         assertTrue("Too slow. Time was " + time, time < allowedTime);
379     }
380 
381     /**
382      * Multi-thread read-only test.
383      */
384     public void testReadOnlyThreads() throws Exception {
385 
386         // Add a couple of elements
387         store.put(new Element("key0", "value"));
388         store.put(new Element("key1", "value"));
389 
390         // Run a set of threads, that attempt to fetch the elements
391         final List executables = new ArrayList();
392         for (int i = 0; i < 10; i++) {
393             final String key = "key" + (i % 2);
394             final MemoryStoreTester.Executable executable = new LruMemoryStoreTest.Executable() {
395                 public void execute() throws Exception {
396                     final Element element = store.get(key);
397                     assertNotNull(element);
398                     assertEquals("value", element.getObjectValue());
399                 }
400             };
401             executables.add(executable);
402         }
403         runThreads(executables);
404     }
405 
406     /**
407      * Multi-thread read-write test.
408      */
409     public void testReadWriteThreads() throws Exception {
410 
411         final String value = "value";
412         final String key = "key";
413 
414         // Add the entry
415         final Element element = new Element(key, value);
416         store.put(element);
417 
418         // Run a set of threads that get, put and remove an entry
419         final List executables = new ArrayList();
420         for (int i = 0; i < 5; i++) {
421             final MemoryStoreTester.Executable executable = new MemoryStoreTester.Executable() {
422                 public void execute() throws Exception {
423                     final Element element = store.get("key");
424                     assertNotNull(element);
425                 }
426             };
427             executables.add(executable);
428         }
429         for (int i = 0; i < 5; i++) {
430             final MemoryStoreTester.Executable executable = new MemoryStoreTester.Executable() {
431                 public void execute() throws Exception {
432                     store.put(element);
433                 }
434             };
435             executables.add(executable);
436         }
437 
438         runThreads(executables);
439     }
440 
441     /**
442      * Multi-thread read, put and removeAll test.
443      * This checks for memory leaks
444      * using the removeAll which was the known cause of memory leaks with MemoryStore in JCS
445      */
446     public void testMemoryLeak() throws Exception {
447         long differenceMemoryCache = thrashCache();
448         assertTrue(differenceMemoryCache < 500000);
449     }
450 
451 
452     /**
453      * This method tries to get the store too leak.
454      */
455     protected long thrashCache() throws Exception {
456 
457 
458         long startingSize = measureMemoryUse();
459         LOG.info("Memory Used is: " + startingSize);
460 
461         final String value = "value";
462         final String key = "key";
463 
464         // Add the entry
465         final Element element = new Element(key, value);
466         store.put(element);
467 
468         // Create 15 threads that read the keys;
469         final List executables = new ArrayList();
470         for (int i = 0; i < 15; i++) {
471             final LruMemoryStoreTest.Executable executable = new MemoryStoreTester.Executable() {
472                 public void execute() throws Exception {
473                     for (int i = 0; i < 500; i++) {
474                         final String key = "key" + i;
475                         store.get(key);
476                     }
477                     store.get("key");
478                 }
479             };
480             executables.add(executable);
481         }
482         //Create 15 threads that are insert 500 keys with large byte[] as values
483         for (int i = 0; i < 15; i++) {
484             final LruMemoryStoreTest.Executable executable = new MemoryStoreTester.Executable() {
485                 public void execute() throws Exception {
486 
487                     // Add a bunch of entries
488                     for (int i = 0; i < 500; i++) {
489                         // Use a random length value
490                         final String key = "key" + i;
491                         byte[] value = new byte[10000];
492                         Element element = new Element(key, value);
493                         store.put(element);
494                     }
495                 }
496             };
497             executables.add(executable);
498         }
499 
500         runThreads(executables);
501         store.removeAll();
502 
503         long finishingSize = measureMemoryUse();
504         LOG.info("Memory Used is: " + finishingSize);
505         return finishingSize - startingSize;
506     }
507 
508 
509     /**
510      * Multi-thread read-write test.
511      */
512     public void testReadWriteThreadsSurya() throws Exception {
513 
514         long start = System.currentTimeMillis();
515         final List executables = new ArrayList();
516         final Random random = new Random();
517 
518         // 50% of the time get data
519         for (int i = 0; i < 10; i++) {
520             final Executable executable = new Executable() {
521                 public void execute() throws Exception {
522                     store.get("key" + random.nextInt(10000));
523                 }
524             };
525             executables.add(executable);
526         }
527 
528         //25% of the time add data
529         for (int i = 0; i < 5; i++) {
530             final Executable executable = new Executable() {
531                 public void execute() throws Exception {
532                     store.put(new Element("key" + random.nextInt(20000), "value"));
533                 }
534             };
535             executables.add(executable);
536         }
537 
538         //25% if the time remove the data
539         for (int i = 0; i < 5; i++) {
540             final Executable executable = new Executable() {
541                 public void execute() throws Exception {
542                     store.remove("key" + random.nextInt(10000));
543                 }
544             };
545             executables.add(executable);
546         }
547 
548         runThreads(executables);
549         long end = System.currentTimeMillis();
550         LOG.info("Total time for the test: " + (end + start) + " ms");
551     }
552 
553 
554     /**
555      * Test behaviour of memory store using 1 million records.
556      * This is expected to run out of memory on a 64MB machine. Where it runs out
557      * is asserted so that design changes do not start using more memory per element.
558      * <p/>
559      * This test will fail (ie not get an out of memory error) on VMs configured to be server which do not have a fixed upper memory limit.
560      * <p/>
561      * Takes too long to run therefore switch off
562      * <p/>
563      * These memory size asserts were 100,000 and 60,000. The ApacheLRU map does not get quite as high numbers.
564      * This test varies according to architecture. 64 bit architectures 
565      */
566     public void testMemoryStoreOutOfMemoryLimit() throws Exception {
567         //Set size so the second element overflows to disk.
568         cache = new Cache("memoryLimitTest", 1000000, false, false, 500, 500);
569         manager.addCache(cache);
570         int i = 0;
571         try {
572             for (; i < 1000000; i++) {
573                 cache.put(new Element("" +
574                         i, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
575                         + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
576                         + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
577                         + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
578                         + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
579                         + "AAAAA " + i));
580             }
581             fail();
582         } catch (OutOfMemoryError e) {
583             if (JVMUtil.isJDK15()) {
584                 assertTrue(i > 90000);
585             } else {
586                 assertTrue(i > 50000);
587             }
588             LOG.info("Ran out of memory putting " + i + "th element");
589         }
590     }
591 
592 }