001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: * The Original Software is NetBeans. The Initial Developer of the Original
026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
027: * Microsystems, Inc. All Rights Reserved.
028: *
029: * If you wish your version of this file to be governed by only the CDDL
030: * or only the GPL Version 2, indicate your decision by adding
031: * "[Contributor] elects to include this software in this distribution
032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
033: * single choice of license, a recipient has the option to distribute
034: * your version of this file under either the CDDL, the GPL Version 2 or
035: * to extend the choice of license to its licensees as provided above.
036: * However, if you add GPL Version 2 code and therefore, elected the GPL
037: * Version 2 license, then the option applies only if the new code is
038: * made subject to such option by the copyright holder.
039: */
040:
041: package org.netbeans.lib.profiler.instrumentation;
042:
043: import org.netbeans.lib.profiler.ProfilerEngineSettings;
044: import org.netbeans.lib.profiler.classfile.DynamicClassInfo;
045: import org.netbeans.lib.profiler.global.ProfilingSessionStatus;
046: import org.netbeans.lib.profiler.utils.StringUtils;
047: import org.netbeans.lib.profiler.utils.Wildcards;
048:
049: /**
050: * Recursive method scaner that implements the total instrumentation scheme.
051: * In fact, it's not even a scaner, since it just instruments absolutely everything -
052: * but it uses the same interface.
053: *
054: * @author Tomas Hurka
055: * @author Misha Dmitriev
056: */
057: public class RecursiveMethodInstrumentor3 extends
058: RecursiveMethodInstrumentor {
059: //~ Instance fields ----------------------------------------------------------------------------------------------------------
060:
061: // Attributes to hold saved values for root classes, methods and signatures
062: private boolean noExplicitRootsSpecified = false,
063: mainMethodInstrumented = false;
064:
065: //~ Constructors -------------------------------------------------------------------------------------------------------------
066:
067: public RecursiveMethodInstrumentor3(ProfilingSessionStatus status,
068: ProfilerEngineSettings settings) {
069: super (status, settings);
070: }
071:
072: //~ Methods ------------------------------------------------------------------------------------------------------------------
073:
074: public Object[] getMethodsToInstrumentUponClassLoad(
075: String className, int classLoaderId,
076: boolean threadInCallGraph) {
077: boolean DEBUG = false;
078:
079: /* if (className.startsWith("java2d")) {
080: DEBUG = true;
081: }
082: */
083: if (DEBUG) {
084: System.out.println("*** MS2: instr. upon CL: " + className); // NOI18N
085: }
086:
087: className = className.replace('.', '/').intern(); // NOI18N
088:
089: DynamicClassInfo clazz = javaClassForName(className,
090: classLoaderId);
091:
092: if (clazz == null) {
093: return null;
094: }
095:
096: if (DEBUG) {
097: System.out.println("*** MS2: instr. upon CL 2: "
098: + clazz.getNameAndLoader()); // NOI18N
099: }
100:
101: clazz.setLoaded(true);
102: addToSubclassList(clazz, clazz); // Have to call this in advance to determine if a class implements Runnable (possibly indirectly)
103:
104: if (clazz.isInterface()) {
105: return null;
106: }
107:
108: initInstrMethodData();
109:
110: // Mark as roots all methods that the total instrumentation method views as implicit roots
111: boolean isRootClass = false;
112: int rootIdxForAll = -1;
113:
114: isRootClass = tryInstrumentSpawnedThreads(clazz); // This only checks for Runnable.run()
115:
116: if (noExplicitRootsSpecified && !mainMethodInstrumented) { // Check if this class has main method. The first loaded class with main method should be main class.
117:
118: if (tryMainMethodInstrumentation(clazz)) {
119: isRootClass = true;
120: mainMethodInstrumented = true;
121: }
122: }
123:
124: // Check to see if this class has been marked as root by the user:
125: if (!isRootClass) {
126: for (int rIdx = 0; rIdx < rootMethods.classNames.length; rIdx++) {
127: String rootClassName = rootMethods.classNames[rIdx];
128:
129: if (rootMethods.classesWildcard[rIdx]) {
130: if (matchesWildcard(Wildcards
131: .unwildPackage(rootClassName), className)) {
132: // System.out.println("Matched package wildcard - " + rootClassName);
133: isRootClass = true;
134:
135: break;
136: }
137: } else {
138: if (className.equals(rootClassName)) {
139: isRootClass = true;
140:
141: break;
142: }
143: }
144: }
145: }
146:
147: boolean normallyFilteredOut = !instrFilter
148: .passesFilter(className);
149:
150: if (!isRootClass) {
151: if (normallyFilteredOut) {
152: return null;
153: }
154: }
155:
156: // Check to see if this class has been marked as root by the user:
157: for (int rIdx = 0; rIdx < rootMethods.classNames.length; rIdx++) {
158: String rootClassName = rootMethods.classNames[rIdx];
159: boolean isMatch = false;
160:
161: if (rootMethods.classesWildcard[rIdx]) {
162: if (matchesWildcard(Wildcards
163: .unwildPackage(rootClassName), className)) {
164: // System.out.println("Matched package wildcard - " + rootClassName);
165: isMatch = true;
166: }
167: } else {
168: if (className.equals(rootClassName)) { // precise match
169: isMatch = true;
170: }
171: }
172:
173: if (isMatch) { // it is indeed a root class
174:
175: if (Wildcards.isPackageWildcard(rootClassName)
176: || Wildcards
177: .isMethodWildcard(rootMethods.methodNames[rIdx])) {
178: if (rootMethods.markerMethods[rIdx]) {
179: markAllMethodsMarker(clazz);
180: } else {
181: markAllMethodsRoot(clazz);
182: }
183: } else {
184: markMethod(clazz, rIdx);
185: checkAndMarkMethodForInstrumentation(clazz,
186: rootMethods.methodNames[rIdx],
187: rootMethods.methodSignatures[rIdx]);
188: }
189: }
190: }
191:
192: if (!normallyFilteredOut) {
193: checkAndMarkAllMethodsForInstrumentation(clazz);
194: }
195:
196: return createInstrumentedMethodPack();
197: }
198:
199: public Object[] getMethodsToInstrumentUponMethodInvocation(
200: String className, int classLoaderId, String methodName,
201: String methodSignature) {
202: return null; // This method is just not used with this flavour of MethodScanner
203: }
204:
205: public Object[] getMethodsToInstrumentUponReflectInvoke(
206: String className, int classLoaderId, String methodName,
207: String methodSignature) {
208: return null; // Doesn't have to do anything - everything is handled upon class load
209: }
210:
211: protected void findAndMarkOverridingMethodsReachable(
212: DynamicClassInfo super Class, DynamicClassInfo subClass) {
213: // Doesn't do anything (actually not used/called at all) in this scaner
214: }
215:
216: protected void processInvoke(DynamicClassInfo clazz,
217: boolean virtualCall, int index) {
218: // Doesn't do anything (not used) in this scaner
219: }
220:
221: protected boolean tryInstrumentSpawnedThreads(DynamicClassInfo clazz) {
222: // System.err.println("TryInstrumentSpawnedThreads: " + instrumentSpawnedThreads + "/" + noExplicitRootsSpecified);
223: if (instrumentSpawnedThreads || noExplicitRootsSpecified) {
224: if (clazz.implements Interface("java/lang/Runnable")
225: && (clazz.getName() != "java/lang/Thread")) { // NOI18N
226:
227: boolean res = markMethodRoot(clazz, "run", "()V"); // NOI18N
228: checkAndMarkMethodForInstrumentation(clazz, "run",
229: "()V"); // NOI18N
230:
231: return res;
232: }
233: }
234:
235: return false;
236: }
237:
238: protected boolean tryMainMethodInstrumentation(
239: DynamicClassInfo clazz) {
240: int idx = clazz
241: .getMethodIndex("main", "([Ljava/lang/String;)V"); // NOI18N
242:
243: if (idx == -1) {
244: return false;
245: }
246:
247: if (!(clazz.isMethodStatic(idx) && clazz.isMethodPublic(idx))) {
248: return false;
249: }
250:
251: markMethodRoot(clazz, "main", "([Ljava/lang/String;)V"); // NOI18N
252: checkAndMarkMethodForInstrumentation(clazz, idx);
253:
254: return true;
255: }
256:
257: Object[] getInitialMethodsToInstrument(String[] loadedClasses,
258: int[] loadedClassLoaderIds, byte[][] cachedClassFileBytes,
259: RootMethods roots) {
260: DynamicClassInfo[] loadedClassInfos = preGetInitialMethodsToInstrument(
261: loadedClasses, loadedClassLoaderIds,
262: cachedClassFileBytes);
263:
264: rootMethods = roots;
265: checkForNoRootsSpecified(roots);
266:
267: // Check which root classes have already been loaded, and mark their root methods accordingly
268: for (int j = 0; j < loadedClassInfos.length; j++) {
269: if (loadedClassInfos[j] == null) {
270: continue; // Can this happen?
271: }
272:
273: tryInstrumentSpawnedThreads(loadedClassInfos[j]); // This only checks for Runnable.run()
274:
275: for (int rIdx = 0; rIdx < rootMethods.classNames.length; rIdx++) {
276: String rootClassName = rootMethods.classNames[rIdx];
277: boolean isMatch = false;
278:
279: if (rootMethods.classesWildcard[rIdx]) {
280: if (matchesWildcard(Wildcards
281: .unwildPackage(rootClassName),
282: loadedClassInfos[j].getName())) {
283: // System.out.println("Matched package wildcard - " + rootClassName);
284: isMatch = true;
285: }
286: } else {
287: if (loadedClassInfos[j].getName().equals(
288: rootClassName)) { // precise match
289: isMatch = true;
290: }
291: }
292:
293: if (isMatch) {
294: if (Wildcards.isPackageWildcard(rootClassName)
295: || Wildcards
296: .isMethodWildcard(rootMethods.methodNames[rIdx])) {
297: if (rootMethods.markerMethods[rIdx]) {
298: markAllMethodsMarker(loadedClassInfos[j]);
299: } else {
300: markAllMethodsRoot(loadedClassInfos[j]);
301: }
302: } else {
303: markMethod(loadedClassInfos[j], rIdx);
304: checkAndMarkMethodForInstrumentation(
305: loadedClassInfos[j],
306: rootMethods.methodNames[rIdx],
307: rootMethods.methodSignatures[rIdx]);
308: }
309: }
310: }
311:
312: checkAndMarkAllMethodsForInstrumentation(loadedClassInfos[j]);
313: }
314:
315: // So that class loading is measured correctly from the beginning
316: checkAndMarkMethodForInstrumentation(javaClassForName(
317: "java/lang/ClassLoader", 0), "loadClass",
318: "(Ljava/lang/String;)Ljava/lang/Class;"); // NOI18N
319:
320: return createInstrumentedMethodPack();
321: }
322:
323: private void checkAndMarkAllMethodsForInstrumentation(
324: DynamicClassInfo clazz) {
325: if (clazz.isInterface()) {
326: return;
327: }
328:
329: String[] methods = clazz.getMethodNames();
330:
331: for (int i = 0; i < methods.length; i++) {
332: checkAndMarkMethodForInstrumentation(clazz, i);
333: }
334: }
335:
336: /** Mark the given method reachable, if there are no barriers for that (like native, empty, etc. method) */
337: private void checkAndMarkMethodForInstrumentation(
338: DynamicClassInfo clazz, String methodName,
339: String methodSignature) {
340: if (clazz == null) {
341: return;
342: }
343:
344: int idx = clazz.getMethodIndex(methodName, methodSignature);
345:
346: if (idx == -1) {
347: return;
348: }
349:
350: checkAndMarkMethodForInstrumentation(clazz, idx);
351: }
352:
353: private void checkAndMarkMethodForInstrumentation(
354: DynamicClassInfo clazz, int idx) {
355: String className = clazz.getName();
356:
357: if (!clazz.isMethodReachable(idx)) {
358: clazz.setMethodReachable(idx);
359:
360: if (clazz.isMethodNative(idx)
361: || clazz.isMethodAbstract(idx)
362: || (!clazz.isMethodRoot(idx)
363: && !clazz.isMethodMarker(idx) && !instrFilter
364: .passesFilter(className))
365: || (className == "java/lang/Object") // NOI18N // Actually, just the Object.<init> method?
366: ) {
367: clazz.setMethodUnscannable(idx);
368: } else {
369: byte[] bytecode = clazz.getMethodBytecode(idx);
370:
371: if ((dontInstrumentEmptyMethods && isEmptyMethod(bytecode))
372: || (dontScanGetterSetterMethods && isGetterSetterMethod(bytecode))) {
373: clazz.setMethodUnscannable(idx);
374: } else {
375: clazz.setMethodLeaf(idx);
376: }
377: }
378:
379: // Class is loaded, method is reachable and not unscannable are sufficient conditions for instrumenting method
380: if (!clazz.isMethodUnscannable(idx)) {
381: markClassAndMethodForInstrumentation(clazz, idx);
382: }
383: }
384: }
385:
386: private void checkForNoRootsSpecified(RootMethods roots) {
387: // System.err.println("Checking for no roots specified");
388: // It may happen, for example when directly attaching to a remote application and choosing the Entire App CPU
389: // profiling, that there are no explicitly specified root methods (because the main method is not known in advance).
390: // To get sensible profiling results, we take special measures, by just guessing what the main class is.
391: noExplicitRootsSpecified = true;
392:
393: if ((roots != null) && (roots.classNames.length != 0)) {
394: int rootCount = roots.markerMethods.length;
395:
396: if (rootCount > 0) {
397: for (int i = 0; i < rootCount; i++) {
398: if (!roots.markerMethods[i]) {
399: noExplicitRootsSpecified = false;
400:
401: break;
402: }
403: }
404: }
405: }
406: // System.err.println("NoRootsSpecified = " + noExplicitRootsSpecified);
407: }
408:
409: //----------------------------------- Private implementation ------------------------------------------------
410: private DynamicClassInfo[] preGetInitialMethodsToInstrument(
411: String[] loadedClasses, int[] loadedClassLoaderIds,
412: byte[][] cachedClassFileBytes) {
413: //System.out.println("*** MS2: instr. initial");
414: reflectInvokeInstrumented = true; // We don't need to instrument reflection specially in this mode
415:
416: resetLoadedClassData();
417: storeClassFileBytesForCustomLoaderClasses(loadedClasses,
418: loadedClassLoaderIds, cachedClassFileBytes);
419: initInstrMethodData();
420:
421: DynamicClassInfo[] loadedClassInfos = null;
422: loadedClassInfos = new DynamicClassInfo[loadedClasses.length]; //EJB Work: removed the condition in the above line as we need to return the temp array anyway (used to check for multiple roots, see the overloaded getInitialMethodsToInstrument )
423:
424: for (int i = 0; i < loadedClasses.length; i++) {
425: String className = loadedClasses[i].replace('.', '/')
426: .intern(); // NOI18N
427: DynamicClassInfo clazz = javaClassForName(className,
428: loadedClassLoaderIds[i]);
429:
430: if (clazz == null) {
431: // warning already issued in ClassRepository.lookupClass method, no need to do it again
432: continue;
433: }
434:
435: clazz.setLoaded(true);
436: addToSubclassList(clazz, clazz); // Needed basically only for methods like implementsInterface() to work correctly
437: loadedClassInfos[i] = clazz;
438: }
439:
440: return loadedClassInfos;
441: }
442: }
|