Skip to content

Commit

Permalink
Improve start up time of H2O (#800)
Browse files Browse the repository at this point in the history
* Add command line option `--no_latest_check` to skip retrieving the latest version from S3 during startup
* Skip REST API & schemas registration when `--disable_web` command line option is provided
* Add profiling information about H2O start time breakdown
  • Loading branch information
st-pasha committed Feb 17, 2017
1 parent 1b7deeb commit 014b1cb
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 26 deletions.
2 changes: 1 addition & 1 deletion h2o-app/src/main/java/water/H2OApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ public static void main(String[] args) {
public static void main2(String relativeResourcePath) {
start(new String[0], relativeResourcePath);
}
}
}
63 changes: 50 additions & 13 deletions h2o-core/src/main/java/water/H2O.java
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public static void printHelp() {
/** -flow_dir=/path/to/dir; directory to save flows in */
public String flow_dir;

/** -disable_web; disable web API port (used by Sparkling Water) */
/** -disable_web; disable Jetty and REST API interface */
public boolean disable_web = false;

/** -context_path=jetty_context_path; the context path for jetty */
Expand Down Expand Up @@ -303,6 +303,9 @@ public static void printHelp() {

public boolean useUDP = false;

/** -no_latest_check Do not attempt to retrieve latest H2O version from S3 on startup */
public boolean noLatestCheck = false;

@Override public String toString() {
StringBuilder result = new StringBuilder();

Expand Down Expand Up @@ -524,6 +527,9 @@ else if (s.matches("internal_security_conf")) {
i = s.incrementAndCheck(i, args);
ARGS.internal_security_conf = args[i];
}
else if (s.matches("no_latest_check")) {
ARGS.noLatestCheck = true;
}
else {
parseFailed("Unknown argument (" + s + ")");
}
Expand Down Expand Up @@ -1244,7 +1250,7 @@ public H2OCallback(){}
* @return String of the form ipaddress:port
*/
public static String getIpPortString() {
return H2O.SELF_ADDRESS.getHostAddress() + ":" + H2O.API_PORT;
return H2O.ARGS.disable_web? "" : H2O.SELF_ADDRESS.getHostAddress() + ":" + H2O.API_PORT;
}

public static String getURL(String schema) {
Expand Down Expand Up @@ -1340,8 +1346,8 @@ public static JettyHTTPD getJetty() {
/** If logging has not been setup yet, then Log.info will only print to
* stdout. This allows for early processing of the '-version' option
* without unpacking the jar file and other startup stuff. */
static void printAndLogVersion(String[] arguments) {
String latestVersion = ABV.getLatestH2OVersion();
private static void printAndLogVersion(String[] arguments) {
String latestVersion = ARGS.noLatestCheck ? "?" : ABV.getLatestH2OVersion();
Log.init(ARGS.log_level, ARGS.quiet);
Log.info("----- H2O started " + (ARGS.client?"(client)":"") + " -----");
Log.info("Build git branch: " + ABV.branchName());
Expand Down Expand Up @@ -1408,11 +1414,12 @@ private static void startLocalNode() {
? (", discovery address "+CLOUD_MULTICAST_GROUP+":"+CLOUD_MULTICAST_PORT)
: ", static configuration based on -flatfile "+ARGS.flatfile));

Log.info("If you have trouble connecting, try SSH tunneling from your local machine (e.g., via port 55555):\n" +
" 1. Open a terminal and run 'ssh -L 55555:localhost:"
+ API_PORT + " " + System.getProperty("user.name") + "@" + SELF_ADDRESS.getHostAddress() + "'\n" +
" 2. Point your browser to " + jetty.getScheme() + "://localhost:55555");

if (!H2O.ARGS.disable_web) {
Log.info("If you have trouble connecting, try SSH tunneling from your local machine (e.g., via port 55555):\n" +
" 1. Open a terminal and run 'ssh -L 55555:localhost:"
+ API_PORT + " " + System.getProperty("user.name") + "@" + SELF_ADDRESS.getHostAddress() + "'\n" +
" 2. Point your browser to " + jetty.getScheme() + "://localhost:55555");
}

// Create the starter Cloud with 1 member
SELF._heartbeat._jar_md5 = JarHash.JARHASH;
Expand Down Expand Up @@ -1473,7 +1480,7 @@ public static void registerResourceRoot(File f) {
/** Start the web service; disallow future URL registration.
* Blocks until the server is up. */
static public void finalizeRegistration() {
if (_doneRequests) return;
if (_doneRequests || H2O.ARGS.disable_web) return;
_doneRequests = true;

water.api.SchemaServer.registerAllSchemasIfNecessary();
Expand Down Expand Up @@ -1724,6 +1731,7 @@ public static boolean checkUnsupportedJava() {

// --------------------------------------------------------------------------
public static void main( String[] args ) {
long time0 = System.currentTimeMillis();

if (checkUnsupportedJava())
throw new RuntimeException("Unsupported Java version");
Expand All @@ -1750,6 +1758,7 @@ public static void main( String[] args ) {
parseArguments(arguments);

// Get ice path before loading Log or Persist class
long time1 = System.currentTimeMillis();
String ice = DEFAULT_ICE_ROOT();
if( ARGS.ice_root != null ) ice = ARGS.ice_root.replace("\\", "/");
try {
Expand All @@ -1759,14 +1768,18 @@ public static void main( String[] args ) {
}

// Always print version, whether asked-for or not!
long time2 = System.currentTimeMillis();
printAndLogVersion(arguments);
if( ARGS.version ) {
Log.flushStdout();
exit(0);
}

// Print help & exit
if( ARGS.help ) { printHelp(); exit(0); }
if (ARGS.help) {
printHelp();
exit(0);
}

// Validate arguments
validateArguments();
Expand All @@ -1775,11 +1788,12 @@ public static void main( String[] args ) {
Log.info("User name: '" + H2O.ARGS.user_name + "'");

// Register with GA or not
List<String> gaidList = JarHash.getResourcesList("gaid");
long time3 = System.currentTimeMillis();
List<String> gaidList; // fetching this list takes ~100ms
if((new File(".h2o_no_collect")).exists()
|| (new File(System.getProperty("user.home")+File.separator+".h2o_no_collect")).exists()
|| ARGS.ga_opt_out
|| gaidList.contains("CRAN")
|| (gaidList = JarHash.getResourcesList("gaid")).contains("CRAN")
|| H2O.ABV.projectVersion().split("\\.")[3].equals("99999")) { // dev build has minor version 99999
GA = null;
Log.info("Opted out of sending usage metrics.");
Expand Down Expand Up @@ -1813,6 +1827,7 @@ public static void main( String[] args ) {
}

// Epic Hunt for the correct self InetAddress
long time4 = System.currentTimeMillis();
Log.info("IPv6 stack selected: " + IS_IPV6);
SELF_ADDRESS = NetworkInit.findInetAddressForSelf();
// Right now the global preference is to use IPv4 stack
Expand All @@ -1829,9 +1844,11 @@ public static void main( String[] args ) {
}

// Start the local node. Needed before starting logging.
long time5 = System.currentTimeMillis();
startLocalNode();

// Allow extensions to perform initialization that requires the network.
long time6 = System.currentTimeMillis();
for (AbstractH2OExtension ext: extensions) {
ext.onLocalNodeStarted();
}
Expand All @@ -1847,13 +1864,15 @@ public static void main( String[] args ) {
Log.info("Cur dir: '" + System.getProperty("user.dir") + "'");

//Print extra debug info now that logs are setup
long time7 = System.currentTimeMillis();
RuntimeMXBean rtBean = ManagementFactory.getRuntimeMXBean();
Log.debug("H2O launch parameters: "+ARGS.toString());
Log.debug("Boot class path: "+ rtBean.getBootClassPath());
Log.debug("Java class path: "+ rtBean.getClassPath());
Log.debug("Java library path: "+ rtBean.getLibraryPath());

// Load up from disk and initialize the persistence layer
long time8 = System.currentTimeMillis();
initializePersistence();

// Initialize NPS
Expand All @@ -1879,11 +1898,13 @@ public static void main( String[] args ) {
}

// Start network services, including heartbeats
long time9 = System.currentTimeMillis();
startNetworkServices(); // start server services
Log.trace("Network services started");

// The "Cloud of size N formed" message printed out by doHeartbeat is the trigger
// for users of H2O to know that it's OK to start sending REST API requests.
long time10 = System.currentTimeMillis();
Paxos.doHeartbeat(SELF);
assert SELF._heartbeat._cloud_hash != 0 || ARGS.client;

Expand All @@ -1892,11 +1913,27 @@ public static void main( String[] args ) {
// join an existing Cloud.
new HeartBeatThread().start();

long time11 = System.currentTimeMillis();
if (GA != null)
startGAStartupReport();

// Log registered parsers
Log.info("Registered parsers: " + Arrays.toString(ParserService.INSTANCE.getAllProviderNames(true)));

long time12 = System.currentTimeMillis();
Log.debug("Timing within H2O.main():");
Log.debug(" Args parsing & validation: " + (time1 - time0) + "ms");
Log.debug(" Get ICE root: " + (time2 - time1) + "ms");
Log.debug(" Print log version: " + (time3 - time2) + "ms");
Log.debug(" Register GA: " + (time4 - time3) + "ms");
Log.debug(" Detect network address: " + (time5 - time4) + "ms");
Log.debug(" Start local node: " + (time6 - time5) + "ms");
Log.debug(" Extensions onLocalNodeStarted(): " + (time7 - time6) + "ms");
Log.debug(" RuntimeMxBean: " + (time8 - time7) + "ms");
Log.debug(" Initialize persistence layer: " + (time9 - time8) + "ms");
Log.debug(" Start network services: " + (time10 - time9) + "ms");
Log.debug(" Cloud up: " + (time11 - time10) + "ms");
Log.debug(" Start GA: " + (time12 - time11) + "ms");
}

// Die horribly
Expand Down
13 changes: 9 additions & 4 deletions h2o-core/src/main/java/water/H2OStarter.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,23 @@ public class H2OStarter {
* @param finalizeRestRegistration close registration of REST API
*/
public static void start(String[] args, String relativeResourcePath, boolean finalizeRestRegistration) {
long time0 = System.currentTimeMillis();
H2O.configureLogging();
H2O.registerExtensions();

// Fire up the H2O Cluster
H2O.main(args);

H2O.registerRestApis(relativeResourcePath);
if (finalizeRestRegistration) {
H2O.finalizeRegistration();
if (!H2O.ARGS.disable_web) {
H2O.registerRestApis(relativeResourcePath);
if (finalizeRestRegistration) {
H2O.finalizeRegistration();
}
}

if (! H2O.ARGS.disable_web) {
long timeF = System.currentTimeMillis();
Log.info("H2O started in " + (timeF - time0) + "ms");
if (!H2O.ARGS.disable_web) {
Log.info("");
Log.info("Open H2O Flow in your web browser: " + H2O.getURL(H2O.getJetty().getScheme()));
Log.info("");
Expand Down
27 changes: 19 additions & 8 deletions h2o-core/src/main/java/water/init/NetworkInit.java
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ public static void initializeNetworkSockets( ) {
H2O.API_PORT = H2O.ARGS.port == 0 ? H2O.ARGS.baseport : H2O.ARGS.port;

// Late instantiation of Jetty object, if needed.
if (H2O.getJetty() == null) {
if (H2O.getJetty() == null && !H2O.ARGS.disable_web) {
H2O.setJetty(new JettyHTTPD());
}

Expand All @@ -402,7 +402,7 @@ public static void initializeNetworkSockets( ) {
// At this point we would like to allocate 2 consecutive ports
//
while (true) {
H2O.H2O_PORT = H2O.API_PORT+1;
H2O.H2O_PORT = H2O.API_PORT + 1;
try {
// kbn. seems like we need to set SO_REUSEADDR before binding?
// http://www.javadocexamples.com/java/net/java.net.ServerSocket.html#setReuseAddress:boolean
Expand Down Expand Up @@ -431,12 +431,13 @@ public static void initializeNetworkSockets( ) {
_tcpSocket.socket().bind(isa);

// Warning: There is a ip:port race between socket close and starting Jetty
if (! H2O.ARGS.disable_web) {
if (!H2O.ARGS.disable_web) {
apiSocket.close();
H2O.getJetty().start(H2O.ARGS.web_ip, H2O.API_PORT);
}

break;

} catch (Exception e) {
Log.trace("Cannot allocate API port " + H2O.API_PORT + " because of following exception: ", e);
if( apiSocket != null ) try { apiSocket.close(); } catch( IOException ohwell ) { Log.err(ohwell); }
Expand All @@ -460,15 +461,25 @@ public static void initializeNetworkSockets( ) {
}
boolean isIPv6 = H2O.SELF_ADDRESS instanceof Inet6Address; // Is IPv6 address was assigned to this node
H2O.SELF = H2ONode.self(H2O.SELF_ADDRESS);
Log.info("Internal communication uses port: ", H2O.H2O_PORT, "\n" +
"Listening for HTTP and REST traffic on " + H2O.getURL(H2O.getJetty().getScheme()) + "/");
try { Log.debug("Interface MTU: ", (NetworkInterface.getByInetAddress(H2O.SELF_ADDRESS)).getMTU());
} catch (SocketException se) { Log.debug("No MTU due to SocketException. "+se.toString()); }
if (!H2O.ARGS.disable_web) {
Log.info("Internal communication uses port: ", H2O.H2O_PORT, "\n" +
"Listening for HTTP and REST traffic on " + H2O.getURL(H2O.getJetty().getScheme()) + "/");
}
try {
Log.debug("Interface MTU: ", (NetworkInterface.getByInetAddress(H2O.SELF_ADDRESS)).getMTU());
} catch (SocketException se) {
Log.debug("No MTU due to SocketException. " + se.toString());
}

String embeddedConfigFlatfile = null;
AbstractEmbeddedH2OConfig ec = H2O.getEmbeddedH2OConfig();
if (ec != null) {
ec.notifyAboutEmbeddedWebServerIpPort (H2O.SELF_ADDRESS, H2O.API_PORT);
// TODO: replace this call with ec.notifyAboutH2oCommunicationChannel(H2O.SELF_ADDRESS, H2O.H2O_PORT)
// As of right now, the function notifies about the H2O.API_PORT, and then the listener adds +1
// to that in order to determine the H2O_PORT (which what it really cares about). Such
// assumption is dangerous: we should be free of using independent API_PORT and H2O_PORT,
// including the ability of not using any API_PORT at all...
ec.notifyAboutEmbeddedWebServerIpPort(H2O.SELF_ADDRESS, H2O.API_PORT);
if (ec.providesFlatfile()) {
try {
embeddedConfigFlatfile = ec.fetchFlatfile();
Expand Down

0 comments on commit 014b1cb

Please sign in to comment.