/* * Copyright 2013-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.embedded.init; import java.io.Closeable; import java.io.File; import java.io.Serializable; import java.lang.instrument.Instrumentation; import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nullable; import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; import com.google.common.base.Ticker; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glowroot.agent.collector.Collector.AgentConfigUpdater; import org.glowroot.agent.config.ConfigService; import org.glowroot.agent.config.PluginCache; import org.glowroot.agent.embedded.repo.PlatformMBeanServerLifecycle; import org.glowroot.agent.embedded.repo.SimpleRepoModule; import org.glowroot.agent.embedded.util.DataSource; import org.glowroot.agent.init.AgentDirLocking; import org.glowroot.agent.init.AgentModule; import org.glowroot.agent.init.CentralGlowrootAgentInit; import org.glowroot.agent.init.CollectorProxy; import org.glowroot.agent.init.EnvironmentCreator; import org.glowroot.agent.init.JRebelWorkaround; import org.glowroot.agent.util.LazyPlatformMBeanServer; import org.glowroot.agent.util.ThreadFactories; import org.glowroot.common.config.ImmutableRoleConfig; import org.glowroot.common.config.RoleConfig; import org.glowroot.common.config.RoleConfig.SimplePermission; import org.glowroot.common.live.LiveAggregateRepository.LiveAggregateRepositoryNop; import org.glowroot.common.live.LiveTraceRepository.LiveTraceRepositoryNop; import org.glowroot.common.repo.AgentRepository; import org.glowroot.common.repo.ConfigRepository; import org.glowroot.common.repo.ImmutableAgentRollup; import org.glowroot.common.util.Clock; import org.glowroot.common.util.OnlyUsedByTests; import org.glowroot.common.util.Versions; import org.glowroot.ui.CreateUiModuleBuilder; import org.glowroot.ui.SessionMapFactory; import org.glowroot.ui.UiModule; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.concurrent.TimeUnit.SECONDS; class EmbeddedAgentModule { // log startup messages using logger name "org.glowroot" private static final Logger startupLogger = LoggerFactory.getLogger("org.glowroot"); private final File agentDir; private final File glowrootDir; private final Ticker ticker; private final Clock clock; // only null in viewer mode private final @Nullable ScheduledExecutorService backgroundExecutor; private volatile @MonotonicNonNull SimpleRepoModule simpleRepoModule; private final @Nullable AgentModule agentModule; private final @Nullable ViewerAgentModule viewerAgentModule; private final Closeable agentDirLockingCloseable; private final String version; private volatile @MonotonicNonNull UiModule uiModule; private final CountDownLatch simpleRepoModuleInit = new CountDownLatch(1); EmbeddedAgentModule(final File glowrootDir, final File agentDir, Map<String, String> properties, @Nullable Instrumentation instrumentation, final String glowrootVersion, boolean offline) throws Exception { agentDirLockingCloseable = AgentDirLocking.lockAgentDir(agentDir); ticker = Ticker.systemTicker(); clock = Clock.systemClock(); // mem db is only used for testing (by glowroot-agent-it-harness) final boolean h2MemDb = Boolean.parseBoolean(properties.get("glowroot.internal.h2.memdb")); final File dataDir = new File(agentDir, "data"); // need to perform jrebel workaround prior to loading any jackson classes JRebelWorkaround.performWorkaroundIfNeeded(); PluginCache pluginCache = PluginCache.create(glowrootDir, false); if (offline) { viewerAgentModule = new ViewerAgentModule(glowrootDir, agentDir); backgroundExecutor = null; agentModule = null; ConfigRepository configRepository = ConfigRepositoryImpl.create(glowrootDir, viewerAgentModule.getConfigService(), pluginCache); DataSource dataSource = createDataSource(h2MemDb, dataDir); simpleRepoModule = new SimpleRepoModule(dataSource, dataDir, clock, ticker, configRepository, null); simpleRepoModuleInit.countDown(); } else { // trace module needs to be started as early as possible, so that weaving will be // applied to as many classes as possible // in particular, it needs to be started before StorageModule which uses shaded H2, // which loads java.sql.DriverManager, which loads 3rd party jdbc drivers found via // services/java.sql.Driver, and those drivers need to be woven final CollectorProxy collectorProxy = new CollectorProxy(); ConfigService configService = ConfigService.create(agentDir, pluginCache.pluginDescriptors()); // need to delay creation of the scheduled executor until instrumentation is set up Supplier<ScheduledExecutorService> backgroundExecutorSupplier = CentralGlowrootAgentInit.createBackgroundExecutorSupplier(); agentModule = new AgentModule(clock, null, pluginCache, configService, backgroundExecutorSupplier, collectorProxy, instrumentation, agentDir); backgroundExecutor = backgroundExecutorSupplier.get(); PreInitializeStorageShutdownClasses.preInitializeClasses(); final ConfigRepository configRepository = ConfigRepositoryImpl.create(glowrootDir, agentModule.getConfigService(), pluginCache); ExecutorService singleUseExecutor = Executors.newSingleThreadExecutor(ThreadFactories.create("Glowroot-Init-Repo")); singleUseExecutor.execute(new Runnable() { @Override public void run() { try { // TODO report checker framework issue that occurs without checkNotNull checkNotNull(clock); checkNotNull(ticker); checkNotNull(agentModule); DataSource dataSource = createDataSource(h2MemDb, dataDir); if (needToAddAlertPermission(dataSource)) { addAlertPermission(configRepository); } SimpleRepoModule simpleRepoModule = new SimpleRepoModule(dataSource, dataDir, clock, ticker, configRepository, backgroundExecutor); simpleRepoModule.registerMBeans(new PlatformMBeanServerLifecycleImpl( agentModule.getLazyPlatformMBeanServer())); // now inject the real collector into the proxy CollectorImpl collectorImpl = new CollectorImpl( simpleRepoModule.getEnvironmentDao(), simpleRepoModule.getAggregateDao(), simpleRepoModule.getTraceDao(), simpleRepoModule.getGaugeValueDao(), configRepository, simpleRepoModule.getAlertingService()); collectorProxy.setInstance(collectorImpl); // embedded CollectorImpl does nothing with agent config parameter collectorImpl.init(glowrootDir, agentDir, EnvironmentCreator.create(glowrootVersion), AgentConfig.getDefaultInstance(), new AgentConfigUpdater() { @Override public void update(AgentConfig agentConfig) {} }); EmbeddedAgentModule.this.simpleRepoModule = simpleRepoModule; } catch (Throwable t) { startupLogger.error("Glowroot cannot start: {}", t.getMessage(), t); } finally { // TODO report checker framework issue that occurs without checkNotNull checkNotNull(simpleRepoModuleInit).countDown(); } } }); // prefer to wait for repo to start up on its own, then no worry about losing collected // data due to limits in CollectorProxy, but don't wait too long as first launch after // upgrade when adding new columns to large H2 database can take some time simpleRepoModuleInit.await(5, SECONDS); viewerAgentModule = null; } this.glowrootDir = glowrootDir; this.agentDir = agentDir; this.version = glowrootVersion; } void initEmbeddedServer() throws Exception { if (simpleRepoModule == null) { // repo module failed to start return; } if (agentModule != null) { uiModule = new CreateUiModuleBuilder() .central(false) .servlet(false) .offline(false) .certificateDir(glowrootDir) .logDir(agentDir) .ticker(ticker) .clock(clock) .liveJvmService(agentModule.getLiveJvmService()) .configRepository(simpleRepoModule.getConfigRepository()) .agentRepository(new AgentRepositoryImpl()) .environmentRepository(simpleRepoModule.getEnvironmentDao()) .transactionTypeRepository(simpleRepoModule.getTransactionTypeRepository()) .traceAttributeNameRepository( simpleRepoModule.getTraceAttributeNameRepository()) .aggregateRepository(simpleRepoModule.getAggregateDao()) .traceRepository(simpleRepoModule.getTraceDao()) .gaugeValueRepository(simpleRepoModule.getGaugeValueDao()) .syntheticResultRepository(null) .triggeredAlertRepository(simpleRepoModule.getTriggeredAlertDao()) .repoAdmin(simpleRepoModule.getRepoAdmin()) .rollupLevelService(simpleRepoModule.getRollupLevelService()) .liveTraceRepository(agentModule.getLiveTraceRepository()) .liveAggregateRepository(agentModule.getLiveAggregateRepository()) .liveWeavingService(agentModule.getLiveWeavingService()) .sessionMapFactory(new SessionMapFactory() { @Override public <V extends /*@NonNull*/ Serializable> ConcurrentMap<String, V> create() { return Maps.newConcurrentMap(); } }) .numWorkerThreads(2) .version(version) .build(); } else { checkNotNull(viewerAgentModule); uiModule = new CreateUiModuleBuilder() .central(false) .servlet(false) .offline(true) .certificateDir(glowrootDir) .logDir(agentDir) .ticker(ticker) .clock(clock) .liveJvmService(null) .configRepository(simpleRepoModule.getConfigRepository()) .agentRepository(new AgentRepositoryImpl()) .environmentRepository(simpleRepoModule.getEnvironmentDao()) .transactionTypeRepository(simpleRepoModule.getTransactionTypeRepository()) .traceAttributeNameRepository( simpleRepoModule.getTraceAttributeNameRepository()) .aggregateRepository(simpleRepoModule.getAggregateDao()) .traceRepository(simpleRepoModule.getTraceDao()) .gaugeValueRepository(simpleRepoModule.getGaugeValueDao()) .syntheticResultRepository(null) .triggeredAlertRepository(simpleRepoModule.getTriggeredAlertDao()) .repoAdmin(simpleRepoModule.getRepoAdmin()) .rollupLevelService(simpleRepoModule.getRollupLevelService()) .liveTraceRepository(new LiveTraceRepositoryNop()) .liveAggregateRepository(new LiveAggregateRepositoryNop()) .liveWeavingService(null) .sessionMapFactory(new SessionMapFactory() { @Override public <V extends /*@NonNull*/ Serializable> ConcurrentMap<String, V> create() { return Maps.newConcurrentMap(); } }) .numWorkerThreads(10) .version(version) .build(); } } boolean isSimpleRepoModuleReady() throws InterruptedException { return simpleRepoModuleInit.await(0, SECONDS); } void waitForSimpleRepoModule() throws InterruptedException { simpleRepoModuleInit.await(); } @OnlyUsedByTests public SimpleRepoModule getSimpleRepoModule() throws InterruptedException { simpleRepoModuleInit.await(); return checkNotNull(simpleRepoModule); } @OnlyUsedByTests public AgentModule getAgentModule() { checkNotNull(agentModule); return agentModule; } @OnlyUsedByTests public UiModule getUiModule() throws InterruptedException { Stopwatch stopwatch = Stopwatch.createStarted(); while (stopwatch.elapsed(SECONDS) < 60) { if (uiModule != null) { return uiModule; } Thread.sleep(10); } throw new IllegalStateException("UI Module failed to start"); } @OnlyUsedByTests public void close() throws Exception { if (uiModule != null) { uiModule.close(true); } if (agentModule != null) { agentModule.close(); } checkNotNull(simpleRepoModule).close(); if (backgroundExecutor != null) { // close background executor last to prevent exceptions due to above modules attempting // to use a shutdown executor backgroundExecutor.shutdown(); if (!backgroundExecutor.awaitTermination(10, SECONDS)) { throw new IllegalStateException("Could not terminate executor"); } } // and unlock the agent directory agentDirLockingCloseable.close(); } private static DataSource createDataSource(boolean h2MemDb, File dataDir) throws SQLException { if (h2MemDb) { // mem db is only used for testing (by glowroot-agent-it-harness) return new DataSource(); } else { return new DataSource(new File(dataDir, "data.h2.db")); } } private static boolean needToAddAlertPermission(DataSource dataSource) throws SQLException { if (dataSource.tableExists("trace")) { // new database, not an upgrade return false; } if (!dataSource.tableExists("triggered_alert")) { // upgrade from database created _after_ TriggeredAlertDao was removed return true; } if (dataSource.columnExists("triggered_alert", "alert_config_version")) { // upgrade from database created _before_ TriggeredAlertDao was removed return true; } return false; } private static void addAlertPermission(ConfigRepository configRepository) throws Exception { for (RoleConfig config : configRepository.getRoleConfigs()) { if (config.isPermitted(SimplePermission.create("agent:transaction:overview")) || config.isPermitted(SimplePermission.create("agent:error:overview")) || config.isPermitted(SimplePermission.create("agent:jvm:gauges"))) { ImmutableRoleConfig updatedConfig = ImmutableRoleConfig.builder() .copyFrom(config) .addPermissions("agent:alert") .build(); configRepository.updateRoleConfig(updatedConfig, Versions.getJsonVersion(config)); } } } private static class PlatformMBeanServerLifecycleImpl implements PlatformMBeanServerLifecycle { private final LazyPlatformMBeanServer lazyPlatformMBeanServer; private PlatformMBeanServerLifecycleImpl(LazyPlatformMBeanServer lazyPlatformMBeanServer) { this.lazyPlatformMBeanServer = lazyPlatformMBeanServer; } @Override public void lazyRegisterMBean(Object object, String name) { lazyPlatformMBeanServer.lazyRegisterMBean(object, name); } } private static class AgentRepositoryImpl implements AgentRepository { @Override public List<AgentRollup> readAgentRollups() { return ImmutableList.<AgentRollup>of(ImmutableAgentRollup.builder() .id("") .display("") .agent(true) .build()); } @Override public String readAgentRollupDisplay(String agentRollupId) { return ""; } @Override public boolean isAgent(String agentId) { return true; } } }