org.netbeans.lib.profiler.server.ProfilerInterface Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.lib.profiler.server;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.net.URL;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.WeakHashMap;
import org.netbeans.lib.profiler.global.CommonConstants;
import org.netbeans.lib.profiler.global.Platform;
import org.netbeans.lib.profiler.global.ProfilingSessionStatus;
import org.netbeans.lib.profiler.global.TransactionalSupport;
import org.netbeans.lib.profiler.server.system.*;
import org.netbeans.lib.profiler.wireprotocol.*;
/**
* Main interface to the target VM side introspection functionality.
*
* @author Tomas Hurka
* @author Misha Dmitriev
* @author Adrian Mos
* @author Ian Formanek
*/
public class ProfilerInterface implements CommonConstants {
//~ Inner Classes ------------------------------------------------------------------------------------------------------------
private static class HFIRIThread extends Thread {
//~ Constructors ---------------------------------------------------------------------------------------------------------
HFIRIThread() {
ThreadInfo.addProfilerServerThread(this);
this.setName(PROFILER_SPECIAL_EXEC_THREAD_NAME + " 1"); // NOI18N
setDaemon(true);
}
//~ Methods --------------------------------------------------------------------------------------------------------------
public void run() {
RootClassLoadedCommand cmd = new RootClassLoadedCommand(new String[] { "*FAKE_CLASS_1*", "*FAKE_CLASS_2*" }, // NOI18N
new int[] { 0, 0 }, null, new int[] { 0, 0 }, new int[2][], 2, new int[] { -1 });
profilerServer.sendComplexCmdToClient(cmd);
InstrumentMethodGroupResponse imgr = (InstrumentMethodGroupResponse) profilerServer.getLastResponse();
ThreadInfo.removeProfilerServerThread(this);
}
}
private static class InitiateProfilingThread extends Thread {
//~ Instance fields ------------------------------------------------------------------------------------------------------
private InitiateProfilingCommand cmd;
private boolean targetAppRunning;
//~ Constructors ---------------------------------------------------------------------------------------------------------
InitiateProfilingThread(InitiateProfilingCommand cmd, boolean targetAppRunning) {
ThreadInfo.addProfilerServerThread(this);
this.setName(PROFILER_SPECIAL_EXEC_THREAD_NAME + " 2"); // NOI18N
this.cmd = cmd;
this.targetAppRunning = targetAppRunning;
}
//~ Methods --------------------------------------------------------------------------------------------------------------
public void run() {
Monitors.enterServerState(CommonConstants.SERVER_INITIALIZING);
// We take a serialClientOperationsLock, then turn class load hook on, to prevent possible class loads, that
// will neither get into loadedClassesArray nor be intercepted properly and reported to client by classLoadHook.
// In other words, classes that are loaded before this point get into loadedClassesArray; classes loaded
// afterwards should be individually intercepted and reported to client.
serialClientOperationsLock.beginTrans(true);
try {
initInstrumentationThread = Thread.currentThread();
int instrType = cmd.getInstrType();
setCurrentInstrType(instrType);
switch (instrType) {
case INSTR_NONE:
// do nothing
break;
case INSTR_NONE_SAMPLING:
ProfilerRuntimeSampler.initialize();
break;
case INSTR_CODE_REGION:
case INSTR_RECURSIVE_FULL:
case INSTR_RECURSIVE_SAMPLED:
case INSTR_OBJECT_ALLOCATIONS:
case INSTR_OBJECT_LIVENESS:
initiateInstrumentation(instrType);
break;
case INSTR_NONE_MEMORY_SAMPLING:
if (Histogram.initialize(Platform.getJDKVersionNumber() >= Platform.JDK_19)) {
profilerServer.notifyClientOnResultsAvailability();
}
break;
default:
throw new IllegalArgumentException("Instr. type: "+instrType);
}
initInstrumentationThread = null;
} finally {
serialClientOperationsLock.endTrans();
Monitors.exitServerState();
}
ThreadInfo.removeProfilerServerThread(this);
}
private void initiateInstrumentation(final int instrType) {
Monitors.DeterminateProgress progress = Monitors.enterServerState(CommonConstants.SERVER_INITIALIZING, 2);
int[] profilingPointIDs = cmd.getProfilingPointIDs();
String[] handlers = cmd.getProfilingPointHandlers();
String[] infos = cmd.getProfilingPointInfos();
rootClassNames = cmd.getRootClassNames();
status.startProfilingPointsActive = cmd.isStartProfilingPointsActive();
ProfilingPointServerHandler.initInstances(profilingPointIDs, handlers, infos);
computeRootWildcard();
rootClassLoaded = false;
// the following code is needed to avoid issue 59660: Remote profiling can cause the agent to hang if CPU
// or Code Fragment profiling is used
// see http://profiler.netbeans.org/issues/show_bug.cgi?id=59660
// and http://profiler.netbeans.org/issues/show_bug.cgi?id=61968
new LinkedHashMap().keySet().iterator();
try {
// for take heap dump
Class.forName("java.lang.reflect.InvocationTargetException"); // NOI18N
Class.forName("java.lang.InterruptedException"); // NOI18N
Class.forName("java.lang.ClassFormatError"); // NOI18N class caching
} catch (ClassNotFoundException e) {
e.printStackTrace(System.err);
}
if (Platform.getJDKVersionNumber() >= CommonConstants.JDK_19) {
try {
// preload classes for classLoadHook()
Class.forName("java.lang.WeakPairMap$Pair"); // NOI18N
Class.forName("java.lang.WeakPairMap$WeakRefPeer"); // NOI18N
Class.forName("java.lang.WeakPairMap$Pair$Weak"); // NOI18N
Class.forName("java.lang.WeakPairMap$Pair$Weak$1"); // NOI18N
} catch (ClassNotFoundException e) {
e.printStackTrace(System.err);
}
}
// The following code is needed to enforce native method bind for Thread.sleep before instrumentation, so
// that the NativeMethodBind it can be disabled as first thing in instrumentation
// this is needed as a workaround for JDK bug:
// CR 6318850 Updated P3 hotspot/jvmti RedefineClasses() and NativeMethodBind event crash
try {
Thread.sleep(1);
} catch (InterruptedException e) {
} // ignore
synchronized (this) {
try {
wait(1);
} catch (InterruptedException e) {
} // ignore
}
// reset loadedClassesArray so that next time it contains current loaded classes
loadedClassesArray = null;
loadedClassesLoaders = null;
Classes.enableClassLoadHook();
instrSpawnedThreads = cmd.getInstrSpawnedThreads();
if (targetAppRunning || hasAnyCoreClassNames(cmd.getRootClassNames()) || instrSpawnedThreads
|| (instrType == INSTR_OBJECT_ALLOCATIONS) || (instrType == INSTR_OBJECT_LIVENESS)) {
getLoadedClasses(); // Init loadedClassesArray
boolean loadedRootClassesExist = false;
if (!detachStarted) {
switch (instrType) {
case INSTR_RECURSIVE_FULL:
case INSTR_RECURSIVE_SAMPLED:
// This will look into loadedClassesArray to check if there are any root classes already loaded
loadedRootClassesExist = instrSpawnedThreads ? true : checkForLoadedRootClasses();
break;
case INSTR_CODE_REGION:
loadedRootClassesExist = checkForLoadedRootClasses();
break;
case INSTR_OBJECT_ALLOCATIONS:
case INSTR_OBJECT_LIVENESS:
loadedRootClassesExist = true;
break;
}
}
progress.next();
if (loadedRootClassesExist) { // Root class(es) has been loaded or none is needed - start
// instrumentation-related operations right away
sendRootClassLoadedCommand(false);
if (!getAndInstrumentClasses(true)) {
disableProfilerHooks();
}
rootClassLoaded = true; // See the comment in classLoadHook why it's worth setting rootClassLoaded
// to true after the first instrumentation, not before
} else {
// if root class is not loaded, reset loadedClassesArray so that next time it contains current loaded classes
loadedClassesArray = null;
loadedClassesLoaders = null;
}
}
Monitors.exitServerState();
}
private static void computeRootWildcard() {
rootClassNameWildcard = new boolean[rootClassNames.length];
rootClassNamePackageWildcard = new boolean[rootClassNames.length];
for (int i = 0; i < rootClassNames.length; i++) {
int nameLen = rootClassNames[i].length();
rootClassNameWildcard[i] = (nameLen == 0) // default package wildcard
|| (rootClassNames[i].charAt(nameLen - 1) == '.'); // ends with "." // NOI18N
if (!rootClassNameWildcard[i]) {
if (rootClassNames[i].charAt(nameLen - 1) == '*') { // package wild card - instrument all classes including subpackages
rootClassNames[i] = rootClassNames[i].substring(0,nameLen - 1); // remove *
rootClassNameWildcard[i] = true;
rootClassNamePackageWildcard[i] = true;
}
// System.out.println("Root "+rootClassNames[i]+" wild "+rootClassNameWildcard[i]+" package "+rootClassNamePackageWildcard[i]);
}
}
}
}
//~ Static fields/initializers -----------------------------------------------------------------------------------------------
private static final int REDEFINE_CHUNK_SIZE = 500;
static final char BOOLEAN = 'Z'; // NOI18N
static final char CHAR = 'C'; // NOI18N
static final char BYTE = 'B'; // NOI18N
static final char SHORT = 'S'; // NOI18N
static final char INT = 'I'; // NOI18N
static final char LONG = 'J'; // NOI18N
static final char FLOAT = 'F'; // NOI18N
static final char DOUBLE = 'D'; // NOI18N
static final char VOID = 'V'; // NOI18N
static final char REFERENCE = 'L'; // NOI18N
// -----
// I18N String constants
// !!! Warning - do not use ResourceBundle.getBundle here, won't work in context of direct/dynamic attach !!!
// Default EN messages initialized here, will be replaced by localized messages in static initializer
private static String INTERNAL_ERROR_MSG = "Internal error:\nExpected InstrumentMethodGroupResponse, got response of class {0},\nvalue = {1}\nAll instrumentation will be removed"; // NOI18N
private static String UNEXPECTED_EXCEPTION_MSG = "Unexpected exception caught when trying to instrument classes.\nOriginal exception:\n{0}\nStack trace:\n\n{1}"; // NOI18N
private static String INSTRUMENTATION_SUCCESSFUL_MSG = "Deferred instrumentation performed successfully"; // NOI18N
private static String HISTOGRAM_NOT_AVAILABLE_MSG = "Histogram is not available."; // NOI18N
// -----
static {
ResourceBundle messages = ProfilerServer.getProfilerServerResourceBundle();
if (messages != null) {
INTERNAL_ERROR_MSG = messages.getString("ProfilerInterface_InternalErrorMsg"); // NOI18N
UNEXPECTED_EXCEPTION_MSG = messages.getString("ProfilerInterface_UnexpectedExceptionMsg"); // NOI18N
INSTRUMENTATION_SUCCESSFUL_MSG = messages.getString("ProfilerInterface_InstrumentationSuccessfulMsg"); // NOI18N
HISTOGRAM_NOT_AVAILABLE_MSG = messages.getString("ProfilerInterface_HistogramNotAvailableMsg"); // NOI18N
}
}
// TODO [release]: change value to FALSE to remove the print code below entirely by compiler
private static final boolean DEBUG = Boolean.getBoolean("org.netbeans.lib.profiler.server.ProfilerInterface.classLoadHook"); // NOI18N
private static final boolean INSTRUMENT_JFLUID_CLASSES = Boolean.getBoolean("org.netbeans.lib.profiler.server.instrumentJFluidClasses"); // NOI18N
private static final byte[] EMPTY = new byte[0];
// The lock used to serialize requests from server to client. May be used outside this class.
public static final TransactionalSupport serialClientOperationsLock = new TransactionalSupport();
private static ProfilerServer profilerServer;
private static ProfilingSessionStatus status;
private static EventBufferManager evBufManager;
private static WeakReference[] loadedClassesArray; // Temporary array, used to send all loaded class names to client
// on instrumentation initiation.
private static int[] loadedClassesLoaders; // Ditto, for loaders
private static WeakHashMap reflectMethods; // Cache of methods called using reflection
private static boolean targetAppSuspended = false;
private static boolean instrumentReflection = false;
private static boolean instrSpawnedThreads;
private static int[] packedArrayOffsets;
private static int nSystemThreads;
private static Thread initInstrumentationThread;
private static String[] rootClassNames;
private static boolean[] rootClassNameWildcard;
private static boolean[] rootClassNamePackageWildcard;
// For statistics
static int nClassLoads;
// For statistics
static int nFirstMethodInvocations;
// For statistics
static int nEmptyInstrMethodGroupResponses;
static int nNonEmptyInstrMethodGroupResponses;
static int nSingleMethodInstrMethodGroupResponses;
static int nTotalInstrMethods;
static long totalHotswappingTime;
static long minHotswappingTime = 10000000000L;
static long maxHotswappingTime;
static long clientInstrStartTime;
static long clientInstrTime;
static long clientDataProcStartTime;
static long clientDataProcTime;
//----------------------------------------- Private implementation --------------------------------------------------
private static boolean rootClassLoaded; // has root class been loaded?
// The following variable addresses the issue of classLoadHook called for a class, that is already registered as
// loaded, (through getAllLoadedClasses) but is actually initialized only when extendConstantPool() is called on it.
// Initialization would cause classLoadHook invocation on this class, and subsequent "second class load event"
// messages in client (which is just confusing). But this may also cause more subtle deadlock bug due to
// classLoadHook() trying to record adjustTime event, while serialClientOperationsLock is held by an outer invocation
// of classLoadHook() or methodInvokedFirstTime(). To avoid all these problems we use this simple way to avoid
// unnecessary classLoadHook() invocations.
private static volatile Thread instrumentMethodGroupCallThread;
private static volatile boolean detachStarted;
private static HeapHistogramManager heapHistgramManager;
//~ Methods ------------------------------------------------------------------------------------------------------------------
public static CodeRegionCPUResultsResponse getCodeRegionCPUResults() {
CodeRegionCPUResultsResponse resp = new CodeRegionCPUResultsResponse(ProfilerRuntimeCPUCodeRegion.getProfilingResults());
return resp;
}
public static void setCurrentInstrType(int type) {
boolean isMemoryProfiling = (type == INSTR_OBJECT_ALLOCATIONS) || (type == INSTR_OBJECT_LIVENESS);
status.currentInstrType = type;
Classes.setVMObjectAllocEnabled(isMemoryProfiling);
}
public static int getCurrentInstrType() {
return status.currentInstrType;
}
public static ThreadLivenessStatusResponse getCurrentThreadLivenessStatus() {
ThreadLivenessStatusResponse resp = new ThreadLivenessStatusResponse(ThreadInfo.getCurrentLivenessStatus());
return resp;
}
public static void setInstrumentReflection(boolean v) {
if (status.targetAppRunning) {
ProfilerRuntimeCPU.setJavaLangReflectMethodInvokeInterceptEnabled(v);
} else {
instrumentReflection = v;
}
}
public static MethodNamesResponse getMethodNamesForJMethodIds(int[] methodIds) {
final int PACKEDARR_ITEMS = 4; // must match PACKEDARR_ITEMS in Stacks.c
int nMethods = methodIds.length;
int len = nMethods * PACKEDARR_ITEMS;
packedArrayOffsets = new int[len];
byte[] packedData = Stacks.getMethodNamesForJMethodIds(nMethods, methodIds, packedArrayOffsets);
MethodNamesResponse resp = new MethodNamesResponse(packedData, packedArrayOffsets);
return resp;
}
public static int getNPrerecordedSystemThreads() {
return nSystemThreads;
}
public static ObjectAllocationResultsResponse getObjectAllocationResults() {
status.beginTrans(false);
try {
ObjectAllocationResultsResponse resp = new ObjectAllocationResultsResponse(status.getAllocatedInstancesCount(),
status.getNInstrClasses());
return resp;
} finally {
status.endTrans();
}
}
public static void setProfilerServer(ProfilerServer server) {
profilerServer = server;
}
/**
* This method cleans up the data structures managed by this class, but not by various ProfilerRuntimeXXX classes.
* The latter are cleaned up separately, by the following deactivateInjectedCode() method.
*/
public static void clearProfilerDataStructures() {
//loadedClassesArray = null;
//loadedClassesCPLengths = null;
//loadedClassesLoaders = null;
//ClassLoaderManager.reset();
//packedArrayOffsets = null;
reflectMethods = null;
//evBufManager.freeBufferFile();
}
public static boolean cpuResultsExist() {
return ProfilerRuntime.profiledTargetAppThreadsExist();
}
static void disableProfiling() {
int instrType = getCurrentInstrType();
switch (instrType) {
case INSTR_NONE:
case INSTR_NONE_MEMORY_SAMPLING:
ProfilerRuntime.clearDataStructures();
break;
case INSTR_NONE_SAMPLING:
ProfilerRuntimeSampler.shutdown();
break;
case INSTR_CODE_REGION:
ProfilerRuntimeCPUCodeRegion.enableProfiling(false);
if (rootClassNames != null) {
rootClassNames = null;
}
break;
case INSTR_RECURSIVE_FULL:
ProfilerRuntimeCPUFullInstr.enableProfiling(false);
ProfilerRuntimeCPU.setTimerTypes(false, false); // Mainly to clean up microstate accounting on Solaris
break;
case INSTR_RECURSIVE_SAMPLED:
ProfilerRuntimeCPUSampledInstr.enableProfiling(false);
ProfilerRuntimeCPU.setTimerTypes(false, false);
break;
case INSTR_OBJECT_ALLOCATIONS:
ProfilerRuntimeObjAlloc.enableProfiling(false);
break;
case INSTR_OBJECT_LIVENESS:
ProfilerRuntimeObjLiveness.enableProfiling(false);
break;
}
}
/**
* Deactivate the injected code for the current instrumentation type, and clean up all the supporting data structures
* maintained by the corresponding ProfilerRuntimeXXX class.
*/
public static void deactivateInjectedCode() {
disableProfilerHooks();
disableProfiling();
status.resetInstrClassAndMethodInfo();
setCurrentInstrType(INSTR_NONE);
}
public static void disableProfilerHooks() {
Classes.setWaitTrackingEnabled(false);
Classes.setParkTrackingEnabled(false);
Classes.setSleepTrackingEnabled(false);
Classes.setVMObjectAllocEnabled(false);
Classes.disableClassLoadHook();
ProfilerRuntimeCPU.setJavaLangReflectMethodInvokeInterceptEnabled(false);
ClassLoaderManager.setNotifyToolAboutUnloadedClasses(false);
}
public static void dumpExistingResults(boolean live) {
if (!live && (getCurrentInstrType() == INSTR_OBJECT_LIVENESS) && ProfilerRuntimeObjLiveness.getRunGCOnGetResults()) {
GC.runGC();
try {
// Give WeakReference collector thread a chance to register some (hopefully most of) object GCs
Thread.sleep(500);
} catch (Exception ex) {
}
}
ProfilerRuntime.dumpEventBuffer();
}
/**
* This method initializes the internal data structures, and also records the profiler's own thread(s), so that
* they are not affected by our suspend/resume operations. If we run in the normal mode, i.e. the target JVM was
* started by the client, specialThread is the current thread, which will then become the target app main thread.
* It should be excluded from the list of the profiler's own threads. If we run in the attached mode, specialThread
* is the only thread that we can reliably characterize as the profiler's own.
*/
public static void initProfilerInterface(ProfilingSessionStatus status, Thread specialThread) {
boolean jdk15 = Platform.getJDKVersionNumber() == Platform.JDK_15;
Timers.initialize();
Classes.initialize();
GC.initialize();
Stacks.initialize();
Threads.initialize();
HeapDump.initialize(jdk15);
ThreadDump.initialize(jdk15);
ClassLoaderManager.initialize(profilerServer);
ClassLoaderManager.addLoader(ClassLoader.getSystemClassLoader());
reflectMethods = new WeakHashMap();
evBufManager = new EventBufferManager(profilerServer);
heapHistgramManager = new HeapHistogramManager();
ProfilerInterface.status = status;
detachStarted = false;
// Check that all profiler's own threads are running, and then record them internally, so that target app threads
// are accounted for properly.
while (!Monitors.monitorThreadsStarted()) {
try {
Thread.sleep(50);
} catch (Exception ex) {
}
}
if (status.runningInAttachedMode) {
Threads.recordProfilerOwnThreads(false, specialThread);
nSystemThreads = -1; // Indicates that we really don't know how many of these threads
// are VM-own, or system, threads
} else {
nSystemThreads = Threads.recordProfilerOwnThreads(true, specialThread);
}
ProfilerRuntime.init(new ProfilerRuntime.ExternalActionsHandler() {
public void handleFirstTimeMethodInvoke(char methodId) {
firstTimeMethodInvokeHook(methodId);
}
public void handleReflectiveInvoke(Method method) {
reflectiveMethodInvokeHook(method);
}
public int handleFirstTimeVMObjectAlloc(String className, int definingClassLoaderId) {
return firstTimeVMObjectAlloc(className, definingClassLoaderId);
}
public void handleEventBufferDump(byte[] eventBuffer, int startPos, int curPtrPos) {
serialClientOperationsLock.beginTrans(true);
try { // So that this event does not interfere with class
// loads / method invocations
clientDataProcStartTime = Timers.getCurrentTimeInCounts();
evBufManager.eventBufferDumpHook(eventBuffer, startPos, curPtrPos);
clientDataProcTime += (Timers.getCurrentTimeInCounts() - clientDataProcStartTime);
} finally {
serialClientOperationsLock.endTrans();
}
}
});
}
public static void initiateProfiling(final InitiateProfilingCommand cmd, final boolean targetAppRunning)
throws Exception {
int instrType = cmd.getInstrType();
String instrClassName = cmd.getRootClassName();
if (instrClassName.equals("*FAKE_CLASS_FOR_INTERNAL_TEST*")) { // NOI18N
handleFakeInitRecursiveInstrumentationCommand(); // To initialize certain internal classes, see comments
// in handleFake... method
return;
}
switch (instrType) {
case INSTR_NONE:
case INSTR_NONE_MEMORY_SAMPLING:
createEventBuffer();
break;
case INSTR_RECURSIVE_FULL:
case INSTR_RECURSIVE_SAMPLED:
case INSTR_OBJECT_ALLOCATIONS:
case INSTR_OBJECT_LIVENESS:
createEventBuffer();
status.resetInstrClassAndMethodInfo();
if ((instrType == INSTR_OBJECT_ALLOCATIONS) || (instrType == INSTR_OBJECT_LIVENESS)) {
ClassLoaderManager.setNotifyToolAboutUnloadedClasses(true);
} else {
ClassLoaderManager.setNotifyToolAboutUnloadedClasses(false);
}
break;
case INSTR_CODE_REGION:
ProfilerRuntimeCPUCodeRegion.resetProfilerCollectors();
break;
case INSTR_NONE_SAMPLING:
createEventBuffer();
break;
default:
throw new IllegalArgumentException("Instr. type: "+instrType);
}
// We have to perform the following operations in a separate thread, since they may involve further dialog with
// the tool (client), whereas this thread has to return quickly to send the "OK" response to the tool.
new InitiateProfilingThread(cmd, targetAppRunning).start();
}
public static void instrumentMethods(InstrumentMethodGroupCommand cmd)
throws Exception {
if (!cmd.isEmpty()) {
try {
instrumentMethodGroupNow(cmd.getBase());
} catch (Exception ex) {
deactivateInjectedCode();
setCurrentInstrType(INSTR_NONE);
throw ex;
}
}
setCurrentInstrType(cmd.getInstrType());
}
public static void resetProfilerCollectors() {
ProfilerRuntime.resetProfilerCollectors(getCurrentInstrType());
reflectMethods = new WeakHashMap(); // So that methods that are possibly holding unreachable classes are
// removed and classes allowed to be GCed
}
public static void resumeTargetApp() {
if (getCurrentInstrType() == INSTR_RECURSIVE_FULL) {
ProfilerRuntimeCPUFullInstr.resumeActiveTimers();
}
Threads.resumeTargetAppThreads(null);
targetAppSuspended = false;
}
public static void suspendTargetApp() {
Threads.suspendTargetAppThreads(null);
if (getCurrentInstrType() == INSTR_RECURSIVE_FULL) {
ProfilerRuntimeCPUFullInstr.suspendActiveTimers();
}
targetAppSuspended = true;
}
static void createEventBuffer() throws IOException {
evBufManager.openBufferFile(EVENT_BUFFER_SIZE_IN_BYTES);
ProfilerRuntime.createEventBuffer(EVENT_BUFFER_SIZE_IN_BYTES);
}
static String getBufferFileName() {
if (evBufManager != null) {
return evBufManager.getBufferFileName();
}
return "";
}
static void setDetachStarted(boolean detachStarted) {
ProfilerInterface.detachStarted = detachStarted;
}
static boolean isDetachStarted() {
return detachStarted;
}
static Response computeHistogram() {
Response resp = null;
if (Histogram.isAvailable()) {
resp = heapHistgramManager.computeHistogram(Histogram.getRawHistogram());
}
if (resp == null) {
resp = new Response(HISTOGRAM_NOT_AVAILABLE_MSG);
}
return resp;
}
static byte[][] getClassFileBytes(String[] classNames, int[] classLoaderIds) {
int MAX_CLASSES = 1000;
Class[] nonSystemClasses = new Class[MAX_CLASSES+1]; // classes loaded by classloaders other that bootstrap and system
int nonSystemIndex = 0;
byte[][] bytes = new byte[classNames.length][];
Class[] classes = new Class[classNames.length];
for (int i = 0; i < loadedClassesArray.length; i++) {
Class loadedClass = getOrdinaryClass(loadedClassesArray[i]);
if (loadedClass == null) {
// class was unloaded or has special name
continue;
}
int classLoaderId = loadedClassesLoaders[i];
String name = loadedClass.getName();
// At the client side we treat classes loaded by the bootstrap and by the system classloaders in the same way
if (classLoaderId == -1) classLoaderId = 0;
for (int j = 0; j < classNames.length; j++) {
if (classLoaderIds[j] == classLoaderId && classNames[j].equals(name)) {
classes[j] = loadedClass;
nonSystemClasses[nonSystemIndex++] = loadedClass;
break;
}
}
if (nonSystemIndex == MAX_CLASSES) {
cacheLoadedClasses(nonSystemClasses,nonSystemIndex);
nonSystemIndex = 0;
profilerServer.sendSimpleCmdToClient(Command.STILL_ALIVE);
}
}
if (nonSystemIndex > 0) {
cacheLoadedClasses(nonSystemClasses,nonSystemIndex);
}
for (int i = 0; i < classes.length; i++) {
if (classes[i] != null) {
bytes[i] = getClassFileBytes(classes[i], classLoaderIds[i]);
}
}
return bytes;
}
private static boolean getAndInstrumentClasses(boolean rootClassInstrumentation) {
Response r = profilerServer.getLastResponse();
if (!(r instanceof InstrumentMethodGroupResponse)) { // This is an internal error which, hopefully, has been fixed.
String msg = MessageFormat.format(INTERNAL_ERROR_MSG, new Object[] { r.getClass(), r });
deactivateInjectedCode();
profilerServer.sendComplexCmdToClient(new AsyncMessageCommand(false, msg));
return false;
}
InstrumentMethodGroupResponse imgr = (InstrumentMethodGroupResponse) r;
clientInstrTime += (Timers.getCurrentTimeInCounts() - clientInstrStartTime);
if (!imgr.isOK()) {
return false;
}
if (imgr.isEmpty()) {
nEmptyInstrMethodGroupResponses++;
// Don't return immediately, because may have rootClassInstrumentation == true (see above)
} else {
// Do the following update before instrumentation, since if we do this after it, chances are some instrumented
// method in another thread enters e.g. methodEntry() and hits the not-yet-updated invocation array before
// updating has been completed.
updateInstrClassAndMethodNames(imgr.getBase(), true);
if (rootClassInstrumentation && (getCurrentInstrType() == INSTR_OBJECT_LIVENESS)) {
// Create a ThreadInfo for the current thread immediately to avoid recursion with trace object allocation calls
ThreadInfo.getThreadInfo();
}
try {
instrumentMethodGroupNow(imgr.getBase());
} catch (Exception ex) {
//deactivateInjectedCode(); // It looks like it often makes more sense to proceed and get at least some info
profilerServer.sendComplexCmdToClient(new AsyncMessageCommand(false, ex.getMessage()));
return true; // Used to be "return false" (but see comment above).
}
}
if (rootClassInstrumentation) {
switch (getCurrentInstrType()) {
case INSTR_RECURSIVE_FULL:
if (instrumentReflection) {
ProfilerRuntimeCPU.setJavaLangReflectMethodInvokeInterceptEnabled(true);
}
ProfilerRuntimeCPUFullInstr.enableProfiling(true);
break;
case INSTR_RECURSIVE_SAMPLED:
if (instrumentReflection) {
ProfilerRuntimeCPU.setJavaLangReflectMethodInvokeInterceptEnabled(true);
}
ProfilerRuntimeCPUSampledInstr.enableProfiling(true);
break;
case INSTR_CODE_REGION:
ProfilerRuntimeCPUCodeRegion.enableProfiling(true);
break;
case INSTR_OBJECT_ALLOCATIONS:
ProfilerRuntimeObjAlloc.enableProfiling(true);
break;
case INSTR_OBJECT_LIVENESS:
ProfilerRuntimeObjLiveness.enableProfiling(true);
break;
}
}
return true;
}
private static boolean isCoreClassName(String name) {
name = name.replace('.', '/'); // NOI18N
return (name.startsWith("java/") || name.startsWith("sun/") || name.startsWith("javax/")); // NOI18N
}
private static void getLoadedClasses() {
if (loadedClassesArray == null) {
int nonSystemIndex = 0;
int MAX_CLASSES = 1000;
Class[] nonSystemClasses = new Class[MAX_CLASSES+1]; // classes loaded by classloaders other that bootstrap and system
boolean dynamic = profilerServer.isDynamic();
Class[] allClasses = Classes.getAllLoadedClasses();
loadedClassesArray = new WeakReference[allClasses.length];
loadedClassesLoaders = new int[allClasses.length];
Monitors.DeterminateProgress progress = Monitors.enterServerState(CommonConstants.SERVER_PREPARING, loadedClassesArray.length);
try {
for (int i = 0; i < loadedClassesArray.length; i++) {
progress.next();
if(detachStarted) {
return;
}
Class> clazz = allClasses[i];
loadedClassesArray[i] = new WeakReference(clazz);
loadedClassesLoaders[i] = ClassLoaderManager.registerLoader(clazz);
}
} finally {
Monitors.exitServerState();
}
}
}
private static boolean isRootClass(String className) {
for (int i = 0; i < rootClassNames.length; i++) {
String rootName = rootClassNames[i];
if (rootClassNameWildcard[i]) {
if (className.startsWith(rootName)) {
if (rootClassNamePackageWildcard[i]) { // instrument also subpackages
return true;
}
if (className.indexOf('.', rootName.length()) == -1) { // not a subpackage
return true;
}
} else if (rootClassNamePackageWildcard[i]){
if (className.equals(rootName.substring(0,rootName.length()-1)))
{
return true;
}
}
} else if (rootName.equals(className)) {
return true;
}
}
return false;
}
private static void appendTypeName(StringBuffer sb, Class type) {
if (type.isArray()) {
do {
sb.append('['); // NOI18N
type = type.getComponentType();
} while (type.isArray());
}
if (type == Integer.TYPE) {
sb.append(INT);
} else if (type == Boolean.TYPE) {
sb.append(BOOLEAN);
} else if (type == Byte.TYPE) {
sb.append(BYTE);
} else if (type == Character.TYPE) {
sb.append(CHAR);
} else if (type == Long.TYPE) {
sb.append(LONG);
} else if (type == Float.TYPE) {
sb.append(FLOAT);
} else if (type == Double.TYPE) {
sb.append(DOUBLE);
} else if (type == Void.TYPE) {
sb.append(VOID);
} else {
sb.append(REFERENCE);
sb.append(type.getName().replace('.', '/')); // NOI18N
sb.append(';'); // NOI18N
}
}
private static boolean checkForLoadedRootClasses() {
for (int i = 0; i < loadedClassesArray.length; i++) {
Class loadedClass = (Class) loadedClassesArray[i].get();
if (loadedClass != null && isRootClass(loadedClass.getName())) {
return true;
}
}
return false;
}
/** Called on CLASS_PREPARE JVMTI event */
private static void classLoadHook(Class clazz) {
ThreadInfo threadInfo = ThreadInfo.getThreadInfo();
threadInfo.inProfilingRuntimeMethod++;
try {
String className = clazz.getName();
if (instrumentMethodGroupCallThread == Thread.currentThread() || internalClassName(className)) { // See comment at inInstrumentMethodGroupCall
ClassLoaderManager.registerLoader(clazz); // Still register the loader, for reasons related with
// management of jmethodIds
return;
}
Thread currentThread = Thread.currentThread();
if (PROFILER_SERVER_THREAD_NAME.equals(currentThread.getName())) {
System.err.println(ENGINE_WARNING + "class " + className + " loaded by " + PROFILER_SERVER_THREAD_NAME); // NOI18N
return;
}
if ((initInstrumentationThread != null) && (currentThread == initInstrumentationThread)) {
// Looks like on rare occasions we can get this problem - class load hook called when it shouldn't.
// If we are already here, we can't (easily at least) fix this problem, but at least we can warn the user.
System.err.println(ENGINE_WARNING + "class load hook invoked at inappropriate time for " // NOI18N
+ className + ", loader = " + clazz.getClassLoader()); // NOI18N
System.err.println("*** This class will not be instrumented unless you re-run the instrumentation command"); // NOI18N
System.err.println(PLEASE_REPORT_PROBLEM);
System.err.println("=============================== Stack trace ====================="); // NOI18N
Thread.dumpStack();
System.err.println("=============================== End stack trace ================="); // NOI18N
return;
}
//System.out.println("+++ Class load hook invoked for " + className + ", loader = " + clazz.getClassLoader());
int classLoaderId = ClassLoaderManager.registerLoader(clazz);
boolean resumeTimer = false;
if (DEBUG) {
System.err.println("ProfilerInterface.classLoadHook.DEBUG: " + className + ", classLoaderId: " + classLoaderId); // NOI18N
}
serialClientOperationsLock.beginTrans(true);
try {
boolean rootInstrumented = false;
String excMessage = null;
int instrType = getCurrentInstrType();
if (instrType == INSTR_NONE) {
return; // Instrumentation was turned off in the mean time
}
// bugfix for issue http://profiler.netbeans.org/issues/show_bug.cgi?id=65968
boolean resumeProfiling = false;
if (ThreadInfo.profilingSuspended()) {
ThreadInfo.suspendProfiling();
resumeProfiling = true;
}
try {
if (rootClassLoaded) { // if yes, it means instrumentation has been started
// [ian] why the following if???
if ((instrType != INSTR_RECURSIVE_FULL) && (instrType != INSTR_RECURSIVE_SAMPLED)
&& (instrType != INSTR_OBJECT_ALLOCATIONS) && (instrType != INSTR_OBJECT_LIVENESS)) {
if (!((instrType == INSTR_CODE_REGION)
&& className.equals(rootClassNames[ProfilingSessionStatus.CODE_REGION_CLASS_IDX]))) {
return; // Nothing to do
}
}
ThreadInfo ti = null;
if ((instrType == INSTR_RECURSIVE_FULL) || (instrType == INSTR_RECURSIVE_SAMPLED)) {
nClassLoads++;
ti = ProfilerRuntimeCPU.suspendCurrentThreadTimer(); // start blackout period
clientInstrStartTime = Timers.getCurrentTimeInCounts();
// We'll be unable to call resumeCurrentThreadTimer() right here, since here we are holding serialClientOperationsLock.
// The same lock is acquired when we dump the event buffer. So if here we call resumeTimer(), which calls writeEvent(),
// we can get into a deadlock if some other thread at this time is dumping the event buffer and tries to acquire that lock.
resumeTimer = true; // resume blackout period at the end
}
byte[] classFileBytes = getClassFileBytes(clazz, classLoaderId);
// send request to tool to instrument the bytecode
ClassLoadedCommand cmd = new ClassLoadedCommand(className,
ClassLoaderManager.getThisAndParentLoaderData(classLoaderId),
classFileBytes, (ti != null) ? ti.isInCallGraph() : false);
profilerServer.sendComplexCmdToClient(cmd);
// read response from tool that should contain the instrumented bytecode, and redefine the methods/classes
if (!getAndInstrumentClasses(false)) {
disableProfilerHooks();
return;
}
} else {
// in total inst scheme for CPU profiling we instrument everything
boolean rootWasLoaded = ((instrType == INSTR_RECURSIVE_FULL) || (instrType == INSTR_RECURSIVE_SAMPLED))
&& (status.instrScheme == INSTRSCHEME_TOTAL);
// No root classes have been loaded - check if it's one of them
if (!rootWasLoaded && !isRootClass(className)) {
return;
}
// This is a root class - proceed with requesting client for instrumented code.
nClassLoads++;
clientInstrStartTime = Timers.getCurrentTimeInCounts();
sendRootClassLoadedCommand(true);
if (!getAndInstrumentClasses(true)) {
disableProfilerHooks();
return;
}
// Note: it is important to have 'rootClassLoaded = true' here, i.e. *after* (not before) the call to getAndInstrumentClasses().
// It looks like some classes returned by getAllLoadedClasses() may be not completely initialized, and thus when we finally
// load them properly in instrumentMethodGroup() before intrumenting, they get initialized and classLoadHook is called for each
// of them. If rootClassLoaded is true, then for each such class a request is sent to the client, which wonders why it got a
// second class load event for the same class. Having rootClassLoaded not set until all such classes are loaded eliminates this
// issue. WARNING: may it happen that some really new class is loaded as a side effect of initializing of the classes described
// above? If so, it will be effectively lost. Need to try to come up with a test to confirm or prove this worry wrong.
rootInstrumented = true;
rootClassLoaded = true;
// This is done to avoid counting the time spent in instrumentation etc. upon root class load, but before our app (or actually
// data recording) started. That's because we use this internal statistics to calculate/verify the gross run time of the app.
ProfilerCalibrator.resetInternalStatsCollectors();
}
if (rootInstrumented || (excMessage != null)) {
AsyncMessageCommand cmd = null;
if (excMessage == null) {
cmd = new AsyncMessageCommand(true, INSTRUMENTATION_SUCCESSFUL_MSG);
} else {
cmd = new AsyncMessageCommand(false, excMessage);
}
profilerServer.sendComplexCmdToClient(cmd);
}
} finally {
if (resumeProfiling) {
ThreadInfo.resumeProfiling();
}
}
} finally { // end of synchronized(serialClientOperationsLock)
serialClientOperationsLock.endTrans();
}
if (resumeTimer) {
int instrType = getCurrentInstrType();
if ((instrType == INSTR_RECURSIVE_FULL) || (instrType == INSTR_RECURSIVE_SAMPLED)) {
ProfilerRuntimeCPU.resumeCurrentThreadTimer();
}
}
} finally {
threadInfo.inProfilingRuntimeMethod--;
}
}
private static void firstTimeMethodInvokeHook(char methodId) {
serialClientOperationsLock.beginTrans(true);
try {
int instrType = getCurrentInstrType();
if ((instrType != INSTR_RECURSIVE_FULL) && (instrType != INSTR_RECURSIVE_SAMPLED)) {
return; // Chances are that instrumentation is already stopped
}
clientInstrStartTime = Timers.getCurrentTimeInCounts();
MethodInvokedFirstTimeCommand cmd = new MethodInvokedFirstTimeCommand(methodId);
profilerServer.sendComplexCmdToClient(cmd);
if (!getAndInstrumentClasses(false)) {
disableProfilerHooks();
return;
}
// The following reset is done to avoid counting the time spent in instrumentation before data recording started.
if (nFirstMethodInvocations == 0) {
ProfilerCalibrator.resetInternalStatsCollectors();
}
nFirstMethodInvocations++;
} finally {
serialClientOperationsLock.endTrans();
}
}
private static int firstTimeVMObjectAlloc(String className, int definingClassLoaderId) {
GetClassIdResponse resp;
if (internalClassName(className)) {
return -1;
}
serialClientOperationsLock.beginTrans(true);
try {
GetClassIdCommand cmd = new GetClassIdCommand(className, definingClassLoaderId);
profilerServer.sendComplexCmdToClient(cmd);
resp = (GetClassIdResponse) profilerServer.getLastResponse();
} finally {
serialClientOperationsLock.endTrans();
}
if (resp.isOK()) {
int classId = resp.getClassId();
int instrClasses = classId+1;
if (instrClasses > status.getNInstrClasses()) {
status.updateAllocatedInstancesCountInfoInServer(instrClasses);
ProfilerRuntimeMemory.setAllocatedInstancesCountArray(status.getAllocatedInstancesCount());
}
return classId;
}
return -1;
}
private static void handleFakeInitRecursiveInstrumentationCommand() {
// Send a fake RootClassLoadedCommand to the client and get a reply from it. This is done to force initialization
// of all classes related to this operation. If this happens later, it can cause deadlock due to classLoadHook called upon
// loading of some of these classes, when classLoadHook is already locked to "serialize" class load events.
new HFIRIThread().start();
}
/**
* support for multiple roots needed by EJB work
* will check each class to see if it is a candidate to be a core class
*/
private static boolean hasAnyCoreClassNames(String[] classes) {
if (!(classes.length > 0)) {
return false;
}
for (int i = 0; i < classes.length; i++) {
if (isCoreClassName(classes[i])) {
return true;
}
}
return false; //none found...
}
private static void instrumentMethodGroupNow(InstrumentMethodGroupData imgb)
throws Exception {
//divided into 2 parts: cass loading and class instrumenting
Monitors.DeterminateProgress wholeProgress = Monitors.enterServerState(CommonConstants.SERVER_INITIALIZING, 2);
try {
instrumentMethodGroupCallThread = Thread.currentThread();
int res = 0;
long time = Timers.getCurrentTimeInCounts();
nNonEmptyInstrMethodGroupResponses++;
int nClasses = imgb.getNClasses();
String[] instrClassNames = imgb.getMethodClasses();
int[] instrClassLoaders = imgb.getClassLoaderIds();
int nMethods = imgb.getNMethods();
Class[] clazzes = new Class[nClasses];
byte[][] b = imgb.getReplacementClassFileBytes();
int k = 0;
Monitors.DeterminateProgress progress = Monitors.enterServerState(CommonConstants.SERVER_PREPARING, nClasses);
try {
for (int i = 0; i < nClasses; i++) {
progress.next();
clazzes[k] = ClassLoaderManager.getLoadedClass(instrClassNames[i], instrClassLoaders[i]);
if (clazzes[k] != null) {
if (b[k] == null) {
// An optimization to avoid overhead of creating and sending original class file bytes from client
// to server
if (instrClassLoaders[i] == 0) {
b[k] = ClassBytesLoader.getClassFileBytes(instrClassNames[i]);
}
if (b[k] == null) {
b[k] = getCachedClassFileBytes(clazzes[k]);
}
}
k++;
} else {
reportUnloadedClass(instrClassNames[i]);
int classesToMove = nClasses - k - 1;
System.arraycopy(clazzes, k + 1, clazzes, k, classesToMove);
System.arraycopy(b, k + 1, b, k, classesToMove);
}
}
if (k < nClasses) {
Class[] oldClazzes = clazzes;
clazzes = new Class[k];
System.arraycopy(oldClazzes, 0, clazzes, 0, k);
}
} finally {
progress = null;
Monitors.exitServerState();
}
wholeProgress.next();
redefineClasses(clazzes, imgb.getReplacementClassFileBytes());
time = Timers.getCurrentTimeInCounts() - time;
totalHotswappingTime += time;
if (time < minHotswappingTime) {
minHotswappingTime = time;
} else if (time > maxHotswappingTime) {
maxHotswappingTime = time;
}
instrumentMethodGroupCallThread = null;
} catch (Throwable t) {
if (t instanceof Classes.RedefineException) {
int nClasses = imgb.getNClasses();
String[] instrClassNames = imgb.getMethodClasses();
System.err.println("Profiler Agent Error: Redefinition failed for classes:"); // NOI18N
for (int i = 0; i < nClasses; i++) {
System.err.println(instrClassNames[i]);
}
System.err.println("Profiler Agent Error: with message: " + ((Classes.RedefineException) t).getMessage()); // NOI18N
byte[][] newBytes = imgb.getReplacementClassFileBytes();
for (int i = 0; i < nClasses; i++) {
String name = instrClassNames[i];
File outFile = new File(name + ".class"); // NOI18N
System.err.println("Debug: writing class file: " + name + ", into file: " + outFile.getPath()); // NOI18N
try {
FileOutputStream fos = new FileOutputStream(outFile);
fos.write(newBytes[i]);
fos.close();
} catch (IOException exc) {
System.err.println("error: " + exc + " writing class file: " + outFile.getPath()); // NOI18N
}
}
throw ((Classes.RedefineException) t);
} else {
java.io.StringWriter sw = new java.io.StringWriter();
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
t.printStackTrace(pw);
throw new Exception(MessageFormat.format(UNEXPECTED_EXCEPTION_MSG, new Object[] { t, sw.toString() }));
}
} finally {
instrumentMethodGroupCallThread = null;
Monitors.exitServerState();
}
}
private static void redefineClasses(Class[] classes, byte[][] bytecode) throws Classes.RedefineException {
Monitors.DeterminateProgress progress = Monitors.enterServerState(CommonConstants.SERVER_INSTRUMENTING,
(classes.length+REDEFINE_CHUNK_SIZE-1)/REDEFINE_CHUNK_SIZE);
try {
int index = 0;
Class[] chunkClasses = null;
byte[][] chunkBytecode = null;
while(index < classes.length) {
int size = Math.min(classes.length - index, REDEFINE_CHUNK_SIZE);
if(chunkClasses == null || chunkClasses.length != size ) {
chunkClasses = new Class[size];
chunkBytecode = new byte[size][];
}
System.arraycopy(classes, index, chunkClasses, 0, size);
System.arraycopy(bytecode, index, chunkBytecode, 0, size);
Classes.redefineClasses(chunkClasses, chunkBytecode);
progress.next();
index += size;
}
} finally {
Monitors.exitServerState();
}
}
private static boolean internalClassName(String name) {
return (serverInternalClassName(name)
|| // WARNING: sun.reflect.* are not really internal classes, but they may create too many problems by being loaded unexpectedly
// by our internal code and causing classLoadHook to be invoked recursively. At least we need sun.reflect.Generated* to be
// dismissed. Others could probably be less of a problem if ClassLoaderManager didn't use reflection.
(name.startsWith("sun.reflect.") && !name.startsWith("sun.reflect.GeneratedSerializationConstructorAccessor")
&& !name.startsWith("sun.reflect.GeneratedConstructorAccessor")) // NOI18N
|| name.startsWith("sun.instrument.") // NOI18N
// FIXME: below is a (hopefully temporary) fix to the strange problem showing up as ClassCircularityError when
// we try to profile PetStore with eager instrumentation scheme on Sun ONE AS 7. This makes the problem go away,
// but its root cause is still unclear to me.
|| name.equals("com.sun.enterprise.J2EESecurityManager") // NOI18N
// do not instrument classes created by Unsafe.defineAnonymousClass()
|| name.contains("/") // NOI18N
);
}
static boolean serverInternalClassName(String name) {
if (INSTRUMENT_JFLUID_CLASSES) {
return name.startsWith("org.netbeans.lib.profiler.server") || // NOI18N
name.startsWith("org.netbeans.lib.profiler.global") || // NOI18N
name.startsWith("org.netbeans.lib.profiler.wireprotocol"); // NOI18N
} else {
return name.startsWith(PROFILER_DOTTED_CLASS_PREFIX);
}
}
private static void reflectiveMethodInvokeHook(Method method) {
serialClientOperationsLock.beginTrans(true);
try {
if (reflectMethods.containsKey(method)) {
return;
}
ProfilerRuntimeCPU.suspendCurrentThreadTimer();
reflectMethods.put(method, null);
Class> clazz = method.getDeclaringClass();
String className = clazz.getName();
String methodName = method.getName();
Class[] paramTypes = method.getParameterTypes();
StringBuffer sb = new StringBuffer();
sb.append('(');
for (int i = 0; i < paramTypes.length; i++) {
appendTypeName(sb, paramTypes[i]);
}
sb.append(')');
appendTypeName(sb, method.getReturnType());
String methodSignature = sb.toString();
clientInstrStartTime = Timers.getCurrentTimeInCounts();
MethodLoadedCommand cmd = new MethodLoadedCommand(className, ClassLoaderManager.registerLoader(clazz), methodName,
methodSignature);
profilerServer.sendComplexCmdToClient(cmd);
if (!getAndInstrumentClasses(false)) {
disableProfilerHooks();
return;
}
ProfilerRuntimeCPU.resumeCurrentThreadTimer();
} finally {
serialClientOperationsLock.endTrans();
}
}
private static void reportUnloadedClass(String className) {
System.err.println(ENGINE_WARNING + "target VM cannot load class to instrument " + className); // NOI18N
System.err.println("*** probably it has been unloaded recently"); // NOI18N
}
private static void reportCacheMiss(final byte[] bytes, final Class clazz) {
if (bytes == null) {
System.err.println(ENGINE_WARNING + "Failed to lookup cached class " + clazz.getName()); // NOI18N
}
}
private static byte[] getCachedClassFileBytes(Class clazz) {
byte[] bytes = Classes.getCachedClassFileBytes(clazz);
reportCacheMiss(bytes, clazz);
return bytes;
}
private static void cacheLoadedClass(Class clazz) {
Class[] classes = new Class[2];
classes[0] = clazz;
cacheLoadedClasses(classes,1);
}
private static void cacheLoadedClasses(Class[] nonSystemClasses, int nonSystemIndex) {
if (DEBUG) System.out.println("Caching "+nonSystemIndex+" classes");
nonSystemClasses[nonSystemIndex++] = ProfilerInterface.InitiateProfilingThread.class;
Classes.cacheLoadedClasses(nonSystemClasses,nonSystemIndex);
}
/* Get class file bytes if they are available, i.e. if the class is loaded by a custom classloader
* If remote profiling is used, get these class file bytes from system classpath
* classLoaderId = 0 means that it is a system or bootstrap classloader
*/
private static byte[] getClassFileBytes(Class clazz, int classLoaderId) {
return getClassFileBytes(clazz, classLoaderId, true);
}
private static byte[] getClassFileBytes(Class clazz, int classLoaderId, boolean force) {
byte[] classFileBytes = null;
URL classURL = null;
if (classLoaderId > 0 || (classURL = ClassBytesLoader.getClassFileURL(clazz.getName())) == null) {
classFileBytes = Classes.getCachedClassFileBytes(clazz);
if (classFileBytes == null) {
if (DEBUG) {
System.err.println("Cannot get classbytes for "+clazz.getName()+" loader "+classLoaderId);
}
if (!force) return EMPTY;
cacheLoadedClass(clazz);
classFileBytes = getCachedClassFileBytes(clazz);
if (classFileBytes == null) {
if (Platform.getJDKVersionNumber() != Platform.JDK_CVM) {
System.err.println("***Profiler agent warning: could not get .class file for a synthetic class " + clazz.getName()
+ " in ProfilerInterface.getClassFileBytes"); // NOI18N
}
}
}
} else if (status.remoteProfiling || Platform.getJDKVersionNumber() >= Platform.JDK_19) {
classFileBytes = ClassBytesLoader.getClassFileBytes(classURL);
}
return classFileBytes;
}
private static void sendRootClassLoadedCommand(boolean doGetLoadedClasses) {
if (doGetLoadedClasses) {
getLoadedClasses(); // Otherwise we know loadedClassesArray has already been initialized
}
int len = loadedClassesArray.length;
String[] loadedClassNames = new String[len];
int[] loaders = new int[len];
byte[][] cachedClassFileBytes = new byte[len][];
int[] loadedClassesSuper = new int[len];
int[][] loadedClassesInterfaces = new int[len][];
int instrType = getCurrentInstrType();
boolean isLazyInstrType = profilerServer.isDynamic()
&& (instrType == INSTR_RECURSIVE_FULL || instrType == INSTR_RECURSIVE_SAMPLED)
&& status.instrScheme == INSTRSCHEME_LAZY
&& !instrSpawnedThreads;
boolean isMemoryProfiling = profilerServer.isDynamic()
&& (instrType == INSTR_OBJECT_ALLOCATIONS || instrType == INSTR_OBJECT_LIVENESS);
Map classIndex = new HashMap(loadedClassesArray.length*4/3);
Class[] classesArray = new Class[len];
for (int i = 0; i < len; i++) {
Class> clazz = getOrdinaryClass(loadedClassesArray[i]);
if (clazz == null) {
// class was unloaded or has special name
continue;
}
int index = classIndex.size();
classesArray[index] = clazz;
loaders[index] = loadedClassesLoaders[i];
classIndex.put(clazz, index);
}
for (int i = 0; i < classIndex.size(); i++) {
Class> clazz = classesArray[i];
String name = clazz.getName();
boolean forceClassFile = (!isLazyInstrType && !isMemoryProfiling) || isRootClass(name);
loadedClassNames[i] = name;
cachedClassFileBytes[i] = getClassFileBytes(clazz, loaders[i], forceClassFile);
if (!forceClassFile) {
Class superClass = clazz.getSuperclass();
Class[] interfaces = clazz.getInterfaces();
if (superClass != null) {
Integer clsId = (Integer) classIndex.get(superClass);
if (clsId != null) {
loadedClassesSuper[i] = clsId.intValue();
} else {
//System.out.println("Super class of class "+name+" not found: "+superClass.getName());
loadedClassesSuper[i] = -1;
}
} else {
loadedClassesSuper[i] = -1;
}
loadedClassesInterfaces[i] = new int[interfaces.length];
for (int j = 0; j < interfaces.length; j++) {
Integer clsId = (Integer)classIndex.get(interfaces[j]);
if (clsId != null) {
loadedClassesInterfaces[i][j] = clsId.intValue();
} else {
//System.out.println("Interface of class "+name+" not found: "+interfaces[j].getName());
loadedClassesSuper[i] = -1;
}
}
}
}
RootClassLoadedCommand cmd = new RootClassLoadedCommand(loadedClassNames, loaders, cachedClassFileBytes,
loadedClassesSuper, loadedClassesInterfaces, classIndex.size(),
ClassLoaderManager.getParentLoaderIdTable());
profilerServer.sendComplexCmdToClient(cmd);
}
private static Class getOrdinaryClass(WeakReference classRef) {
Class> clazz = (Class) classRef.get();
if (clazz != null) {
String name = clazz.getName();
if (!name.startsWith("[") && !internalClassName(name)) {
return clazz;
}
}
return null;
}
private static void updateInstrClassAndMethodNames(InstrumentMethodGroupData imgb, boolean firstTime) {
status.beginTrans(false);
try {
switch (getCurrentInstrType()) {
case INSTR_RECURSIVE_FULL:
case INSTR_RECURSIVE_SAMPLED:
status.updateInstrMethodsInfo(imgb.getNClasses(), imgb.getNMethods(), null, null, null, null, null,
imgb.getInstrMethodLeaf());
ProfilerRuntimeCPU.setInstrMethodsInvoked(status.getInstrMethodInvoked());
break;
case INSTR_OBJECT_ALLOCATIONS:
case INSTR_OBJECT_LIVENESS:
status.updateAllocatedInstancesCountInfoInServer(imgb.getAddInfo());
ProfilerRuntimeMemory.setAllocatedInstancesCountArray(status.getAllocatedInstancesCount());
break;
}
} finally {
status.endTrans();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy