/* * Copyright 2014-2016 Netflix, Inc. * * 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 com.netflix.spectator.agent; import com.netflix.spectator.api.Clock; import com.netflix.spectator.api.Spectator; import com.netflix.spectator.atlas.AtlasConfig; import com.netflix.spectator.atlas.AtlasRegistry; import com.netflix.spectator.gc.GcLogger; import com.netflix.spectator.jvm.Jmx; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.lang.instrument.Instrumentation; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Agent that can be added to JVM to get basic stats about GC, memory, and optionally * JMX. */ public final class Agent { private static final Logger LOGGER = LoggerFactory.getLogger(Agent.class); private Agent() { } private static Config loadConfig(String userResources) { Config config = ConfigFactory.load("agent"); if (userResources != null && !"".equals(userResources)) { for (String userResource : userResources.split("[,\\s]+]")) { if (userResource.startsWith("file:")) { File file = new File(userResource.substring("file:".length())); LOGGER.info("loading configuration from file: {}", file); Config user = ConfigFactory.parseFile(file).resolve(); config = user.withFallback(config); } else { LOGGER.info("loading configuration from resource: {}", userResource); Config user = ConfigFactory.load(userResource); config = user.withFallback(config); } } } return config.getConfig("netflix.spectator.agent"); } /** * To make debugging easier since we usually create a fat jar, system properties can * be created so that all jar versions can easily be accessed via JMX or using tools * like {@code jinfo}. */ private static void createDependencyProperties(Config config) { if (config.hasPath("dependencies")) { List<String> deps = config.getStringList("dependencies"); for (int i = 0; i < deps.size(); ++i) { String prop = String.format("netflix.spectator.agent.dependency.%03d", i); System.setProperty(prop, deps.get(i)); } } } /** Entry point for the agent. */ public static void premain(String arg, Instrumentation instrumentation) throws Exception { // Setup logging Config config = loadConfig(arg); createDependencyProperties(config); // Setup Registry AtlasRegistry registry = new AtlasRegistry(Clock.SYSTEM, new AgentAtlasConfig(config)); // Add to global registry for http stats and GC logger Spectator.globalRegistry().add(registry); // Enable GC logger GcLogger gcLogger = new GcLogger(); if (config.getBoolean("collection.gc")) { gcLogger.start(null); } // Enable JVM data collection if (config.getBoolean("collection.jvm")) { Jmx.registerStandardMXBeans(registry); } // Enable JMX query collection if (config.getBoolean("collection.jmx")) { for (Config cfg : config.getConfigList("jmx.mappings")) { registry.register(new JmxMeter(registry, JmxConfig.from(cfg))); } } // Start collection for the registry registry.start(); // Shutdown registry Runtime.getRuntime().addShutdownHook(new Thread(registry::stop, "spectator-agent-shutdown")); } private static class AgentAtlasConfig implements AtlasConfig { private final Config config; AgentAtlasConfig(Config config) { this.config = config; } @Override public String get(String k) { return config.hasPath(k) ? config.getString(k) : null; } @Override public Map<String, String> commonTags() { Map<String, String> tags = new HashMap<>(); for (Config cfg : config.getConfigList("atlas.tags")) { // These are often populated by environment variables that can sometimes be empty // rather than not set when missing. Empty strings are not allowed by Atlas. String value = cfg.getString("value"); if (!value.isEmpty()) { tags.put(cfg.getString("key"), cfg.getString("value")); } } return tags; } } }