// Copyright 2016 Twitter. All rights reserved.
//
// 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.twitter.heron.simulator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.twitter.heron.api.Config;
import com.twitter.heron.api.HeronTopology;
import com.twitter.heron.api.generated.TopologyAPI;
import com.twitter.heron.common.basics.ByteAmount;
import com.twitter.heron.common.basics.SingletonRegistry;
import com.twitter.heron.common.config.SystemConfig;
import com.twitter.heron.common.config.SystemConfigKey;
import com.twitter.heron.proto.system.PhysicalPlans;
import com.twitter.heron.simulator.executors.InstanceExecutor;
import com.twitter.heron.simulator.executors.MetricsExecutor;
import com.twitter.heron.simulator.executors.StreamExecutor;
import com.twitter.heron.simulator.utils.PhysicalPlanUtil;
import com.twitter.heron.spi.utils.TopologyUtils;
/**
* One Simulator instance can only submit one topology. Please have multiple Simulator instances
* for multiple topologies.
*/
public class Simulator {
private static final Logger LOG = Logger.getLogger(Simulator.class.getName());
private final List<InstanceExecutor> instanceExecutors = new LinkedList<>();
// Thread pool to run StreamExecutor, MetricsExecutor and InstanceExecutor
private final ExecutorService threadsPool = Executors.newCachedThreadPool();
private SystemConfig systemConfig;
private StreamExecutor streamExecutor;
private MetricsExecutor metricsExecutor;
public Simulator() {
this(true);
}
public Simulator(boolean initialize) {
if (initialize) {
init();
}
}
protected void init() {
// Instantiate the System Config
this.systemConfig = getSystemConfig();
// Add the SystemConfig into SingletonRegistry. We synchronized on the singleton object here to
// make sure the "check and register" is atomic. And wrapping the containsSingleton and
// registerSystemConfig for easy of unit testing
synchronized (SingletonRegistry.INSTANCE) {
if (!isSystemConfigExisted()) {
LOG.info("System config not existed. Registering...");
registerSystemConfig(systemConfig);
LOG.info("System config registered.");
} else {
LOG.info("System config already existed.");
}
}
}
/**
* Check if the system config is already registered into the SingleRegistry
*
* @return true if it's registered; false otherwise
*/
protected boolean isSystemConfigExisted() {
return SingletonRegistry.INSTANCE.containsSingleton(SystemConfig.HERON_SYSTEM_CONFIG);
}
/**
* Register the given system config
*/
protected void registerSystemConfig(SystemConfig sysConfig) {
SingletonRegistry.INSTANCE.registerSingleton(SystemConfig.HERON_SYSTEM_CONFIG, sysConfig);
}
public void submitTopology(String name, Config heronConfig, HeronTopology heronTopology) {
TopologyAPI.Topology topologyToRun =
heronTopology.
setConfig(heronConfig).
setName(name).
setState(TopologyAPI.TopologyState.RUNNING).
getTopology();
if (!TopologyUtils.verifyTopology(topologyToRun)) {
throw new RuntimeException("Topology object is Malformed");
}
PhysicalPlans.PhysicalPlan pPlan = PhysicalPlanUtil.getPhysicalPlan(topologyToRun);
LOG.info("Physical Plan: \n" + pPlan);
// Create the stream executor
streamExecutor = new StreamExecutor(pPlan);
// Create the metrics executor
metricsExecutor = new MetricsExecutor(systemConfig);
// Create instance Executor
for (PhysicalPlans.Instance instance : pPlan.getInstancesList()) {
InstanceExecutor instanceExecutor = new InstanceExecutor(pPlan, instance.getInstanceId());
streamExecutor.addInstanceExecutor(instanceExecutor);
metricsExecutor.addInstanceExecutor(instanceExecutor);
instanceExecutors.add(instanceExecutor);
}
// Start - run executors
// Add exception handler for any uncaught exception here.
Thread.setDefaultUncaughtExceptionHandler(new DefaultExceptionHandler());
threadsPool.execute(metricsExecutor);
threadsPool.execute(streamExecutor);
for (InstanceExecutor instanceExecutor : instanceExecutors) {
threadsPool.execute(instanceExecutor);
}
}
public void killTopology(String topologyName) {
LOG.info("To kill topology: " + topologyName);
stop();
LOG.info("Topology killed successfully");
}
public void activate(String topologyName) {
LOG.info("To activate topology: " + topologyName);
for (InstanceExecutor executor : instanceExecutors) {
executor.activate();
}
LOG.info("Activated topology: " + topologyName);
}
public void deactivate(String topologyName) {
LOG.info("To deactivate topology: " + topologyName);
for (InstanceExecutor executor : instanceExecutors) {
executor.deactivate();
}
LOG.info("Deactivated topology:" + topologyName);
}
public void shutdown() {
LOG.info("To shutdown thread pool");
if (threadsPool.isShutdown()) {
threadsPool.shutdownNow();
}
LOG.info("Heron simulator exited.");
}
public void stop() {
for (InstanceExecutor executor : instanceExecutors) {
executor.stop();
}
LOG.info("To stop Stream Executor");
streamExecutor.stop();
LOG.info("To stop Metrics Executor");
metricsExecutor.stop();
threadsPool.shutdown();
}
protected SystemConfig getSystemConfig() {
SystemConfig.Builder builder = SystemConfig.newBuilder(true)
.put(SystemConfigKey.INSTANCE_SET_DATA_TUPLE_CAPACITY, 256)
.put(SystemConfigKey.INSTANCE_SET_CONTROL_TUPLE_CAPACITY, 256)
.put(SystemConfigKey.HERON_METRICS_EXPORT_INTERVAL, 60)
.put(SystemConfigKey.INSTANCE_EXECUTE_BATCH_TIME, 16)
.put(SystemConfigKey.INSTANCE_EXECUTE_BATCH_SIZE, ByteAmount.fromBytes(32768))
.put(SystemConfigKey.INSTANCE_EMIT_BATCH_TIME, 16)
.put(SystemConfigKey.INSTANCE_EMIT_BATCH_SIZE, ByteAmount.fromBytes(32768))
.put(SystemConfigKey.INSTANCE_ACK_BATCH_TIME, 128)
.put(SystemConfigKey.INSTANCE_ACKNOWLEDGEMENT_NBUCKETS, 10);
return builder.build();
}
/**
* Handler for catching exceptions thrown by any threads (owned either by topology or heron
* infrastructure).
* Will flush all attached log handler and close them.
* Attempt to flush all the connection.
* Terminate the JVM.
*/
public class DefaultExceptionHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread thread, Throwable exception) {
// Add try and catch block to prevent new exceptions stop the handling thread
try {
// Delegate to the actual one
handleException(thread, exception);
// SUPPRESS CHECKSTYLE IllegalCatch
} catch (Throwable t) {
LOG.log(Level.SEVERE, "Failed to handle exception. Process halting", t);
Runtime.getRuntime().halt(1);
}
}
// The actual uncaught exceptions handing logic
private void handleException(Thread thread, Throwable exception) {
LOG.severe("Local Mode Process exiting.");
LOG.log(Level.SEVERE,
"Exception caught in thread: " + thread.getName() + " with id: " + thread.getId(),
exception);
for (Handler handler : java.util.logging.Logger.getLogger("").getHandlers()) {
handler.close();
}
// Attempts to shutdown all the thread in threadsPool. This will send Interrupt to every
// thread in the pool. Threads may implement a clean Interrupt logic.
threadsPool.shutdownNow();
// not owned by HeronInstance). To be safe, not sending these interrupts.
Runtime.getRuntime().halt(1);
}
}
}