//! Entrypoint of InfluxDB IOx binary #![deny(rustdoc::broken_intra_doc_links, rustdoc::bare_urls, rust_2018_idioms)] #![warn( missing_debug_implementations, clippy::explicit_iter_loop, clippy::use_self, clippy::clone_on_ref_ptr, clippy::future_not_send )] use dotenv::dotenv; use once_cell::sync::Lazy; use structopt::StructOpt; use tokio::runtime::Runtime; use commands::tracing::{init_logs_and_tracing, init_simple_logs}; use observability_deps::tracing::warn; use crate::commands::tracing::TroggingGuard; use influxdb_iox_client::connection::Builder; use std::str::FromStr; mod commands { pub mod database; pub mod debug; pub mod operations; pub mod run; pub mod server; pub mod server_remote; pub mod sql; pub mod tracing; } mod object_store; mod server_id; pub mod influxdb_ioxd; enum ReturnCode { Failure = 1, } static VERSION_STRING: Lazy = Lazy::new(|| { format!( "{}, revision {}", option_env!("CARGO_PKG_VERSION").unwrap_or("UNKNOWN"), option_env!("GIT_HASH").unwrap_or("UNKNOWN") ) }); #[cfg(all(feature = "heappy", feature = "jemalloc_replacing_malloc"))] compile_error!("heappy and jemalloc_replacing_malloc features are mutually exclusive"); #[derive(Debug, StructOpt)] #[structopt( name = "influxdb_iox", version = &VERSION_STRING[..], about = "InfluxDB IOx server and command line tools", long_about = r#"InfluxDB IOx server and command line tools Examples: # Run the InfluxDB IOx server: influxdb_iox run # Run the interactive SQL prompt influxdb_iox sql # Display all server settings influxdb_iox run --help # Run the InfluxDB IOx server with extra verbose logging influxdb_iox run -v # Run InfluxDB IOx with full debug logging specified with RUST_LOG RUST_LOG=debug influxdb_iox run Command are generally structured in the form: For example, a command such as the following shows all actions available for database chunks, including get and list. influxdb_iox database chunk --help "# )] struct Config { /// Log filter short-hand. /// /// Convenient way to set log severity level filter. /// Overrides --log-filter / LOG_FILTER. /// /// -v 'info' /// /// -vv 'debug,hyper::proto::h1=info,h2=info' /// /// -vvv 'trace,hyper::proto::h1=info,h2=info' #[structopt( short = "-v", long = "--verbose", multiple = true, takes_value = false, parse(from_occurrences) )] pub log_verbose_count: u8, /// gRPC address of IOx server to connect to #[structopt( short, long, global = true, env = "IOX_ADDR", default_value = "http://127.0.0.1:8082" )] host: String, /// Additional headers to add to CLI requests /// /// Values should be key value pairs separated by ':' #[structopt(long, global = true)] header: Vec>, #[structopt(long)] /// Set the maximum number of threads to use. Defaults to the number of /// cores on the system num_threads: Option, #[structopt(subcommand)] command: Command, } #[derive(Debug, StructOpt)] enum Command { Database(commands::database::Config), // Clippy recommended boxing this variant because it's much larger than the others Run(Box), Server(commands::server::Config), Operation(commands::operations::Config), Sql(commands::sql::Config), Debug(commands::debug::Config), } fn main() -> Result<(), std::io::Error> { install_crash_handler(); // attempt to render a useful stacktrace to stderr // load all environment variables from .env before doing anything load_dotenv(); let config: Config = StructOpt::from_args(); let tokio_runtime = get_runtime(config.num_threads)?; tokio_runtime.block_on(async move { let host = config.host; let headers = config.header; let log_verbose_count = config.log_verbose_count; let connection = || async move { let builder = headers.into_iter().fold(Builder::default(), |builder, kv| { builder.header(kv.key, kv.value) }); match builder.build(&host).await { Ok(connection) => connection, Err(e) => { eprintln!("Error connecting to {}: {}", host, e); std::process::exit(ReturnCode::Failure as _) } } }; fn handle_init_logs(r: Result) -> TroggingGuard { match r { Ok(guard) => guard, Err(e) => { eprintln!("Initializing logs failed: {}", e); std::process::exit(ReturnCode::Failure as _); } } } match config.command { Command::Database(config) => { let _tracing_guard = handle_init_logs(init_simple_logs(log_verbose_count)); let connection = connection().await; if let Err(e) = commands::database::command(connection, config).await { eprintln!("{}", e); std::process::exit(ReturnCode::Failure as _) } } Command::Operation(config) => { let _tracing_guard = handle_init_logs(init_simple_logs(log_verbose_count)); let connection = connection().await; if let Err(e) = commands::operations::command(connection, config).await { eprintln!("{}", e); std::process::exit(ReturnCode::Failure as _) } } Command::Server(config) => { let _tracing_guard = handle_init_logs(init_simple_logs(log_verbose_count)); let connection = connection().await; if let Err(e) = commands::server::command(connection, config).await { eprintln!("Server command failed: {}", e); std::process::exit(ReturnCode::Failure as _) } } Command::Run(config) => { let _tracing_guard = handle_init_logs(init_logs_and_tracing(log_verbose_count, &config)); if let Err(e) = commands::run::command(*config).await { eprintln!("Server command failed: {}", e); std::process::exit(ReturnCode::Failure as _) } } Command::Sql(config) => { let _tracing_guard = handle_init_logs(init_simple_logs(log_verbose_count)); let connection = connection().await; if let Err(e) = commands::sql::command(connection, config).await { eprintln!("{}", e); std::process::exit(ReturnCode::Failure as _) } } Command::Debug(config) => { let _tracing_guard = handle_init_logs(init_simple_logs(log_verbose_count)); if let Err(e) = commands::debug::command(config).await { eprintln!("{}", e); std::process::exit(ReturnCode::Failure as _) } } } }); Ok(()) } /// Creates the tokio runtime for executing IOx /// /// if nthreads is none, uses the default scheduler /// otherwise, creates a scheduler with the number of threads fn get_runtime(num_threads: Option) -> Result { // NOTE: no log macros will work here! // // That means use eprintln!() instead of error!() and so on. The log emitter // requires a running tokio runtime and is initialised after this function. use tokio::runtime::Builder; let kind = std::io::ErrorKind::Other; match num_threads { None => Runtime::new(), Some(num_threads) => { println!( "Setting number of threads to '{}' per command line request", num_threads ); match num_threads { 0 => { let msg = format!( "Invalid num-threads: '{}' must be greater than zero", num_threads ); Err(std::io::Error::new(kind, msg)) } 1 => Builder::new_current_thread().enable_all().build(), _ => Builder::new_multi_thread() .enable_all() .worker_threads(num_threads) .build(), } } } } /// Source the .env file before initialising the Config struct - this sets /// any envs in the file, which the Config struct then uses. /// /// Precedence is given to existing env variables. fn load_dotenv() { match dotenv() { Ok(_) => {} Err(dotenv::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => { // Ignore this - a missing env file is not an error, defaults will // be applied when initialising the Config struct. } Err(e) => { eprintln!("FATAL Error loading config from: {}", e); eprintln!("Aborting"); std::process::exit(1); } }; } // Based on ideas from // https://github.com/servo/servo/blob/f03ddf6c6c6e94e799ab2a3a89660aea4a01da6f/ports/servo/main.rs#L58-L79 fn install_crash_handler() { unsafe { set_signal_handler(libc::SIGSEGV, signal_handler); // handle segfaults set_signal_handler(libc::SIGILL, signal_handler); // handle stack overflow and unsupported CPUs set_signal_handler(libc::SIGBUS, signal_handler); // handle invalid memory access } } unsafe extern "C" fn signal_handler(sig: i32) { use backtrace::Backtrace; use std::process::abort; let name = std::thread::current() .name() .map(|n| format!(" for thread \"{}\"", n)) .unwrap_or_else(|| "".to_owned()); eprintln!( "Signal {}, Stack trace{}\n{:?}", sig, name, Backtrace::new() ); abort(); } // based on https://github.com/adjivas/sig/blob/master/src/lib.rs#L34-L52 unsafe fn set_signal_handler(signal: libc::c_int, handler: unsafe extern "C" fn(libc::c_int)) { use libc::{sigaction, sigfillset, sighandler_t}; let mut sigset = std::mem::zeroed(); // Block all signals during the handler. This is the expected behavior, but // it's not guaranteed by `signal()`. if sigfillset(&mut sigset) != -1 { // Done because sigaction has private members. // This is safe because sa_restorer and sa_handlers are pointers that // might be null (that is, zero). let mut action: sigaction = std::mem::zeroed(); // action.sa_flags = 0; action.sa_mask = sigset; action.sa_sigaction = handler as sighandler_t; sigaction(signal, &action, std::ptr::null_mut()); } } /// A ':' separated key value pair #[derive(Debug, Clone)] struct KeyValue { pub key: K, pub value: V, } impl std::str::FromStr for KeyValue where K: FromStr, V: FromStr, K::Err: std::fmt::Display, V::Err: std::fmt::Display, { type Err = String; fn from_str(s: &str) -> Result { use itertools::Itertools; match s.splitn(2, ':').collect_tuple() { Some((key, value)) => { let key = K::from_str(key).map_err(|e| e.to_string())?; let value = V::from_str(value).map_err(|e| e.to_string())?; Ok(Self { key, value }) } None => Err(format!( "Invalid key value pair - expected 'KEY:VALUE' got '{}'", s )), } } }