File indexing completed on 2024-05-12 05:26:20

0001 /*
0002  * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org>
0003  *
0004  *   This program is free software; you can redistribute it and/or modify
0005  *   it under the terms of the GNU General Public License as published by
0006  *   the Free Software Foundation; either version 2 of the License, or
0007  *   (at your option) any later version.
0008  *
0009  *   This program is distributed in the hope that it will be useful,
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012  *   GNU General Public License for more details.
0013  *
0014  *   You should have received a copy of the GNU General Public License
0015  *   along with this program; if not, write to the
0016  *   Free Software Foundation, Inc.,
0017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
0018  */
0019 
0020 #include <QGuiApplication>
0021 #include <QLockFile>
0022 #include <QDir>
0023 #include <QElapsedTimer>
0024 
0025 #include <signal.h>
0026 #ifndef Q_OS_WIN
0027 #include <unistd.h>
0028 #else
0029 #include <io.h>
0030 #include <process.h>
0031 #endif
0032 
0033 #include "listener.h"
0034 #include "log.h"
0035 #include "test.h"
0036 #include "definitions.h"
0037 #include "backtrace.h"
0038 #ifdef Q_OS_OSX
0039 #include <CoreFoundation/CoreFoundation.h>
0040 #endif
0041 
0042 
0043 
0044 
0045 /*
0046  * We capture all qt debug messages in the same process and feed it into the sink debug system.
0047  * This way we get e.g. kimap debug messages as well together with the rest.
0048  */
0049 void qtMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
0050 {
0051     QByteArray localMsg = msg.toLocal8Bit();
0052     switch (type) {
0053     case QtDebugMsg:
0054         Sink::Log::debugStream(Sink::Log::DebugLevel::Trace, context.line, context.file, context.function, context.category) << msg;
0055         break;
0056     case QtInfoMsg:
0057         Sink::Log::debugStream(Sink::Log::DebugLevel::Log, context.line, context.file, context.function, context.category) << msg;
0058         break;
0059     case QtWarningMsg:
0060         Sink::Log::debugStream(Sink::Log::DebugLevel::Warning, context.line, context.file, context.function, context.category) << msg;
0061         break;
0062     case QtCriticalMsg:
0063         Sink::Log::debugStream(Sink::Log::DebugLevel::Error, context.line, context.file, context.function, context.category) << msg;
0064         break;
0065     case QtFatalMsg:
0066         Sink::Log::debugStream(Sink::Log::DebugLevel::Error, context.line, context.file, context.function, context.category) << msg;
0067         abort();
0068     }
0069 }
0070 
0071 QString read(const QString &filename)
0072 {
0073     QFile file{filename};
0074     file.open(QIODevice::ReadOnly);
0075     return file.readAll();
0076 }
0077 
0078 void printStats()
0079 {
0080 
0081 #if defined(Q_OS_LINUX)
0082     /*
0083      * See 'man proc' for details
0084      */
0085     {
0086         auto statm = read("/proc/self/statm").split(' ');
0087         SinkTrace() << "Program size:" << statm.value(0).toInt() << "pages";
0088         SinkTrace() << "RSS:"<< statm.value(1).toInt() << "pages";
0089         SinkTrace() << "Resident Shared:" << statm.value(2).toInt() << "pages";
0090         SinkTrace() << "Text (code):" << statm.value(3).toInt() << "pages";
0091         SinkTrace() << "Data (data + stack):" << statm.value(5).toInt() << "pages";
0092     }
0093 
0094     {
0095         auto stat = read("/proc/self/stat").split(' ');
0096         SinkTrace() << "Minor page faults: " << stat.value(10).toInt();
0097         SinkTrace() << "Children minor page faults: " << stat.value(11).toInt();
0098         SinkTrace() << "Major page faults: " << stat.value(12).toInt();
0099         SinkTrace() << "Children major page faults: " << stat.value(13).toInt();
0100     }
0101 
0102     //Dump the complete memory map for the process
0103     // std::cout << "smaps: " << read("/proc/self/smaps").toStdString();
0104     //Dump all sorts of stats for the process
0105     // std::cout << read("/proc/self/status").toStdString();
0106 
0107     {
0108         auto io = read("/proc/self/io").split('\n');
0109         QHash<QString, QString> hash;
0110         for (const auto &s : io) {
0111             const auto parts = s.split(": ");
0112             hash.insert(parts.value(0), parts.value(1));
0113         }
0114         SinkTrace() << "Read syscalls: " << hash.value("syscr").toInt();
0115         SinkTrace() << "Write syscalls: " << hash.value("syscw").toInt();
0116         SinkTrace() << "Read from disk: " << hash.value("read_bytes").toInt() / 1024 << "kb";
0117         SinkTrace() << "Written to disk: " << hash.value("write_bytes").toInt() / 1024 << "kb";
0118         SinkTrace() << "Cancelled write bytes: " << hash.value("cancelled_write_bytes").toInt();
0119     }
0120 
0121 #endif
0122 }
0123 
0124 class SynchronizerApplication : public QGuiApplication
0125 {
0126     Q_OBJECT
0127 protected:
0128     using QGuiApplication::QGuiApplication;
0129 
0130     QElapsedTimer time;
0131 
0132     /*
0133      * If we block the event loop for too long the system becomes unresponsive to user inputs,
0134      * so we monitor it and attempt to avoid blocking behaviour
0135      */
0136     bool notify(QObject *receiver, QEvent *event) override
0137     {
0138         if (time.isValid()) {
0139             time.restart();
0140         } else {
0141             time.start();
0142         }
0143         const auto ret = QGuiApplication::notify(receiver, event);
0144         const auto elapsed = time.elapsed();
0145         if (elapsed > 1000) {
0146             SinkWarning() << "Blocked the eventloop for " << Sink::Log::TraceTime(elapsed) << " with event " << event->type();
0147         }
0148         return ret;
0149     }
0150 };
0151 
0152 int main(int argc, char *argv[])
0153 {
0154     if (qEnvironmentVariableIsSet("SINK_GDB_DEBUG")) {
0155 #ifndef Q_OS_WIN
0156         SinkWarning() << "Running resource in debug mode and waiting for gdb to attach: gdb attach " << getpid();
0157         raise(SIGSTOP);
0158 #endif
0159     } else {
0160         Sink::installCrashHandler();
0161     }
0162 
0163     qInstallMessageHandler(qtMessageHandler);
0164 
0165 #ifdef Q_OS_OSX
0166     //Necessary to hide this QGuiApplication from the dock and application switcher on mac os.
0167     if (CFBundleRef mainBundle = CFBundleGetMainBundle()) {
0168         // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist,
0169         if (CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle)) {
0170             // Add or set the "LSUIElement" key with/to value "1". This can simply be a CFString.
0171             CFDictionarySetValue(infoDict, CFSTR("LSUIElement"), CFSTR("1"));
0172             // That's it. We're now considered as an "agent" by the window server, and thus will have
0173             // neither menubar nor presence in the Dock or App Switcher.
0174         }
0175     }
0176 #endif
0177 
0178     SynchronizerApplication app(argc, argv);
0179     app.setQuitLockEnabled(false);
0180 
0181     QByteArrayList arguments;
0182     for (int i = 0; i < argc; i++) {
0183         arguments << argv[i];
0184     }
0185     if (arguments.contains("--test")) {
0186         SinkLog() << "Running in test-mode";
0187         arguments.removeAll("--test");
0188         Sink::Test::setTestModeEnabled(true);
0189     }
0190 
0191     if (arguments.count() < 3) {
0192         SinkWarning() << "Not enough args passed, no resource loaded.";
0193         return app.exec();
0194     }
0195 
0196     const QByteArray instanceIdentifier = arguments.at(1);
0197     const QByteArray resourceType = arguments.at(2);
0198     app.setApplicationName(instanceIdentifier);
0199     Sink::Log::setPrimaryComponent(instanceIdentifier);
0200     SinkLog() << "Starting: " << instanceIdentifier << resourceType;
0201 
0202     QDir{}.mkpath(Sink::resourceStorageLocation(instanceIdentifier));
0203     QLockFile lockfile(Sink::storageLocation() + QString("/%1.lock").arg(QString(instanceIdentifier)));
0204     lockfile.setStaleLockTime(500);
0205     if (!lockfile.tryLock(0)) {
0206         const auto error = lockfile.error();
0207         if (error == QLockFile::LockFailedError) {
0208             qint64 pid;
0209             QString hostname, appname;
0210             lockfile.getLockInfo(&pid, &hostname, &appname);
0211             SinkWarning() << "Failed to acquire exclusive resource lock.";
0212             SinkLog() << "Pid:" << pid << "Host:" << hostname << "App:" << appname;
0213         } else {
0214             SinkError() << "Error while trying to acquire exclusive resource lock: " << error;
0215         }
0216         return -1;
0217     }
0218 
0219     auto listener = new Listener(instanceIdentifier, resourceType, &app);
0220     Sink::setListener(listener);
0221     listener->checkForUpgrade();
0222 
0223     QObject::connect(&app, &QCoreApplication::aboutToQuit, listener, &Listener::closeAllConnections);
0224     QObject::connect(listener, &Listener::noClients, &app, &QCoreApplication::quit);
0225 
0226     auto ret = app.exec();
0227     SinkLog() << "Exiting: " << instanceIdentifier;
0228     printStats();
0229     return ret;
0230 }
0231 
0232 #include "main.moc"