Program Listing for File main.cpp

Return to documentation for file (falcon/main.cpp)

// ---------------------------------------------------------------------
// This file is part of falcon-core.
//
// Copyright (C) 2015, 2016, 2017 Neuro-Electronics Research Flanders
//
// Falcon-server 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.
//
// Falcon-server 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 falcon-core. If not, see <http://www.gnu.org/licenses/>.
// ---------------------------------------------------------------------

#include <cstdlib>
#include <deque>
#include <iostream>
#include <regex>
#include <string>
#include <exception>

#include "cmdline/cmdline.h"
#include "logging/log.hpp"
#include "logging/customsink.hpp"
#include "commandsource.hpp"
#include "commandhandler.hpp"
#include "graphmanager.hpp"
#include "buildconstant.hpp"
#include "configuration.hpp"
#include "context.hpp"
#include "options/units.hpp"
#include "utilities/time.hpp"

using namespace std;

int main(int argc, char** argv) {

  // create a parser
  cmdline::parser parser;

  // add specified type of variable.
  // 1st argument is long name
  // 2nd argument is short name (no short name if '\0' specified)
  // 3rd argument is description
  // 4th argument is mandatory (optional. default is false)
  // 5th argument is default value  (optional. it used when mandatory is false)
  parser.add<string>("config", 'c', "configuration file", false,
                     "$HOME/.config/falcon/config.yaml");
  parser.add("autostart", 'a', "auto start processing (needs graph)");
  parser.add("debug", 'd', "show debug messages");
  parser.add("noscreenlog", '\0', "disable logging to screen");
  parser.add("nocloudlog", '\0', "disable logging to cloud");
  parser.add("test", 't', "turn testing on by default");
  parser.add("version", 'v', "Show the falcon version number and exit.");
  parser.footer("[graph file] ...");
  // Run parser
  // It returns only if command line arguments are valid.
  // If arguments are invalid, a parser output error msgs then exit program.
  // If help flag ('--help' or '-?') is specified, a parser output usage message then exit program.
  parser.parse_check(argc, argv);

  if (parser.exist("version")) {
    std::cout << "Falcon " << GIT_REVISION << std::endl;
    std::cout << "Last build: " << BUILD_TIMESTAMP << std::endl;
    std::cout << "Configuration: " << BUILD_TYPE << std::endl;
    std::cout << "Extensions: " << std::endl;

    std::vector<std::string> extensions =  split(EXTENSIONS_BUILD, ';');
    for(auto it:extensions){
        std::cout << it << std::endl;
    }
    return EXIT_SUCCESS;
  }
  // create default configuration
  FalconConfiguration config;

  // add custom units
  units::addUserDefinedUnit("sample", units::precise::sample_units);
  units::addUserDefinedUnit("spike", units::precise::spike_units);

  // load configuration file
  try {
    config.load(parser.get<std::string>("config"));
  } catch (std::runtime_error &e) {
    std::cout << e.what() << std::endl;
    std::cout << "Falcon terminated." << std::endl;
    return EXIT_FAILURE;
  }

  // apply command line arguments
  if (parser.exist("autostart")) {
    config.graph_autostart = true;
  }
  if (parser.exist("debug")) {
    config.debug_enabled = true;
  }
  if (parser.exist("noscreenlog")) {
    config.logging_screen_enabled = false;
  }
  if (parser.exist("nocloudlog")) {
    config.logging_cloud_enabled = false;
  }
  if (parser.exist("test")) {
    config.testing_enabled = true;
  }

  // add default URIs
  config.server_side_storage_custom["resources"] =
      config.server_side_storage_resources();
  config.server_side_storage_custom["graphs"] =
      config.server_side_storage_resources() + "/graphs";
  config.server_side_storage_custom["filters"] =
      config.server_side_storage_resources() + "/filters";
  config.server_side_storage_custom["runroot"] =
      config.server_side_storage_environment();

  GlobalContext context(config.testing_enabled(),
                        config.server_side_storage_custom());

  // set up loggers
  // file logger
  char *home = getenv("HOME");
  std::regex re("(\\$HOME|~)");
  std::string logpath = std::regex_replace(config.logging_path(), re, home);

  auto worker = g3::LogWorker::createLogWorker();
  auto defaultHandler = worker->addDefaultLogger("falcon", logpath);

  // initialize logging before creating additional loggers
  g3::initializeLogging(worker.get());
  g3::only_change_at_initialization::addLogLevel(STATE);
  g3::only_change_at_initialization::addLogLevel(UPDATE);
  g3::only_change_at_initialization::addLogLevel(ERROR);

  // enable DEBUG logging
  if(config.debug_enabled()){
      g3::log_levels::enable(DEBUG);
  }else{
      g3::log_levels::disable(DEBUG);
  }

  // screen logger
  if (config.logging_screen_enabled()) {
    worker->addSink(std::make_unique<ScreenSink>(),
                    &ScreenSink::ReceiveLogMessage);
    LOG(INFO) << "Enabled logging to screen.";
  }
  // cloud logger
  if (config.logging_cloud_enabled()) {
    worker->addSink(
        std::make_unique<ZMQSink>(context.zmq(), config.logging_cloud_port()),
        &ZMQSink::ReceiveLogMessage);
    // wait so that any existing subscriber has a change to connect before we send out first messages
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    LOG(INFO) << "Enabled logging to cloud on port "
              << config.logging_cloud_port();
  }

  LOG(INFO) << "Logging initialized. Log file saved to " << logpath;

  // Check clock used for internal timing
  LOG_IF(WARNING, not Clock::is_steady)
      << "The clock used for timing is not steady.";

  LOG(INFO) << "Resolution of clock used for timing is "
            << 10e6 * static_cast<double>(Clock::period::num) /
                   Clock::period::den
            << " microseconds.";

  // create and start GraphManager in separate thread
  graph::GraphManager gm(context);
  gm.start();

  // create command sources
  // keyboard commands
  commands::KeyboardCommands kb;
  // cloud commands
  commands::ZMQCommands zc(context.zmq(), config.network_port());
  // command line commands
  commands::CommandLineCommands cl;

  std::string graph_file = config.graph_file();
  if (parser.rest().size() > 0) {
    graph_file = parser.rest().back();
  }

  std::deque<std::string> command;
  if (graph_file.size() > 0) {

    command.push_back("graph");
    command.push_back("build");
    command.push_back(graph_file);

    cl.AddCommand(command);

    command.clear();

    if (config.graph_autostart()) {
      command.push_back("graph");
      command.push_back("start");

      cl.AddCommand(command);

      command.clear();
    }
  }

  // set up Command handler
  commands::CommandHandler commandhandler(context);

  // add command sources to handler
  commandhandler.addSource(cl);
  commandhandler.addSource(kb);
  commandhandler.addSource(zc);

  LOG(INFO) << "Enabled keyboard commands.";
  LOG(INFO) << "Enabled cloud commands on port " << config.network_port() ;

  LOG(INFO) << "Falcon started successfully.";
  // start handling commands
  commandhandler.start();

  LOG(INFO) << "Falcon shutting down normally.";
  g3::internal::shutDownLogging();
  return EXIT_SUCCESS;
}