Skip to content
Snippets Groups Projects
LogCompressor.cc 8.75 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.
    
    lm's avatar
    lm committed
        
    
    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.
    
    lm's avatar
    lm committed
        
    
    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/>.
    
    lm's avatar
    lm committed
        
    
    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) :
    
    pixhawk's avatar
    pixhawk committed
            logFileName(logFileName),
    
            outFileName(outFileName),
            running(true),
            currentDataLine(0),
            dataLines(1),
    
    pixhawk's avatar
    pixhawk committed
            uasid(uasid)
    {
    }
    
    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;
    
    lm's avatar
    lm committed
        
    
    lm's avatar
    lm committed
        if (!file.exists() || !file.open(QIODevice::ReadOnly | QIODevice::Text))
        {
            qDebug() << "LOG COMPRESSOR: INPUT FILE DOES NOT EXIST";
            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
        }
    
    lm's avatar
    lm committed
        
    
            // Check if file is writeable
    
    lm's avatar
    lm committed
            if (outFileName == ""/* || !QFileInfo(outfile).isWritable()*/)
    
    lm's avatar
    lm committed
                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()));
    
    lm's avatar
    lm committed
        
    
    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();
    
    lm's avatar
    lm committed
        
    
    pixhawk's avatar
    pixhawk committed
        QString header = "";
        QString spacer = "";
        for (int i = 0; i < keys->length(); i++)
        {
            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);
            if (ok)
    
    pixhawk's avatar
    pixhawk committed
            {
    
    lm's avatar
    lm committed
                times.append(time);
    
    pixhawk's avatar
    pixhawk committed
            }
        }
    
    lm's avatar
    lm committed
        
        qSort(times);
        
        qint64 lastTime = -1;
        
    
    pixhawk's avatar
    pixhawk committed
        // Create lines
        QStringList* outLines = new QStringList();
    
    lm's avatar
    lm committed
        for (int i = 0; i < times.length(); i++)
    
    pixhawk's avatar
    pixhawk committed
        {
    
    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;
        
    
            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")
            {
                value = "NaN";
            }
    
    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;
                }
                else
                {
                    offset = 0;
                }
    
    lm's avatar
    lm committed
                
                index = finalTimes.indexOf(time, offset);
    
    lm's avatar
    lm committed
                    if (offset == 0)
                    {
                        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;
                    }
                    else
                    {
                        emit logProcessingStatusChanged(tr("Log compressor: Timestamp %1 not found in dataset, restarting search.").arg(time));
                        offsetLimit*=2;
                    }
    
    lm's avatar
    lm committed
            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
            if (!failed)
            {
                // 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 == "")
        {
            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());
    
    pixhawk's avatar
    pixhawk committed
        //QString fileHeader = QString("unix_timestamp") + header.replace(" ", "_") + QString("\n");
    
    pixhawk's avatar
    pixhawk committed
        for (int i = 0; i < outLines->length(); i++)
        {
            //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
    }