#include "App.h" #include "Logger.h" #include "Util.h" // below headers are for getN*Processors, etc. #if defined(Q_OS_DARWIN) # include # include # include #elif defined(Q_OS_LINUX) # include #endif #include #include namespace Util { QString basename(const QString &s) { QRegExp re("[\\/]"); auto toks = s.split(re); return toks.last(); } static const auto t0 = std::chrono::high_resolution_clock::now(); qint64 getTime() { const auto now = std::chrono::high_resolution_clock::now(); return std::chrono::duration_cast(now - t0).count(); } qint64 getTimeNS() { const auto now = std::chrono::high_resolution_clock::now(); return std::chrono::duration_cast(now - t0).count(); } double getTimeSecs() { return double(getTime()) / 1000.0; } bool isClockSteady() { return std::chrono::high_resolution_clock::is_steady; } namespace Json { QVariant parseString(const QString &str, bool expectMap) { QJsonParseError e; QJsonDocument d = QJsonDocument::fromJson(str.toUtf8(), &e); if (d.isNull()) throw ParseError(QString("Error parsing Json from string: %1").arg(e.errorString())); auto v = d.toVariant(); if (expectMap && v.type() != QVariant::Map) throw Error("Json Error, expected map, got a list instead"); if (!expectMap && v.type() != QVariant::List) throw Error("Json Error, expected list, got a map instead"); return v; } QVariant parseFile(const QString &file, bool expectMap) { QFile f(file); if (!f.open(QFile::ReadOnly)) throw Error(QString("Could not open file: %1").arg(file)); QString s(f.readAll()); return parseString(s, expectMap); } QString toString(const QVariant &v, bool compact) { if (v.isNull() || !v.isValid()) throw Error("Empty or invalid QVariant passed to Json::toString"); auto d = QJsonDocument::fromVariant(v); if (d.isNull()) throw Error("Bad QVariant pased to Json::toString"); return d.toJson(compact ? QJsonDocument::Compact : QJsonDocument::Indented); } } // end namespace Json RunInThread::RunInThread(const VoidFunc & work, QObject *receiver, const VoidFunc & completion, const QString & name) : QThread(receiver), work(work) { auto sigReceiver = receiver ? receiver : this; if (!name.isNull()) setObjectName(name); connect(this, &RunInThread::onCompletion, sigReceiver, [completion, this]{ if (completion) completion(); deleteLater(); }); if (!blockNew) { { QMutexLocker ml(&mut); extant.insert(this); } start(); } else { Warning() << "App shutting down, will not start thread " << (name.isNull() ? "(RunInThread)" : name); } } void RunInThread::run() { if (work) work(); emit onCompletion(); // runs in receiver's thread or main thread if no receiver. done(); } /// app exit cleanup handling /*static*/ std::atomic_bool RunInThread::blockNew = false; /*static*/ QSet RunInThread::extant; /*static*/ QMutex RunInThread::mut; /*static*/ QWaitCondition RunInThread::cond; void RunInThread::done() { QMutexLocker ml(&mut); extant.remove(this); if (extant.isEmpty()) { cond.wakeAll(); } } // static bool RunInThread::waitForAll(unsigned long timeout, const QString &msg, int *num) { QMutexLocker ml(&mut); if (!extant.isEmpty()) { Log() << msg; if (num) *num = extant.count(); auto notTimedOut = cond.wait(&mut, timeout); if (!notTimedOut && num) { // if we timed out, tell caller how many were still running *num = extant.count(); } return notTimedOut; } else if (num) *num = 0; return true; } // static void RunInThread::test(QObject *receiver) { auto rit = Util::RunInThread::Do([]{ for (int i = 0; i < 100; ++i) { Debug() << "Worker thread..."; QThread::msleep(100); //if (blockNew) // return; } }, receiver, []{ Debug() << "COMPLETION!"; }, "(RunInThread)"); connect(rit, &QObject::destroyed, receiver ? receiver : qApp, []{ Debug() << "DESTROYED!!"; }); } /// Like the above but for VoidFunc lambdas. Returns true if the lambda was called before timeout, /// false otherwise. (Note lambda may still run later asynchronously). bool VoidFuncOnObjectNoThrow(const QObject *obj, const std::function & lambda, int timeout_ms) { try { LambdaOnObject(obj, lambda, timeout_ms); return true; } catch (const Exception &) {} return false; } #if defined(Q_OS_DARWIN) unsigned getNVirtualProcessors() { static std::atomic nVProcs = 0; if (!nVProcs) { int a = 0; size_t b = sizeof(a); if (0 == sysctlbyname("hw.ncpu",&a, &b, nullptr, 0)) { nVProcs = unsigned(a); // this returns virtual CPUs which isn't always what we want.. } } return nVProcs.load() ? nVProcs.load() : 1; } unsigned getNPhysicalProcessors() { static std::atomic nProcs = 0; if (!nProcs) { int a = 0; size_t b = sizeof(a); if (0 == sysctlbyname("hw.physicalcpu",&a,&b,nullptr,0)) { nProcs = unsigned(a); } //Debug() << "nProcs = " << nProcs;// << " a:" << a << " b:" << b; } return nProcs.load() ? nProcs.load() : 1; } #elif defined(Q_OS_LINUX) unsigned getNVirtualProcessors() { return std::thread::hardware_concurrency(); } unsigned getNPhysicalProcessors() { static std::atomic nProcs = 0; if (!nProcs) { nProcs = unsigned(sysconf(_SC_NPROCESSORS_ONLN)); } return nProcs.load() ? nProcs.load() : 1; } #else unsigned getNVirtualProcessors() { return std::thread::hardware_concurrency(); } unsigned getNPhysicalProcessors() { return std::thread::hardware_concurrency(); } #endif QByteArray ParseHexFast(const QByteArray &hex, bool checkDigits) { const int size = hex.size(); QByteArray ret(size / 2, Qt::Initialization::Uninitialized); if (UNLIKELY(size % 2)) { // bad / not hex because not even number of chars. ret.clear(); return ret; } const char *d = hex.constData(), * const dend = d + size; for (char c1, c2, *out = ret.data(); d < dend; d += 2, ++out) { static constexpr char offset_A = 'A' - 0xa, offset_a = 'a' - 0xa, offset_0 = '0'; // slightly unrolled loop, does 2 chars at a time c1 = d[0]; c2 = d[1]; // c1 if (c1 <= '9') // this is the most likely for any random digit, so we check this first c1 -= offset_0; else if (c1 >= 'a') // next, we anticipate lcase, so we do this check first c1 -= offset_a; else // c1 >= 'A' c1 -= offset_A; // c2 if (c2 <= '9') // this is the most likely for any random digit, so we check this first c2 -= offset_0; else if (c2 >= 'a') // next, we anticipate lcase, so we do this check first c2 -= offset_a; else // c2 >= 'A' c2 -= offset_A; // The below is slowish... we can just accept bad hex data as 'corrupt' ... // checkDigit = false allows us to skip this check, making this function >5x faster! if (UNLIKELY(checkDigits && (c1 < 0 || c1 > 0xf || c2 < 0 || c2 > 0xf))) { // ensure data was actually in range ret.clear(); break; } *out = char(c1 << 4) | c2; } return ret; } QByteArray ToHexFast(const QByteArray &ba) { const int size = ba.size(); QByteArray ret(size*2, Qt::Initialization::Uninitialized); const char *cur = ba.constData(), * const end = cur + size; for (char c1, c2, *out = ret.data(); cur < end; ++cur, out += 2) { static constexpr char dist_from_9_to_a = ('a'-'9')-1; c1 = ((*cur >> 4) & 0xf) + '0'; c2 = (*cur & 0xf) + '0'; if (c1 > '9') c1 += dist_from_9_to_a; if (c2 > '9') c2 += dist_from_9_to_a; out[0] = c1; out[1] = c2; } return ret; } } // end namespace Util Log::Log() {} Log::Log(Color c) { setColor(c); } Log::Log(const char *fmt...) : s() { va_list ap; va_start(ap,fmt); str = QString::vasprintf(fmt,ap); va_end(ap); s.setString(&str, QIODevice::WriteOnly|QIODevice::Append); } Log::~Log() { if (doprt) { App *ourApp = app(); s.flush(); // does nothing probably.. // note: we always want to log the timestamp, even in syslog mode. // this is because if logging from a thread, log lines be out-of-order. // The timestamp is the only record of the actual order in which things // occurred. Currently the timestamp is to 4 decimal places (hundreds of micros) const auto unow = Util::getTimeNS()/1000LL; const QString tsStr = QString::asprintf("[%lld.%04d] ", unow/1000000LL, int((unow/100LL)%10000)); QString thrdStr = ""; if (QThread *th = QThread::currentThread(); th && ourApp && th != ourApp->thread()) { QString thrdName = th->objectName(); if (thrdName.trimmed().isEmpty()) thrdName = QString::asprintf("%p", reinterpret_cast(QThread::currentThreadId())); thrdStr = QString(" ").arg(thrdName); } Logger *logger = ourApp ? ourApp->logger() : nullptr; QString theString = tsStr + thrdStr + (logger && logger->isaTTY() ? colorify(str, color) : str); if (logger) { emit logger->log(level, theString); } else { // just print to console for now.. std::cerr << Q2C(theString) << std::endl << std::flush; } } } /* static */ QString Log::colorString(Color c) { const char *suffix = "[0m"; // normal switch(c) { case Black: suffix = "[30m"; break; case Red: suffix = "[31m"; break; case Green: suffix = "[32m"; break; case Yellow: suffix = "[33m"; break; case Blue: suffix = "[34m"; break; case Magenta: suffix = "[35m"; break; case Cyan: suffix = "[36m"; break; case White: suffix = "[37m"; break; case BrightBlack: suffix = "[30;1m"; break; case BrightRed: suffix = "[31;1m"; break; case BrightGreen: suffix = "[1,32m"; break; case BrightYellow: suffix = "[33;1m"; break; case BrightBlue: suffix = "[34;1m"; break; case BrightMagenta: suffix = "[35;1m"; break; case BrightCyan: suffix = "[36;1m"; break; case BrightWhite: suffix = "[37;1m"; break; default: // will just use normal break; } static const char prefix[2] = { 033, 0 }; // esc 033 in octal return QString::asprintf("%s%s", prefix, suffix); } QString Log::colorify(const QString &str, Color c) { QString colorStr = useColor && c != Normal ? colorString(c) : ""; QString normalStr = useColor && c != Normal ? colorString(Normal) : ""; return colorStr + str + normalStr; } template <> Log & Log::operator<<(const Color &c) { setColor(c); return *this; } Debug::~Debug() { level = Logger::Level::Debug; doprt = isEnabled(); if (!doprt) return; if (!colorOverridden) color = Cyan; str = QString("(Debug) ") + str; } bool Debug::isEnabled() { auto ourApp = app(); return !ourApp || !ourApp->options || ourApp->options->verboseDebug; } Trace::~Trace() { level = Logger::Level::Debug; doprt = isEnabled(); if (!doprt) return; if (!colorOverridden) color = Green; str = QString("(Trace) ") + str; } bool Trace::isEnabled() { auto ourApp = app(); return ourApp && ourApp->options && ourApp->options->verboseTrace && Debug::isEnabled(); } Error::~Error() { level = Logger::Level::Critical; if (!colorOverridden) color = BrightRed; } Warning::~Warning() { level = Logger::Level::Warning; if (!colorOverridden) color = Yellow; } Fatal::~Fatal() { level = Logger::Level::Fatal; str = QString("FATAL: ") + str; if (!colorOverridden) color = BrightRed; } FatalAssert::FatalAssert(bool expr) : assertion(expr) { doprt = !assertion; } FatalAssert::~FatalAssert() { if ((doprt = !assertion)) { level = Logger::Level::Fatal; str = QString("ASSERTION FAILED: ") + str; if (!colorOverridden) color = BrightRed; } }