package rocks.inspectit.agent.java.instrumentation; import java.lang.instrument.Instrumentation; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.collections.CollectionUtils; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import com.google.common.collect.HashMultiset; import rocks.inspectit.agent.java.Agent; import rocks.inspectit.agent.java.IThreadTransformHelper; import rocks.inspectit.agent.java.analyzer.impl.ClassHashHelper; import rocks.inspectit.agent.java.event.AgentMessagesReceivedEvent; import rocks.inspectit.agent.java.util.ClassUtil; import rocks.inspectit.shared.all.communication.message.IAgentMessage; import rocks.inspectit.shared.all.communication.message.UpdatedInstrumentationMessage; import rocks.inspectit.shared.all.instrumentation.config.impl.InstrumentationDefinition; import rocks.inspectit.shared.all.spring.logger.Log; /** * Handles incoming {@link UpdatedInstrumentationMessage}s and triggers a retransformation if * necessary. * * @author Marius Oehler * */ @Component public class RetransformManager implements ApplicationListener<AgentMessagesReceivedEvent>, IInstrumentationAware { /** * The logger for this class. */ @Log private Logger log; /** * The used {@link Instrumentation}. */ private Instrumentation instrumentation; /** * {@link IClassHashHelper}. */ @Autowired private ClassHashHelper classHashHelper; /** * The {@link IThreadTransformHelper}. */ @Autowired private IThreadTransformHelper threadTransformHelper; /** * {@inheritDoc} */ @Override public void setInstrumentation(Instrumentation instrumentation) { if (log.isDebugEnabled()) { log.debug("Assign instrumentation to retransform manager."); } this.instrumentation = instrumentation; } /** * {@inheritDoc} */ @Override public void onApplicationEvent(AgentMessagesReceivedEvent event) { if (event == null) { if (log.isDebugEnabled()) { log.debug("A 'null' event will not be processed."); } return; } // When we update to Spring v4, we should use @Conditional that the RetransformationManager // is not running if retransformation is not used. if (!Agent.agent.isUsingRetransformation()) { if (log.isInfoEnabled()) { log.info("Retransformation is disabled by the used retransformation strategy."); } return; } List<InstrumentationDefinition> instrumentationDefinitions = getInstrumentatioDefinitions(event.getAgentMessages()); if (CollectionUtils.isEmpty(instrumentationDefinitions)) { return; } // remove out-dated duplicates Collection<InstrumentationDefinition> cleanedInstrumentationDefinitions = removeOutdatedInstrumentationDefinitions(instrumentationDefinitions); processInstrumentationDefinitions(cleanedInstrumentationDefinitions); } /** * Returns a {@link Collection} containing all received {@link InstrumentationDefinition} which * are contained in the given {@link IAgentMessage}s. * * @param agentMessages * the {@link IAgentMessage}s * @return all existing {@link InstrumentationDefinition}s */ private List<InstrumentationDefinition> getInstrumentatioDefinitions(Collection<IAgentMessage<?>> agentMessages) { List<InstrumentationDefinition> instrumentationDefinitions = new ArrayList<InstrumentationDefinition>(); for (IAgentMessage<?> message : agentMessages) { if (message instanceof UpdatedInstrumentationMessage) { instrumentationDefinitions.addAll(((UpdatedInstrumentationMessage) message).getMessageContent()); } } return instrumentationDefinitions; } /** * Removes duplicate {@link InstrumentationDefinition}s which are out-dated or would have been * overwritten by a newer {@link InstrumentationDefinition}. * * @param instrumentationDefinitions * the {@link InstrumentationDefinition}s of union * @return {@link Collection} of union {@link InstrumentationDefinition}s */ private Collection<InstrumentationDefinition> removeOutdatedInstrumentationDefinitions(List<InstrumentationDefinition> instrumentationDefinitions) { Map<String, InstrumentationDefinition> definitionMap = new HashMap<String, InstrumentationDefinition>(); for (InstrumentationDefinition iDefinition : instrumentationDefinitions) { definitionMap.put(iDefinition.getClassName(), iDefinition); } return definitionMap.values(); } /** * Process the given [@link {@link InstrumentationDefinition}s. In this case, they are getting * registered on the respective class, subsequently, the classes are getting retransformed. * * @param instrumentationDefinitions * {@link Collection} of {@link InstrumentationDefinition} */ private void processInstrumentationDefinitions(Collection<InstrumentationDefinition> instrumentationDefinitions) { if (log.isInfoEnabled()) { log.info("Trying to retransform {} class(es)", instrumentationDefinitions.size()); } Collection<Class<?>> classesToRetransform = new ArrayList<Class<?>>(); // create map of instrumentation definitions (for fast look-up) Map<String, InstrumentationDefinition> instrumentationDefinitionMap = new HashMap<String, InstrumentationDefinition>(); for (InstrumentationDefinition definition : instrumentationDefinitions) { instrumentationDefinitionMap.put(definition.getClassName(), definition); // register new implementation classHashHelper.registerInstrumentationDefinition(definition.getClassName(), definition); } HashMultiset<String> classLoaderMultiset = HashMultiset.<String> create(); Class<?>[] loadedClasses = instrumentation.getAllLoadedClasses(); for (Class<?> clazz : loadedClasses) { try { if (!instrumentation.isModifiableClass(clazz)) { continue; } String className = clazz.getName(); if (instrumentationDefinitionMap.containsKey(className)) { classesToRetransform.add(clazz); String classLoaderName = ClassUtil.getClassLoaderName(clazz, "unknown classloader"); classLoaderMultiset.add(classLoaderName); if (log.isDebugEnabled()) { log.debug("|-{} : {} (is instrumented: {})", className, classLoaderName, !instrumentationDefinitionMap.get(className).getMethodInstrumentationConfigs().isEmpty()); } } } catch (Exception e) { // This catch prevents the thread/loop from crashing. if (log.isDebugEnabled()) { log.debug("Failed to add class to the list of classes to retransform.", e); } } } if (log.isInfoEnabled()) { log.info("|-Going to retransform {} loaded class(es) of {} class loader(s).", classLoaderMultiset.size(), classLoaderMultiset.elementSet().size()); } if (CollectionUtils.isNotEmpty(classesToRetransform)) { try { threadTransformHelper.setThreadTransformDisabled(false); instrumentation.retransformClasses(classesToRetransform.toArray(new Class[classesToRetransform.size()])); } catch (Exception e) { if (log.isErrorEnabled()) { log.error("Failed to triggering retransformation of loaded classes.", e); } } finally { threadTransformHelper.setThreadTransformDisabled(true); } } if (log.isInfoEnabled()) { log.info("|-Retransformation finished."); } } }