001: package com.protomatter.util;
002:
003: /**
004: * {{{ The Protomatter Software License, Version 1.0
005: * derived from The Apache Software License, Version 1.1
006: *
007: * Copyright (c) 1998-2002 Nate Sammons. All rights reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution,
022: * if any, must include the following acknowledgment:
023: * "This product includes software developed for the
024: * Protomatter Software Project
025: * (http://protomatter.sourceforge.net/)."
026: * Alternately, this acknowledgment may appear in the software itself,
027: * if and wherever such third-party acknowledgments normally appear.
028: *
029: * 4. The names "Protomatter" and "Protomatter Software Project" must
030: * not be used to endorse or promote products derived from this
031: * software without prior written permission. For written
032: * permission, please contact support@protomatter.com.
033: *
034: * 5. Products derived from this software may not be called "Protomatter",
035: * nor may "Protomatter" appear in their name, without prior written
036: * permission of the Protomatter Software Project
037: * (support@protomatter.com).
038: *
039: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
040: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
041: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
042: * DISCLAIMED. IN NO EVENT SHALL THE PROTOMATTER SOFTWARE PROJECT OR
043: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
044: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
045: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
046: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
047: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
048: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
049: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
050: * SUCH DAMAGE. }}}
051: */
052:
053: import java.io.*;
054: import java.text.DecimalFormat;
055:
056: /**
057: * A utility class for parsing stack traces.<P>
058: *
059: * Determining the stack information at runtime
060: * is a relatively expensive operation. I've tested this
061: * on a 650MHz PIII Coppermine Sony Vaio
062: * laptop running RedHat Linux 7.2, kernel 2.4.9,
063: * I saw these results with single-threaded tests:<P>
064: *
065: * <table border=1 cellpadding=4 cellspacing=0>
066: *
067: * <tr>
068: * <td>Classic VM (build JDK-1.2.2_012, green threads, nojit)</td>
069: * <td>Average 0.73543ms</td>
070: * </tr>
071: *
072: * <tr>
073: * <td>Classic VM (build 1.3.1, J2RE 1.3.1 IBM build cxia32131-20020410 (JIT enabled: jitc))</td>
074: * <td>Average 0.31817ms</td>
075: * </tr>
076: *
077: * <tr>
078: * <td>Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1_03-b03)</td>
079: * <td>Average 0.18201ms</td>
080: * </tr>
081: *
082: * <tr>
083: * <td>Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)</td>
084: * <td>Average 0.1024ms</td>
085: * </tr>
086: *
087: * <tr>
088: * <td>JRockit Virtual Machine (build 3.1.4-dax.appeal.se-20020319-1000)<BR>
089: * Native Threads, Generational Concurrent Garbage Collector</td>
090: * <td valign=top>Average 0.0486ms</td>
091: * </tr>
092: *
093: * </table><P>
094: *
095: * If possible, this class loads an implementation
096: * of itself that uses new APIs available in JDK 1.4
097: * to improve performance. Under JDK 1.2 and 1.3,
098: * this class parses a stack trace to determine
099: * call stack information.<P>
100: *
101: * Also, under the IBM and JRockit JVMs, line numbers
102: * are usually not available. They may or may
103: * not be availble under other JVMs because the JIT
104: * may or may not strip that information from
105: * stack traces.<P>
106: *
107: * Sustained, rapid creation of <tt>java.lang.Throwable</tt>
108: * objects under the IBM JDK repeatedly caused JVM deadlocks.
109: * This should not be an issue in actual systems, but
110: * it is concerning.
111: */
112: public class StackTraceUtil {
113: private static StackTraceUtil instance = null;
114:
115: static {
116: // try and load the JDK 1.4 version of ourselves
117: // and fail back on the "normal" version.
118: // The JDK 1.4 version is much faster.
119: try {
120: instance = (StackTraceUtil) Class.forName(
121: "com.protomatter.util.JDK14StackTraceUtil")
122: .newInstance();
123: instance.getInfo(0);
124: } catch (Throwable t) {
125: instance = new StackTraceUtil();
126: }
127: }
128:
129: /**
130: * Protected constructor so nobody instantiates this class.
131: */
132: protected StackTraceUtil() {
133: }
134:
135: private static String LINE_SEP = System
136: .getProperty("line.separator");
137:
138: private static char SPACE = ' ';
139: private static char DOT = '.';
140: private static char COLON = ':';
141: private static char OPEN_P = '(';
142: private static char CLOSE_P = ')';
143:
144: /**
145: * Determine what class and method you are in.
146: */
147: public static StackTraceInfo whereAmI() {
148: return instance.getInfo(1);
149: }
150:
151: /**
152: * Determine what class and method you are in.
153: * The offset is how many levels above where
154: * this method is called.
155: */
156: public static StackTraceInfo whereAmI(int stackOffset) {
157: return instance.getInfo(++stackOffset);
158: }
159:
160: protected StackTraceInfo getInfo(int stackOffset) {
161: Throwable t = new Throwable();
162: StringWriter sw = new StringWriter(256);
163: PrintWriter pw = new PrintWriter(sw);
164: t.printStackTrace(pw);
165: String stack = sw.toString();
166:
167: stackOffset++;
168:
169: int start;
170: int end;
171: int dot;
172:
173: String className = null;
174: String methodName = null;
175: int lineNumber = StackTraceInfo.LINE_NUMBER_UNKNOWN;
176:
177: try {
178: start = stack.indexOf(LINE_SEP) + 1;
179: for (dot = 0; dot < stackOffset; dot++) {
180: start = stack.indexOf(LINE_SEP, ++start);
181: }
182: start = stack.indexOf(SPACE, start);
183: end = stack.indexOf(OPEN_P, start);
184: dot = stack.lastIndexOf(DOT, end);
185:
186: className = stack.substring(++start, dot);
187: methodName = stack.substring(++dot, end);
188:
189: // see if we can get the line number
190: dot = stack.indexOf(COLON, end);
191: if (dot > 0) {
192: end = stack.indexOf(CLOSE_P, dot);
193: lineNumber = Integer.parseInt(stack.substring(++dot,
194: end));
195: }
196: } catch (Exception x) {
197: ; // just return as much as is ready
198: }
199:
200: return new StackTraceInfo(className, methodName, lineNumber);
201: }
202:
203: private static class TestThread extends Thread {
204: private int numRuns = 0;
205: private long time = 0;
206: private StackTraceInfo info = null;
207:
208: public TestThread(int runs) {
209: super ();
210: this .numRuns = runs;
211: }
212:
213: public void run() {
214: time = System.currentTimeMillis();
215: for (int i = 0; i < numRuns; i++) {
216: info = StackTraceUtil.whereAmI();
217: }
218: time = System.currentTimeMillis() - time;
219: }
220:
221: public void info() {
222: DecimalFormat format = new DecimalFormat("####.######");
223: DecimalFormat tf = new DecimalFormat("###,###,##0");
224: System.out.println(" " + tf.format(numRuns) + " runs in "
225: + tf.format(time) + "ms");
226: double average = ((double) time / (double) numRuns);
227: System.out.println(" Average is "
228: + format.format(average) + "ms");
229: System.out.println(" Stack trace info = " + info);
230: System.out.println("");
231: }
232: }
233:
234: /**
235: * Performance test rig. Optional command-line
236: * arguments are the number of threads (default is 5),
237: * and number of calls per thread (default is 10,000).
238: */
239: public static void main(String args[]) {
240: try {
241: int numThreads = 5;
242: int numTries = 10000;
243:
244: if (args.length == 2) {
245: try {
246: numThreads = Integer.parseInt(args[0]);
247: numTries = Integer.parseInt(args[1]);
248: } catch (NumberFormatException x) {
249: System.out
250: .println("Usage: java com.protomatter.util.StackTraceUtil num-threads num-calls");
251: System.exit(0);
252: }
253: } else if ((args.length == 1) || (args.length > 2)) {
254: System.out
255: .println("Usage: java com.protomatter.util.StackTraceUtil num-threads num-calls");
256: System.exit(0);
257: }
258:
259: DecimalFormat tf = new DecimalFormat("###,###,##0");
260: System.out.println("StackTraceUtil.whereAmI() test:");
261: System.out.println("");
262: System.out.println("JVM Information:");
263: System.out.println(" VM Name: "
264: + System.getProperty("java.vm.name"));
265: System.out.println(" VM Version: "
266: + System.getProperty("java.vm.version"));
267: System.out.println(" Runtime name: "
268: + System.getProperty("java.runtime.name"));
269: System.out.println(" Runtime version: "
270: + System.getProperty("java.runtime.version"));
271: System.out.println("");
272: System.out.println("OS Information:");
273: System.out.println(" " + System.getProperty("os.name")
274: + " " + System.getProperty("os.version"));
275: System.out.println("");
276: System.out.println("Creating " + numThreads
277: + " test threads (" + tf.format(numTries)
278: + " calls each)...");
279: TestThread threads[] = new TestThread[numThreads];
280: for (int i = 0; i < numThreads; i++) {
281: threads[i] = new TestThread(numTries);
282: }
283:
284: System.out.println("Running test threads...");
285: long time = System.currentTimeMillis();
286: for (int i = 0; i < numThreads; i++) {
287: threads[i].start();
288: }
289: for (int i = 0; i < numThreads; i++) {
290: threads[i].join();
291: }
292: time = System.currentTimeMillis() - time;
293:
294: System.out.println("Per-thread results:");
295: for (int i = 0; i < numThreads; i++) {
296: threads[i].info();
297: }
298:
299: System.out.println("");
300: System.out.println("Overall:");
301: DecimalFormat format = new DecimalFormat("####.######");
302: System.out.println(" "
303: + tf.format((numThreads * numTries)) + " runs in "
304: + tf.format(time) + "ms");
305: double average = ((double) time / (double) (numThreads * numTries));
306: System.out.println(" Average is "
307: + format.format(average) + "ms");
308: System.out.println("");
309: } catch (Exception x) {
310: x.printStackTrace();
311: }
312: }
313: }
|