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.BaseClassInfo;
045: import org.netbeans.lib.profiler.classfile.ClassLoaderTable;
046: import org.netbeans.lib.profiler.classfile.ClassRepository;
047: import org.netbeans.lib.profiler.classfile.DynamicClassInfo;
048: import org.netbeans.lib.profiler.client.ClientUtils.SourceCodeSelection;
049: import org.netbeans.lib.profiler.client.RuntimeProfilingPoint;
050: import org.netbeans.lib.profiler.global.CommonConstants;
051: import org.netbeans.lib.profiler.global.ProfilingSessionStatus;
052: import org.netbeans.lib.profiler.utils.MiscUtils;
053: import org.netbeans.lib.profiler.wireprotocol.*;
054: import java.io.IOException;
055: import java.util.ArrayList;
056: import java.util.List;
057:
058: /**
059: * A high-level interface to all method instrumentation operations.
060: * <p/>
061: * Instrumentor subclasses find methods/classes to be instrumented.
062: *
063: * @author Tomas Hurka
064: * @author Misha Dmitriev
065: * @author Adrian Mos
066: * @author Ian Formanek
067: */
068: public class Instrumentor implements CommonConstants {
069: //~ Static fields/initializers -----------------------------------------------------------------------------------------------
070:
071: // TODO [release]: change value to TRUE to remove the print code below entirely by compiler
072: private static final boolean DEBUG = System
073: .getProperty("org.netbeans.lib.profiler.instrumentation.Instrumentor") != null; // NOI18N
074:
075: //~ Instance fields ----------------------------------------------------------------------------------------------------------
076:
077: private CodeRegionMethodInstrumentor crms;
078: private MemoryProfMethodInstrumentor oms;
079: private ProfilerEngineSettings settings;
080: private ProfilingSessionStatus status;
081: private RecursiveMethodInstrumentor ms;
082: private RootMethods rootMethods;
083:
084: // Data for the case of code region instrumentation
085: private SourceCodeSelection savedSourceCodeSelection;
086:
087: //~ Constructors -------------------------------------------------------------------------------------------------------------
088:
089: /**
090: * Creates a new instance of Instrumentor. A single instance is created by ProfileClient and reused on subsequent
091: * profiling sessions.
092: *
093: * @param status ProfilingSessionStatus used for profiling
094: * @param settings Engine settings - same instance is reused for all profiling sessions, the settings are modified
095: * each time before the session is started.
096: */
097: public Instrumentor(ProfilingSessionStatus status,
098: ProfilerEngineSettings settings) {
099: this .status = status;
100: this .settings = settings;
101: }
102:
103: //~ Methods ------------------------------------------------------------------------------------------------------------------
104:
105: public int getClassId(String className, int classLoaderId) {
106: BaseClassInfo clazz;
107:
108: if (className.charAt(0) == '[') { // array , need special lookup
109: // strip L and ; from className, see ClassFileParser.classNameAtCPIndex
110:
111: if (className.endsWith(";")) {
112: int elIndex = className.indexOf('L');
113: className = new StringBuffer(className).deleteCharAt(
114: className.length() - 1).deleteCharAt(elIndex)
115: .toString();
116: }
117:
118: clazz = ClassRepository.lookupSpecialClass(className);
119: } else {
120: clazz = ClassRepository.lookupClassOrCreatePlaceholder(
121: className, classLoaderId);
122: }
123:
124: if (clazz == null) {
125: System.err
126: .println("Warning: could not find class "
127: + className
128: + " loaded by the VM on the class path");
129:
130: // warning already issued in ClassRepository.lookupClass method, no need to do it again
131: return -1;
132: }
133:
134: return clazz.getInstrClassId();
135: }
136:
137: public synchronized InstrumentMethodGroupCommand getCommandToUnprofileClasses(
138: boolean[] unprofiledClassStatusArray) {
139: ObjLivenessMethodInstrumentor olms = (ObjLivenessMethodInstrumentor) oms;
140: Object[] ret = olms
141: .getMethodsToInstrumentUponClassUnprofiling(unprofiledClassStatusArray);
142:
143: if (ret == null) {
144: return new InstrumentMethodGroupCommand(null);
145: } else {
146: return new InstrumentMethodGroupCommand(
147: INSTR_OBJECT_LIVENESS, (String[]) ret[0],
148: (int[]) ret[1], (byte[][]) ret[2], null, oms
149: .getNInstantiatableClasses());
150: }
151: }
152:
153: // --------------------------------------- Public interface ----------------------------------------------------------
154: public String[] getRootClassNames() {
155: List /*<String>*/rootClassNames = rootMethods
156: .getRootClassNames();
157: RuntimeProfilingPoint[] pps = settings
158: .getRuntimeProfilingPoints();
159:
160: if ((rootClassNames == null) && (pps.length > 0)) {
161: rootClassNames = new ArrayList();
162: }
163:
164: for (int i = 0; i < pps.length; i++) {
165: RuntimeProfilingPoint pp = pps[i];
166: String className = pp.getClassName();
167:
168: if (!rootClassNames.contains(className)) {
169: rootClassNames.add(className);
170: }
171: }
172:
173: if (rootClassNames == null) {
174: return null;
175: }
176:
177: return (String[]) rootClassNames
178: .toArray(new String[rootClassNames.size()]);
179: }
180:
181: public void setSavedSourceCodeSelection(SourceCodeSelection[] s) {
182: savedSourceCodeSelection = s[0];
183: }
184:
185: public void setStatusInfoFromSourceCodeSelection(
186: SourceCodeSelection[] s) throws ClassNotFoundException,
187: BadLocationException, IOException, ClassFormatError {
188: ClassRepository.CodeRegionBCI location = null;
189:
190: if (s.length > 0) {
191: SourceCodeSelection sel = s[0];
192:
193: if (sel.definedViaSourceLines()) {
194: status.instrStartLine = sel.getStartLine();
195: status.instrEndLine = sel.getEndLine();
196: }
197: }
198:
199: rootMethods = new RootMethods(s);
200: }
201:
202: public synchronized InstrumentMethodGroupCommand createClearAllInstrumentationCommand() {
203: Object[] ret = null;
204:
205: switch (status.currentInstrType) {
206: case INSTR_RECURSIVE_FULL:
207: case INSTR_RECURSIVE_SAMPLED:
208: case INSTR_OBJECT_ALLOCATIONS:
209: case INSTR_OBJECT_LIVENESS:
210: ms = null; // Free some memory
211: ret = (new MiscInstrumentationOps(status))
212: .getOrigCodeForAllInstrumentedMethods();
213:
214: break;
215: case INSTR_CODE_REGION:
216: ret = (new MiscInstrumentationOps(status))
217: .getOrigCodeForSingleInstrumentedMethod(rootMethods);
218:
219: break;
220: }
221:
222: ms = null;
223: oms = null;
224: crms = null; // Free some memory
225:
226: if (ret == null) {
227: return new InstrumentMethodGroupCommand(null);
228: } else {
229: return new InstrumentMethodGroupCommand(INSTR_NONE,
230: (String[]) ret[0], (int[]) ret[1],
231: (byte[][]) ret[2], null, 0);
232: }
233: }
234:
235: public synchronized InstrumentMethodGroupResponse createFollowUpInstrumentMethodGroupResponse(
236: Command cmd) {
237: if (cmd instanceof ClassLoadedCommand) {
238: ClassLoadedCommand clcmd = (ClassLoadedCommand) cmd;
239: int[] this AndParentLoaderData = clcmd
240: .getThisAndParentLoaderData();
241:
242: if (DEBUG) {
243: System.err
244: .println("Instrumentor.DEBUG: Class loaded command: "
245: + cmd.toString()); // NOI18N
246: }
247:
248: byte[] classFileBytes = clcmd.getClassFileBytes();
249:
250: if (classFileBytes != null) {
251: ClassRepository.addVMSuppliedClassFile(clcmd
252: .getClassName(), this AndParentLoaderData[0],
253: classFileBytes);
254: }
255:
256: ClassLoaderTable.addChildAndParent(this AndParentLoaderData);
257: } else if (cmd instanceof MethodLoadedCommand) {
258: MethodLoadedCommand mcmd = (MethodLoadedCommand) cmd;
259:
260: if (DEBUG) {
261: System.err
262: .println("Instrumentor.DEBUG: Method loaded command: "
263: + mcmd.toString()); // NOI18N
264: }
265: }
266:
267: InstrumentMethodGroupResponse imgr = null;
268:
269: switch (status.currentInstrType) {
270: case INSTR_RECURSIVE_FULL:
271: case INSTR_RECURSIVE_SAMPLED:
272: imgr = createFollowUpInstrumentMethodGroupResponseForCallGraph(cmd);
273:
274: break;
275: case INSTR_CODE_REGION: // Follow-up can happen only if the same class is loaded with a different loader
276: // Just in case this is say MethodInvokedFirstTimeCommand generated from the previously
277: // active CPU instrumentation
278:
279: if (!(cmd instanceof ClassLoadedCommand)) {
280: return new InstrumentMethodGroupResponse(null);
281: }
282:
283: imgr = createFollowUpInstrumentMethodGroupResponseForCodeRegion((ClassLoadedCommand) cmd);
284:
285: break;
286: case INSTR_OBJECT_ALLOCATIONS:
287: case INSTR_OBJECT_LIVENESS:
288:
289: // Just in case this is say MethodInvokedFirstTimeCommand generated from the previously
290: // active CPU instrumentation
291: if (!(cmd instanceof ClassLoadedCommand)) {
292: return new InstrumentMethodGroupResponse(null);
293: }
294:
295: imgr = createFollowUpInstrumentMethodGroupResponseForMemoryProfiling((ClassLoadedCommand) cmd);
296:
297: break;
298: default:
299: imgr = new InstrumentMethodGroupResponse(null);
300: }
301:
302: return imgr;
303: }
304:
305: public synchronized InstrumentMethodGroupResponse createInitialInstrumentMethodGroupResponse(
306: RootClassLoadedCommand cmd) throws ClassNotFoundException,
307: BadLocationException {
308: ClassLoaderTable.initTable(cmd.getParentLoaderIds());
309:
310: InstrumentMethodGroupResponse imgr = null;
311:
312: switch (status.currentInstrType) {
313: case INSTR_RECURSIVE_FULL:
314: case INSTR_RECURSIVE_SAMPLED:
315: imgr = createInitialInstrumentMethodGroupResponseForCallGraph(
316: cmd.getAllLoadedClassNames(), cmd
317: .getAllLoadedClassLoaderIds(), cmd
318: .getCachedClassFileBytes());
319:
320: break;
321: case INSTR_CODE_REGION:
322: imgr = createInitialInstrumentMethodGroupResponseForCodeRegion(
323: cmd.getAllLoadedClassNames(), cmd
324: .getAllLoadedClassLoaderIds(), cmd
325: .getCachedClassFileBytes());
326:
327: break;
328: case INSTR_OBJECT_ALLOCATIONS:
329: case INSTR_OBJECT_LIVENESS:
330: imgr = createInitialInstrumentMethodGroupResponseForMemoryProfiling(
331: status.currentInstrType, cmd
332: .getAllLoadedClassNames(), cmd
333: .getAllLoadedClassLoaderIds(), cmd
334: .getCachedClassFileBytes());
335:
336: break;
337: default:
338: System.err
339: .println(ENGINE_WARNING
340: + "Instrumentor.createInitialInstrumentMethodGroupResponse() called with INSTR_NONE?" // NOI18N
341: );
342: System.err.println(PLEASE_REPORT_PROBLEM);
343: imgr = new InstrumentMethodGroupResponse(null);
344:
345: break;
346: }
347:
348: return imgr;
349: }
350:
351: /**
352: * This is called every time just before the target application is started or right after we attach to it.
353: * It resets the internal data for loaded/instrumented classes etc.
354: */
355: public void resetPerVMInstanceData() {
356: ClassRepository.clearCache();
357: }
358:
359: private InstrumentMethodGroupResponse createFollowUpInstrumentMethodGroupResponseForCallGraph(
360: Command cmd) {
361: Object[] ret = null;
362:
363: // It may happen that if profiling is modified during intensive class loading, some class load message from
364: // server may be already in the pipeline and eventually get here despite the change, and before the relevant
365: // Method Scaner is initialized. This check should prevent problems caused by this inconsistency.
366: if (ms == null) {
367: return new InstrumentMethodGroupResponse(null);
368: }
369:
370: if (cmd instanceof MethodInvokedFirstTimeCommand) {
371: int id = ((MethodInvokedFirstTimeCommand) cmd)
372: .getMethodId();
373: //System.out.println("--------- Received method invoked event for id = " + id + ", method = "
374: // + status.instrMethodClasses[id] + "." + status.instrMethodNames[id] + status.instrMethodSignatures[id]);
375: status.beginTrans(false);
376:
377: try {
378: if ((id >= status.getInstrMethodClasses().length)
379: || (status.getInstrMethodClasses()[id] == null)) {
380: // Defensive programming: this situation may happen if something went wrong with previous deinstrumentation,
381: // so some old methodEntry() call isn't removed and gets called. Avoid a crash and issue a warning instead
382: return new InstrumentMethodGroupResponse(null);
383: }
384:
385: ret = ms.getMethodsToInstrumentUponMethodInvocation(
386: status.getInstrMethodClasses()[id], status
387: .getClassLoaderIds()[id], status
388: .getInstrMethodNames()[id], status
389: .getInstrMethodSignatures()[id]);
390: } finally {
391: status.endTrans();
392: }
393: } else if (cmd instanceof ClassLoadedCommand) {
394: ClassLoadedCommand ccmd = (ClassLoadedCommand) cmd;
395: //System.out.println("--------- Received class load event for class " + ccmd.getClassName());
396: ret = ms.getMethodsToInstrumentUponClassLoad(ccmd
397: .getClassName(),
398: ccmd.getThisAndParentLoaderData()[0], ccmd
399: .getThreadInCallGraph());
400: } else if (cmd instanceof MethodLoadedCommand) {
401: MethodLoadedCommand mcmd = (MethodLoadedCommand) cmd;
402: //System.out.println("--------- Recieved method load event for " + mcmd.getClassName() + "."
403: // + mcmd.getMethodName() + mcmd.getMethodSignature());
404: ret = ms.getMethodsToInstrumentUponReflectInvoke(mcmd
405: .getClassName(), mcmd.getClassLoaderId(), mcmd
406: .getMethodName(), mcmd.getMethodSignature());
407: }
408:
409: if (ret == null) {
410: return new InstrumentMethodGroupResponse(null);
411: } else {
412: return new InstrumentMethodGroupResponse((String[]) ret[0],
413: (int[]) ret[1], (byte[][]) ret[3],
414: (boolean[]) ret[2], 0);
415: }
416: }
417:
418: private InstrumentMethodGroupResponse createFollowUpInstrumentMethodGroupResponseForCodeRegion(
419: ClassLoadedCommand cmd) {
420: //System.out.println("--------- Received class load event for class " + cmd.getClassName());
421: // It may happen that if profiling is modified during intensive class loading, some class load message from
422: // server may be already in the pipeline and eventually get here despite the change, and before the relevant
423: // Method Scaner is initialized. This check should prevent problems caused by this inconsistency.
424: if (crms == null) {
425: return new InstrumentMethodGroupResponse(null);
426: }
427:
428: Object[] ret = crms.getFollowUpInstrumentCodeRegionResponse(cmd
429: .getThisAndParentLoaderData()[0]);
430:
431: if (ret == null) {
432: return new InstrumentMethodGroupResponse(null);
433: } else {
434: return new InstrumentMethodGroupResponse((String[]) ret[0],
435: (int[]) ret[1], (byte[][]) ret[2], null, 0);
436: }
437: }
438:
439: private InstrumentMethodGroupResponse createFollowUpInstrumentMethodGroupResponseForMemoryProfiling(
440: ClassLoadedCommand cmd) {
441: //System.out.println("--------- Received class load event for class " + cmd.getClassName());
442: // It may happen that if profiling is modified during intensive class loading, some class load message from
443: // server may be already in the pipeline and eventually get here despite the change, and before the relevant
444: // Method Scaner is initialized. This check should prevent problems caused by this inconsistency.
445: if (oms == null) {
446: return new InstrumentMethodGroupResponse(null);
447: }
448:
449: Object[] ret = oms.getMethodsToInstrumentUponClassLoad(cmd
450: .getClassName(), cmd.getThisAndParentLoaderData()[0]);
451:
452: if (ret == null) {
453: return new InstrumentMethodGroupResponse(null);
454: } else {
455: int maxInstrClassId = status.getNInstrClasses();
456:
457: return new InstrumentMethodGroupResponse((String[]) ret[0],
458: (int[]) ret[1], (byte[][]) ret[2], null,
459: maxInstrClassId);
460: }
461: }
462:
463: // ------------------------------------ Transitive method closure instrumentation ------------------------------------
464: private InstrumentMethodGroupResponse createInitialInstrumentMethodGroupResponseForCallGraph(
465: String[] loadedClasses, int[] loadedClassLoaderIds,
466: byte[][] cachedClassFileBytes) {
467: //System.err.println("*** Received root class load event for class names: ");
468: //for (int i = 0; i < rootClassNames.length; i++) System.err.println(" " + rootClassNames[i] + "." +
469: // rootMethodNames[i] + rootMethodSignatures[i]);
470: //System.err.println("*** Number of target VM loaded classes: " + loadedClasses.length);
471: //System.err.println("*** Root classes are at positions:");
472: //for (int i = 0; i < loadedClasses.length; i++) {
473: //System.err.println(loadedClasses[i]);
474: //for (int j = 0; j < rootClassNames.length; j++) {
475: // if (loadedClasses[i].equals(rootClassNames[j])) System.err.println(" " + i + " - " + rootClassNames[j]);
476: //}
477: //}
478: Object[] ret;
479:
480: switch (settings.getInstrScheme()) {
481: case INSTRSCHEME_LAZY:
482: ms = new RecursiveMethodInstrumentor1(status, settings);
483:
484: break;
485: case INSTRSCHEME_EAGER:
486: ms = new RecursiveMethodInstrumentor2(status, settings);
487:
488: break;
489: case INSTRSCHEME_TOTAL:
490: ms = new RecursiveMethodInstrumentor3(status, settings);
491:
492: break;
493: }
494:
495: ret = ms
496: .getInitialMethodsToInstrument(loadedClasses,
497: loadedClassLoaderIds, cachedClassFileBytes,
498: rootMethods);
499:
500: if (ret == null) {
501: return new InstrumentMethodGroupResponse(null);
502: } else {
503: return new InstrumentMethodGroupResponse((String[]) ret[0],
504: (int[]) ret[1], (byte[][]) ret[3],
505: (boolean[]) ret[2], 0);
506: }
507: }
508:
509: // ---------------------------------- Code region instrumentation ----------------------------------------------------
510: private InstrumentMethodGroupResponse createInitialInstrumentMethodGroupResponseForCodeRegion(
511: String[] loadedClasses, int[] loadedClassLoaderIds,
512: byte[][] loadedClassBytes) throws ClassNotFoundException,
513: BadLocationException {
514: CodeRegionMethodInstrumentor.resetLoadedClassData();
515: ClassManager.storeClassFileBytesForCustomLoaderClasses(
516: loadedClasses, loadedClassLoaderIds, loadedClassBytes);
517:
518: crms = new CodeRegionMethodInstrumentor(status,
519: savedSourceCodeSelection);
520:
521: Object[] ret = crms.getInitialInstrumentCodeRegionResponse(
522: loadedClasses, loadedClassLoaderIds);
523:
524: if (ret == null) {
525: return new InstrumentMethodGroupResponse(null);
526: }
527:
528: return new InstrumentMethodGroupResponse((String[]) ret[0],
529: (int[]) ret[1], (byte[][]) ret[2], null, 0);
530: }
531:
532: // -------------------------------------- Memory profiling instrumentation -------------------------------------------
533: private InstrumentMethodGroupResponse createInitialInstrumentMethodGroupResponseForMemoryProfiling(
534: int instrType, String[] loadedClasses,
535: int[] loadedClassLoaderIds, byte[][] cachedClassFileBytes) {
536: //System.out.println("+++++++++ Received memory profiling instrumentation initialization event of type "
537: // + instrType);
538: //System.out.println("+++++++++ Number of target VM loaded classes: " + loadedClasses.length);
539: oms = new ObjLivenessMethodInstrumentor(status, settings,
540: (instrType == INSTR_OBJECT_LIVENESS));
541:
542: Object[] ret = oms.getInitialMethodsToInstrument(loadedClasses,
543: loadedClassLoaderIds, cachedClassFileBytes);
544:
545: if (ret == null) {
546: return new InstrumentMethodGroupResponse(null);
547: } else {
548: int maxInstrClassId = oms.getNInstantiatableClasses()
549: + status.getNInstrClasses();
550:
551: return new InstrumentMethodGroupResponse((String[]) ret[0],
552: (int[]) ret[1], (byte[][]) ret[2], null,
553: maxInstrClassId);
554: }
555: }
556: }
|