New Meta commit

This commit is contained in:
Phencys 2021-04-21 18:10:03 +02:00 committed by Thulinma
parent fccf66fba2
commit 2b99f2f5ea
183 changed files with 13333 additions and 14421 deletions

View file

@ -476,6 +476,31 @@ int main_loop(int argc, char **argv){
}
}
// Upgrade old configurations
{
bool foundCMAF = false;
bool edit = false;
JSON::Value newVal;
jsonForEach(Controller::Storage["config"]["protocols"], it){
if ((*it)["connector"].asStringRef() == "HSS"){
edit = true;
continue;
}
if ((*it)["connector"].asStringRef() == "DASH"){
edit = true;
continue;
}
if ((*it)["connector"].asStringRef() == "CMAF"){foundCMAF = true;}
newVal.append(*it);
}
if (edit && !foundCMAF){newVal.append(JSON::fromString("{\"connector\":\"CMAF\"}"));}
if (edit){
Controller::Storage["config"]["protocols"] = newVal;
Controller::Log("CONF", "Translated protocols to new versions");
}
}
Controller::Log("CONF", "Controller started");
// Generate instanceId once per boot.
if (Controller::instanceId == ""){

View file

@ -95,17 +95,11 @@ namespace Controller{
trgs["STREAM_PUSH"]["response"] = "always";
trgs["STREAM_PUSH"]["response_action"] = "If false, rejects the incoming push.";
trgs["STREAM_TRACK_ADD"]["when"] = "Before a new track is accepted by a live stream buffer";
trgs["STREAM_TRACK_ADD"]["stream_specific"] = true;
trgs["STREAM_TRACK_ADD"]["payload"] = "stream name (string)\ntrack ID (integer)\n";
trgs["STREAM_TRACK_ADD"]["response"] = "ignored";
trgs["STREAM_TRACK_ADD"]["response_action"] = "None.";
trgs["STREAM_TRACK_REMOVE"]["when"] = "Before a track is removed by a live stream buffer";
trgs["STREAM_TRACK_REMOVE"]["stream_specific"] = true;
trgs["STREAM_TRACK_REMOVE"]["payload"] = "stream name (string)\ntrack ID (integer)\n";
trgs["STREAM_TRACK_REMOVE"]["response"] = "ignored";
trgs["STREAM_TRACK_REMOVE"]["response_action"] = "None.";
trgs["LIVE_TRACK_LIST"]["when"] = "After the list of valid tracks has been updated";
trgs["LIVE_TRACK_LIST"]["stream_specific"] = true;
trgs["LIVE_TRACK_LIST"]["payload"] = "stream name (string)\ntrack list (JSON)\n";
trgs["LIVE_TRACK_LIST"]["response"] = "ignored";
trgs["LIVE_TRACK_LIST"]["response_action"] = "None.";
trgs["STREAM_BUFFER"]["when"] = "Every time a live stream buffer changes state";
trgs["STREAM_BUFFER"]["stream_specific"] = true;

View file

@ -145,17 +145,18 @@ namespace Controller{
if (pipedCapa.isMember("optional")){builPipedPart(p, argarr, argnum, pipedCapa["optional"]);}
}
///\brief Checks current protocol configuration, updates state of enabled connectors if neccessary.
///\param p An object containing all protocols.
///\param capabilities An object containing the detected capabilities.
///\returns True if any action was taken
///\brief Checks current protocol configuration, updates state of enabled connectors if
/// neccessary. \param p An object containing all protocols. \param capabilities An object
/// containing the detected capabilities. \returns True if any action was taken
///
/// \triggers
/// The `"OUTPUT_START"` trigger is global, and is ran whenever a new protocol listener is started. It cannot be cancelled. Its payload is:
/// The `"OUTPUT_START"` trigger is global, and is ran whenever a new protocol listener is
/// started. It cannot be cancelled. Its payload is:
/// ~~~~~~~~~~~~~~~
/// output listener commandline
/// ~~~~~~~~~~~~~~~
/// The `"OUTPUT_STOP"` trigger is global, and is ran whenever a protocol listener is terminated. It cannot be cancelled. Its payload is:
/// The `"OUTPUT_STOP"` trigger is global, and is ran whenever a protocol listener is terminated.
/// It cannot be cancelled. Its payload is:
/// ~~~~~~~~~~~~~~~
/// output listener commandline
/// ~~~~~~~~~~~~~~~

View file

@ -118,11 +118,12 @@ namespace Controller{
for (unsigned int i = 0; i < 8; ++i){
aesKey[15 - i] = ((currID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
}
char ivec[16];
memset(ivec, 0, 16);
Encryption::AES crypter;
crypter.setEncryptKey(aesKey);
// 0 here for 0-filled ivec.
dl.setHeader("X-IRDGAF",
Encodings::Base64::encode(Encryption::AES_Crypt(
RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec)));
Encodings::Base64::encode(crypter.encryptBlockCTR(0, RELEASE "|" PACKAGE_VERSION)));
}
if (!dl.get(url) || !dl.isOk()){return;}
response = JSON::fromString(dl.data());
@ -143,11 +144,12 @@ namespace Controller{
aesKey[15 - i] = ((licID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
}
std::string cipher = Encodings::Base64::decode(input);
std::string deCrypted;
// magic ivecs, they are empty. It's secretly 16 times \0.
char ivec[16];
memset(ivec, 0, 16);
deCrypted = Encryption::AES_Crypt(cipher.c_str(), cipher.size(), aesKey, ivec);
Encryption::AES crypter;
crypter.setEncryptKey(aesKey);
// 0 here for 0-filled ivec.
std::string deCrypted = crypter.encryptBlockCTR(0, cipher);
// get time stamps and license.
// verify checksum

View file

@ -135,7 +135,10 @@ namespace Controller{
void pushCheckLoop(void *np){
{
IPC::sharedPage pushReadPage("MstPush", 8 * 1024 * 1024, false, false);
if (pushReadPage.mapped){readPushList(pushReadPage.mapped);}
if (pushReadPage.mapped){
readPushList(pushReadPage.mapped);
pushReadPage.master = true;
}
}
pushListRead = true;
IPC::sharedPage pushPage("MstPush", 8 * 1024 * 1024, true, false);

View file

@ -30,6 +30,7 @@
#define STAT_CLI_BPS_DOWN 128
#define STAT_CLI_BPS_UP 256
#define STAT_CLI_CRC 512
#define STAT_CLI_SESSID 1024
#define STAT_CLI_ALL 0xFFFF
// These are used to store "totals" field requests in a bitfield for speedup.
#define STAT_TOT_CLIENTS 1
@ -48,6 +49,8 @@ std::map<std::string, Controller::triggerLog> Controller::triggerStats; ///< Hol
bool Controller::killOnExit = KILL_ON_EXIT;
tthread::mutex Controller::statsMutex;
unsigned int Controller::maxConnsPerIP = 0;
uint64_t Controller::statDropoff = 0;
char noBWCountMatches[1717];
uint64_t bwLimit = 128 * 1024 * 1024; // gigabit default limit
@ -96,35 +99,27 @@ static uint64_t servInputs = 0;
static uint64_t servOutputs = 0;
static uint64_t servViewers = 0;
Controller::sessIndex::sessIndex(std::string dhost, unsigned int dcrc, std::string dstreamName,
std::string dconnector){
ID = "UNSET";
host = dhost;
crc = dcrc;
streamName = dstreamName;
connector = dconnector;
}
Controller::sessIndex::sessIndex(){
crc = 0;
}
/// Initializes a sessIndex from a statistics object + index, converting binary format IP addresses
/// into strings. This extracts the host, stream name, connector and crc field, ignoring everything
/// else.
Controller::sessIndex::sessIndex(const Comms::Statistics &statComm, size_t id){
host = statComm.getHost(id);
streamName = statComm.getStream(id);
connector = statComm.getConnector(id);
crc = statComm.getCRC(id);
ID = statComm.getSessId(id);
}
std::string Controller::sessIndex::toStr(){
std::stringstream s;
s << ID << "(" << 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){
Socket::hostBytesToStr(data.host().c_str(), 16, host);
streamName = data.streamName();
connector = data.connector();
crc = data.crc();
ID = data.getSessId();
}
bool Controller::sessIndex::operator==(const Controller::sessIndex &b) const{
return (host == b.host && crc == b.crc && streamName == b.streamName && connector == b.connector);
}
@ -166,13 +161,13 @@ void Controller::streamStopped(std::string stream){
INFO_MSG("Stream %s became inactive", stream.c_str());
}
/// \todo Make this prettier.
IPC::sharedServer *statPointer = 0;
Comms::Statistics statComm;
bool statCommActive = false;
/// Invalidates all current sessions for the given streamname
/// Updates the session cache, afterwards.
void Controller::sessions_invalidate(const std::string &streamname){
if (!statPointer){
if (!statCommActive){
FAIL_MSG("In shutdown procedure - cannot invalidate sessions.");
return;
}
@ -209,7 +204,7 @@ void Controller::sessions_shutdown(JSON::Iter &i){
/// Shuts down the given session
/// Updates the session cache, afterwards.
void Controller::sessId_shutdown(const std::string &sessId){
if (!statPointer){
if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions.");
return;
}
@ -231,7 +226,7 @@ void Controller::sessId_shutdown(const std::string &sessId){
/// Tags the given session
void Controller::sessId_tag(const std::string &sessId, const std::string &tag){
if (!statPointer){
if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot tag sessions.");
return;
}
@ -250,7 +245,7 @@ void Controller::sessId_tag(const std::string &sessId, const std::string &tag){
/// Shuts down sessions with the given tag set
/// Updates the session cache, afterwards.
void Controller::tag_shutdown(const std::string &tag){
if (!statPointer){
if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions.");
return;
}
@ -272,7 +267,7 @@ void Controller::tag_shutdown(const std::string &tag){
/// Shuts down all current sessions for the given streamname
/// Updates the session cache, afterwards.
void Controller::sessions_shutdown(const std::string &streamname, const std::string &protocol){
if (!statPointer){
if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions.");
return;
}
@ -325,9 +320,13 @@ void Controller::writeSessionCache(){
/// old statistics that have disconnected over 10 minutes ago.
void Controller::SharedMemStats(void *config){
HIGH_MSG("Starting stats thread");
IPC::sharedServer statServer(SHM_STATISTICS, STAT_EX_SIZE, true);
statPointer = &statServer;
shmSessions = new IPC::sharedPage(SHM_SESSIONS, SHM_SESSIONS_SIZE, true);
statComm.reload(true);
statCommActive = true;
shmSessions = new IPC::sharedPage(SHM_SESSIONS, SHM_SESSIONS_SIZE, false, false);
if (!shmSessions || !shmSessions->mapped){
if (shmSessions){delete shmSessions;}
shmSessions = new IPC::sharedPage(SHM_SESSIONS, SHM_SESSIONS_SIZE, true);
}
cacheLock = new IPC::semaphore(SEM_SESSCACHE, O_CREAT | O_RDWR, ACCESSPERMS, 1);
cacheLock->unlink();
cacheLock->open(SEM_SESSCACHE, O_CREAT | O_RDWR, ACCESSPERMS, 1);
@ -341,7 +340,10 @@ void Controller::SharedMemStats(void *config){
tthread::lock_guard<tthread::mutex> guard2(statsMutex);
cacheLock->wait(); /*LTS*/
// parse current users
statServer.parseEach(parseStatistics);
statLeadIn();
COMM_LOOP(statComm, statOnActive(id), statOnDisconnect(id));
statLeadOut();
if (firstRun){
firstRun = false;
servUpOtherBytes = 0;
@ -357,18 +359,27 @@ void Controller::SharedMemStats(void *config){
// wipe old statistics
if (sessions.size()){
std::list<sessIndex> mustWipe;
unsigned long long cutOffPoint = Util::epoch() - STAT_CUTOFF;
unsigned long long disconnectPointIn = Util::epoch() - STATS_INPUT_DELAY;
unsigned long long disconnectPointOut = Util::epoch() - STATS_DELAY;
uint64_t cutOffPoint = Util::bootSecs() - STAT_CUTOFF;
uint64_t disconnectPointIn = Util::bootSecs() - STATS_INPUT_DELAY;
uint64_t disconnectPointOut = Util::bootSecs() - STATS_DELAY;
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
unsigned long long dPoint = it->second.getSessType() == SESS_INPUT ? disconnectPointIn : disconnectPointOut;
it->second.ping(it->first, dPoint);
uint64_t dPoint = it->second.getSessType() == SESS_INPUT ? disconnectPointIn : disconnectPointOut;
if (it->second.sync == 100){
// Denied entries are connection-entry-wiped as soon as they become boring
it->second.wipeOld(dPoint);
}else{
// Normal entries are summarized after STAT_CUTOFF seconds
it->second.wipeOld(cutOffPoint);
}
if (!it->second.hasData()){mustWipe.push_back(it->first);}
// This part handles ending sessions, keeping them in cache for now
if (it->second.isTracked() && !it->second.isConnected() && it->second.getEnd() < dPoint){
it->second.dropSession(it->first);
}
// This part handles wiping from the session cache
if (!it->second.hasData()){
it->second.dropSession(it->first); // End the session, just in case it wasn't yet
mustWipe.push_back(it->first);
}
}
while (mustWipe.size()){
sessions.erase(mustWipe.front());
@ -429,15 +440,18 @@ void Controller::SharedMemStats(void *config){
}
Util::wait(1000);
}
statPointer = 0;
statCommActive = false;
HIGH_MSG("Stopping stats thread");
if (Util::Config::is_restarting){
statServer.abandon();
statComm.setMaster(false);
shmSessions->master = false;
}else{/*LTS-START*/
if (Controller::killOnExit){
WARN_MSG("Killing all connected clients to force full shutdown");
statServer.finishEach();
for (uint32_t id = statComm.firstValid(); id != statComm.endValid(); id++){
if (statComm.getStatus(id) == COMM_STATUS_INVALID){continue;}
statComm.kill(id, true);
}
}
/*LTS-END*/
}
@ -474,12 +488,10 @@ std::set<std::string> Controller::getActiveStreams(const std::string &prefix){
uint32_t Controller::statSession::invalidate(){
uint32_t ret = 0;
sync = 1;
if (curConns.size() && statPointer){
if (curConns.size() && statCommActive){
for (std::map<uint64_t, statStorage>::iterator jt = curConns.begin(); jt != curConns.end(); ++jt){
char *data = statPointer->getIndex(jt->first);
if (data){
IPC::statExchange tmpEx(data);
tmpEx.setSync(2);
if (statComm.getStatus(jt->first) != COMM_STATUS_INVALID){
statComm.setSync(2, jt->first);
ret++;
}
}
@ -492,16 +504,14 @@ uint32_t Controller::statSession::invalidate(){
uint32_t Controller::statSession::kill(){
uint32_t ret = 0;
sync = 100;
if (curConns.size() && statPointer){
if (curConns.size() && statCommActive){
for (std::map<uint64_t, statStorage>::iterator jt = curConns.begin(); jt != curConns.end(); ++jt){
char *data = statPointer->getIndex(jt->first);
if (data){
IPC::statExchange tmpEx(data);
tmpEx.setSync(100);
uint32_t pid = tmpEx.getPID();
if (statComm.getStatus(jt->first) != COMM_STATUS_INVALID){
statComm.setSync(100, jt->first);
uint32_t pid = statComm.getPid(jt->first);
if (pid > 1){
Util::Procs::Stop(pid);
INFO_MSG("Killing PID %lu", pid);
INFO_MSG("Killing PID %" PRIu32, pid);
}
ret++;
}
@ -511,15 +521,18 @@ uint32_t Controller::statSession::kill(){
}
/// Updates the given active connection with new stats data.
void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
// update the sync byte: 0 = requesting fill, 2 = requesting refill, 1 = needs checking, > 2 = state known (100=denied, 10=accepted)
if (!data.getSync()){
sessIndex tmpidx(data);
std::string myHost = tmpidx.host;
void Controller::statSession::update(uint64_t index, Comms::Statistics &statComm){
std::string myHost = statComm.getHost(index);
std::string myStream = statComm.getStream(index);
std::string myConnector = statComm.getConnector(index);
// update the sync byte: 0 = requesting fill, 2 = requesting refill, 1 = needs checking, > 2 =
// state known (100=denied, 10=accepted)
if (!statComm.getSync(index)){
sessIndex tmpidx(statComm, index);
// if we have a maximum connection count per IP, enforce it
if (maxConnsPerIP && !data.getSync()){
if (maxConnsPerIP && !statComm.getSync(index)){
unsigned int currConns = 1;
long long shortly = Util::epoch();
long long shortly = Util::bootSecs();
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
if (&it->second != this && it->first.host == myHost &&
@ -533,35 +546,35 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
}
if (currConns > maxConnsPerIP){
WARN_MSG("Disconnecting session from %s: exceeds max connection count of %u", myHost.c_str(), maxConnsPerIP);
data.setSync(100);
statComm.setSync(100, index);
}
}
if (data.getSync() != 100){
if (statComm.getSync(index) != 100){
// only set the sync if this is the first connection in the list
// we also catch the case that there are no connections, which is an error-state
if (!sessions[tmpidx].curConns.size() || sessions[tmpidx].curConns.begin()->first == index){
MEDIUM_MSG("Requesting sync to %u for %s, %s, %s, %lu", sync, data.streamName().c_str(),
data.connector().c_str(), myHost.c_str(), data.crc() & 0xFFFFFFFFu);
data.setSync(sync);
MEDIUM_MSG("Requesting sync to %u for %s, %s, %s, %" PRIu32, sync, myStream.c_str(),
myConnector.c_str(), myHost.c_str(), statComm.getCRC(index) & 0xFFFFFFFFu);
statComm.setSync(sync, index);
}
// and, always set the sync if it is > 2
if (sync > 2){
MEDIUM_MSG("Setting sync to %u for %s, %s, %s, %lu", sync, data.streamName().c_str(),
data.connector().c_str(), myHost.c_str(), data.crc() & 0xFFFFFFFFu);
data.setSync(sync);
MEDIUM_MSG("Setting sync to %u for %s, %s, %s, %" PRIu32, sync, myStream.c_str(),
myConnector.c_str(), myHost.c_str(), statComm.getCRC(index) & 0xFFFFFFFFu);
statComm.setSync(sync, index);
}
}
}else{
if (sync < 2 && data.getSync() > 2){sync = data.getSync();}
if (sync < 2 && statComm.getSync(index) > 2){sync = statComm.getSync(index);}
}
long long prevDown = getDown();
long long prevUp = getUp();
curConns[index].update(data);
curConns[index].update(statComm, index);
// store timestamp of first received data, if older
if (firstSec > data.now()){firstSec = data.now();}
if (firstSec > statComm.getNow(index)){firstSec = statComm.getNow(index);}
// store timestamp of last received data, if newer
if (data.now() > lastSec){
lastSec = data.now();
if (statComm.getNow(index) > lastSec){
lastSec = statComm.getNow(index);
if (!tracked){
tracked = true;
firstActive = firstSec;
@ -571,13 +584,13 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
long long currUp = getUp();
if (currUp - prevUp < 0 || currDown - prevDown < 0){
INFO_MSG("Negative data usage! %lldu/%lldd (u%lld->%lld) in %s over %s, #%lu", currUp - prevUp,
currDown - prevDown, prevUp, currUp, data.streamName().c_str(), data.connector().c_str(), index);
currDown - prevDown, prevUp, currUp, myStream.c_str(), myConnector.c_str(), index);
}else{
if (!noBWCount){
size_t bwMatchOffset = 0;
noBWCount = 1;
while (noBWCountMatches[bwMatchOffset + 16] != 0 && bwMatchOffset < 1700){
if (Socket::matchIPv6Addr(data.host(), std::string(noBWCountMatches + bwMatchOffset, 16),
if (Socket::matchIPv6Addr(statComm.getHost(index), std::string(noBWCountMatches + bwMatchOffset, 16),
noBWCountMatches[bwMatchOffset + 16])){
noBWCount = 2;
break;
@ -599,40 +612,39 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
}
}
if (currDown + currUp >= COUNTABLE_BYTES){
std::string streamName = data.streamName();
if (sessionType == SESS_UNSET){
if (data.connector() == "INPUT"){
if (myConnector == "INPUT"){
++servInputs;
streamStats[streamName].inputs++;
streamStats[streamName].currIns++;
streamStats[myStream].inputs++;
streamStats[myStream].currIns++;
sessionType = SESS_INPUT;
}else if (data.connector() == "OUTPUT"){
}else if (myConnector == "OUTPUT"){
++servOutputs;
streamStats[streamName].outputs++;
streamStats[streamName].currOuts++;
streamStats[myStream].outputs++;
streamStats[myStream].currOuts++;
sessionType = SESS_OUTPUT;
}else{
++servViewers;
streamStats[streamName].viewers++;
streamStats[streamName].currViews++;
streamStats[myStream].viewers++;
streamStats[myStream].currViews++;
sessionType = SESS_VIEWER;
}
}
// If previous < COUNTABLE_BYTES, we haven't counted any data so far.
// We need to count all the data in that case, otherwise we only count the difference.
if (prevUp + prevDown < COUNTABLE_BYTES){
if (!streamName.size() || streamName[0] == 0){
if (streamStats.count(streamName)){streamStats.erase(streamName);}
if (!myStream.size() || myStream[0] == 0){
if (streamStats.count(myStream)){streamStats.erase(myStream);}
}else{
streamStats[streamName].upBytes += currUp;
streamStats[streamName].downBytes += currDown;
streamStats[myStream].upBytes += currUp;
streamStats[myStream].downBytes += currDown;
}
}else{
if (!streamName.size() || streamName[0] == 0){
if (streamStats.count(streamName)){streamStats.erase(streamName);}
if (!myStream.size() || myStream[0] == 0){
if (streamStats.count(myStream)){streamStats.erase(myStream);}
}else{
streamStats[streamName].upBytes += currUp - prevUp;
streamStats[streamName].downBytes += currDown - prevDown;
streamStats[myStream].upBytes += currUp - prevUp;
streamStats[myStream].downBytes += currDown - prevDown;
}
}
}
@ -642,7 +654,7 @@ Controller::sessType Controller::statSession::getSessType(){
return sessionType;
}
/// Archives the given connection.
/// Archives connection log entries older than the given cutOff point.
void Controller::statSession::wipeOld(uint64_t cutOff){
if (firstSec > cutOff){return;}
firstSec = 0xFFFFFFFFFFFFFFFFull;
@ -673,76 +685,74 @@ void Controller::statSession::wipeOld(uint64_t cutOff){
}
}
void Controller::statSession::ping(const Controller::sessIndex &index, uint64_t disconnectPoint){
void Controller::statSession::dropSession(const Controller::sessIndex &index){
if (!tracked || curConns.size()){return;}
if (lastSec < disconnectPoint){
switch (sessionType){
case SESS_INPUT:
if (streamStats[index.streamName].currIns){streamStats[index.streamName].currIns--;}
break;
case SESS_OUTPUT:
if (streamStats[index.streamName].currOuts){streamStats[index.streamName].currOuts--;}
break;
case SESS_VIEWER:
if (streamStats[index.streamName].currViews){streamStats[index.streamName].currViews--;}
break;
default: break;
}
uint64_t duration = lastSec - firstActive;
if (duration < 1){duration = 1;}
std::stringstream tagStream;
if (tags.size()){
for (std::set<std::string>::iterator it = tags.begin(); it != tags.end(); ++it){
tagStream << "[" << *it << "]";
}
}
Controller::logAccess(index.ID, index.streamName, index.connector, index.host, duration,
getUp(), getDown(), tagStream.str());
if (Controller::accesslog.size()){
if (Controller::accesslog == "LOG"){
std::stringstream accessStr;
accessStr << "Session <" << index.ID << "> " << index.streamName << " (" << index.connector
<< ") from " << index.host << " ended after " << duration << "s, avg "
<< getUp() / duration / 1024 << "KB/s up " << getDown() / duration / 1024 << "KB/s down.";
if (tags.size()){accessStr << " Tags: " << tagStream.str();}
Controller::Log("ACCS", accessStr.str());
}else{
static std::ofstream accLogFile;
static std::string accLogFileName;
if (accLogFileName != Controller::accesslog || !accLogFile.good()){
accLogFile.close();
accLogFile.open(Controller::accesslog.c_str(), std::ios_base::app);
if (!accLogFile.good()){
FAIL_MSG("Could not open access log file '%s': %s", Controller::accesslog.c_str(), strerror(errno));
}else{
accLogFileName = Controller::accesslog;
}
}
if (accLogFile.good()){
time_t rawtime;
struct tm *timeinfo;
struct tm tmptime;
char buffer[100];
time(&rawtime);
timeinfo = localtime_r(&rawtime, &tmptime);
strftime(buffer, 100, "%F %H:%M:%S", timeinfo);
accLogFile << buffer << ", " << index.ID << ", " << index.streamName << ", "
<< index.connector << ", " << index.host << ", " << duration << ", "
<< getUp() / duration / 1024 << ", " << getDown() / duration / 1024 << ", ";
if (tags.size()){accLogFile << tagStream.str();}
accLogFile << std::endl;
}
}
}
tracked = false;
firstActive = 0;
firstSec = 0xFFFFFFFFFFFFFFFFull;
lastSec = 0;
wipedUp = 0;
wipedDown = 0;
oldConns.clear();
sessionType = SESS_UNSET;
switch (sessionType){
case SESS_INPUT:
if (streamStats[index.streamName].currIns){streamStats[index.streamName].currIns--;}
break;
case SESS_OUTPUT:
if (streamStats[index.streamName].currOuts){streamStats[index.streamName].currOuts--;}
break;
case SESS_VIEWER:
if (streamStats[index.streamName].currViews){streamStats[index.streamName].currViews--;}
break;
default: break;
}
uint64_t duration = lastSec - firstActive;
if (duration < 1){duration = 1;}
std::stringstream tagStream;
if (tags.size()){
for (std::set<std::string>::iterator it = tags.begin(); it != tags.end(); ++it){
tagStream << "[" << *it << "]";
}
}
Controller::logAccess(index.ID, index.streamName, index.connector, index.host, duration, getUp(),
getDown(), tagStream.str());
if (Controller::accesslog.size()){
if (Controller::accesslog == "LOG"){
std::stringstream accessStr;
accessStr << "Session <" << index.ID << "> " << index.streamName << " (" << index.connector
<< ") from " << index.host << " ended after " << duration << "s, avg "
<< getUp() / duration / 1024 << "KB/s up " << getDown() / duration / 1024 << "KB/s down.";
if (tags.size()){accessStr << " Tags: " << tagStream.str();}
Controller::Log("ACCS", accessStr.str());
}else{
static std::ofstream accLogFile;
static std::string accLogFileName;
if (accLogFileName != Controller::accesslog || !accLogFile.good()){
accLogFile.close();
accLogFile.open(Controller::accesslog.c_str(), std::ios_base::app);
if (!accLogFile.good()){
FAIL_MSG("Could not open access log file '%s': %s", Controller::accesslog.c_str(), strerror(errno));
}else{
accLogFileName = Controller::accesslog;
}
}
if (accLogFile.good()){
time_t rawtime;
struct tm *timeinfo;
struct tm tmptime;
char buffer[100];
time(&rawtime);
timeinfo = localtime_r(&rawtime, &tmptime);
strftime(buffer, 100, "%F %H:%M:%S", timeinfo);
accLogFile << buffer << ", " << index.ID << ", " << index.streamName << ", "
<< index.connector << ", " << index.host << ", " << duration << ", "
<< getUp() / duration / 1024 << ", " << getDown() / duration / 1024 << ", ";
if (tags.size()){accLogFile << tagStream.str();}
accLogFile << std::endl;
}
}
}
tracked = false;
firstActive = 0;
firstSec = 0xFFFFFFFFFFFFFFFFull;
lastSec = 0;
wipedUp = 0;
wipedDown = 0;
oldConns.clear();
sessionType = SESS_UNSET;
}
/// Archives the given connection.
@ -836,16 +846,12 @@ bool Controller::statSession::hasDataFor(uint64_t t){
/// Returns true if there is any data for this session.
bool Controller::statSession::hasData(){
if (!firstSec && !lastSec){return false;}
if (curConns.size()){return true;}
if (oldConns.size()){
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
if (it->log.size()){return true;}
}
}
if (curConns.size()){
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
if (it->second.log.size()){return true;}
}
}
return false;
}
@ -854,26 +860,14 @@ bool Controller::statSession::isViewerOn(uint64_t t){
return getUp(t) + getDown(t) > COUNTABLE_BYTES;
}
/// Returns true if this session should count as a viewer
bool Controller::statSession::isViewer(){
long long upTotal = wipedUp + wipedDown;
if (oldConns.size()){
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
if (it->log.size()){
upTotal += it->log.rbegin()->second.up + it->log.rbegin()->second.down;
if (upTotal > COUNTABLE_BYTES){return true;}
}
}
}
if (curConns.size()){
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
if (it->second.log.size()){
upTotal += it->second.log.rbegin()->second.up + it->second.log.rbegin()->second.down;
if (upTotal > COUNTABLE_BYTES){return true;}
}
}
}
return false;
/// Returns true if this session should be considered connected
bool Controller::statSession::isConnected(){
return curConns.size();
}
/// Returns true if this session has started (tracked == true) but not yet ended (log entry written)
bool Controller::statSession::isTracked(){
return tracked;
}
/// Returns the cumulative connected time for this session at timestamp t.
@ -1014,58 +1008,60 @@ Controller::statLog &Controller::statStorage::getDataFor(unsigned long long t){
/// This function is called by parseStatistics.
/// It updates the internally saved statistics data.
void Controller::statStorage::update(IPC::statExchange &data){
void Controller::statStorage::update(Comms::Statistics &statComm, size_t index){
statLog tmp;
tmp.time = data.time();
tmp.lastSecond = data.lastSecond();
tmp.down = data.down();
tmp.up = data.up();
log[data.now()] = tmp;
tmp.time = statComm.getTime(index);
tmp.lastSecond = statComm.getLastSecond(index);
tmp.down = statComm.getDown(index);
tmp.up = statComm.getUp(index);
log[statComm.getNow(index)] = tmp;
// wipe data older than approx. STAT_CUTOFF seconds
/// \todo Remove least interesting data first.
if (log.size() > STAT_CUTOFF){log.erase(log.begin());}
}
/// This function is called by the shared memory page that holds statistics.
/// It updates the internally saved statistics data, moving across sessions or archiving when necessary.
void Controller::parseStatistics(char *data, size_t len, uint32_t id){
// retrieve stats data
IPC::statExchange tmpEx(data);
void Controller::statLeadIn(){
statDropoff = Util::bootSecs() - 3;
}
void Controller::statOnActive(size_t id){
// calculate the current session index, store as idx.
sessIndex idx(tmpEx);
// if the connection was already indexed and it has changed, move it
if (connToSession.count(id) && connToSession[id] != idx){
if (sessions[connToSession[id]].getSessType() != SESS_UNSET){
INFO_MSG("Switching connection %" PRIu32 " from active session %s over to %s", id,
connToSession[id].toStr().c_str(), idx.toStr().c_str());
}else{
INFO_MSG("Switching connection %" PRIu32 " from inactive session %s over to %s", id,
connToSession[id].toStr().c_str(), idx.toStr().c_str());
sessIndex idx(statComm, id);
if (statComm.getNow(id) >= statDropoff){
// if the connection was already indexed and it has changed, move it
if (connToSession.count(id) && connToSession[id] != idx){
if (sessions[connToSession[id]].getSessType() != SESS_UNSET){
INFO_MSG("Switching connection %zu from active session %s over to %s", id,
connToSession[id].toStr().c_str(), idx.toStr().c_str());
}else{
INFO_MSG("Switching connection %zu from inactive session %s over to %s", id,
connToSession[id].toStr().c_str(), idx.toStr().c_str());
}
sessions[connToSession[id]].switchOverTo(sessions[idx], id);
// Destroy this session without calling dropSession, because it was merged into another. What session? We never made it. Stop asking hard questions. Go, shoo. *sprays water*
if (!sessions[connToSession[id]].hasData()){sessions.erase(connToSession[id]);}
}
sessions[connToSession[id]].switchOverTo(sessions[idx], id);
if (!sessions[connToSession[id]].hasData()){sessions.erase(connToSession[id]);}
}
if (!connToSession.count(id)){
INSANE_MSG("New connection: %" PRIu32 " as %s", id, idx.toStr().c_str());
}
// store the index for later comparison
connToSession[id] = idx;
// update the session with the latest data
sessions[idx].update(id, tmpEx);
// check validity of stats data
char counter = (*(data - 1)) & 0x7F;
if (counter == 126 || counter == 127){
// the data is no longer valid - connection has gone away, store for later
INSANE_MSG("Ended connection: %" PRIu32 " as %s", id, idx.toStr().c_str());
sessions[idx].finish(id);
connToSession.erase(id);
if (!connToSession.count(id)){
INSANE_MSG("New connection: %zu as %s", id, idx.toStr().c_str());
}
// store the index for later comparison
connToSession[id] = idx;
// update the session with the latest data
sessions[idx].update(id, statComm);
}
}
void Controller::statOnDisconnect(size_t id){
sessIndex idx(statComm, id);
INSANE_MSG("Ended connection: %zu as %s", id, idx.toStr().c_str());
sessions[idx].finish(id);
connToSession.erase(id);
}
void Controller::statLeadOut(){}
/// Returns true if this stream has at least one connected client.
bool Controller::hasViewers(std::string streamName){
if (sessions.size()){
long long currTime = Util::epoch();
long long currTime = Util::bootSecs();
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
if (it->first.streamName == streamName &&
(it->second.hasDataFor(currTime) || it->second.hasDataFor(currTime - 1))){
@ -1119,7 +1115,11 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
// to make sure no nasty timing business takes place, we store the case "now" as a bool.
bool now = (reqTime == 0);
// add the current time, if negative or zero.
if (reqTime <= 0){reqTime += Util::epoch();}
if (reqTime <= 0){
reqTime += Util::bootSecs();
}else{
reqTime -= (Util::epoch() - Util::bootSecs());
}
// at this point, reqTime is the absolute timestamp.
rep["time"] = reqTime; // fill the absolute timestamp
@ -1136,6 +1136,7 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
if ((*it).asStringRef() == "up"){fields |= STAT_CLI_UP;}
if ((*it).asStringRef() == "downbps"){fields |= STAT_CLI_BPS_DOWN;}
if ((*it).asStringRef() == "upbps"){fields |= STAT_CLI_BPS_UP;}
if ((*it).asStringRef() == "sessid"){fields |= STAT_CLI_SESSID;}
}
}
// select all, if none selected
@ -1162,6 +1163,7 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
if (fields & STAT_CLI_BPS_DOWN){rep["fields"].append("downbps");}
if (fields & STAT_CLI_BPS_UP){rep["fields"].append("upbps");}
if (fields & STAT_CLI_CRC){rep["fields"].append("crc");}
if (fields & STAT_CLI_SESSID){rep["fields"].append("sessid");}
// output the data itself
rep["data"].null();
// loop over all sessions
@ -1185,6 +1187,7 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
if (fields & STAT_CLI_BPS_DOWN){d.append(it->second.getBpsDown(time));}
if (fields & STAT_CLI_BPS_UP){d.append(it->second.getBpsUp(time));}
if (fields & STAT_CLI_CRC){d.append(it->first.crc);}
if (fields & STAT_CLI_SESSID){d.append(it->first.ID);}
rep["data"].append(d);
}
}
@ -1235,8 +1238,8 @@ void Controller::fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow){
// collect the data first
std::set<std::string> streams;
std::map<std::string, uint64_t> clients;
unsigned int tOut = Util::epoch() - STATS_DELAY;
unsigned int tIn = Util::epoch() - STATS_INPUT_DELAY;
uint64_t tOut = Util::bootSecs() - STATS_DELAY;
uint64_t tIn = Util::bootSecs() - STATS_INPUT_DELAY;
// check all sessions
{
tthread::lock_guard<tthread::mutex> guard(statsMutex);
@ -1263,26 +1266,14 @@ void Controller::fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow){
jsonForEach(req, j){
if (j->asStringRef() == "clients"){rep[*it].append(clients[*it]);}
if (j->asStringRef() == "lastms"){
char pageId[NAME_BUFFER_SIZE];
IPC::sharedPage streamIndex;
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, it->c_str());
streamIndex.init(pageId, DEFAULT_STRM_PAGE_SIZE, false, false);
if (streamIndex.mapped){
static char liveSemName[NAME_BUFFER_SIZE];
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, it->c_str());
IPC::semaphore metaLocker(liveSemName, O_CREAT | O_RDWR, (S_IRWXU | S_IRWXG | S_IRWXO), 8);
metaLocker.wait();
DTSC::Scan strm = DTSC::Packet(streamIndex.mapped, streamIndex.len, true).getScan();
DTSC::Meta M(*it, false);
if (M){
uint64_t lms = 0;
DTSC::Scan trcks = strm.getMember("tracks");
unsigned int trcks_ctr = trcks.getSize();
for (unsigned int i = 0; i < trcks_ctr; ++i){
if (trcks.getIndice(i).getMember("lastms").asInt() > lms){
lms = trcks.getIndice(i).getMember("lastms").asInt();
}
std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator jt = validTracks.begin(); jt != validTracks.end(); jt++){
if (M.getLastms(*jt) > lms){lms = M.getLastms(*jt);}
}
rep[*it].append(lms);
metaLocker.post();
}else{
rep[*it].append(-1);
}
@ -1330,9 +1321,9 @@ void Controller::fillTotals(JSON::Value &req, JSON::Value &rep){
if (req.isMember("start")){reqStart = req["start"].asInt();}
if (req.isMember("end")){reqEnd = req["end"].asInt();}
// add the current time, if negative or zero.
if (reqStart < 0){reqStart += Util::epoch();}
if (reqStart == 0){reqStart = Util::epoch() - STAT_CUTOFF;}
if (reqEnd <= 0){reqEnd += Util::epoch();}
if (reqStart < 0){reqStart += Util::bootSecs();}
if (reqStart == 0){reqStart = Util::bootSecs() - STAT_CUTOFF;}
if (reqEnd <= 0){reqEnd += Util::bootSecs();}
// at this point, reqStart and reqEnd are the absolute timestamp.
unsigned int fields = 0;
@ -1458,7 +1449,7 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
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){
if (c_total > cl_total){
cpu_use = (long long int)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
}else{
cpu_use = 0;
@ -1556,8 +1547,8 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
// collect the data first
std::map<std::string, uint32_t> outputs;
unsigned long totViewers = 0, totInputs = 0, totOutputs = 0;
unsigned int tOut = Util::epoch() - STATS_DELAY;
unsigned int tIn = Util::epoch() - STATS_INPUT_DELAY;
unsigned int tOut = Util::bootSecs() - STATS_DELAY;
unsigned int tIn = Util::bootSecs() - STATS_INPUT_DELAY;
// check all sessions
if (sessions.size()){
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
@ -1629,8 +1620,7 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
<< it->second.currOuts << "\n";
response << "mist_viewcount{stream=\"" << it->first << "\"}" << it->second.viewers << "\n";
response << "mist_bw{stream=\"" << it->first << "\",direction=\"up\"}" << it->second.upBytes << "\n";
response << "mist_bw{stream=\"" << it->first << "\",direction=\"down\"}"
<< it->second.downBytes << "\n";
response << "mist_bw{stream=\"" << it->first << "\",direction=\"down\"}" << it->second.downBytes << "\n";
}
}
H.Chunkify(response.str(), conn);
@ -1657,8 +1647,8 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
// collect the data first
std::map<std::string, uint32_t> outputs;
uint64_t totViewers = 0, totInputs = 0, totOutputs = 0;
uint64_t tOut = Util::epoch() - STATS_DELAY;
uint64_t tIn = Util::epoch() - STATS_INPUT_DELAY;
uint64_t tOut = Util::bootSecs() - STATS_DELAY;
uint64_t tIn = Util::bootSecs() - STATS_INPUT_DELAY;
// check all sessions
if (sessions.size()){
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){

View file

@ -1,5 +1,6 @@
#pragma once
#include <map>
#include <mist/comms.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/json.h>
@ -37,9 +38,8 @@ namespace Controller{
/// Whenever two of these objects are not equal, it will create a new session.
class sessIndex{
public:
sessIndex(std::string host, unsigned int crc, std::string streamName, std::string connector);
sessIndex(IPC::statExchange &data);
sessIndex();
sessIndex(const Comms::Statistics &statComm, size_t id);
std::string ID;
std::string host;
unsigned int crc;
@ -57,7 +57,7 @@ namespace Controller{
class statStorage{
public:
void update(IPC::statExchange &data);
void update(Comms::Statistics &statComm, size_t index);
bool hasDataFor(unsigned long long);
statLog &getDataFor(unsigned long long);
std::map<unsigned long long, statLog> log;
@ -87,12 +87,13 @@ namespace Controller{
void wipeOld(uint64_t);
void finish(uint64_t index);
void switchOverTo(statSession &newSess, uint64_t index);
void update(uint64_t index, IPC::statExchange &data);
void ping(const sessIndex &index, uint64_t disconnectPoint);
void update(uint64_t index, Comms::Statistics &data);
void dropSession(const sessIndex &index);
uint64_t getStart();
uint64_t getEnd();
bool isViewerOn(uint64_t time);
bool isViewer();
bool isConnected();
bool isTracked();
bool hasDataFor(uint64_t time);
bool hasData();
uint64_t getConnTime(uint64_t time);
@ -110,6 +111,7 @@ namespace Controller{
extern std::map<sessIndex, statSession> sessions;
extern std::map<unsigned long, sessIndex> connToSession;
extern tthread::mutex statsMutex;
extern uint64_t statDropoff;
struct triggerLog{
uint64_t totalCount;
@ -119,8 +121,12 @@ namespace Controller{
extern std::map<std::string, triggerLog> triggerStats;
void statLeadIn();
void statOnActive(size_t id);
void statOnDisconnect(size_t id);
void statLeadOut();
std::set<std::string> getActiveStreams(const std::string &prefix = "");
void parseStatistics(char *data, size_t len, unsigned int id);
void killStatistics(char *data, size_t len, unsigned int id);
void fillClients(JSON::Value &req, JSON::Value &rep);
void fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow = false);

View file

@ -139,7 +139,11 @@ namespace Controller{
void initState(){
tthread::lock_guard<tthread::mutex> guard(logMutex);
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, true); // max 1M of logs cached
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, false, false); // max 1M of logs cached
if (!shmLogs || !shmLogs->mapped){
if (shmLogs){delete shmLogs;}
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, true); // max 1M of logs cached
}
if (!shmLogs->mapped){
FAIL_MSG("Could not open memory page for logs buffer");
return;
@ -156,7 +160,11 @@ namespace Controller{
}
maxLogsRecs = (1024 * 1024 - rlxLogs->getOffset()) / rlxLogs->getRSize();
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, true); // max 1M of accesslogs cached
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, false, false); // max 1M of accesslogs cached
if (!shmAccs || !shmAccs->mapped){
if (shmAccs){delete shmAccs;}
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, true); // max 1M of accesslogs cached
}
if (!shmAccs->mapped){
FAIL_MSG("Could not open memory page for access logs buffer");
return;
@ -176,7 +184,11 @@ namespace Controller{
}
maxAccsRecs = (1024 * 1024 - rlxAccs->getOffset()) / rlxAccs->getRSize();
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, true); // max 1M of stream data
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, false, false); // max 1M of stream data
if (!shmStrm || !shmStrm->mapped){
if (shmStrm){delete shmStrm;}
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, true); // max 1M of stream data
}
if (!shmStrm->mapped){
FAIL_MSG("Could not open memory page for stream data");
return;

View file

@ -295,8 +295,8 @@ namespace Controller{
Util::sanitizeName(cleaned);
std::string strmSource;
if (Util::getStreamStatus(cleaned) != STRMSTAT_OFF){
DTSC::Meta mData = Util::getStreamMeta(cleaned);
if (mData.sourceURI.size()){strmSource = mData.sourceURI;}
DTSC::Meta M(cleaned, false);
if (M && M.getSource().size()){strmSource = M.getSource();}
}
if (!strmSource.size()){
std::string smp = cleaned.substr(0, cleaned.find_first_of("+ "));

View file

@ -24,30 +24,6 @@
#define SHARED_SECRET "empty"
#endif
static std::string readFile(std::string filename){
std::ifstream file(filename.c_str());
if (!file.good()){return "";}
file.seekg(0, std::ios::end);
unsigned int len = file.tellg();
file.seekg(0, std::ios::beg);
std::string out;
out.reserve(len);
unsigned int i = 0;
while (file.good() && i++ < len){out += file.get();}
file.close();
return out;
}
static bool writeFile(std::string filename, std::string &contents){
unlink(filename.c_str());
std::ofstream file(filename.c_str(), std::ios_base::trunc | std::ios_base::out);
if (!file.is_open()){return false;}
file << contents;
file.close();
chmod(filename.c_str(), S_IRWXU | S_IRWXG);
return true;
}
tthread::mutex updaterMutex;
uint8_t updatePerc = 0;
JSON::Value updates;