#include #include "buffered_request.h" #include #include #ifdef ONLINE_MODE void logStr(const char *format, ...); /** * Records an argument line with non-timestamps events (objectId,propertyValue) */ class SimpleRequestBuffer : public IRequestBuffer { public: bool addRequest(IRequestBuffer::ObjectId objectId, const char *propertyValue); std::string getRequestLine(bool consume); private: std::string _ArgLine; }; //********************************************************** bool SimpleRequestBuffer::addRequest(IRequestBuffer::ObjectId objectId, const char *propertyValue) { // TODO: size limit std::ostringstream s; s << objectId << "," << propertyValue << ";"; _ArgLine += s.str(); return true; } //********************************************************** std::string SimpleRequestBuffer::getRequestLine(bool consume) { std::string s = _ArgLine; if (consume) _ArgLine.clear(); return s; } #define DISABLED_TIMESTAMP ~0 /** * Records an argument line with timestamps events (objectId only, propertyValue is ignored) */ class TimestampedProplessRequestBuffer : public IRequestBuffer { public: bool addRequest(IRequestBuffer::ObjectId objectId, const char *propertyValue=""); std::string getRequestLine(bool consume); private: class ObjectStat { public: ObjectStat() : /*_ObjectId(0),*/ _LastUseTimestamp(0), _FirstUseTimestamp(0), _UseCount(0) {} bool addEvent(); std::string getStatStr() const; std::string popStatStr(); private: //IRequestBuffer::ObjectId _ObjectId; int _UseCount; time_t _FirstUseTimestamp; time_t _LastUseTimestamp; }; typedef std::map ObjectStatMap; ObjectStatMap _Stat; }; //********************************************************** bool TimestampedProplessRequestBuffer::ObjectStat::addEvent() { ++_UseCount; if (_FirstUseTimestamp == 0) time(&_FirstUseTimestamp); time(&_LastUseTimestamp); return true; } //********************************************************** std::string TimestampedProplessRequestBuffer::ObjectStat::getStatStr() const { if (_UseCount == 0) return std::string(); std::ostringstream s; time_t now; s << _UseCount << "," << ((_FirstUseTimestamp != DISABLED_TIMESTAMP) ? (time(&now) - _FirstUseTimestamp) : -1) << ","// time diff from the first event << (time(&now) - _LastUseTimestamp); // time diff from the last event return s.str(); } //********************************************************** std::string TimestampedProplessRequestBuffer::ObjectStat::popStatStr() { std::string s = getStatStr(); _UseCount = 0; // _FirstUseTimestamp is for the whole session, disable in subsequent sendings if (_FirstUseTimestamp != 0) _FirstUseTimestamp = DISABLED_TIMESTAMP; _LastUseTimestamp = 0; return s; } //********************************************************** bool TimestampedProplessRequestBuffer::addRequest(IRequestBuffer::ObjectId objectId, const char *propertyValue) { return _Stat[objectId].addEvent(); } //********************************************************** std::string TimestampedProplessRequestBuffer::getRequestLine(bool consume) { std::ostringstream s; std::string statStr; for (ObjectStatMap::iterator ist=_Stat.begin(); ist!=_Stat.end(); ++ist) { const IRequestBuffer::ObjectId& objectId = ist->first; ObjectStat& stat = ist->second; statStr = (consume ? stat.popStatStr() : stat.getStatStr()); if (!statStr.empty()) s << objectId << "," << statStr << ";"; } return s.str(); } /** * Records the usage count, time and duration of objects. * Usage: set propertyValue to "1" for On, "0" for Off. */ class ActiveTimeRequestBuffer : public IRequestBuffer { public: bool addRequest(IRequestBuffer::ObjectId objectId, const char *propertyValue); std::string getRequestLine(bool consume); private: class ObjectStat { public: ObjectStat() : /*_ObjectId(0),*/ _LastBeginTimestamp(0), _LastBeginTimestampInterm(0), _ElapsedTime(0), _UseCount(0), _InUse(false), _Anomaly(false) {} bool addEvent(const char *eventValue); std::string getStatStr() const; std::string popStatStr(); private: //IRequestBuffer::ObjectId _ObjectId; bool _InUse; bool _Anomaly; int _UseCount; time_t _LastBeginTimestamp; // sent for DateUpd time_t _LastBeginTimestampInterm; // used to calculate _ElapsedTime time_t _ElapsedTime; }; typedef std::map ObjectStatMap; ObjectStatMap _Stat; }; //********************************************************** bool ActiveTimeRequestBuffer::ObjectStat::addEvent(const char *eventValue) { if (strncmp(eventValue, "1", 2) == 0) // object enabled { if (_InUse) return !(_Anomaly = true); _InUse = true; time(&_LastBeginTimestamp); _LastBeginTimestampInterm = _LastBeginTimestamp; } else // object disabled { if (!_InUse) return !(_Anomaly = true); _InUse = false; ++_UseCount; time_t now; _ElapsedTime += time(&now) - _LastBeginTimestampInterm; } return true; } //********************************************************** std::string ActiveTimeRequestBuffer::ObjectStat::getStatStr() const { if ((_UseCount == 0) && (_ElapsedTime == 0)) return std::string(); std::ostringstream s; time_t now; s << _UseCount << "," << _ElapsedTime << "," << (time(&now) - _LastBeginTimestamp) // time diff from the last begin event << (_InUse ? ",1" : ",0") << (_Anomaly ? ",1" : ",0"); return s.str(); } //********************************************************** std::string ActiveTimeRequestBuffer::ObjectStat::popStatStr() { if (_InUse) { // We will return the current state ++_UseCount; // count this partial use as one (we'll not count the remaining) time_t now; _ElapsedTime += time(&now) - _LastBeginTimestampInterm; // count partial elapsed time } std::string s = getStatStr(); if (_InUse) { // We reset the state so that the remaining of the current use will consistenly be taken into account _UseCount = -1; // on end of use, or on next pop if during the same use, will return 0 time(&_LastBeginTimestampInterm); } else { _UseCount = 0; } _ElapsedTime = 0; return s; } //********************************************************** bool ActiveTimeRequestBuffer::addRequest(IRequestBuffer::ObjectId objectId, const char *propertyValue) { return _Stat[objectId].addEvent(propertyValue); } //********************************************************** std::string ActiveTimeRequestBuffer::getRequestLine(bool consume) { std::ostringstream s; std::string statStr; for (ObjectStatMap::iterator ist=_Stat.begin(); ist!=_Stat.end(); ++ist) { const IRequestBuffer::ObjectId& objectId = ist->first; ObjectStat& stat = ist->second; statStr = (consume ? stat.popStatStr() : stat.getStatStr()); if (!statStr.empty()) s << objectId << "," << statStr << ";"; } return s.str(); } //********************************************************** IRequestBuffer *IRequestBuffer::factory(const std::string& destServerScript) { if (destServerScript == "trk-text") return new SimpleRequestBuffer(); else if (destServerScript == "trk-action") return new TimestampedProplessRequestBuffer(); else return new ActiveTimeRequestBuffer(); } //********************************************************** bool BufferedRequester::addRequest(const char *destServerScript, IRequestBuffer::ObjectId objectId, const char *propertyValue) { std::string destS = destServerScript; IRequestBuffer* &rb = _BRMap[destS]; if (!rb) rb = IRequestBuffer::factory(destS); return rb && rb->addRequest(objectId, propertyValue); } //********************************************************** std::string BufferedRequester::getLineToSend(bool consume) { std::string argLine, s; for (BufferedRequesterMap::iterator im=_BRMap.begin(); im!=_BRMap.end(); ++im) { s = im->second->getRequestLine(consume); if (!s.empty()) argLine += im->first + ":" + s + "|"; } return argLine; } //********************************************************** // Can be called from 1 thread only (actually the background one) bool BufferedRequester::sendBatch(const std::string& lineToSend) { // TODO: if the request fails, try to send next time (but don't overstress the server...) //logStr("Sending batch: %s", lineToSend.c_str()); static time_t dbPingS = 0; // not multithread safe time_t now; time(&now); std::ostringstream args; args << "batch=" << lineToSend << "&dbp=" << dbPingS; if (!doRequest("batch-req", args.str().c_str(), USE_MORE_INFO, __FUNCTION__, "Could not send batch request")) return false; dbPingS = time(&dbPingS) - now; // not quite precise (in secs), only significant if >1 (says at least 1 sec) const HttpPage& buffer = _HttpClient.getResponsePage(); const char *bufPos = buffer.c_str() + 2; // skip "0|" std::string nextPeriodStr = getNextResultField(&bufPos); if (!nextPeriodStr.empty()) _BufferedSendingPeriodS = atoi(nextPeriodStr.c_str()); return true; } // *************************************************************************** void BufferedRequester::clear() { for (BufferedRequesterMap::iterator im=_BRMap.begin(); im!=_BRMap.end(); ++im) { if (im->second) delete im->second; } _BRMap.clear(); } // *************************************************************************** bool BckgndBufferedRequester::init(const SimpleString<16> *pIdAccount) { CBackgroundTask::init(); MUTEX_SETUP(_MutexToRequest); _ExpediteNextSending = false; _WantExit = false; return CamCamHttpClient::init(pIdAccount); } //********************************************************** BckgndBufferedRequester::~BckgndBufferedRequester() { MUTEX_CLEANUP(_MutexToRequest); } //********************************************************** bool BckgndBufferedRequester::addRequest(const char *destServerScript, IRequestBuffer::ObjectId objectId, const char *propertyValue, bool expediteSending) { MUTEX_LOCK(_MutexToRequest); BufferedRequester::addRequest(destServerScript, objectId, propertyValue); _ExpediteNextSending = expediteSending; MUTEX_UNLOCK(_MutexToRequest); return true; } //********************************************************** bool BckgndBufferedRequester::flushAndExit() { MUTEX_LOCK(_MutexToRequest); _ExpediteNextSending = true; _WantExit = true; MUTEX_UNLOCK(_MutexToRequest); return true; } //********************************************************** // Called from background thread void BckgndBufferedRequester::run() { time_t lastSendingTime; time(&lastSendingTime); std::string lineToSend; while (true) { Sleep(500); time_t now; bool wantExit = _WantExit; // check flag from other thread, only here // Is sending completely disabled/-1 (including on log-out)? if (_BufferedSendingPeriodS == -1) { if (wantExit) break; continue; } // Is it time to send an update (except if disabled/0 or if there is nothing to send) or to flush (last update)? time(&now); if (_ExpediteNextSending || ((_BufferedSendingPeriodS > 0) && (now > lastSendingTime + _BufferedSendingPeriodS))) { lastSendingTime = now; // Get stat from all requests that have been added MUTEX_LOCK(_MutexToRequest); _ExpediteNextSending = false; lineToSend = getLineToSend(true); MUTEX_UNLOCK(_MutexToRequest); // Send to server (can take some time) sendBatch(lineToSend); // send even if (!lineToSend.empty()) to check dual-log-in case if (wantExit) // not checking _WantExit as it could have changed during sending break; } if (_HttpClient.isAborting()) break; } MUTEX_LOCK(_MutexToRequest); clear(); // TODO: replace this by a global account release() on log-out MUTEX_UNLOCK(_MutexToRequest); _IsRunning = false; _WantExit = false; } #endif