package rocks.inspectit.server.instrumentation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import org.apache.commons.collections.CollectionUtils; import org.slf4j.Logger; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import rocks.inspectit.server.event.AgentDeletedEvent; import rocks.inspectit.server.event.AgentRegisteredEvent; import rocks.inspectit.server.instrumentation.classcache.ClassCache; import rocks.inspectit.server.instrumentation.classcache.ClassCacheModificationException; import rocks.inspectit.server.instrumentation.config.AgentCacheEntry; import rocks.inspectit.server.instrumentation.config.ConfigurationHolder; import rocks.inspectit.server.instrumentation.config.ConfigurationResolver; import rocks.inspectit.server.instrumentation.config.applier.JmxMonitoringApplier; import rocks.inspectit.shared.all.exception.BusinessException; import rocks.inspectit.shared.all.exception.enumeration.AgentManagementErrorCodeEnum; import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableClassType; import rocks.inspectit.shared.all.instrumentation.classcache.ImmutableType; import rocks.inspectit.shared.all.instrumentation.classcache.Type; import rocks.inspectit.shared.all.instrumentation.config.impl.AgentConfig; import rocks.inspectit.shared.all.instrumentation.config.impl.InstrumentationDefinition; import rocks.inspectit.shared.all.instrumentation.config.impl.JmxAttributeDescriptor; import rocks.inspectit.shared.all.spring.logger.Log; import rocks.inspectit.shared.cs.ci.Environment; import rocks.inspectit.shared.cs.cmr.service.IRegistrationService; /** * Manager for handling the instrumentation decisions for the {@link Type}s that are send by the * agent. Also handles the agent registration. * * @author Ivan Senic * */ @Component public class NextGenInstrumentationManager implements ApplicationListener<AgentDeletedEvent> { /** * Logger for the class. */ @Log Logger log; /** * Factory for creating new class caches. */ @Autowired private ObjectFactory<ClassCache> classCacheFactory; /** * Factory for creating new configuration holder. */ @Autowired private ObjectFactory<ConfigurationHolder> configurationHolderFactory; /** * Registration service. */ @Autowired private IRegistrationService registrationService; /** * Configuration resolver. */ @Autowired private ConfigurationResolver configurationResolver; /** * Executor for dealing with configuration updates. */ @Autowired @Qualifier("agentServiceExecutorService") private ExecutorService executor; /** * Event publisher. */ @Autowired private ApplicationEventPublisher eventPublisher; /** * Cache for the agents and it's used class cache, environments and configurations. */ private final ConcurrentHashMap<Long, AgentCacheEntry> agentCacheMap = new ConcurrentHashMap<>(); /** * {@inheritDoc} */ public AgentConfig register(List<String> definedIPs, String agentName, String version) throws BusinessException { // load environment for the agent Environment environment = configurationResolver.getEnvironmentForAgent(definedIPs, agentName); // if environment load is success register agent final long id = registrationService.registerPlatformIdent(definedIPs, agentName, version); // get or create the agent cache entry AgentCacheEntry agentCacheEntry = getAgentCacheEntry(id); ClassCache classCache = agentCacheEntry.getClassCache(); ConfigurationHolder configurationHolder = agentCacheEntry.getConfigurationHolder(); // check if this agent was already registered and we have environment Environment cachedEnvironment = configurationHolder.getEnvironment(); // if we have same environment and configuration return configuration if (configurationHolder.isInitialized() && Objects.equals(environment, cachedEnvironment)) { AgentConfig agentConfiguration = configurationHolder.getAgentConfiguration(); Map<Collection<String>, InstrumentationDefinition> initial = classCache.getInstrumentationService().getInstrumentationResultsWithHashes(); agentConfiguration.setInitialInstrumentationResults(initial); agentConfiguration.setClassCacheExistsOnCmr(true); return agentConfiguration; } // else kick the configuration creator update configurationHolder.update(environment, id); // publish agent registered event executor.submit(new Runnable() { @Override public void run() { AgentRegisteredEvent registeredEvent = new AgentRegisteredEvent(this, id); eventPublisher.publishEvent(registeredEvent); } }); // return configuration return configurationHolder.getAgentConfiguration(); } /** * {@inheritDoc} */ public void unregister(long platformIdent) throws BusinessException { registrationService.unregisterPlatformIdent(platformIdent); } /** * {@inheritDoc} */ public InstrumentationDefinition analyze(long platformIdent, String hash, Type sentType) throws BusinessException { AgentCacheEntry agentCacheEntry = agentCacheMap.get(Long.valueOf(platformIdent)); if (null == agentCacheEntry) { throw new BusinessException("Instrumenting class with hash '" + hash + "' for the agent with id=" + platformIdent, AgentManagementErrorCodeEnum.AGENT_DOES_NOT_EXIST); } ClassCache classCache = agentCacheEntry.getClassCache(); ImmutableType type = classCache.getLookupService().findByHash(hash); // if does not exists, parse, merge & configure instrumentation points if (null == type) { try { classCache.getModificationService().merge(sentType); // get real object after merging type = classCache.getLookupService().findByHash(hash); } catch (ClassCacheModificationException e) { log.error("Type can not be analyzed due to the exception during merging.", e); return null; } } // no need to do anything with types that are not classes // just return if (!type.isClass()) { return null; } ImmutableClassType classType = type.castToClass(); ConfigurationHolder configurationHolder = agentCacheEntry.getConfigurationHolder(); // if configuration holder is for any reason not initialized we can not define if it can be // instrumented if (!configurationHolder.isInitialized()) { return null; } return classCache.getInstrumentationService().addAndGetInstrumentationResult(classType, configurationHolder.getAgentConfiguration(), configurationHolder.getInstrumentationAppliers()); } /** * Generates {@link RefreshInstrumentationTimestampsJob} for the given method IDs. * * @param platformId * Id of the platform. * @param methodToSensorMap * methods being instrumented on agent */ public void instrumentationApplied(final long platformId, final Map<Long, long[]> methodToSensorMap) { // Asynchronously refresh idents executor.submit(new Runnable() { @Override public void run() { for (Entry<Long, long[]> entry : methodToSensorMap.entrySet()) { long methodId = entry.getKey().longValue(); long[] sensorIds = entry.getValue(); for (long sensorID : sensorIds) { registrationService.addSensorTypeToMethod(platformId, sensorID, methodId); } } } }); } /** * Analyzes the given {@link JmxAttributeDescriptor} and decides which ones will be monitored, * based on the current configuration. * * @param platformIdent * Id of the agent sending the descriptors. * @param attributeDescriptors * {@link JmxAttributeDescriptor}s that are available on the agent for monitoring. * @return Collection of {@link JmxAttributeDescriptor} to be monitored with their correctly set * IDs. * @throws BusinessException * If agent with given ID does not exist. */ public Collection<JmxAttributeDescriptor> analyzeJmxAttributes(long platformIdent, Collection<JmxAttributeDescriptor> attributeDescriptors) throws BusinessException { AgentCacheEntry agentCacheEntry = agentCacheMap.get(Long.valueOf(platformIdent)); if (null == agentCacheEntry) { throw new BusinessException("Analyzing the JMX attributes for the agent with id=" + platformIdent, AgentManagementErrorCodeEnum.AGENT_DOES_NOT_EXIST); } // if nothing sent do nothing if (CollectionUtils.isEmpty(attributeDescriptors)) { return Collections.emptyList(); } ConfigurationHolder configurationHolder = agentCacheEntry.getConfigurationHolder(); // if configuration holder is for any reason not initialized we can not define if we monitor // anything if (!configurationHolder.isInitialized()) { return Collections.emptyList(); } // if we have no appliers return as well Collection<JmxMonitoringApplier> jmxMonitoringAppliers = configurationHolder.getJmxMonitoringAppliers(); if (CollectionUtils.isEmpty(jmxMonitoringAppliers)) { return Collections.emptyList(); } Collection<JmxAttributeDescriptor> results = new ArrayList<>(); for (JmxAttributeDescriptor descriptor : attributeDescriptors) { for (JmxMonitoringApplier applier : jmxMonitoringAppliers) { if (applier.addMonitoringPoint(configurationHolder.getAgentConfiguration(), descriptor)) { results.add(descriptor); break; } } } return results; } /** * {@inheritDoc} */ @Override public void onApplicationEvent(AgentDeletedEvent event) { agentCacheMap.remove(event.getPlatformId()); } /** * Returns agent cache entry for the agent. * * @param platformIdent * Agent id. * @return {@link AgentCacheEntry} */ private AgentCacheEntry getAgentCacheEntry(long platformIdent) { AgentCacheEntry agentCacheEntry = agentCacheMap.get(Long.valueOf(platformIdent)); if (null == agentCacheEntry) { ClassCache classCache = classCacheFactory.getObject(); ConfigurationHolder configurationHolder = configurationHolderFactory.getObject(); agentCacheEntry = new AgentCacheEntry(platformIdent, classCache, configurationHolder); AgentCacheEntry existing = agentCacheMap.putIfAbsent(Long.valueOf(platformIdent), agentCacheEntry); if (null != existing) { agentCacheEntry = existing; } } return agentCacheEntry; } /** * Gets {@link #agentCacheMap}. * * @return {@link #agentCacheMap} */ public Map<Long, AgentCacheEntry> getAgentCacheMap() { return Collections.unmodifiableMap(agentCacheMap); } }