001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tctest;
006:
007: import com.tc.object.config.ConfigVisitor;
008: import com.tc.object.config.DSOClientConfigHelper;
009: import com.tc.object.config.TransparencyClassSpec;
010: import com.tc.simulator.app.ApplicationConfig;
011: import com.tc.simulator.listener.ListenerProvider;
012: import com.tc.util.Assert;
013: import com.tc.util.DebugUtil;
014: import com.tctest.restart.system.ObjectDataTestApp;
015: import com.tctest.runner.AbstractErrorCatchingTransparentApp;
016:
017: import java.security.SecureRandom;
018: import java.text.DateFormat;
019: import java.text.SimpleDateFormat;
020: import java.util.Date;
021: import java.util.HashMap;
022: import java.util.Map;
023: import java.util.Random;
024: import java.util.Vector;
025:
026: /**
027: * All this test does is create many many objects so that if the Client side Memory Reaper doesnt run properly for some
028: * reason then it forces an OOME This test needs some tuning
029: */
030: public class ClientMemoryReaperTestApp extends
031: AbstractErrorCatchingTransparentApp {
032: public static final String SYNCHRONOUS_WRITE = "synch-write";
033:
034: private static final long OBJECT_COUNT = 2000;
035: private static final long MINIMUM_MEM_NEEDED = 60 * 1024 * 1024;
036: private static final int MEMORY_BLOCKS = 1024 * 1024;
037:
038: final Map root = new HashMap();
039: final Vector unusedBytes = new Vector();
040: static long maxDepth = 1;
041: static int transient_mem_blocks_size = 0;
042:
043: public ClientMemoryReaperTestApp(String appId,
044: ApplicationConfig cfg, ListenerProvider listenerProvider) {
045: super (appId, cfg, listenerProvider);
046: }
047:
048: public static void visitL1DSOConfig(ConfigVisitor visitor,
049: DSOClientConfigHelper config) {
050: visitL1DSOConfig(visitor, config, new HashMap());
051: }
052:
053: public static void visitL1DSOConfig(ConfigVisitor visitor,
054: DSOClientConfigHelper config, Map optionalAttributes) {
055: DebugUtil.DEBUG = true;
056:
057: boolean isSynchronousWrite = false;
058: if (optionalAttributes.size() > 0) {
059: isSynchronousWrite = Boolean.valueOf(
060: (String) optionalAttributes
061: .get(ObjectDataTestApp.SYNCHRONOUS_WRITE))
062: .booleanValue();
063: }
064:
065: String testClass = ClientMemoryReaperTestApp.class.getName();
066: TransparencyClassSpec spec = config.getOrCreateSpec(testClass);
067: spec.addRoot("root", "root");
068: String methodExpression = "* " + testClass + ".getNode(..)";
069: config.addReadAutolock(methodExpression);
070: methodExpression = "* " + testClass + ".putNode(..)";
071: addWriteAutolock(config, isSynchronousWrite, methodExpression);
072: methodExpression = "* " + testClass + ".addNode(..)";
073: addWriteAutolock(config, isSynchronousWrite, methodExpression);
074:
075: testClass = ClientMemoryReaperTestApp.Node.class.getName();
076: spec = config.getOrCreateSpec(testClass);
077: config.addTransient(testClass, "transientBytes");
078:
079: DebugUtil.DEBUG = false;
080: }
081:
082: private static void addWriteAutolock(DSOClientConfigHelper config,
083: boolean isSynchronousWrite, String methodPattern) {
084: if (isSynchronousWrite) {
085: config.addSynchronousWriteAutolock(methodPattern);
086: debugPrintln("***** doing a synchronous write");
087: } else {
088: config.addWriteAutolock(methodPattern);
089: }
090: }
091:
092: private static void debugPrintln(String s) {
093: if (DebugUtil.DEBUG) {
094: System.err.println(s);
095: }
096: }
097:
098: public void runTest() {
099: log("App Id = " + getApplicationId()
100: + " participation count = " + getParticipantCount()
101: + " intensity = " + getIntensity());
102:
103: // This is not used as the same effect is attained by using DSO transient
104: if (false)
105: initHeap();
106: initTransientMemBlockSize();
107:
108: long objectCount = 0;
109: log("Objects to Create = " + OBJECT_COUNT);
110: final SecureRandom sr = new SecureRandom();
111: long seed = sr.nextLong();
112: log(" Seed for Random = " + seed);
113: Random r = new Random(seed);
114:
115: int topLevelObjectCount = (int) (OBJECT_COUNT / 50);
116: while (objectCount++ <= OBJECT_COUNT) {
117: Object myKey = new Integer(r.nextInt(topLevelObjectCount));
118: Node n = getNode(myKey);
119: if (n == null) {
120: putNode(myKey, new Node(objectCount));
121: } else {
122: addNode(n, new Node(objectCount));
123: }
124: if (objectCount % 100 == 0) {
125: log("Objects created = " + objectCount);
126: }
127: }
128: log("Done !!");
129: }
130:
131: private static synchronized void initTransientMemBlockSize() {
132: if (transient_mem_blocks_size > 0) {
133: log("Transient memory block size is already initialized to "
134: + transient_mem_blocks_size);
135: return;
136: }
137: Runtime runtime = Runtime.getRuntime();
138: long max_memory = runtime.maxMemory();
139: if (max_memory == Long.MAX_VALUE) {
140: // With no upperbound it is possible that this test wont fail even when client memory reaper is broken.
141: throw new AssertionError(
142: "This test is memory sensitive. Please specify the max memory using -Xmx option. "
143: + "Currently Max Memory is " + max_memory);
144: }
145: log("Max memory is " + max_memory);
146: transient_mem_blocks_size = (int) ((max_memory * 50) / (512 * 1024)); // 50KB for 512MB, so for max_memory ?
147: log("Transient memory block size is "
148: + transient_mem_blocks_size);
149: }
150:
151: private void initHeap() {
152: Runtime runtime = Runtime.getRuntime();
153: long max_memory = runtime.maxMemory();
154: if (max_memory == Long.MAX_VALUE
155: || max_memory < MINIMUM_MEM_NEEDED) {
156: // With no upperbound it is possible that this test wont fail even when client memory reaper is broken.
157: throw new AssertionError(
158: "This test is memory sensitive. Please specify the max memory using -Xmx option. "
159: + " Ideal value for this test is >= "
160: + MINIMUM_MEM_NEEDED
161: + ". Currently Max Memory is " + max_memory);
162: }
163: log("Max memory is " + max_memory);
164: long totalAllocations = 0;
165: // This is not fail proof, but worst case is a few extra allocations, (no of nodes * 1 MB)
166: synchronized (unusedBytes) {
167: while (max_memory > (runtime.totalMemory() + MINIMUM_MEM_NEEDED / 2)
168: || runtime.freeMemory() > MINIMUM_MEM_NEEDED) {
169: final byte[] unused = new byte[MEMORY_BLOCKS];
170: unusedBytes.add(unused);
171: totalAllocations += MEMORY_BLOCKS;
172: log("Allocated " + unused.length
173: + " bytes. Free memory = "
174: + runtime.freeMemory() + ". Total memory = "
175: + runtime.totalMemory());
176: }
177: }
178: log("Total bytes allocated = " + totalAllocations);
179: }
180:
181: static DateFormat formatter = new SimpleDateFormat("hh:mm:ss,S");
182:
183: private static void log(String message) {
184: System.err.println(Thread.currentThread().getName()
185: + " :: "
186: + formatter
187: .format(new Date(System.currentTimeMillis()))
188: + " : " + message);
189: }
190:
191: private void addNode(Node rootNode, Node node) {
192: synchronized (rootNode) {
193: rootNode.add(node);
194: // System.err.println("Added " + node + " to " + rootNode);
195: }
196: }
197:
198: private synchronized static void setMaxDepth(long depth) {
199: if (maxDepth < depth) {
200: maxDepth = depth;
201: if (maxDepth % 10 == 0) {
202: log("Max Depth reached : " + maxDepth);
203: }
204: }
205: }
206:
207: private void putNode(Object myKey, Node n) {
208: synchronized (root) {
209: root.put(myKey, n);
210: }
211: }
212:
213: private Node getNode(Object myKey) {
214: synchronized (root) {
215: return (Node) root.get(myKey);
216: }
217: }
218:
219: private static final class Node {
220: final long id; // Not necessarily unique as each node might create
221: // with
222: // the same id.
223: long lastAccess;
224: long level;
225:
226: /* Just to make the object big */
227: byte[] transientBytes = new byte[transient_mem_blocks_size];
228:
229: Node odd;
230: Node even;
231:
232: Node(long id) {
233: this .id = id;
234: this .level = 0;
235: this .lastAccess = System.currentTimeMillis();
236: }
237:
238: void add(Node c) {
239: this .lastAccess = System.currentTimeMillis();
240: if (this .transientBytes == null) {
241: // TODO:: Comeback :: If it is toooo deep, this can take quite some memory
242: this .transientBytes = new byte[transient_mem_blocks_size];
243: }
244: Assert.assertFalse(this == c);
245: if (c.id % 2 == 1) {
246: if (odd == null) {
247: odd = c;
248: c.level = this .level + 1;
249: setMaxDepth(c.level);
250: } else {
251: odd.add(c);
252: }
253: } else {
254: if (even == null) {
255: even = c;
256: c.level = this .level + 1;
257: setMaxDepth(c.level);
258: } else {
259: even.add(c);
260: }
261: }
262: }
263:
264: public String toString() {
265: return "Node(" + id + ") : level = " + level + " : odd = "
266: + (odd == null) + " : even = " + (even == null);
267: }
268:
269: }
270:
271: }
|