org.glowroot.agent.embedded.init.EmbeddedAgentModule.java Source code

Java tutorial

Introduction

Here is the source code for org.glowroot.agent.embedded.init.EmbeddedAgentModule.java

Source

/*
 * Copyright 2013-2019 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.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.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Pattern;

import com.google.common.base.Stopwatch;
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.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.glowroot.agent.collector.Collector;
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.AgentDisplayRepositoryImpl;
import org.glowroot.agent.embedded.repo.ConfigRepositoryImpl;
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.impl.BytecodeServiceImpl.OnEnteringMain;
import org.glowroot.agent.init.AgentModule;
import org.glowroot.agent.init.CollectorProxy;
import org.glowroot.agent.init.EnvironmentCreator;
import org.glowroot.agent.init.JRebelWorkaround;
import org.glowroot.agent.init.PreCheckLoadedClasses.PreCheckClassFileTransformer;
import org.glowroot.agent.util.LazyPlatformMBeanServer;
import org.glowroot.agent.util.ThreadFactories;
import org.glowroot.common.live.LiveAggregateRepository.LiveAggregateRepositoryNop;
import org.glowroot.common.live.LiveTraceRepository.LiveTraceRepositoryNop;
import org.glowroot.common.util.Clock;
import org.glowroot.common.util.OnlyUsedByTests;
import org.glowroot.common.util.Versions;
import org.glowroot.common2.config.ImmutableRoleConfig;
import org.glowroot.common2.config.RoleConfig;
import org.glowroot.common2.config.RoleConfig.SimplePermission;
import org.glowroot.common2.repo.ActiveAgentRepository;
import org.glowroot.common2.repo.ImmutableAgentRollup;
import org.glowroot.common2.repo.ImmutableTopLevelAgentRollup;
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.MILLISECONDS;
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");

    // only look at system property, b/c there doesn't seem any point in setting agent.port in
    // the glowroot.properties file, since in that case better to set it in the config.json file
    private static final @Nullable Integer WEB_PORT_OVERRIDE = Integer.getInteger("glowroot.agent.port");

    private final File logDir;
    private final List<File> confDirs;
    private final Ticker ticker;
    private final Clock clock;

    private final PluginCache pluginCache;

    private final @Nullable AgentModule agentModule;
    private final @Nullable OfflineViewerAgentModule offlineViewerAgentModule;

    private final String version;

    private volatile @MonotonicNonNull ScheduledExecutorService backgroundExecutor;
    private volatile @MonotonicNonNull SimpleRepoModule simpleRepoModule;

    private volatile @MonotonicNonNull UiModule uiModule;

    private final CountDownLatch simpleRepoModuleInit = new CountDownLatch(1);

    EmbeddedAgentModule(@Nullable File pluginsDir, List<File> confDirs, boolean configReadOnly, File logDir,
            File tmpDir, @Nullable Instrumentation instrumentation,
            @Nullable PreCheckClassFileTransformer preCheckClassFileTransformer, @Nullable File glowrootJarFile,
            String glowrootVersion, boolean offlineViewer) throws Exception {

        ticker = Ticker.systemTicker();
        clock = Clock.systemClock();

        // need to perform jrebel workaround prior to loading any jackson classes
        JRebelWorkaround.perform();
        pluginCache = PluginCache.create(pluginsDir, false);
        if (offlineViewer) {
            agentModule = null;
            offlineViewerAgentModule = new OfflineViewerAgentModule(pluginsDir, confDirs, configReadOnly);
        } else {
            // agent 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 SimpleRepoModule 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
            ConfigService configService = ConfigService.create(confDirs, configReadOnly,
                    pluginCache.pluginDescriptors());
            agentModule = new AgentModule(clock, null, pluginCache, configService, instrumentation, glowrootJarFile,
                    tmpDir, preCheckClassFileTransformer);
            offlineViewerAgentModule = null;
        }
        this.confDirs = confDirs;
        this.logDir = logDir;
        this.version = glowrootVersion;
    }

    void setOnEnteringMain(OnEnteringMain onEnteringMain) {
        checkNotNull(agentModule);
        agentModule.setOnEnteringMain(onEnteringMain);
    }

    void onEnteringMain(final List<File> confDirs, boolean configReadOnly, final File dataDir,
            @Nullable File glowrootJarFile, Map<String, String> properties,
            @Nullable Instrumentation instrumentation,
            final @Nullable Class<? extends Collector> collectorProxyClass, final String glowrootVersion,
            @Nullable String mainClass) throws Exception {

        PreInitializeStorageShutdownClasses.preInitializeClasses();
        // mem db is only used for testing (by glowroot-agent-it-harness)
        final boolean h2MemDb = Boolean.parseBoolean(properties.get("glowroot.internal.h2.memdb"));
        if (agentModule == null) {
            checkNotNull(offlineViewerAgentModule);
            ConfigRepositoryImpl configRepository = new ConfigRepositoryImpl(confDirs, configReadOnly,
                    WEB_PORT_OVERRIDE, offlineViewerAgentModule.getConfigService(), pluginCache);
            DataSource dataSource = createDataSource(h2MemDb, dataDir);
            simpleRepoModule = new SimpleRepoModule(dataSource, dataDir, clock, ticker, configRepository, null);
            simpleRepoModuleInit.countDown();
        } else {
            backgroundExecutor = Executors.newScheduledThreadPool(2,
                    ThreadFactories.create("Glowroot-Background-%d"));
            final CollectorProxy collectorProxy = new CollectorProxy();
            agentModule.onEnteringMain(backgroundExecutor, collectorProxy, instrumentation, glowrootJarFile,
                    mainClass);

            final ConfigRepositoryImpl configRepository = new ConfigRepositoryImpl(confDirs, configReadOnly,
                    WEB_PORT_OVERRIDE, agentModule.getConfigService(), pluginCache);
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // TODO report checker framework issue that occurs without checkNotNull
                        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
                        Collector collector = new EmbeddedCollector(simpleRepoModule.getEnvironmentDao(),
                                simpleRepoModule.getAggregateDao(), simpleRepoModule.getTraceDao(),
                                simpleRepoModule.getGaugeValueDao(), configRepository,
                                simpleRepoModule.getAlertingService(), simpleRepoModule.getAlertingDisabledDao(),
                                simpleRepoModule.getHttpClient(), clock);
                        if (collectorProxyClass != null) {
                            startupLogger.info("using collector proxy: {}", collectorProxyClass.getName());
                            collector = collectorProxyClass.getConstructor(Collector.class).newInstance(collector);
                        }
                        collectorProxy.setInstance(collector);
                        // embedded collector does nothing with agent config parameter
                        collector.init(confDirs,
                                EnvironmentCreator.create(glowrootVersion,
                                        agentModule.getConfigService().getJvmConfig()),
                                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 {
                        simpleRepoModuleInit.countDown();
                    }
                }
            });
            thread.setName("Glowroot-Init-Repo");
            thread.setDaemon(true);
            thread.start();

            // 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);
        }
    }

    void initEmbeddedServer() throws Exception {
        if (simpleRepoModule == null) {
            // repo module failed to start
            return;
        }
        if (agentModule != null) {
            uiModule = new CreateUiModuleBuilder().central(false).servlet(false).offlineViewer(false)
                    .webPortReadOnly(WEB_PORT_OVERRIDE != null).confDirs(confDirs).logDir(logDir)
                    .logFileNamePattern(Pattern.compile("glowroot.*\\.log")).ticker(ticker).clock(clock)
                    .liveJvmService(agentModule.getLiveJvmService())
                    .agentDisplayRepository(new AgentDisplayRepositoryImpl())
                    .configRepository(simpleRepoModule.getConfigRepository())
                    .alertingDisabledRepository(simpleRepoModule.getAlertingDisabledDao())
                    .activeAgentRepository(new ActiveAgentRepositoryImpl())
                    .environmentRepository(simpleRepoModule.getEnvironmentDao())
                    .transactionTypeRepository(simpleRepoModule.getTransactionTypeRepository())
                    .traceAttributeNameRepository(simpleRepoModule.getTraceAttributeNameRepository())
                    .aggregateRepository(simpleRepoModule.getAggregateDao())
                    .traceRepository(simpleRepoModule.getTraceDao())
                    .gaugeValueRepository(simpleRepoModule.getGaugeValueDao()).syntheticResultRepository(null)
                    .incidentRepository(simpleRepoModule.getIncidentDao())
                    .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();
                        }
                    }).httpClient(simpleRepoModule.getHttpClient()).numWorkerThreads(2).version(version).build();
        } else {
            checkNotNull(offlineViewerAgentModule);
            uiModule = new CreateUiModuleBuilder().central(false).servlet(false).offlineViewer(true)
                    .webPortReadOnly(WEB_PORT_OVERRIDE != null).confDirs(confDirs).logDir(logDir)
                    .logFileNamePattern(Pattern.compile("glowroot.*\\.log")).ticker(ticker).clock(clock)
                    .liveJvmService(null).agentDisplayRepository(new AgentDisplayRepositoryImpl())
                    .configRepository(simpleRepoModule.getConfigRepository())
                    .alertingDisabledRepository(simpleRepoModule.getAlertingDisabledDao())
                    .activeAgentRepository(new ActiveAgentRepositoryImpl())
                    .environmentRepository(simpleRepoModule.getEnvironmentDao())
                    .transactionTypeRepository(simpleRepoModule.getTransactionTypeRepository())
                    .traceAttributeNameRepository(simpleRepoModule.getTraceAttributeNameRepository())
                    .aggregateRepository(simpleRepoModule.getAggregateDao())
                    .traceRepository(simpleRepoModule.getTraceDao())
                    .gaugeValueRepository(simpleRepoModule.getGaugeValueDao()).syntheticResultRepository(null)
                    .incidentRepository(simpleRepoModule.getIncidentDao())
                    .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();
                        }
                    }).httpClient(simpleRepoModule.getHttpClient()).numWorkerThreads(10).version(version).build();
        }
    }

    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;
            }
            MILLISECONDS.sleep(10);
        }
        throw new IllegalStateException("UI Module failed to start");
    }

    @OnlyUsedByTests
    public void close() throws Exception {
        if (uiModule != null) {
            uiModule.close(false);
        }
        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");
            }
        }
    }

    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;
        }
        // upgrade from database created _before_ TriggeredAlertDao was removed
        return dataSource.columnExists("triggered_alert", "alert_config_version");
    }

    private static void addAlertPermission(ConfigRepositoryImpl 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 ActiveAgentRepositoryImpl implements ActiveAgentRepository {

        @Override
        public List<TopLevelAgentRollup> readActiveTopLevelAgentRollups(long from, long to) {
            return ImmutableList
                    .<TopLevelAgentRollup>of(ImmutableTopLevelAgentRollup.builder().id("").display("").build());
        }

        @Override
        public List<AgentRollup> readActiveChildAgentRollups(String topLevelId, long from, long to) {
            return ImmutableList
                    .<AgentRollup>of(ImmutableAgentRollup.builder().id("").display("").lastDisplayPart("").build());
        }

        @Override
        public List<AgentRollup> readRecentlyActiveAgentRollups(long lastXMillis) {
            return ImmutableList
                    .<AgentRollup>of(ImmutableAgentRollup.builder().id("").display("").lastDisplayPart("").build());
        }

        @Override
        public List<AgentRollup> readActiveAgentRollups(long from, long to) {
            return ImmutableList
                    .<AgentRollup>of(ImmutableAgentRollup.builder().id("").display("").lastDisplayPart("").build());
        }
    }
}