de.jackwhite20.apex.Apex.java Source code

Java tutorial

Introduction

Here is the source code for de.jackwhite20.apex.Apex.java

Source

/*
 * Copyright (c) 2017 "JackWhite20"
 *
 * This file is part of Apex.
 *
 * Apex is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.jackwhite20.apex;

import ch.qos.logback.classic.Level;
import de.jackwhite20.apex.command.Command;
import de.jackwhite20.apex.command.CommandManager;
import de.jackwhite20.apex.command.impl.DebugCommand;
import de.jackwhite20.apex.command.impl.EndCommand;
import de.jackwhite20.apex.command.impl.HelpCommand;
import de.jackwhite20.apex.command.impl.StatsCommand;
import de.jackwhite20.apex.rest.RestServer;
import de.jackwhite20.apex.strategy.BalancingStrategy;
import de.jackwhite20.apex.strategy.BalancingStrategyFactory;
import de.jackwhite20.apex.strategy.StrategyType;
import de.jackwhite20.apex.task.CheckBackendTask;
import de.jackwhite20.apex.task.ConnectionsPerSecondTask;
import de.jackwhite20.apex.task.impl.CheckDatagramBackendTask;
import de.jackwhite20.apex.task.impl.CheckSocketBackendTask;
import de.jackwhite20.apex.util.*;
import de.jackwhite20.cope.CopeConfig;
import de.jackwhite20.cope.config.Header;
import de.jackwhite20.cope.config.Key;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.traffic.GlobalTrafficShapingHandler;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * Created by JackWhite20 on 05.11.2016.
 */
public abstract class Apex {

    private static final String APEX_PACKAGE_NAME = "de.jackwhite20.apex";

    private static final Pattern ARGS_PATTERN = Pattern.compile(" ");

    private static Logger logger = LoggerFactory.getLogger(Apex.class);

    private static Apex instance;

    private static ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory
            .getLogger(APEX_PACKAGE_NAME);

    private CopeConfig copeConfig;

    private BalancingStrategy balancingStrategy;

    private CheckBackendTask backendTask;

    private ScheduledExecutorService scheduledExecutorService;

    private Channel serverChannel;

    private EventLoopGroup bossGroup;

    private EventLoopGroup workerGroup;

    private GlobalTrafficShapingHandler trafficShapingHandler;

    private RestServer restServer;

    private CommandManager commandManager;

    private Scanner scanner;

    private ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    private ConnectionsPerSecondTask connectionsPerSecondTask;

    public Apex(CopeConfig copeConfig) {

        Apex.instance = this;

        this.copeConfig = copeConfig;
        this.scheduledExecutorService = Executors
                .newSingleThreadScheduledExecutor(new ApexThreadFactory("Check Task"));
        this.commandManager = new CommandManager();
    }

    public abstract Channel bootstrap(EventLoopGroup bossGroup, EventLoopGroup workerGroup, String ip, int port,
            int backlog, int readTimeout, int writeTimeout) throws Exception;

    public void start(Mode mode) {

        commandManager.addCommand(new HelpCommand("help", "List of available commands", "h"));
        commandManager.addCommand(new EndCommand("end", "Stops Apex", "stop", "exit"));
        commandManager.addCommand(new DebugCommand("debug", "Turns the debug mode on/off", "d"));
        commandManager.addCommand(new StatsCommand("stats", "Shows live stats", "s", "info"));

        Header generalHeader = copeConfig.getHeader("general");
        Key serverKey = generalHeader.getKey("server");
        Key balanceKey = generalHeader.getKey("balance");
        Key bossKey = generalHeader.getKey("boss");
        Key workerKey = generalHeader.getKey("worker");
        Key timeoutKey = generalHeader.getKey("timeout");
        Key backlogKey = generalHeader.getKey("backlog");
        Key probeKey = generalHeader.getKey("probe");
        Key debugKey = generalHeader.getKey("debug");
        Key statsKey = generalHeader.getKey("stats");

        // Set the log level to debug or info based on the config value
        changeDebug((Boolean.valueOf(debugKey.next().asString())) ? Level.DEBUG : Level.INFO);

        List<BackendInfo> backendInfo = copeConfig.getHeader("backend").getKeys().stream().map(
                backend -> new BackendInfo(backend.getName(), backend.next().asString(), backend.next().asInt()))
                .collect(Collectors.toList());

        logger.debug("Mode: {}", mode);
        logger.debug("Host: {}", serverKey.next().asString());
        logger.debug("Port: {}", serverKey.next().asString());
        logger.debug("Balance: {}", balanceKey.next().asString());
        logger.debug("Backlog: {}", backlogKey.next().asInt());
        logger.debug("Boss: {}", bossKey.next().asInt());
        logger.debug("Worker: {}", workerKey.next().asInt());
        logger.debug("Stats: {}", statsKey.next().asString());
        logger.debug("Probe: {}", probeKey.next().asInt());
        logger.debug("Backend: {}",
                backendInfo.stream().map(BackendInfo::getHost).collect(Collectors.joining(", ")));

        StrategyType type = StrategyType.valueOf(balanceKey.next().asString());

        balancingStrategy = BalancingStrategyFactory.create(type, backendInfo);

        // Disable the resource leak detector
        ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);

        if (PipelineUtils.isEpoll()) {
            logger.info("Using high performance epoll event notification mechanism");
        } else {
            logger.info("Using normal select/poll event notification mechanism");
        }

        // Check boss thread config value
        int bossThreads = bossKey.next().asInt();
        if (bossThreads < PipelineUtils.DEFAULT_THREADS_THRESHOLD) {
            bossThreads = PipelineUtils.DEFAULT_BOSS_THREADS;

            logger.warn("Boss threads needs to be greater or equal than {}. Using default value of {}",
                    PipelineUtils.DEFAULT_THREADS_THRESHOLD, PipelineUtils.DEFAULT_BOSS_THREADS);
        }

        // Check worker thread config value
        int workerThreads = workerKey.next().asInt();
        if (workerThreads < PipelineUtils.DEFAULT_THREADS_THRESHOLD) {
            workerThreads = PipelineUtils.DEFAULT_WORKER_THREADS;

            logger.warn("Worker threads needs to be greater or equal than {}. Using default value of {}",
                    PipelineUtils.DEFAULT_THREADS_THRESHOLD, PipelineUtils.DEFAULT_WORKER_THREADS);
        }

        bossGroup = PipelineUtils.newEventLoopGroup(bossThreads, new ApexThreadFactory("Apex Boss Thread"));
        workerGroup = PipelineUtils.newEventLoopGroup(workerThreads, new ApexThreadFactory("Apex Worker Thread"));

        if (statsKey.next().asBoolean()) {
            // Only measure connections per second if stats are enabled
            connectionsPerSecondTask = new ConnectionsPerSecondTask();

            // Load the total stats
            long[] totalBytes = FileUtil.loadStats();

            logger.debug("Loaded total read bytes: {}", totalBytes[0]);
            logger.debug("Loaded total written bytes: {}", totalBytes[1]);

            // Traffic shaping handler with default check interval of one second
            trafficShapingHandler = new GlobalTrafficShapingHandler(workerGroup, 0, 0);

            // Set the total stats
            ReflectionUtil.setAtomicLong(trafficShapingHandler.trafficCounter(), "cumulativeReadBytes",
                    totalBytes[0]);
            ReflectionUtil.setAtomicLong(trafficShapingHandler.trafficCounter(), "cumulativeWrittenBytes",
                    totalBytes[1]);

            logger.debug("Traffic stats collect handler initialized");
        }

        try {
            serverChannel = bootstrap(bossGroup, workerGroup, serverKey.next().asString(), serverKey.next().asInt(),
                    backlogKey.next().asInt(), timeoutKey.next().asInt(), timeoutKey.next().asInt());

            int probe = probeKey.next().asInt();
            if (probe < -1 || probe == 0) {
                probe = 10000;

                logger.warn("Probe time value must be -1 to turn it off or greater than 0");
                logger.warn("Using default probe time of 10000 milliseconds (10 seconds)");
            }

            if (probe != -1) {
                backendTask = (mode == Mode.TCP) ? new CheckSocketBackendTask(balancingStrategy)
                        : new CheckDatagramBackendTask(balancingStrategy);

                scheduledExecutorService.scheduleAtFixedRate(backendTask, 0, probe, TimeUnit.MILLISECONDS);
            } else {
                // Shutdown unnecessary scheduler
                scheduledExecutorService.shutdown();
            }

            restServer = new RestServer(copeConfig);
            restServer.start();

            logger.info("Apex listening on {}:{}", serverKey.next().asString(), serverKey.next().asInt());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void console() {

        scanner = new Scanner(System.in);

        try {
            String line;
            while ((line = scanner.nextLine()) != null) {
                if (!line.isEmpty()) {
                    String[] split = ARGS_PATTERN.split(line);

                    if (split.length == 0) {
                        continue;
                    }

                    // Get the command name
                    String commandName = split[0].toLowerCase();

                    // Try to get the command with the name
                    Command command = commandManager.findCommand(commandName);

                    if (command != null) {
                        logger.info("Executing command: {}", line);

                        String[] cmdArgs = Arrays.copyOfRange(split, 1, split.length);
                        command.execute(cmdArgs);
                    } else {
                        logger.info("Command not found!");
                    }
                }
            }
        } catch (IllegalStateException ignore) {
        }
    }

    public void changeDebug(Level level) {

        // Set the log level to debug or info based on the config value
        rootLogger.setLevel(level);

        logger.info("Logger level is now {}", rootLogger.getLevel());
    }

    public void changeDebug() {

        // Change the log level based on the current level
        changeDebug((rootLogger.getLevel() == Level.INFO) ? Level.DEBUG : Level.INFO);
    }

    public void stop() {

        logger.info("Apex is going to be stopped");

        // Close the scanner
        scanner.close();

        // Close the server channel
        if (serverChannel != null) {
            serverChannel.close();
        }

        if (connectionsPerSecondTask != null) {
            connectionsPerSecondTask.stop();
        }

        scheduledExecutorService.shutdown();

        // Release the traffic shaping handler
        if (trafficShapingHandler != null) {
            FileUtil.saveStats(trafficShapingHandler.trafficCounter().cumulativeReadBytes(),
                    trafficShapingHandler.trafficCounter().cumulativeWrittenBytes());

            logger.info("Total bytes stats saved");

            trafficShapingHandler.release();
        }

        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();

        try {
            restServer.stop();
        } catch (Exception e) {
            logger.warn("RESTful API server already stopped");
        }

        logger.info("Apex has been stopped");
    }

    public static CommandManager getCommandManager() {

        return instance.commandManager;
    }

    public static BalancingStrategy getBalancingStrategy() {

        return instance.balancingStrategy;
    }

    public static CheckBackendTask getBackendTask() {

        return instance.backendTask;
    }

    public static Channel getServerChannel() {

        return instance.serverChannel;
    }

    public static ChannelGroup getChannelGroup() {

        return instance.channelGroup;
    }

    public GlobalTrafficShapingHandler getTrafficShapingHandler() {

        return trafficShapingHandler;
    }

    public ConnectionsPerSecondTask getConnectionsPerSecondTask() {

        return connectionsPerSecondTask;
    }

    public static Apex getInstance() {

        return instance;
    }
}