package rocks.inspectit.server.instrumentation.classcache; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import org.apache.commons.collections.CollectionUtils; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import rocks.inspectit.server.instrumentation.config.ClassCacheSearchNarrower; import rocks.inspectit.server.instrumentation.config.applier.IInstrumentationApplier; import rocks.inspectit.server.instrumentation.config.applier.RemoveAllInstrumentationApplier; import rocks.inspectit.shared.all.instrumentation.classcache.ClassType; import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableClassType; import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableType; import rocks.inspectit.shared.all.instrumentation.config.impl.AgentConfig; import rocks.inspectit.shared.all.instrumentation.config.impl.InstrumentationDefinition; import rocks.inspectit.shared.all.spring.logger.Log; import rocks.inspectit.shared.cs.ci.assignment.AbstractClassSensorAssignment; /** * Instrumentation service for the {@link ClassCache}. This class is responsible for adding, getting * or removing the instrumentation points to/from class types. Also provides the * {@link #addAndGetInstrumentationResult(ImmutableClassType, AgentConfig, Collection)} method for * easy add/get instrumentation points for a single type. * * @author Ivan Senic * */ @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Lazy public class ClassCacheInstrumentation { /** * Log of the class. */ @Log Logger log; /** * {@link ClassCache} instrumentation service belongs to. */ private ClassCache classCache; /** * {@link ClassCacheSearchNarrower} to help when analyzing the whole class cache. */ @Autowired private ClassCacheSearchNarrower searchNarrower; /** * Init the {@link ClassCacheLookup}. * * @param classcache * {@link ClassCache} it belongs to. */ public void init(ClassCache classcache) { this.classCache = classcache; } /** * Directly adds instrumentation points the given {@link ImmutableClassType} and return * instrumentation result if process added instrumentation points. Otherwise this method returns * <code>null</code> and this means that type has no instrumentation. * * @param type * {@link ImmutableClassType} to check. * @param agentConfiguration * configuration to use * @param appliers * Collection of {@link IInstrumentationApplier}s to process type against. * @return {@link InstrumentationDefinition} if the class has been instrumented, otherwise * <code>null</code>. */ public InstrumentationDefinition addAndGetInstrumentationResult(final ImmutableClassType type, final AgentConfig agentConfiguration, final Collection<IInstrumentationApplier> appliers) { if (!type.isInitialized()) { return null; } try { return classCache.executeWithWriteLock(new Callable<InstrumentationDefinition>() { @Override public InstrumentationDefinition call() throws Exception { ClassType classType = (ClassType) type; boolean added = false; for (IInstrumentationApplier applier : appliers) { added |= applier.addInstrumentationPoints(agentConfiguration, classType); } if (added) { return createInstrumentationResult(type); } else { return null; } } }); } catch (Exception e) { log.error("Error occurred while trying to instrument class type from the class cache.", e); return null; } } /** * Collects instrumentation points for all the initialized class types in the class cache. * * @return Collection holding all the {@link InstrumentationDefinition}. */ public Collection<InstrumentationDefinition> getInstrumentationResults() { return getInstrumentationResults(classCache.getLookupService().findAll()); } /** * Collects instrumentation points for given types in the given class cache. Only initialized * class types will be checked. * * @param types * to get instrumentation results * @return Collection holding all the {@link InstrumentationDefinition}. */ public Collection<InstrumentationDefinition> getInstrumentationResults(final Collection<? extends ImmutableType> types) { if (CollectionUtils.isEmpty(types)) { return Collections.emptyList(); } try { return classCache.executeWithReadLock(new Callable<Collection<InstrumentationDefinition>>() { @Override public Collection<InstrumentationDefinition> call() throws Exception { Collection<InstrumentationDefinition> results = new ArrayList<>(); for (ImmutableType type : types) { if (type.isInitialized() && type.isClass()) { ImmutableClassType immutableClassType = type.castToClass(); InstrumentationDefinition instrumentationResult = createInstrumentationResult(immutableClassType); if (null != instrumentationResult) { results.add(instrumentationResult); } } } return results; } }); } catch (Exception e) { log.error("Error occurred while trying to collect instrumentation results from the class cache.", e); return Collections.emptyList(); } } /** * Collects instrumentation results for all the initialized class types in the class cache. The * return map will contain a key-value pairs where key is set of hashes that correspond to the * instrumentation result (value). * * @return Map holding key-value pairs that connect set of hashes to the * {@link InstrumentationDefinition}. */ public Map<Collection<String>, InstrumentationDefinition> getInstrumentationResultsWithHashes() { return getInstrumentationResultsWithHashes(classCache.getLookupService().findAll()); } /** * Collects instrumentation results for for given types in the given class cache. Only * initialized class types will be checked. The return map will contain a key-value pairs where * key is set of hashes that correspond to the instrumentation result (value). * * @param types * to get instrumentation results * @return Map holding key-value pairs that connect set of hashes to the * {@link InstrumentationDefinition}. */ public Map<Collection<String>, InstrumentationDefinition> getInstrumentationResultsWithHashes(final Collection<? extends ImmutableType> types) { if (CollectionUtils.isEmpty(types)) { return Collections.emptyMap(); } try { return classCache.executeWithReadLock(new Callable<Map<Collection<String>, InstrumentationDefinition>>() { @Override public Map<Collection<String>, InstrumentationDefinition> call() throws Exception { Map<Collection<String>, InstrumentationDefinition> map = new HashMap<>(); for (ImmutableType type : types) { if (type.isInitialized() && type.isClass()) { ImmutableClassType immutableClassType = type.castToClass(); InstrumentationDefinition instrumentationResult = createInstrumentationResult(immutableClassType); if (null != instrumentationResult) { map.put(immutableClassType.getHashes(), instrumentationResult); } } } return map; } }); } catch (Exception e) { log.error("Error occurred while trying to collect instrumentation results (with hashes) from the class cache.", e); return Collections.emptyMap(); } } /** * Processes all types in the class cache in order to add instrumentation points for the given * agent configuration. Instrumentation points added will be created based on given * {@link IInstrumentationApplier}s. * * @param agentConfiguration * configuration to use * @param appliers * Collection of {@link IInstrumentationApplier}s to process types against. * @return Returns collection of class types to which the instrumentation points have been * added. */ public Collection<? extends ImmutableClassType> addInstrumentationPoints(AgentConfig agentConfiguration, Collection<IInstrumentationApplier> appliers) { Collection<ImmutableClassType> results = new ArrayList<>(0); for (IInstrumentationApplier applier : appliers) { AbstractClassSensorAssignment<?> assignment = applier.getSensorAssignment(); Collection<? extends ImmutableType> types; if (null != assignment) { types = searchNarrower.narrowByClassSensorAssignment(classCache, assignment); } else { types = classCache.getLookupService().findAll(); } Collection<? extends ImmutableClassType> instrumented = addInstrumentationPoints(types, agentConfiguration, appliers); if (CollectionUtils.isNotEmpty(instrumented)) { results.addAll(instrumented); } } return results; } /** * Processes given types in the class cache in order to add instrumentation points. * Instrumentation points added will be created based on given {@link IInstrumentationApplier}s. * * @param types * to add instrumentation points based on given configuration and environment. * @param agentConfiguration * configuration to use * @param appliers * Collection of {@link IInstrumentationApplier}s to process types against. * @return Returns collection of class types to which the instrumentation points have been * added. */ public Collection<? extends ImmutableClassType> addInstrumentationPoints(final Collection<? extends ImmutableType> types, final AgentConfig agentConfiguration, final Collection<IInstrumentationApplier> appliers) { if (CollectionUtils.isEmpty(types)) { return Collections.emptyList(); } try { return classCache.executeWithWriteLock(new Callable<Collection<? extends ImmutableClassType>>() { @Override public Collection<? extends ImmutableClassType> call() throws Exception { Collection<ImmutableClassType> results = new ArrayList<>(); for (ImmutableType type : types) { // only initialized class types can have instrumentation points if (type.isClass() && type.isInitialized()) { ClassType classType = (ClassType) type.castToClass(); boolean added = false; for (IInstrumentationApplier applier : appliers) { added |= applier.addInstrumentationPoints(agentConfiguration, classType); } if (added) { results.add(type.castToClass()); } } } return results; } }); } catch (Exception e) { log.error("Error occurred while trying to add instrumentation points from the class cache.", e); return Collections.emptyList(); } } /** * Removes all instrumentation points from the class cache. * * @return types from which instrumentation points have been removed */ public Collection<? extends ImmutableClassType> removeInstrumentationPoints() { return removeInstrumentationPoints(classCache.getLookupService().findAll()); } /** * Removes all instrumentation point from the given types. * * @param types * to remove instrumentation points * @return types from which instrumentation points have been removed */ public Collection<? extends ImmutableClassType> removeInstrumentationPoints(final Collection<? extends ImmutableType> types) { return removeInstrumentationPoints(types, Collections.<IInstrumentationApplier> singleton(RemoveAllInstrumentationApplier.getInstance())); } /** * Removes all instrumentation point from the given types that that might be created as result * of given instrumentation appliers. * * @param types * to remove instrumentation points * @param instrumentationAppliers * Collection of {@link IInstrumentationApplier}s to process types against. * @return types from which instrumentation points have been removed */ public Collection<? extends ImmutableClassType> removeInstrumentationPoints(final Collection<? extends ImmutableType> types, final Collection<IInstrumentationApplier> instrumentationAppliers) { if (CollectionUtils.isEmpty(types)) { return Collections.emptyList(); } try { return classCache.executeWithWriteLock(new Callable<Collection<? extends ImmutableClassType>>() { @Override public Collection<? extends ImmutableClassType> call() throws Exception { Collection<ImmutableClassType> results = new ArrayList<>(); for (ImmutableType type : types) { // only initialized class types can have instrumentation points if (type.isClass() && type.isInitialized()) { ClassType classType = (ClassType) type.castToClass(); boolean added = false; for (IInstrumentationApplier applier : instrumentationAppliers) { added |= applier.removeInstrumentationPoints(classType); } if (added) { results.add(type.castToClass()); } } } return results; } }); } catch (Exception e) { log.error("Error occurred while trying to remove specific instrumentation points from the class cache.", e); return Collections.emptyList(); } } /** * Creates {@link InstrumentationDefinition} for the given {@link ImmutableClassType}. Returns * <code>null</code> if class has no instrumentation points. * * @param classType * {@link ImmutableClassType} to create {@link InstrumentationDefinition} for. * @return {@link InstrumentationDefinition} for this class type or <code>null</code> if class * has no instrumentation points. */ private InstrumentationDefinition createInstrumentationResult(ImmutableClassType classType) { // if there are no instrumentation points return null if (!classType.hasInstrumentationPoints()) { return null; } InstrumentationDefinition instrumentationResult = new InstrumentationDefinition(classType.getFQN()); if (classType.hasInstrumentationPoints()) { instrumentationResult.setMethodInstrumentationConfigs(classType.getInstrumentationPoints()); } return instrumentationResult; } }