/*
Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications
Research Center Canada)
Copyright (C) 2014, 2015
Matthias P. Braendli, matthias.braendli@mpb.li
http://www.opendigitalradio.org
A TCP Socket server that serves state information and statistics for
monitoring purposes, and also serves the internal configuration
property tree.
*/
/*
This file is part of ODR-DabMux.
ODR-DabMux 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.
ODR-DabMux 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 ODR-DabMux. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "ManagementServer.h"
#include "Log.h"
ManagementServer& get_mgmt_server()
{
static ManagementServer mgmt_server;
return mgmt_server;
/* Warning, do not use the mgmt_server in the destructor
* of another global object: you don't know which one
* gets destroyed first
*/
}
void ManagementServer::registerInput(InputStat* is)
{
boost::mutex::scoped_lock lock(m_statsmutex);
std::string id(is->get_name());
if (m_inputStats.count(id) == 1) {
etiLog.level(error) <<
"Double registration in MGMT Server with id '" <<
id << "'";
return;
}
m_inputStats[id] = is;
}
void ManagementServer::unregisterInput(std::string id)
{
boost::mutex::scoped_lock lock(m_statsmutex);
if (m_inputStats.count(id) == 1) {
m_inputStats.erase(id);
}
}
bool ManagementServer::isInputRegistered(std::string& id)
{
boost::mutex::scoped_lock lock(m_statsmutex);
if (m_inputStats.count(id) == 0) {
etiLog.level(error) <<
"Management Server: id '" <<
id << "' does was not registered";
return false;
}
return true;
}
std::string ManagementServer::getStatConfigJSON()
{
boost::mutex::scoped_lock lock(m_statsmutex);
std::ostringstream ss;
ss << "{ \"config\" : [\n";
std::map::iterator iter;
int i = 0;
for(iter = m_inputStats.begin(); iter != m_inputStats.end();
++iter, i++)
{
std::string id = iter->first;
if (i > 0) {
ss << ", ";
}
ss << " \"" << id << "\" ";
}
ss << "] }\n";
return ss.str();
}
std::string ManagementServer::getValuesJSON()
{
boost::mutex::scoped_lock lock(m_statsmutex);
std::ostringstream ss;
ss << "{ \"values\" : {\n";
std::map::iterator iter;
int i = 0;
for(iter = m_inputStats.begin(); iter != m_inputStats.end();
++iter, i++)
{
const std::string& id = iter->first;
InputStat* stats = iter->second;
if (i > 0) {
ss << " ,\n";
}
ss << " \"" << id << "\" : ";
ss << stats->encodeValuesJSON();
stats->reset();
}
ss << "}\n}\n";
return ss.str();
}
std::string ManagementServer::getStateJSON()
{
boost::mutex::scoped_lock lock(m_statsmutex);
std::ostringstream ss;
ss << "{\n";
std::map::iterator iter;
int i = 0;
for(iter = m_inputStats.begin(); iter != m_inputStats.end();
++iter, i++)
{
const std::string& id = iter->first;
InputStat* stats = iter->second;
if (i > 0) {
ss << " ,\n";
}
ss << " \"" << id << "\" : ";
ss << stats->encodeStateJSON();
stats->reset();
}
ss << "}\n";
return ss.str();
}
void ManagementServer::restart()
{
m_restarter_thread = boost::thread(&ManagementServer::restart_thread,
this, 0);
}
// This runs in a separate thread, because
// it would take too long to be done in the main loop
// thread.
void ManagementServer::restart_thread(long)
{
m_running = false;
if (m_listenport) {
m_thread.interrupt();
m_thread.join();
}
m_thread = boost::thread(&ManagementServer::serverThread, this);
}
void ManagementServer::serverThread()
{
using boost::asio::ip::tcp;
m_running = true;
m_fault = false;
while (m_running) {
m_io_service.reset();
tcp::acceptor acceptor(m_io_service, tcp::endpoint(
boost::asio::ip::address::from_string("127.0.0.1"),
m_listenport) );
// Add a job to start accepting connections.
boost::shared_ptr socket(
new tcp::socket(acceptor.get_io_service()));
// Add an accept call to the service. This will prevent io_service::run()
// from returning.
etiLog.level(warn) << "MGMT: Waiting on connection";
acceptor.async_accept(*socket,
boost::bind(&ManagementServer::handle_accept,
this,
boost::asio::placeholders::error,
socket,
boost::ref(acceptor)));
// Process event loop.
m_io_service.run();
}
m_fault = true;
}
void ManagementServer::handle_accept(
const boost::system::error_code& boost_error,
boost::shared_ptr< boost::asio::ip::tcp::socket > socket,
boost::asio::ip::tcp::acceptor& acceptor)
{
if (boost_error)
{
etiLog.level(error) << "MGMT: Error accepting connection";
return;
}
std::stringstream welcome_msg;
welcome_msg <<
"{ \"service\": \"" <<
PACKAGE_NAME << " " <<
#if defined(GITVERSION)
GITVERSION <<
#else
PACKAGE_VERSION <<
#endif
" MGMT Server\" }\n";
try {
etiLog.level(info) << "RC: Accepted";
boost::system::error_code ignored_error;
boost::asio::write(*socket, boost::asio::buffer(welcome_msg.str()),
boost::asio::transfer_all(),
ignored_error);
boost::asio::streambuf buffer;
size_t length =
boost::asio::read_until(*socket, buffer, "\n", ignored_error);
std::string in_message;
std::istream str(&buffer);
std::getline(str, in_message);
if (in_message == "config") {
std::string json = getStatConfigJSON();
boost::asio::write(*socket, boost::asio::buffer(json),
boost::asio::transfer_all(),
ignored_error);
}
else if (in_message == "values") {
std::string json = getValuesJSON();
boost::asio::write(*socket, boost::asio::buffer(json),
boost::asio::transfer_all(),
ignored_error);
}
else if (in_message == "state") {
std::string json = getStateJSON();
boost::asio::write(*socket, boost::asio::buffer(json),
boost::asio::transfer_all(),
ignored_error);
}
else if (in_message == "setptree") {
boost::asio::streambuf jsonbuffer;
length = boost::asio::read_until(
*socket, jsonbuffer, "\n", ignored_error);
if (length > 0) {
boost::unique_lock lock(m_configmutex);
m_pt.clear();
boost::property_tree::json_parser::read_json(jsonbuffer, m_pt);
}
else if (length == 0) {
etiLog.level(warn) <<
"MGMT: No JSON data received";
}
else {
etiLog.level(error) <<
"MGMT: Error JSON reception";
}
}
else if (in_message == "getptree") {
boost::unique_lock lock(m_configmutex);
m_pending = true;
while (m_pending && !m_retrieve_pending) {
m_condition.wait(lock);
}
std::stringstream ss;
boost::property_tree::json_parser::write_json(ss, m_pt);
boost::asio::write(*socket, ss,
boost::asio::transfer_all(),
ignored_error);
}
else {
std::stringstream ss;
ss << "Invalid command\n";
boost::asio::write(*socket, ss,
boost::asio::transfer_all(),
ignored_error);
}
}
catch (std::exception& e) {
etiLog.level(error) <<
"MGMT server caught exception: " <<
e.what();
}
}
bool ManagementServer::retrieve_new_ptree(boost::property_tree::ptree& pt)
{
boost::unique_lock lock(m_configmutex);
if (m_retrieve_pending)
{
pt = m_pt;
m_retrieve_pending = false;
m_condition.notify_one();
return true;
}
return false;
}
void ManagementServer::update_ptree(const boost::property_tree::ptree& pt)
{
if (m_running) {
boost::unique_lock lock(m_configmutex);
m_pt = pt;
m_pending = false;
m_condition.notify_one();
}
}
/************************************************/
void InputStat::registerAtServer()
{
get_mgmt_server().registerInput(this);
}
InputStat::~InputStat()
{
get_mgmt_server().unregisterInput(m_name);
}
std::string InputStat::encodeValuesJSON()
{
std::ostringstream ss;
const int16_t int16_max = std::numeric_limits::max();
boost::mutex::scoped_lock lock(m_mutex);
/* convert to dB */
int dB_l = peak_left ? round(20*log10((double)peak_left / int16_max)) : -90;
int dB_r = peak_right ? round(20*log10((double)peak_right / int16_max)) : -90;
ss <<
"{ \"inputstat\" : {"
"\"min_fill\": " << min_fill_buffer << ", "
"\"max_fill\": " << max_fill_buffer << ", "
"\"peak_left\": " << dB_l << ", "
"\"peak_right\": " << dB_r << ", "
"\"num_underruns\": " << num_underruns << ", "
"\"num_overruns\": " << num_overruns <<
" } }";
return ss.str();
}
std::string InputStat::encodeStateJSON()
{
std::ostringstream ss;
ss << "{ \"state\" : ";
switch (determineState()) {
case NoData:
ss << "\"NoData\"";
break;
case Unstable:
ss << "\"Unstable\"";
break;
case Silence:
ss << "\"Silent\"";
break;
case Streaming:
ss << "\"Streaming\"";
break;
default:
ss << "\"Unknown\"";
}
ss << " }";
return ss.str();
}
input_state_t InputStat::determineState(void)
{
boost::mutex::scoped_lock lock(m_mutex);
time_t now = time(NULL);
input_state_t state;
/* if the last event was more that INPUT_COUNTER_RESET_TIME
* minutes ago, the timeout has expired. We can reset our
* glitch counter.
*/
if (now - m_time_last_event > 60*INPUT_COUNTER_RESET_TIME) {
m_glitch_counter = 0;
}
// STATE CALCULATION
/* If the buffer has been empty for more than
* INPUT_NODATA_TIMEOUT, we go to the NoData state.
*/
if (m_buffer_empty &&
now - m_time_last_buffer_nonempty > INPUT_NODATA_TIMEOUT) {
state = NoData;
}
/* Otherwise, the state depends on the glitch counter */
else if (m_glitch_counter >= INPUT_UNSTABLE_THRESHOLD) {
state = Unstable;
}
else {
/* The input is streaming, check if the audio level is too low */
if (m_silence_counter > INPUT_AUDIO_LEVEL_SILENCE_COUNT) {
state = Silence;
}
else {
state = Streaming;
}
}
return state;
}