/* * 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; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.lang.instrument.Instrumentation; import java.lang.management.ManagementFactory; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.ServiceLoader; import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.base.Objects; import com.google.common.base.StandardSystemProperty; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.Files; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glowroot.agent.collector.Collector; import org.glowroot.agent.embedded.init.EmbeddedGlowrootAgentInit; import org.glowroot.agent.init.AgentDirLocking.AgentDirLockedException; import org.glowroot.agent.init.CentralGlowrootAgentInit; import org.glowroot.agent.init.GlowrootAgentInit; import org.glowroot.agent.util.AppServerDetection; import org.glowroot.common.util.OnlyUsedByTests; import org.glowroot.common.util.PropertiesFiles; import org.glowroot.common.util.Version; import static com.google.common.base.Preconditions.checkNotNull; public class MainEntryPoint { // need to wait to init logger until after establishing agentDir private static volatile @MonotonicNonNull Logger startupLogger; @OnlyUsedByTests private static @MonotonicNonNull GlowrootAgentInit glowrootAgentInit; private MainEntryPoint() {} public static void premain(Instrumentation instrumentation, @Nullable File glowrootJarFile) { // DO NOT USE ANY GUAVA CLASSES before initLogging() because they trigger loading of jul // (and thus org.glowroot.agent.jul.Logger and thus glowroot's shaded slf4j) boolean jbossModules = AppServerDetection.isJBossModules(AppServerDetection.getCommand()); if (jbossModules) { String jbossModulesSystemPkgs = System.getProperty("jboss.modules.system.pkgs"); if (jbossModulesSystemPkgs == null || jbossModulesSystemPkgs.isEmpty()) { jbossModulesSystemPkgs = "org.glowroot.agent"; } else { jbossModulesSystemPkgs += ",org.glowroot.agent"; } System.setProperty("jboss.modules.system.pkgs", jbossModulesSystemPkgs); } File glowrootDir; File agentDir; try { glowrootDir = GlowrootDir.getGlowrootDir(glowrootJarFile); agentDir = GlowrootDir.getAgentDir(glowrootDir); // init logger as early as possible File logDir = GlowrootDir.getLogDir(agentDir); initLogging(agentDir, logDir); } catch (Throwable t) { // log error but don't re-throw which would prevent monitored app from starting // also, don't use logger since not initialized yet System.err.println("Glowroot not started: " + t.getMessage()); t.printStackTrace(); return; } try { ImmutableMap<String, String> properties = getGlowrootProperties(glowrootDir); start(glowrootDir, agentDir, properties, instrumentation); } catch (AgentDirLockedException e) { logAgentDirLockedException(agentDir); } catch (Throwable t) { // log error but don't re-throw which would prevent monitored app from starting startupLogger.error("Glowroot not started: {}", t.getMessage(), t); } } static void runViewer(File glowrootDir, File agentDir) throws InterruptedException { // initLogging() already called by OfflineViewer.main() checkNotNull(startupLogger); String version; try { version = Version.getVersion(MainEntryPoint.class); startupLogger.info("Glowroot version: {}", version); startupLogger.info("Java version: {}", StandardSystemProperty.JAVA_VERSION.value()); ImmutableMap<String, String> properties = getGlowrootProperties(glowrootDir); new EmbeddedGlowrootAgentInit().init(glowrootDir, agentDir, null, null, properties, null, version, true); } catch (AgentDirLockedException e) { logAgentDirLockedException(agentDir); return; } catch (Throwable t) { startupLogger.error("Glowroot cannot start: {}", t.getMessage(), t); return; } // Glowroot does not create any non-daemon threads, so need to block jvm from exiting when // running the viewer Thread.sleep(Long.MAX_VALUE); } @EnsuresNonNull("startupLogger") static void initLogging(File agentDir, File logDir) { File logbackXmlOverride = new File(agentDir, "glowroot.logback.xml"); if (logbackXmlOverride.exists()) { System.setProperty("glowroot.logback.configurationFile", logbackXmlOverride.getAbsolutePath()); } String prior = System.getProperty("glowroot.log.dir"); System.setProperty("glowroot.log.dir", logDir.getPath()); try { startupLogger = LoggerFactory.getLogger("org.glowroot"); } finally { System.clearProperty("glowroot.logback.configurationFile"); if (prior == null) { System.clearProperty("glowroot.log.dir"); } else { System.setProperty("glowroot.log.dir", prior); } } // TODO report checker framework issue that occurs without checkNotNull checkNotNull(startupLogger); } @RequiresNonNull("startupLogger") private static void start(File glowrootDir, File agentDir, Map<String, String> properties, @Nullable Instrumentation instrumentation) throws Exception { ManagementFactory.getThreadMXBean().setThreadCpuTimeEnabled(true); ManagementFactory.getThreadMXBean().setThreadContentionMonitoringEnabled(true); String version = Version.getVersion(MainEntryPoint.class); startupLogger.info("Glowroot version: {}", version); startupLogger.info("Java version: {}", StandardSystemProperty.JAVA_VERSION.value()); String collectorAddress = properties.get("glowroot.collector.address"); Collector customCollector = loadCustomCollector(glowrootDir); if (Strings.isNullOrEmpty(collectorAddress) && customCollector == null) { glowrootAgentInit = new EmbeddedGlowrootAgentInit(); } else { if (customCollector != null) { startupLogger.info("using collector: {}", customCollector.getClass().getName()); } glowrootAgentInit = new CentralGlowrootAgentInit(); } glowrootAgentInit.init(glowrootDir, agentDir, collectorAddress, customCollector, properties, instrumentation, version, false); } private static ImmutableMap<String, String> getGlowrootProperties(File glowrootDir) throws IOException { Map<String, String> properties = Maps.newHashMap(); File propFile = new File(glowrootDir, "glowroot.properties"); if (propFile.exists()) { // upgrade from 0.9.6 to 0.9.7 PropertiesFiles.upgradeIfNeeded(propFile, ImmutableMap.of("agent.rollup=", "agent.rollup.id=")); // upgrade from 0.9.13 to 0.9.14 upgradeToCollectorAddressIfNeeded(propFile); Properties props = PropertiesFiles.load(propFile); for (String key : props.stringPropertyNames()) { String value = props.getProperty(key); if (value != null) { properties.put("glowroot." + key, value); } } } for (Entry<Object, Object> entry : System.getProperties().entrySet()) { if (entry.getKey() instanceof String && entry.getValue() instanceof String && ((String) entry.getKey()).startsWith("glowroot.")) { String key = (String) entry.getKey(); properties.put(key, (String) entry.getValue()); } } return ImmutableMap.copyOf(properties); } @RequiresNonNull("startupLogger") private static void logAgentDirLockedException(File agentDir) { // this is common when stopping tomcat since 'catalina.sh stop' launches a java process // to stop the tomcat jvm, and it uses the same JAVA_OPTS environment variable that may // have been used to specify '-javaagent:glowroot.jar', in which case Glowroot tries // to start up, but it finds the h2 database is locked (by the tomcat jvm). // this can be avoided by using CATALINA_OPTS instead of JAVA_OPTS to specify // -javaagent:glowroot.jar, since CATALINA_OPTS is not used by the 'catalina.sh stop'. // however, when running tomcat from inside eclipse, the tomcat server adapter uses the // same 'VM arguments' for both starting and stopping tomcat, so this code path seems // inevitable at least in this case // // no need for logging in the special (but common) case described above if (!isTomcatStop()) { startupLogger.error("Glowroot not started, directory in use by another jvm process: {}", agentDir.getAbsolutePath()); } } private static boolean isTomcatStop() { return Objects.equal(System.getProperty("sun.java.command"), "org.apache.catalina.startup.Bootstrap stop"); } private static @Nullable Collector loadCustomCollector(File glowrootDir) throws MalformedURLException { Collector collector = loadCollector(MainEntryPoint.class.getClassLoader()); if (collector != null) { return collector; } File servicesDir = new File(glowrootDir, "services"); if (!servicesDir.exists()) { return null; } if (!servicesDir.isDirectory()) { return null; } File[] files = servicesDir.listFiles(); if (files == null) { return null; } List<URL> urls = Lists.newArrayList(); for (File file : files) { if (file.isFile() && file.getName().endsWith(".jar")) { urls.add(file.toURI().toURL()); } } if (urls.isEmpty()) { return null; } URLClassLoader servicesClassLoader = new URLClassLoader(urls.toArray(new URL[0])); return loadCollector(servicesClassLoader); } private static @Nullable Collector loadCollector(@Nullable ClassLoader classLoader) { ServiceLoader<Collector> serviceLoader = ServiceLoader.load(Collector.class, classLoader); Iterator<Collector> i = serviceLoader.iterator(); if (!i.hasNext()) { return null; } return i.next(); } private static void upgradeToCollectorAddressIfNeeded(File propFile) throws IOException { // properties files must be ISO_8859_1 List<String> lines = Files.readLines(propFile, Charsets.ISO_8859_1); List<String> newLines = upgradeToCollectorAddressIfNeeded(lines); if (!newLines.equals(lines)) { // properties files must be ISO_8859_1 PrintWriter out = new PrintWriter(Files.newWriter(propFile, Charsets.ISO_8859_1)); try { for (String newLine : newLines) { out.println(newLine); } } finally { out.close(); } } } @VisibleForTesting static List<String> upgradeToCollectorAddressIfNeeded(List<String> lines) { List<String> newLines = Lists.newArrayList(); String host = null; String port = null; int indexForAddress = -1; for (String line : lines) { if (line.startsWith("collector.host=")) { host = line.substring("collector.host=".length()); if (indexForAddress == -1) { indexForAddress = newLines.size(); } } else if (line.startsWith("collector.port=")) { port = line.substring("collector.port=".length()); if (indexForAddress == -1) { indexForAddress = newLines.size(); } } else if (line.startsWith("collector.address=")) { return lines; } else { newLines.add(line); } } if (indexForAddress == -1) { return newLines; } if (host == null) { return newLines; } if (host.isEmpty()) { newLines.add(indexForAddress, "collector.address="); return newLines; } if (port == null || port.isEmpty()) { port = "8181"; } newLines.add(indexForAddress, "collector.address=" + host + ":" + port); return newLines; } @OnlyUsedByTests public static void start(Map<String, String> properties) throws Exception { String testDirPath = properties.get("glowroot.test.dir"); checkNotNull(testDirPath); File testDir = new File(testDirPath); // init logger as early as possible initLogging(testDir, testDir); start(testDir, testDir, properties, null); } @OnlyUsedByTests public static @Nullable GlowrootAgentInit getGlowrootAgentInit() { return glowrootAgentInit; } }