LogCompressor.cc 8.59 KB
Newer Older
lm's avatar
lm committed
1
/*===================================================================
pixhawk's avatar
pixhawk committed
2 3 4 5 6 7 8 9 10 11
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.
12

pixhawk's avatar
pixhawk committed
13 14 15 16
    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.
17

pixhawk's avatar
pixhawk committed
18 19
    You should have received a copy of the GNU General Public License
    along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
20

pixhawk's avatar
pixhawk committed
21 22 23 24 25 26 27 28 29
======================================================================*/

/**
 * @file
 *   @brief Implementation of class LogCompressor
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

pixhawk's avatar
pixhawk committed
30 31 32
#include <QFile>
#include <QTextStream>
#include <QStringList>
33
#include <QFileInfo>
lm's avatar
lm committed
34
#include <QList>
pixhawk's avatar
pixhawk committed
35 36 37 38
#include "LogCompressor.h"

#include <QDebug>

39 40 41
/**
 * It will only get active upon calling startCompression()
 */
42
LogCompressor::LogCompressor(QString logFileName, QString outFileName, int uasid) :
43 44 45 46 47 48
    logFileName(logFileName),
    outFileName(outFileName),
    running(true),
    currentDataLine(0),
    dataLines(1),
    uasid(uasid)
pixhawk's avatar
pixhawk committed
49 50 51 52 53 54 55 56
{
}

void LogCompressor::run()
{
    QString separator = "\t";
    QString fileName = logFileName;
    QFile file(fileName);
57
    QFile outfile(outFileName);
pixhawk's avatar
pixhawk committed
58
    QStringList* keys = new QStringList();
lm's avatar
lm committed
59 60
    QList<quint64> times;// = new QList<quint64>();
    QList<quint64> finalTimes;
lm's avatar
lm committed
61 62

    qDebug() << "LOG COMPRESSOR: Starting" << fileName;
63 64

    if (!file.exists() || !file.open(QIODevice::ReadOnly | QIODevice::Text)) {
65
        //qDebug() << "LOG COMPRESSOR: INPUT FILE DOES NOT EXIST";
lm's avatar
lm committed
66
        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
67
        return;
lm's avatar
lm committed
68
    }
69 70 71 72 73 74 75 76

    // 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
77 78
    // Find all keys
    QTextStream in(&file);
lm's avatar
lm committed
79 80 81 82 83 84 85 86 87

    // 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
88 89 90 91 92
        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
93
        keyCounter++;
pixhawk's avatar
pixhawk committed
94 95
    }
    keys->sort();
96

pixhawk's avatar
pixhawk committed
97 98
    QString header = "";
    QString spacer = "";
99
    for (int i = 0; i < keys->length(); i++) {
pixhawk's avatar
pixhawk committed
100 101 102 103
        header += keys->at(i) + separator;
        spacer += " " + separator;
    }

lm's avatar
lm committed
104
    emit logProcessingStatusChanged(tr("Log compressor: Dataset contains dimension: ") + header);
105

106
    //qDebug() << header;
107

108
    //qDebug() << "NOW READING TIMES";
109

pixhawk's avatar
pixhawk committed
110 111 112 113 114
    // Find all times
    //in.reset();
    file.reset();
    in.reset();
    in.resetStatus();
lm's avatar
lm committed
115
    bool ok;
116
    while (!in.atEnd()) {
pixhawk's avatar
pixhawk committed
117 118
        QString line = in.readLine();
        // Accumulate map of keys
lm's avatar
lm committed
119 120
        // Data field name is at position 2b
        quint64 time = static_cast<QString>(line.split(separator).at(0)).toLongLong(&ok);
121
        if (ok) {
lm's avatar
lm committed
122
            times.append(time);
pixhawk's avatar
pixhawk committed
123 124
        }
    }
125

lm's avatar
lm committed
126
    qSort(times);
127

lm's avatar
lm committed
128
    qint64 lastTime = -1;
129

pixhawk's avatar
pixhawk committed
130 131
    // Create lines
    QStringList* outLines = new QStringList();
132
    for (int i = 0; i < times.length(); i++) {
lm's avatar
lm committed
133
        // Cast to signed on purpose, 64 bit timestamp still long enough
134
        if (static_cast<qint64>(times.at(i)) != lastTime) {
lm's avatar
lm committed
135 136 137 138 139
            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
140 141
    }

lm's avatar
lm committed
142 143 144
    dataLines = finalTimes.length();

    emit logProcessingStatusChanged(tr("Log compressor: Now processing %1 log lines").arg(finalTimes.length()));
145

pixhawk's avatar
pixhawk committed
146 147 148
    // Fill in the values for all keys
    file.reset();
    QTextStream data(&file);
149
    int linecounter = 0;
150
    quint64 lastTimeIndex = 0;
lm's avatar
lm committed
151
    bool failed = false;
152 153

    while (!data.atEnd()) {
154 155
        linecounter++;
        currentDataLine = linecounter;
pixhawk's avatar
pixhawk committed
156 157 158
        QString line = data.readLine();
        QStringList parts = line.split(separator);
        // Get time
lm's avatar
lm committed
159
        quint64 time = static_cast<QString>(parts.first()).toLongLong(&ok);
pixhawk's avatar
pixhawk committed
160 161
        QString field = parts.at(2);
        QString value = parts.at(3);
162
        // Enforce NaN if no value is present
163
        if (value.length() == 0 || value == "" || value == " " || value == "\t" || value == "\n") {
164 165
            value = "NaN";
        }
pixhawk's avatar
pixhawk committed
166
        // Get matching output line
167

168 169 170 171
        // 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
172
        quint64 offsetLimit = 100;
173
        quint64 offset;
lm's avatar
lm committed
174 175 176 177 178
        qint64 index = -1;
        failed = false;

        // Search the index until it is valid (!= -1)
        // or the start of the list has been reached (failed)
179 180
        while (index == -1 && !failed) {
            if (lastTimeIndex > offsetLimit) {
181
                offset = lastTimeIndex - offsetLimit;
182
            } else {
183 184
                offset = 0;
            }
185

lm's avatar
lm committed
186
            index = finalTimes.indexOf(time, offset);
187 188
            if (index == -1) {
                if (offset == 0) {
lm's avatar
lm committed
189
                    emit logProcessingStatusChanged(tr("Log compressor: Timestamp %1 not found in dataset, ignoring log line %2").arg(time).arg(linecounter));
lm's avatar
lm committed
190
                    qDebug() << "Completely failed finding value";
lm's avatar
lm committed
191 192
                    //continue;
                    failed = true;
193
                } else {
lm's avatar
lm committed
194 195 196
                    emit logProcessingStatusChanged(tr("Log compressor: Timestamp %1 not found in dataset, restarting search.").arg(time));
                    offsetLimit*=2;
                }
197 198
            }
        }
lm's avatar
lm committed
199

200
        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));
201 202

        if (!failed) {
lm's avatar
lm committed
203 204 205 206 207 208 209 210 211
            // 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
212
    }
213 214


pixhawk's avatar
pixhawk committed
215 216
    // Add header, write out file
    file.close();
217 218

    if (outFileName == logFileName) {
219 220
        QFile::remove(file.fileName());
        outfile.setFileName(file.fileName());
221

222 223
    }
    if (!outfile.open(QIODevice::WriteOnly | QIODevice::Text))
pixhawk's avatar
pixhawk committed
224
        return;
225
    outfile.write(QString(QString("unix_timestamp") + separator + header.replace(" ", "_") + QString("\n")).toLatin1());
226
    emit logProcessingStatusChanged(tr("Log Compressor: Writing output to file %1").arg(QFileInfo(outFileName).absoluteFilePath()));
pixhawk's avatar
pixhawk committed
227
    //QString fileHeader = QString("unix_timestamp") + header.replace(" ", "_") + QString("\n");
228

229
    // File output
230
    for (int i = 0; i < outLines->length(); i++) {
pixhawk's avatar
pixhawk committed
231
        //qDebug() << outLines->at(i);
232
        outfile.write(QString(outLines->at(i) + "\n").toLatin1());
233

pixhawk's avatar
pixhawk committed
234
    }
235

236 237
    currentDataLine = 0;
    dataLines = 1;
pixhawk's avatar
pixhawk committed
238
    delete keys;
lm's avatar
lm committed
239
    emit logProcessingStatusChanged(tr("Log compressor: Finished processing file: %1").arg(outfile.fileName()));
lm's avatar
lm committed
240
    qDebug() << "Done with logfile processing";
241
    emit finishedFile(outfile.fileName());
242 243 244
    running = false;
}

245 246 247 248 249
void LogCompressor::startCompression()
{
    start();
}

250 251 252 253 254 255 256 257 258 259 260 261 262
bool LogCompressor::isFinished()
{
    return !running;
}

int LogCompressor::getCurrentLine()
{
    return currentDataLine;
}

int LogCompressor::getDataLines()
{
    return dataLines;
pixhawk's avatar
pixhawk committed
263
}