// This file is part of OpenTSDB. // Copyright (C) 2010-2012 The OpenTSDB Authors. // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 2.1 of the License, or (at your // option) any later version. This program is distributed in the hope that it // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser // General Public License for more details. You should have received a copy // of the GNU Lesser General Public License along with this program. If not, // see <http://www.gnu.org/licenses/>. package net.opentsdb.tools; import java.io.IOException; import java.lang.reflect.Constructor; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.HashMap; import java.util.Map; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.socket.ServerSocketChannelFactory; import org.jboss.netty.channel.socket.nio.NioServerBossPool; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.jboss.netty.channel.socket.nio.NioWorkerPool; import org.jboss.netty.channel.socket.oio.OioServerSocketChannelFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.opentsdb.tools.BuildData; import net.opentsdb.core.TSDB; import net.opentsdb.core.Const; import net.opentsdb.tsd.PipelineFactory; import net.opentsdb.tsd.RpcManager; import net.opentsdb.utils.Config; import net.opentsdb.utils.FileSystem; import net.opentsdb.utils.Pair; import net.opentsdb.utils.PluginLoader; import net.opentsdb.utils.Threads; /** * Main class of the TSD, the Time Series Daemon. */ final class TSDMain { /** Prints usage and exits with the given retval. */ static void usage(final ArgP argp, final String errmsg, final int retval) { System.err.println(errmsg); System.err.println("Usage: tsd --port=PORT" + " --staticroot=PATH --cachedir=PATH\n" + "Starts the TSD, the Time Series Daemon"); if (argp != null) { System.err.print(argp.usage()); } System.exit(retval); } /** A map of configured filters for use in querying */ private static Map<String, Pair<Class<?>, Constructor<? extends StartupPlugin>>> startupPlugin_filter_map = new HashMap<String, Pair<Class<?>, Constructor<? extends StartupPlugin>>>(); private static final short DEFAULT_FLUSH_INTERVAL = 1000; private static TSDB tsdb = null; public static void main(String[] args) throws IOException { Logger log = LoggerFactory.getLogger(TSDMain.class); log.info("Starting."); log.info(BuildData.revisionString()); log.info(BuildData.buildString()); try { System.in.close(); // Release a FD we don't need. } catch (Exception e) { log.warn("Failed to close stdin", e); } final ArgP argp = new ArgP(); CliOptions.addCommon(argp); argp.addOption("--port", "NUM", "TCP port to listen on."); argp.addOption("--bind", "ADDR", "Address to bind to (default: 0.0.0.0)."); argp.addOption("--staticroot", "PATH", "Web root from which to serve static files (/s URLs)."); argp.addOption("--cachedir", "PATH", "Directory under which to cache result of requests."); argp.addOption("--worker-threads", "NUM", "Number for async io workers (default: cpu * 2)."); argp.addOption("--async-io", "true|false", "Use async NIO (default true) or traditional blocking io"); argp.addOption("--read-only", "true|false", "Set tsd.mode to ro (default false)"); argp.addOption("--disable-ui", "true|false", "Set tsd.core.enable_ui to false (default true)"); argp.addOption("--disable-api", "true|false", "Set tsd.core.enable_api to false (default true)"); argp.addOption("--backlog", "NUM", "Size of connection attempt queue (default: 3072 or kernel" + " somaxconn."); argp.addOption("--max-connections", "NUM", "Maximum number of connections to accept"); argp.addOption("--flush-interval", "MSEC", "Maximum time for which a new data point can be buffered" + " (default: " + DEFAULT_FLUSH_INTERVAL + ")."); argp.addOption("--statswport", "Force all stats to include the port"); CliOptions.addAutoMetricFlag(argp); args = CliOptions.parse(argp, args); args = null; // free(). // get a config object Config config = CliOptions.getConfig(argp); // check for the required parameters try { if (config.getString("tsd.http.staticroot").isEmpty()) usage(argp, "Missing static root directory", 1); } catch(NullPointerException npe) { usage(argp, "Missing static root directory", 1); } try { if (config.getString("tsd.http.cachedir").isEmpty()) usage(argp, "Missing cache directory", 1); } catch(NullPointerException npe) { usage(argp, "Missing cache directory", 1); } try { if (!config.hasProperty("tsd.network.port")) usage(argp, "Missing network port", 1); config.getInt("tsd.network.port"); } catch (NumberFormatException nfe) { usage(argp, "Invalid network port setting", 1); } // validate the cache and staticroot directories try { FileSystem.checkDirectory(config.getString("tsd.http.staticroot"), !Const.MUST_BE_WRITEABLE, Const.DONT_CREATE); FileSystem.checkDirectory(config.getString("tsd.http.cachedir"), Const.MUST_BE_WRITEABLE, Const.CREATE_IF_NEEDED); } catch (IllegalArgumentException e) { usage(argp, e.getMessage(), 3); } final ServerSocketChannelFactory factory; int connections_limit = 0; try { connections_limit = config.getInt("tsd.core.connections.limit"); } catch (NumberFormatException nfe) { usage(argp, "Invalid connections limit", 1); } if (config.getBoolean("tsd.network.async_io")) { int workers = Runtime.getRuntime().availableProcessors() * 2; if (config.hasProperty("tsd.network.worker_threads")) { try { workers = config.getInt("tsd.network.worker_threads"); } catch (NumberFormatException nfe) { usage(argp, "Invalid worker thread count", 1); } } final Executor executor = Executors.newCachedThreadPool(); final NioServerBossPool boss_pool = new NioServerBossPool(executor, 1, new Threads.BossThreadNamer()); final NioWorkerPool worker_pool = new NioWorkerPool(executor, workers, new Threads.WorkerThreadNamer()); factory = new NioServerSocketChannelFactory(boss_pool, worker_pool); } else { factory = new OioServerSocketChannelFactory( Executors.newCachedThreadPool(), Executors.newCachedThreadPool(), new Threads.PrependThreadNamer()); } StartupPlugin startup = null; try { startup = loadStartupPlugins(config); } catch (IllegalArgumentException e) { usage(argp, e.getMessage(), 3); } catch (Exception e) { throw new RuntimeException("Initialization failed", e); } try { tsdb = new TSDB(config); if (startup != null) { tsdb.setStartupPlugin(startup); } tsdb.initializePlugins(true); if (config.getBoolean("tsd.storage.hbase.prefetch_meta")) { tsdb.preFetchHBaseMeta(); } // Make sure we don't even start if we can't find our tables. tsdb.checkNecessaryTablesExist().joinUninterruptibly(); registerShutdownHook(); final ServerBootstrap server = new ServerBootstrap(factory); // This manager is capable of lazy init, but we force an init // here to fail fast. final RpcManager manager = RpcManager.instance(tsdb); server.setPipelineFactory(new PipelineFactory(tsdb, manager, connections_limit)); if (config.hasProperty("tsd.network.backlog")) { server.setOption("backlog", config.getInt("tsd.network.backlog")); } server.setOption("child.tcpNoDelay", config.getBoolean("tsd.network.tcp_no_delay")); server.setOption("child.keepAlive", config.getBoolean("tsd.network.keep_alive")); server.setOption("reuseAddress", config.getBoolean("tsd.network.reuse_address")); // null is interpreted as the wildcard address. InetAddress bindAddress = null; if (config.hasProperty("tsd.network.bind")) { bindAddress = InetAddress.getByName(config.getString("tsd.network.bind")); } // we validated the network port config earlier final InetSocketAddress addr = new InetSocketAddress(bindAddress, config.getInt("tsd.network.port")); server.bind(addr); if (startup != null) { startup.setReady(tsdb); } log.info("Ready to serve on " + addr); } catch (Throwable e) { factory.releaseExternalResources(); try { if (tsdb != null) tsdb.shutdown().joinUninterruptibly(); } catch (Exception e2) { log.error("Failed to shutdown HBase client", e2); } throw new RuntimeException("Initialization failed", e); } // The server is now running in separate threads, we can exit main. } private static StartupPlugin loadStartupPlugins(Config config) { Logger log = LoggerFactory.getLogger(TSDMain.class); // load the startup plugin if enabled StartupPlugin startup = null; if (config.getBoolean("tsd.startup.enable")) { log.debug("Startup Plugin is Enabled"); final String plugin_path = config.getString("tsd.core.plugin_path"); final String plugin_class = config.getString("tsd.startup.plugin"); log.debug("Plugin Path: " + plugin_path); try { TSDB.loadPluginPath(plugin_path); } catch (Exception e) { log.error("Error loading plugins from plugin path: " + plugin_path, e); } log.debug("Attempt to Load: " + plugin_class); startup = PluginLoader.loadSpecificPlugin(plugin_class, StartupPlugin.class); if (startup == null) { throw new IllegalArgumentException("Unable to locate startup plugin: " + config.getString("tsd.startup.plugin")); } try { startup.initialize(config); } catch (Exception e) { throw new RuntimeException("Failed to initialize startup plugin", e); } log.info("Successfully initialized startup plugin [" + startup.getClass().getCanonicalName() + "] version: " + startup.version()); } else { startup = null; } return startup; } private static void registerShutdownHook() { final class TSDBShutdown extends Thread { public TSDBShutdown() { super("TSDBShutdown"); } public void run() { try { if (RpcManager.isInitialized()) { // Check that its actually been initialized. We don't want to // create a new instance only to shutdown! RpcManager.instance(tsdb).shutdown().join(); } if (tsdb != null) { tsdb.shutdown().join(); } } catch (Exception e) { LoggerFactory.getLogger(TSDBShutdown.class) .error("Uncaught exception during shutdown", e); } } } Runtime.getRuntime().addShutdownHook(new TSDBShutdown()); } }