Backported various little edits from Pro edition.

This commit is contained in:
Thulinma 2016-05-30 15:17:54 +02:00
parent ef9938da0c
commit 4c9c6fa7ba
78 changed files with 2334 additions and 1266 deletions

View file

@ -1,4 +1,5 @@
/// \page api API calls
/// \brief Listing of all controller API calls.
/// The controller listens for commands through a JSON-based API. This page describes the API in full.
///
/// A default interface implementing this API as a single HTML page is included in the controller itself. This default interface will be send for invalid API requests, and is thus triggered by default when a browser attempts to access the API port directly.
@ -20,7 +21,9 @@
///
/// You may also include a `"callback"` or `"jsonp"` HTTP variable, to trigger JSONP compatibility mode. JSONP is useful for getting around the cross-domain scripting protection in most modern browsers. Developers creating non-JavaScript applications will most likely not want to use JSONP mode, though nothing is stopping you if you really want to.
///
/// \brief Listing of all controller API calls.
/// \file controller.cpp
/// Contains all code for the controller executable.
@ -88,6 +91,7 @@ void createAccount (std::string account){
/// Status monitoring thread.
/// Will check outputs, inputs and converters every five seconds
void statusMonitor(void * np){
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
while (Controller::conf.is_active){
//this scope prevents the configMutex from being locked constantly
{
@ -99,19 +103,20 @@ void statusMonitor(void * np){
changed |= Controller::CheckAllStreams(Controller::Storage["streams"]);
//check if the config semaphore is stuck, by trying to lock it for 5 attempts of 1 second...
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
if (!configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond()){
//that failed. We now unlock it, no matter what - and print a warning that it was stuck.
WARN_MSG("Configuration semaphore was stuck. Force-unlocking it and re-writing config.");
changed = true;
}
configLock.post();
if (changed){
if (changed || Controller::configChanged){
Controller::writeConfig();
Controller::configChanged = false;
}
}
Util::wait(5000);//wait at least 5 seconds
}
configLock.unlink();
}
///\brief The main entry point for the controller.
@ -134,10 +139,9 @@ int main(int argc, char ** argv){
stored_user["default"] = "root";
}
Controller::conf = Util::Config(argv[0]);
Controller::conf.addOption("listen_port", stored_port);
Controller::conf.addOption("listen_interface", stored_interface);
Controller::conf.addOption("port", stored_port);
Controller::conf.addOption("interface", stored_interface);
Controller::conf.addOption("username", stored_user);
Controller::conf.addOption("daemonize", JSON::fromString("{\"long\":\"daemon\", \"short\":\"d\", \"default\":0, \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Turns deamon mode on (-d) or off (-n). -d runs quietly in background, -n (default) enables verbose in foreground.\"}"));
Controller::conf.addOption("account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}"));
Controller::conf.addOption("logfile", JSON::fromString("{\"long\":\"logfile\", \"short\":\"L\", \"arg\":\"string\" \"default\":\"\",\"help\":\"Redirect all standard output to a log file, provided with an argument\"}"));
Controller::conf.addOption("configFile", JSON::fromString("{\"long\":\"config\", \"short\":\"c\", \"arg\":\"string\" \"default\":\"config.json\", \"help\":\"Specify a config file other than default.\"}"));
@ -183,14 +187,19 @@ int main(int argc, char ** argv){
//check for port, interface and username in arguments
//if they are not there, take them from config file, if there
if (Controller::Storage["config"]["controller"]["port"]){
Controller::conf.getOption("listen_port", true)[0u] = Controller::Storage["config"]["controller"]["port"];
Controller::conf.getOption("port", true)[0u] = Controller::Storage["config"]["controller"]["port"];
}
if (Controller::Storage["config"]["controller"]["interface"]){
Controller::conf.getOption("listen_interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"];
Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"];
}
if (Controller::Storage["config"]["controller"]["username"]){
Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"];
}
{
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
configLock.unlink();
}
Controller::writeConfig();
Controller::checkAvailProtocols();
createAccount(Controller::conf.getString("account"));
@ -244,16 +253,16 @@ int main(int argc, char ** argv){
}else{//logfile is enabled
//check for username
if ( !Controller::Storage.isMember("account") || Controller::Storage["account"].size() < 1){
std::cout << "No login configured. To create one, attempt to login through the web interface on port " << Controller::conf.getInteger("listen_port") << " and follow the instructions." << std::endl;
std::cout << "No login configured. To create one, attempt to login through the web interface on port " << Controller::conf.getInteger("port") << " and follow the instructions." << std::endl;
}
//check for protocols
if ( !Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") || Controller::Storage["config"]["protocols"].size() < 1){
std::cout << "No protocols enabled, remember to set them up through the web interface on port " << Controller::conf.getInteger("listen_port") << " or API." << std::endl;
std::cout << "No protocols enabled, remember to set them up through the web interface on port " << Controller::conf.getInteger("port") << " or API." << std::endl;
}
}
//check for streams - regardless of logfile setting
if ( !Controller::Storage.isMember("streams") || Controller::Storage["streams"].size() < 1){
std::cout << "No streams configured, remember to set up streams through the web interface on port " << Controller::conf.getInteger("listen_port") << " or API." << std::endl;
std::cout << "No streams configured, remember to set up streams through the web interface on port " << Controller::conf.getInteger("port") << " or API." << std::endl;
}
}//connected to a terminal
@ -274,8 +283,8 @@ int main(int argc, char ** argv){
}else{
shutdown_reason = "socket problem (API port closed)";
}
Controller::Log("CONF", "Controller shutting down because of "+shutdown_reason);
Controller::conf.is_active = false;
Controller::Log("CONF", "Controller shutting down because of "+shutdown_reason);
//join all joinable threads
statsThread.join();
monitorThread.join();
@ -300,3 +309,4 @@ int main(int argc, char ** argv){
std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl;
return 0;
}

View file

@ -58,9 +58,6 @@
/// ~~~~~~~~~~~~~~~
void Controller::checkConfig(JSON::Value & in, JSON::Value & out){
out = in;
if (out["basepath"].asString()[out["basepath"].asString().size() - 1] == '/'){
out["basepath"] = out["basepath"].asString().substr(0, out["basepath"].asString().size() - 1);
}
if (out.isMember("debug")){
if (Util::Config::printDebugLevel != out["debug"].asInt()){
Util::Config::printDebugLevel = out["debug"].asInt();
@ -169,6 +166,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
H.SetHeader("X-Info", "To force an API response, request the file /api");
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
H.SetHeader("Content-Length", server_html_len);
H.SetHeader("X-UA-Compatible","IE=edge;chrome=1");
H.SendResponse("200", "OK", conn);
conn.SendNow(server_html, server_html_len);
H.Clean();
@ -247,6 +245,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
/// ]
/// ]
/// ~~~~~~~~~~~~~~~
///
if(Request.isMember("browse")){
if(Request["browse"] == ""){
Request["browse"] = ".";

View file

@ -278,7 +278,11 @@ namespace Controller {
unsigned long long c_user, c_nice, c_syst, c_idle, c_total;
if (sscanf(line, "cpu %Lu %Lu %Lu %Lu", &c_user, &c_nice, &c_syst, &c_idle) == 4){
c_total = c_user + c_nice + c_syst + c_idle;
if (c_total - cl_total > 0){
capa["cpu_use"] = (long long int)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
}else{
capa["cpu_use"] = 0ll;
}
cl_total = c_total;
cl_idle = c_idle;
break;

View file

@ -2,6 +2,7 @@
#include <list>
#include <mist/config.h>
#include "controller_statistics.h"
#include "controller_storage.h"
// These are used to store "clients" field requests in a bitfield for speedup.
#define STAT_CLI_HOST 1
@ -21,6 +22,8 @@
#define STAT_TOT_BPS_UP 4
#define STAT_TOT_ALL 0xFF
#define COUNTABLE_BYTES 128*1024
std::map<Controller::sessIndex, Controller::statSession> Controller::sessions; ///< list of sessions that have statistics data available
std::map<unsigned long, Controller::sessIndex> Controller::connToSession; ///< Map of socket IDs to session info.
@ -37,6 +40,12 @@ Controller::sessIndex::sessIndex(){
crc = 0;
}
std::string Controller::sessIndex::toStr(){
std::stringstream s;
s << host << " " << crc << " " << streamName << " " << connector;
return s.str();
}
/// Initializes a sessIndex from a statExchange object, converting binary format IP addresses into strings.
/// This extracts the host, stream name, connector and crc field, ignoring everything else.
Controller::sessIndex::sessIndex(IPC::statExchange & data){
@ -80,6 +89,9 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{
return !(*this < b);
}
/// \todo Make this prettier.
IPC::sharedServer * statPointer = 0;
/// This function runs as a thread and roughly once per second retrieves
/// statistics from all connected clients, as well as wipes
@ -87,9 +99,12 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{
void Controller::SharedMemStats(void * config){
DEBUG_MSG(DLVL_HIGH, "Starting stats thread");
IPC::sharedServer statServer(SHM_STATISTICS, STAT_EX_SIZE, true);
statPointer = &statServer;
std::set<std::string> inactiveStreams;
while(((Util::Config*)config)->is_active){
{
tthread::lock_guard<tthread::mutex> guard(statsMutex);
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
tthread::lock_guard<tthread::mutex> guard2(statsMutex);
//parse current users
statServer.parseEach(parseStatistics);
//wipe old statistics
@ -108,8 +123,9 @@ void Controller::SharedMemStats(void * config){
}
}
}
Util::sleep(1000);
Util::wait(1000);
}
statPointer = 0;
DEBUG_MSG(DLVL_HIGH, "Stopping stats thread");
}
@ -147,6 +163,18 @@ void Controller::statSession::wipeOld(unsigned long long cutOff){
oldConns.pop_front();
}
}
if (curConns.size()){
for (std::map<unsigned long, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
while (it->second.log.size() > 1 && it->second.log.begin()->first < cutOff){
it->second.log.erase(it->second.log.begin());
}
if (it->second.log.size()){
if (firstSec > it->second.log.begin()->first){
firstSec = it->second.log.begin()->first;
}
}
}
}
}
/// Archives the given connection.
@ -361,6 +389,11 @@ long long Controller::statSession::getBpsUp(unsigned long long t){
}
}
Controller::statStorage::statStorage(){
removeDown = 0;
removeUp = 0;
}
/// Returns true if there is data available for timestamp t.
bool Controller::statStorage::hasDataFor(unsigned long long t) {
if (!log.size()){return false;}
@ -390,8 +423,13 @@ void Controller::statStorage::update(IPC::statExchange & data) {
statLog tmp;
tmp.time = data.time();
tmp.lastSecond = data.lastSecond();
tmp.down = data.down();
tmp.up = data.up();
tmp.down = data.down() - removeDown;
tmp.up = data.up() - removeUp;
if (!log.size() && tmp.down + tmp.up > COUNTABLE_BYTES){
//substract the start values if they are too high - this is a resumed connection of some sort
removeDown = tmp.down;
removeUp = tmp.up;
}
log[data.now()] = tmp;
//wipe data older than approx. STAT_CUTOFF seconds
/// \todo Remove least interesting data first.
@ -420,7 +458,7 @@ void Controller::parseStatistics(char * data, size_t len, unsigned int id){
sessions[idx].update(id, tmpEx);
//check validity of stats data
char counter = (*(data - 1));
if (counter == 126 || counter == 127 || counter == 254 || counter == 255){
if (counter == 126 || counter == 127){
//the data is no longer valid - connection has gone away, store for later
sessions[idx].finish(id);
connToSession.erase(id);
@ -432,7 +470,7 @@ bool Controller::hasViewers(std::string streamName){
if (sessions.size()){
long long currTime = Util::epoch();
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
if (it->first.streamName == streamName && it->second.hasDataFor(currTime)){
if (it->first.streamName == streamName && (it->second.hasDataFor(currTime) || it->second.hasDataFor(currTime-1))){
return true;
}
}

View file

@ -1,3 +1,4 @@
#pragma once
#include <mist/shared_memory.h>
#include <mist/timing.h>
#include <mist/defines.h>
@ -36,11 +37,16 @@ namespace Controller {
bool operator<= (const sessIndex &o) const;
bool operator< (const sessIndex &o) const;
bool operator>= (const sessIndex &o) const;
std::string toStr();
};
class statStorage {
private:
long long removeUp;
long long removeDown;
public:
statStorage();
void update(IPC::statExchange & data);
bool hasDataFor(unsigned long long);
statLog & getDataFor(unsigned long long);

View file

@ -15,6 +15,8 @@ namespace Controller {
JSON::Value Storage; ///< Global storage of data.
tthread::mutex configMutex;
tthread::mutex logMutex;
bool configChanged = false;
///\brief Store and print a log message.
///\param kind The type of message.
///\param message The message to be logged.
@ -70,6 +72,7 @@ namespace Controller {
printf("%s", buf);
}
}
Log("LOG", "Logger exiting");
fclose(output);
close((long long int)err);
}
@ -95,8 +98,8 @@ namespace Controller {
}
if (!changed){return;}//cancel further processing if no changes
static IPC::sharedPage mistConfOut("!mistConfig", DEFAULT_CONF_PAGE_SIZE, true);
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
static IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, true);
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
//lock semaphore
configLock.wait();
//write config

View file

@ -8,6 +8,7 @@ namespace Controller {
extern JSON::Value Storage; ///< Global storage of data.
extern tthread::mutex logMutex;///< Mutex for log thread.
extern tthread::mutex configMutex;///< Mutex for server config access.
extern bool configChanged; ///< Bool that indicates config must be written to SHM.
/// Store and print a log message.
void Log(std::string kind, std::string message);

View file

@ -207,7 +207,7 @@ namespace Controller {
//actually delete the streams
while (toDelete.size() > 0){
std::string deleting = *(toDelete.begin());
out.removeMember(deleting);
deleteStream(deleting, out);
toDelete.erase(deleting);
}
@ -226,4 +226,13 @@ namespace Controller {
}
void deleteStream(const std::string & name, JSON::Value & out) {
if (!out.isMember(name)){
return;
}
Log("STRM", std::string("Deleted stream ") + name);
out.removeMember(name);
}
} //Controller namespace

View file

@ -6,9 +6,11 @@ namespace Controller {
bool CheckAllStreams(JSON::Value & data);
void CheckStreams(JSON::Value & in, JSON::Value & out);
void AddStreams(JSON::Value & in, JSON::Value & out);
void deleteStream(const std::string & name, JSON::Value & out);
struct liveCheck {
long long int lastms;
long long int last_active;
};
} //Controller namespace