Skip to content
Snippets Groups Projects
LogCompressor.cc 8.59 KiB
Newer Older
  • Learn to ignore specific revisions
  • lm's avatar
    lm committed
    /*===================================================================
    
    pixhawk's avatar
    pixhawk committed
    QGroundControl Open Source Ground Control Station
    
    (c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
    
    This file is part of the QGROUNDCONTROL project
    
        QGROUNDCONTROL is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version.
    
    pixhawk's avatar
    pixhawk committed
        QGROUNDCONTROL is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
    
    pixhawk's avatar
    pixhawk committed
        You should have received a copy of the GNU General Public License
        along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
    
    pixhawk's avatar
    pixhawk committed
    ======================================================================*/
    
    /**
     * @file
     *   @brief Implementation of class LogCompressor
     *   @author Lorenz Meier <mavteam@student.ethz.ch>
     *
     */
    
    
    pixhawk's avatar
    pixhawk committed
    #include <QFile>
    #include <QTextStream>
    #include <QStringList>
    
    #include <QFileInfo>
    
    lm's avatar
    lm committed
    #include <QList>
    
    pixhawk's avatar
    pixhawk committed
    #include "LogCompressor.h"
    
    #include <QDebug>
    
    
    /**
     * It will only get active upon calling startCompression()
     */
    
    LogCompressor::LogCompressor(QString logFileName, QString outFileName, int uasid) :
    
        logFileName(logFileName),
        outFileName(outFileName),
        running(true),
        currentDataLine(0),
        dataLines(1),
        uasid(uasid)
    
    pixhawk's avatar
    pixhawk committed
    {
    }
    
    void LogCompressor::run()
    {
        QString separator = "\t";
        QString fileName = logFileName;
        QFile file(fileName);
    
        QFile outfile(outFileName);
    
    pixhawk's avatar
    pixhawk committed
        QStringList* keys = new QStringList();
    
    lm's avatar
    lm committed
        QList<quint64> times;// = new QList<quint64>();
        QList<quint64> finalTimes;
    
    lm's avatar
    lm committed
    
        qDebug() << "LOG COMPRESSOR: Starting" << fileName;
    
    
        if (!file.exists() || !file.open(QIODevice::ReadOnly | QIODevice::Text)) {
    
            //qDebug() << "LOG COMPRESSOR: INPUT FILE DOES NOT EXIST";
    
    lm's avatar
    lm committed
            emit logProcessingStatusChanged(tr("Log Compressor: Cannot start/compress log file, since input file %1 is not readable").arg(QFileInfo(fileName).absoluteFilePath()));
    
    pixhawk's avatar
    pixhawk committed
            return;
    
    lm's avatar
    lm committed
        }
    
    
        // Check if file is writeable
        if (outFileName == ""/* || !QFileInfo(outfile).isWritable()*/) {
            //qDebug() << "LOG COMPRESSOR: OUTPUT FILE DOES NOT EXIST" << outFileName;
            emit logProcessingStatusChanged(tr("Log Compressor: Cannot start/compress log file, since output file %1 is not writable").arg(QFileInfo(outFileName).absoluteFilePath()));
            return;
        }
    
    
    pixhawk's avatar
    pixhawk committed
        // Find all keys
        QTextStream in(&file);
    
    lm's avatar
    lm committed
    
        // Search only a certain region, assuming that not more
        // than N dimensions at H Hertz can be send
        const unsigned int keySearchLimit = 15000;
        // e.g. 500 Hz * 30 values or
        // e.g. 100 Hz * 150 values
    
        unsigned int keyCounter = 0;
        while (!in.atEnd() && keyCounter < keySearchLimit) {
    
    pixhawk's avatar
    pixhawk committed
            QString line = in.readLine();
            // Accumulate map of keys
            // Data field name is at position 2
            QString key = line.split(separator).at(2);
            if (!keys->contains(key)) keys->append(key);
    
    lm's avatar
    lm committed
            keyCounter++;
    
    pixhawk's avatar
    pixhawk committed
        }
        keys->sort();
    
    pixhawk's avatar
    pixhawk committed
        QString header = "";
        QString spacer = "";
    
        for (int i = 0; i < keys->length(); i++) {
    
    pixhawk's avatar
    pixhawk committed
            header += keys->at(i) + separator;
            spacer += " " + separator;
        }
    
    
    lm's avatar
    lm committed
        emit logProcessingStatusChanged(tr("Log compressor: Dataset contains dimension: ") + header);
    
        //qDebug() << header;
    
        //qDebug() << "NOW READING TIMES";
    
    pixhawk's avatar
    pixhawk committed
        // Find all times
        //in.reset();
        file.reset();
        in.reset();
        in.resetStatus();
    
    lm's avatar
    lm committed
        bool ok;
    
        while (!in.atEnd()) {
    
    pixhawk's avatar
    pixhawk committed
            QString line = in.readLine();
            // Accumulate map of keys
    
    lm's avatar
    lm committed
            // Data field name is at position 2b
            quint64 time = static_cast<QString>(line.split(separator).at(0)).toLongLong(&ok);
    
    lm's avatar
    lm committed
                times.append(time);
    
    pixhawk's avatar
    pixhawk committed
            }
        }
    
    lm's avatar
    lm committed
        qSort(times);
    
    lm's avatar
    lm committed
        qint64 lastTime = -1;
    
    pixhawk's avatar
    pixhawk committed
        // Create lines
        QStringList* outLines = new QStringList();
    
        for (int i = 0; i < times.length(); i++) {
    
    lm's avatar
    lm committed
            // Cast to signed on purpose, 64 bit timestamp still long enough
    
            if (static_cast<qint64>(times.at(i)) != lastTime) {
    
    lm's avatar
    lm committed
                outLines->append(QString("%1").arg(times.at(i)) + separator + spacer);
                lastTime = static_cast<qint64>(times.at(i));
                finalTimes.append(times.at(i));
                //qDebug() << "ADDED:" << outLines->last();
            }
    
    pixhawk's avatar
    pixhawk committed
        }
    
    
    lm's avatar
    lm committed
        dataLines = finalTimes.length();
    
        emit logProcessingStatusChanged(tr("Log compressor: Now processing %1 log lines").arg(finalTimes.length()));
    
    pixhawk's avatar
    pixhawk committed
        // Fill in the values for all keys
        file.reset();
        QTextStream data(&file);
    
        int linecounter = 0;
    
    lm's avatar
    lm committed
        bool failed = false;
    
    
        while (!data.atEnd()) {
    
            linecounter++;
            currentDataLine = linecounter;
    
    pixhawk's avatar
    pixhawk committed
            QString line = data.readLine();
            QStringList parts = line.split(separator);
            // Get time
    
    lm's avatar
    lm committed
            quint64 time = static_cast<QString>(parts.first()).toLongLong(&ok);
    
    pixhawk's avatar
    pixhawk committed
            QString field = parts.at(2);
            QString value = parts.at(3);
    
            // Enforce NaN if no value is present
    
            if (value.length() == 0 || value == "" || value == " " || value == "\t" || value == "\n") {
    
    pixhawk's avatar
    pixhawk committed
            // Get matching output line
    
            // Constraining the search area might result in not finding a key,
            // but it significantly reduces the time needed for the search
            // setting a window of 1000 entries means that a 1 Hz data point
            // can still be located
    
    lm's avatar
    lm committed
            quint64 offsetLimit = 100;
    
    lm's avatar
    lm committed
            qint64 index = -1;
            failed = false;
    
            // Search the index until it is valid (!= -1)
            // or the start of the list has been reached (failed)
    
            while (index == -1 && !failed) {
                if (lastTimeIndex > offsetLimit) {
    
                    offset = lastTimeIndex - offsetLimit;
    
    lm's avatar
    lm committed
                index = finalTimes.indexOf(time, offset);
    
                if (index == -1) {
                    if (offset == 0) {
    
    lm's avatar
    lm committed
                        emit logProcessingStatusChanged(tr("Log compressor: Timestamp %1 not found in dataset, ignoring log line %2").arg(time).arg(linecounter));
    
    lm's avatar
    lm committed
                        qDebug() << "Completely failed finding value";
    
    lm's avatar
    lm committed
                        //continue;
                        failed = true;
    
    lm's avatar
    lm committed
                        emit logProcessingStatusChanged(tr("Log compressor: Timestamp %1 not found in dataset, restarting search.").arg(time));
                        offsetLimit*=2;
                    }
    
            if (dataLines > 100) if (index % (dataLines/100) == 0) emit logProcessingStatusChanged(tr("Log compressor: Processed %1% of %2 lines").arg(index/(float)dataLines*100, 0, 'f', 2).arg(dataLines));
    
    lm's avatar
    lm committed
                // When the algorithm reaches here the correct index was found
                lastTimeIndex = index;
                QString outLine = outLines->at(index);
                QStringList outParts = outLine.split(separator);
                // Replace measurement placeholder with current value
                outParts.replace(keys->indexOf(field)+1, value);
                outLine = outParts.join(separator);
                outLines->replace(index, outLine);
            }
    
    pixhawk's avatar
    pixhawk committed
        }
    
    pixhawk's avatar
    pixhawk committed
        // Add header, write out file
        file.close();
    
    
        if (outFileName == logFileName) {
    
            QFile::remove(file.fileName());
            outfile.setFileName(file.fileName());
    
        }
        if (!outfile.open(QIODevice::WriteOnly | QIODevice::Text))
    
    pixhawk's avatar
    pixhawk committed
            return;
    
        outfile.write(QString(QString("unix_timestamp") + separator + header.replace(" ", "_") + QString("\n")).toLatin1());
    
        emit logProcessingStatusChanged(tr("Log Compressor: Writing output to file %1").arg(QFileInfo(outFileName).absoluteFilePath()));
    
    pixhawk's avatar
    pixhawk committed
        //QString fileHeader = QString("unix_timestamp") + header.replace(" ", "_") + QString("\n");
    
        for (int i = 0; i < outLines->length(); i++) {
    
    pixhawk's avatar
    pixhawk committed
            //qDebug() << outLines->at(i);
    
            outfile.write(QString(outLines->at(i) + "\n").toLatin1());
    
    pixhawk's avatar
    pixhawk committed
        }
    
        currentDataLine = 0;
        dataLines = 1;
    
    pixhawk's avatar
    pixhawk committed
        delete keys;
    
    lm's avatar
    lm committed
        emit logProcessingStatusChanged(tr("Log compressor: Finished processing file: %1").arg(outfile.fileName()));
    
    lm's avatar
    lm committed
        qDebug() << "Done with logfile processing";
    
        emit finishedFile(outfile.fileName());
    
    void LogCompressor::startCompression()
    {
        start();
    }
    
    
    bool LogCompressor::isFinished()
    {
        return !running;
    }
    
    int LogCompressor::getCurrentLine()
    {
        return currentDataLine;
    }
    
    int LogCompressor::getDataLines()
    {
        return dataLines;
    
    pixhawk's avatar
    pixhawk committed
    }