Backported various little edits from Pro edition.
This commit is contained in:
parent
ef9938da0c
commit
4c9c6fa7ba
78 changed files with 2334 additions and 1266 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"] = ".";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue