/* * Copyright 2011-2017 the original author or authors. * * Licensed 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.glowroot.agent.init; import java.io.File; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.management.ManagementFactory; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; import java.util.jar.JarFile; import javax.annotation.Nullable; import com.google.common.base.Joiner; import com.google.common.base.Supplier; import com.google.common.base.Ticker; import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glowroot.agent.api.internal.GlowrootService; import org.glowroot.agent.collector.Collector; import org.glowroot.agent.config.ConfigService; import org.glowroot.agent.config.PluginCache; import org.glowroot.agent.config.PluginDescriptor; import org.glowroot.agent.impl.Aggregator; import org.glowroot.agent.impl.ConfigServiceImpl; import org.glowroot.agent.impl.GlowrootServiceImpl; import org.glowroot.agent.impl.ServiceRegistryImpl; import org.glowroot.agent.impl.ServiceRegistryImpl.ConfigServiceFactory; import org.glowroot.agent.impl.StackTraceCollector; import org.glowroot.agent.impl.TimerNameCache; import org.glowroot.agent.impl.TransactionCollector; import org.glowroot.agent.impl.TransactionRegistry; import org.glowroot.agent.impl.TransactionServiceImpl; import org.glowroot.agent.impl.UserProfileScheduler; import org.glowroot.agent.live.LiveAggregateRepositoryImpl; import org.glowroot.agent.live.LiveJvmServiceImpl; import org.glowroot.agent.live.LiveTraceRepositoryImpl; import org.glowroot.agent.live.LiveWeavingServiceImpl; import org.glowroot.agent.util.LazyPlatformMBeanServer; import org.glowroot.agent.util.OptionalService; import org.glowroot.agent.util.ThreadAllocatedBytes; import org.glowroot.agent.util.Tickers; import org.glowroot.agent.weaving.AdviceCache; import org.glowroot.agent.weaving.AnalyzedWorld; import org.glowroot.agent.weaving.IsolatedWeavingClassLoader; import org.glowroot.agent.weaving.PreInitializeWeavingClasses; import org.glowroot.agent.weaving.Weaver; import org.glowroot.agent.weaving.WeavingClassFileTransformer; import org.glowroot.common.util.Clock; import org.glowroot.common.util.OnlyUsedByTests; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; public class AgentModule { private static final Logger logger = LoggerFactory.getLogger(AgentModule.class); // log startup messages using logger name "org.glowroot" private static final Logger startupLogger = LoggerFactory.getLogger("org.glowroot"); // 1 minute private static final long ROLLUP_0_INTERVAL_MILLIS = Long.getLong("glowroot.internal.rollup.0.intervalMillis", MINUTES.toMillis(1)); @OnlyUsedByTests public static final ThreadLocal</*@Nullable*/ IsolatedWeavingClassLoader> isolatedWeavingClassLoader = new ThreadLocal</*@Nullable*/ IsolatedWeavingClassLoader>(); private final ConfigService configService; private final AnalyzedWorld analyzedWorld; private final TransactionRegistry transactionRegistry; private final AdviceCache adviceCache; private final TransactionCollector transactionCollector; private final Aggregator aggregator; private final ImmediateTraceStoreWatcher immedateTraceStoreWatcher; private final GaugeCollector gaugeCollector; private final StackTraceCollector stackTraceCollector; private final boolean jvmRetransformClassesSupported; private final LiveTraceRepositoryImpl liveTraceRepository; private final LiveAggregateRepositoryImpl liveAggregateRepository; private final LiveWeavingServiceImpl liveWeavingService; private final LiveJvmServiceImpl liveJvmService; private final LazyPlatformMBeanServer lazyPlatformMBeanServer; // accepts @Nullable Ticker to deal with shading issues when called from GlowrootModule public AgentModule(Clock clock, @Nullable Ticker nullableTicker, final PluginCache pluginCache, final ConfigService configService, Supplier<ScheduledExecutorService> backgroundExecutorSupplier, Collector collector, @Nullable Instrumentation instrumentation, File agentDir) throws Exception { Ticker ticker = nullableTicker == null ? Tickers.getTicker() : nullableTicker; this.configService = configService; transactionRegistry = new TransactionRegistry(); if (instrumentation != null) { for (File pluginJar : pluginCache.pluginJars()) { instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(pluginJar)); } } adviceCache = new AdviceCache(pluginCache.pluginDescriptors(), pluginCache.pluginJars(), configService.getInstrumentationConfigs(), instrumentation, agentDir); analyzedWorld = new AnalyzedWorld(adviceCache.getAdvisorsSupplier(), adviceCache.getShimTypes(), adviceCache.getMixinTypes()); final TimerNameCache timerNameCache = new TimerNameCache(); Weaver weaver = new Weaver(adviceCache.getAdvisorsSupplier(), adviceCache.getShimTypes(), adviceCache.getMixinTypes(), analyzedWorld, transactionRegistry, timerNameCache, configService); if (instrumentation == null) { // instrumentation is null when debugging with LocalContainer IsolatedWeavingClassLoader isolatedWeavingClassLoader = AgentModule.isolatedWeavingClassLoader.get(); checkNotNull(isolatedWeavingClassLoader); isolatedWeavingClassLoader.setWeaver(weaver); jvmRetransformClassesSupported = false; } else { PreInitializeWeavingClasses.preInitializeClasses(); ClassFileTransformer transformer = new WeavingClassFileTransformer(weaver); if (instrumentation.isRetransformClassesSupported()) { instrumentation.addTransformer(transformer, true); jvmRetransformClassesSupported = true; } else { instrumentation.addTransformer(transformer); jvmRetransformClassesSupported = false; } logRunnableCallableClassWarningIfNeeded(instrumentation); } // now that instrumentation is set up, it is safe to create scheduled executor ScheduledExecutorService backgroundExecutor = backgroundExecutorSupplier.get(); aggregator = new Aggregator(collector, configService, ROLLUP_0_INTERVAL_MILLIS, clock); transactionCollector = new TransactionCollector(configService, collector, aggregator, clock, ticker); OptionalService<ThreadAllocatedBytes> threadAllocatedBytes = ThreadAllocatedBytes.create(); Random random = new Random(); UserProfileScheduler userProfileScheduler = new UserProfileScheduler(backgroundExecutor, configService, random); GlowrootService glowrootService = new GlowrootServiceImpl(transactionRegistry); TransactionServiceImpl.create(transactionRegistry, transactionCollector, configService, timerNameCache, threadAllocatedBytes.getService(), userProfileScheduler, ticker, clock); ConfigServiceFactory configServiceFactory = new ConfigServiceFactory() { @Override public org.glowroot.agent.plugin.api.config.ConfigService create(String pluginId) { checkNotNull(configService); checkNotNull(pluginCache); return ConfigServiceImpl.create(configService, pluginCache.pluginDescriptors(), pluginId); } }; ServiceRegistryImpl.init(glowrootService, timerNameCache, configServiceFactory); lazyPlatformMBeanServer = LazyPlatformMBeanServer.create(); File[] roots = File.listRoots(); if (roots != null) { for (File root : roots) { String name = root.getCanonicalPath(); if (name.length() > 1 && (name.endsWith("/") || name.endsWith("\\"))) { name = name.substring(0, name.length() - 1); } name = name.replaceAll(":", ""); lazyPlatformMBeanServer.lazyRegisterMBean(new FileSystem(root), "org.glowroot:type=FileSystem,name=" + name); } } gaugeCollector = new GaugeCollector(configService, collector, lazyPlatformMBeanServer, clock, ticker); // using fixed rate to keep gauge collections close to on the second mark long gaugeCollectionIntervalMillis = configService.getGaugeCollectionIntervalMillis(); gaugeCollector.scheduleWithFixedDelay(gaugeCollectionIntervalMillis, MILLISECONDS); stackTraceCollector = new StackTraceCollector(transactionRegistry, configService, random); immedateTraceStoreWatcher = new ImmediateTraceStoreWatcher(backgroundExecutor, transactionRegistry, transactionCollector, configService, ticker); immedateTraceStoreWatcher.scheduleWithFixedDelay(backgroundExecutor, ImmediateTraceStoreWatcher.PERIOD_MILLIS, MILLISECONDS); liveTraceRepository = new LiveTraceRepositoryImpl(transactionRegistry, transactionCollector, clock, ticker); liveAggregateRepository = new LiveAggregateRepositoryImpl(aggregator); liveWeavingService = new LiveWeavingServiceImpl(analyzedWorld, instrumentation, configService, adviceCache, jvmRetransformClassesSupported); liveJvmService = new LiveJvmServiceImpl(lazyPlatformMBeanServer, transactionRegistry, transactionCollector, threadAllocatedBytes.getAvailability()); initPlugins(pluginCache.pluginDescriptors()); List<PluginDescriptor> pluginDescriptors = pluginCache.pluginDescriptors(); List<String> pluginNames = Lists.newArrayList(); for (PluginDescriptor pluginDescriptor : pluginDescriptors) { pluginNames.add(pluginDescriptor.name()); } if (!pluginNames.isEmpty()) { startupLogger.info("plugins loaded: {}", Joiner.on(", ").join(pluginNames)); } } public ConfigService getConfigService() { return configService; } public LazyPlatformMBeanServer getLazyPlatformMBeanServer() { return lazyPlatformMBeanServer; } public LiveTraceRepositoryImpl getLiveTraceRepository() { return liveTraceRepository; } public LiveAggregateRepositoryImpl getLiveAggregateRepository() { return liveAggregateRepository; } public LiveWeavingServiceImpl getLiveWeavingService() { return liveWeavingService; } public LiveJvmServiceImpl getLiveJvmService() { return liveJvmService; } private static void logRunnableCallableClassWarningIfNeeded(Instrumentation instrumentation) { List<String> runnableCallableClasses = Lists.newArrayList(); for (Class<?> clazz : instrumentation.getAllLoadedClasses()) { if (clazz.isInterface()) { continue; } if (!clazz.getName().startsWith("java.util.concurrent.")) { continue; } if (Runnable.class.isAssignableFrom(clazz) || Callable.class.isAssignableFrom(clazz)) { runnableCallableClasses.add(clazz.getName()); } } if (runnableCallableClasses.isEmpty()) { return; } List<String> nonGlowrootAgents = Lists.newArrayList(); for (String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) { if (jvmArg.startsWith("-javaagent:") && !jvmArg.endsWith("glowroot.jar") || jvmArg.startsWith("-agentpath:")) { nonGlowrootAgents.add(jvmArg); } } String extraExplanation = ""; if (!nonGlowrootAgents.isEmpty()) { extraExplanation = "This likely occurred because there"; if (nonGlowrootAgents.size() == 1) { extraExplanation += " is another agent"; } else { extraExplanation += " are other agents"; } extraExplanation += " listed in the JVM args prior to the Glowroot agent (" + Joiner.on(" ").join(nonGlowrootAgents) + ") which gives the other agent"; if (nonGlowrootAgents.size() != 1) { extraExplanation += "s"; } extraExplanation += " higher loading precedence. "; } logger.warn("one or more java.lang.Runnable or java.util.concurrent.Callable" + " implementations were loaded before Glowroot instrumentation could be applied to" + " them: {}. {}This may prevent Glowroot from capturing async requests that span" + " multiple threads.", Joiner.on(", ").join(runnableCallableClasses), extraExplanation); } // now init plugins to give them a chance to do something in their static initializer // e.g. append their package to jboss.modules.system.pkgs private static void initPlugins(List<PluginDescriptor> pluginDescriptors) { for (PluginDescriptor pluginDescriptor : pluginDescriptors) { for (String aspect : pluginDescriptor.aspects()) { try { Class.forName(aspect, true, AgentModule.class.getClassLoader()); } catch (ClassNotFoundException e) { // this would have already been logged as a warning during advice construction logger.debug(e.getMessage(), e); } } } } @OnlyUsedByTests public void close() throws Exception { immedateTraceStoreWatcher.cancel(); transactionCollector.close(); aggregator.close(); gaugeCollector.close(); stackTraceCollector.close(); lazyPlatformMBeanServer.close(); } }