Package genqa

Source Code of genqa.ExportOnServerVerifier

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package genqa;

import genqa.procedures.SampleRecord;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Queue;
import java.util.Random;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

import org.spearce_voltpatches.jgit.transport.OpenSshConfig;
import org.voltcore.utils.Pair;
import org.voltdb.VoltDB;
import org.voltdb.common.Constants;
import org.voltdb.iv2.TxnEgo;
import org.voltdb.types.TimestampType;

import com.google_voltpatches.common.base.Throwables;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;

public class ExportOnServerVerifier {

    public static long FILE_TIMEOUT_MS = 5 * 60 * 1000; // 5 mins

    public static long VALIDATION_REPORT_INTERVAL = 50000;

    private final static String TRACKER_FILENAME = "__active_tracker";
    private final static Pattern EXPORT_FILENAME_REGEXP =
            Pattern.compile("\\A[^-]+-(\\d+)-[^-]+-(\\d+)\\.csv\\z");

    private final JSch m_jsch = new JSch();
    private final List<RemoteHost> m_hosts = new ArrayList<RemoteHost>();

    private static class RemoteHost {
        @SuppressWarnings("unused")
        Session session;
        ChannelSftp channel;
        String path;
        boolean activeSeen = false;
        boolean fileSeen = false;
    }

    public static class ValidationErr extends Exception {
        private static final long serialVersionUID = 1L;
        final String msg;
        final Object value;
        final Object expected;

        ValidationErr(String msg, Object value, Object expected) {
            this.msg = msg;
            this.value = value;
            this.expected = expected;
        }

        public ValidationErr(String string) {
            this.msg = string;
            this.value = "[not provided]";
            this.expected = "[not provided]";
        }
        @Override
        public String toString() {
            return msg + " Value: " + value + " Expected: " + expected;
        }
    }

    ExportOnServerVerifier()
    {
    }

    int splitClientTrace(String trace, long [] splat)
    {
        if (trace == null || splat == null || splat.length == 0) return 0;

        int columnCount = 0;
        int cursor = 0;
        int columnPos = trace.indexOf(':', cursor);

        try {
            while (columnPos >= 0 && splat.length > columnCount+1) {
                splat[columnCount] = Long.parseLong(trace.substring(cursor, columnPos));
                cursor = columnPos + 1;
                columnCount = columnCount + 1;
                columnPos = trace.indexOf(':', cursor);
            }
            if (cursor < trace.length()) {
                columnPos = columnPos < 0 ? trace.length() : columnPos;
                splat[columnCount] = Long.parseLong(trace.substring(cursor, columnPos));
            } else {
                columnCount = columnCount - 1;
            }
        } catch (NumberFormatException nfex) {
            return 0;
        } catch (IndexOutOfBoundsException ioobex) {
            return -1;
        }

        return columnCount+1;
    }

    private long unquoteLong(String quoted)
    {
        StringBuilder sb = new StringBuilder(quoted);
        int i = 0;
        while (i < sb.length()) {
            if (sb.charAt(i) == '"') sb.deleteCharAt(i);
            else ++i;
        }
        return Long.parseLong(sb.toString().trim());
    }

    int splitCSV(String csv, long [] splat)
    {
        if (csv == null || splat == null || splat.length == 0) return 0;

        int columnCount = 0;
        int cursor = 0;
        int columnPos = csv.indexOf(',', cursor);

        try {
            while (columnPos >= 0 && splat.length > columnCount+1) {
                splat[columnCount] = unquoteLong(csv.substring(cursor, columnPos));
                cursor = columnPos + 1;
                columnCount = columnCount + 1;
                columnPos = csv.indexOf(',', cursor);
            }
            if (cursor < csv.length()) {
                columnPos = columnPos < 0 ? csv.length() : columnPos;
                splat[columnCount] = unquoteLong(csv.substring(cursor, columnPos));
            } else {
                columnCount = columnCount - 1;
            }
        } catch (NumberFormatException nfex) {
            return 0;
        } catch (IndexOutOfBoundsException ioobex) {
            return -1;
        }

        return columnCount+1;
    }

    boolean verifySetup( String [] args) throws Exception
    {
        String remoteHosts[] = args[0].split(",");
        final String homeDir = System.getProperty("user.home");
        final String sshDir = homeDir + File.separator + ".ssh";
        final String sshConfigPath = sshDir + File.separator + "config";

        //Oh yes...
        loadAllPrivateKeys( new File(sshDir));

        OpenSshConfig sshConfig = null;
        if (new File(sshConfigPath).exists()) {
            sshConfig = new OpenSshConfig(new File(sshConfigPath));
        }

        final String defaultKnownHosts = sshDir + "/known_hosts";
        if (new File(defaultKnownHosts).exists()) {
            m_jsch.setKnownHosts(defaultKnownHosts);
        }

        for (String hostString : remoteHosts) {
            String split[] = hostString.split(":");
            String host = split[0];

            RemoteHost rh = new RemoteHost();
            rh.path = split[1];

            String user = System.getProperty("user.name") ;
            int port = 22;
            File identityFile = null;
            String configHost = host;
            if (sshConfig != null) {
                OpenSshConfig.Host hostConfig = sshConfig.lookup(host);
                if (hostConfig.getUser() != null) {
                    user = hostConfig.getUser();
                }
                if (hostConfig.getPort() != -1) {
                    port = hostConfig.getPort();
                }
                if (hostConfig.getIdentityFile() != null) {
                    identityFile = hostConfig.getIdentityFile();
                }
                if (hostConfig.getHostName() != null) {
                    configHost = hostConfig.getHostName();
                }
            }

            Session session = null;
            if (identityFile != null) {
                JSch jsch = new JSch();
                jsch.addIdentity(identityFile.getAbsolutePath());
                session = jsch.getSession(user, configHost, port);
            } else {
                session = m_jsch.getSession( user, configHost, port);
            }

            rh.session = session;
            session.setConfig("StrictHostKeyChecking", "no");
            session.setDaemonThread(true);
            session.connect();
            final ChannelSftp channel = (ChannelSftp)session.openChannel("sftp");
            rh.channel = channel;
            channel.connect();
            touchActiveTracker(rh);

            m_hosts.add(rh);
        }

        m_partitions = Integer.parseInt(args[1]);

        for (int i = 0; i < m_partitions; i++)
        {
            m_rowTxnIds.put(i, new TreeMap<Long,Long>());
            m_maxPartTxId.put(i, Long.MIN_VALUE);
            m_checkedUpTo.put(i,0);
            m_readUpTo.put(i, new AtomicLong(0));
        }

        m_clientPath = new File(args[2]);
        if (!m_clientPath.exists() || !m_clientPath.isDirectory())
        {
            if (!m_clientPath.mkdir()) {
                throw new IOException("Issue with transaction ID path");
            }
        }

        for (RemoteHost rh : m_hosts) {
            boolean existsOrIsDir = true;
            try {
                SftpATTRS stat = rh.channel.stat(rh.path);
                if (!stat.isDir()) {
                    existsOrIsDir = false;
                }
            } catch (SftpException e) {
                if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                    existsOrIsDir = false;
                } else {
                    Throwables.propagate(e);
                }
            }
            if (!existsOrIsDir) {
                rh.channel.mkdir(rh.path);
            }
        }

        boolean skinny = false;
        if (args.length > 3 && args[3] != null && !args[3].trim().isEmpty()) {
            skinny = Boolean.parseBoolean(args[3].trim().toLowerCase());
        }

        return skinny;
    }

    /**
     * Verifies the fat version of the exported table. By fat it means that it contains many
     * columns of multiple types
     *
     * @throws Exception
     */
    void verifyFat() throws Exception
    {

        long ttlVerified = 0;

        Pair<BufferedReader, Runnable> csvPair = openNextExportFile();
        BufferedReader csv = csvPair.getFirst();

        String exportLine;
        String[] row;
        boolean quit = false;
        boolean more_rows = true;
        boolean more_txnids = true;
        ValidationErr expect_export_eof = null;
        int emptyRemovalCycles = 0;

        try (
                PrintWriter txout = gzipWriterTo("read-exported-transactions.gz");
                PrintWriter vfout = gzipWriterTo("verified-exported-transactions.gz")
        ) {

            while (!quit)
            {
                markCheckedUpTo();
                int dcount = 0;
                while ((dcount < 50000 || !more_txnids) && more_rows)
                {
                    exportLine = csv.readLine();
                    if (exportLine == null)
                    {
                        expect_export_eof = null;
                        csvPair.getSecond().run();
                        csvPair = openNextExportFile();
                        if (csvPair == null)
                        {
                            System.out.println("No more export rows");
                            more_rows = false;
                            break;
                        }
                        else
                        {
                            csv = csvPair.getFirst();
                            exportLine = csv.readLine();
                        }
                    }
                    else if (expect_export_eof != null)
                    {
                        throw expect_export_eof;
                    }

                    row = RoughCSVTokenizer.tokenize(exportLine);

                    expect_export_eof = verifyRow(row);

                    if (row.length < 8) continue; // row[6] txnId row[7] rowId

                    dcount++;
                    /*
                     * client dude has only confirmed tx id, on asynch writer exceptions we
                     * writer row id for which we don't have confirmed commit, and thus use
                     * rows' own tx id for verification
                     */
                    if (++ttlVerified % VALIDATION_REPORT_INTERVAL == 0) {
                        System.out.println("Verified " + ttlVerified + " rows.");
                    }

                    Integer partition = Integer.parseInt(row[3].trim());
                    Long rowTxnId = Long.parseLong(row[6].trim());
                    Long rowId = Long.parseLong(row[7].trim());

                    if (TxnEgo.getPartitionId(rowTxnId) != partition) {
                        System.err.println("ERROR: mismatched exported partition for txid " + rowTxnId +
                                ", tx says it belongs to " + TxnEgo.getPartitionId(rowTxnId) +
                                ", while export record says " + partition);
                        partition = TxnEgo.getPartitionId(rowTxnId);
                    }

                    txout.printf("%d:%d\n", rowTxnId, rowId);

                    Long previous = m_rowTxnIds.get(partition).put(rowTxnId,rowId);
                    if (previous != null)
                    {
                        System.out.println("WARN Duplicate TXN ID in export stream: " + rowTxnId);
                    }
                }

                System.out.println("\n!_!_! DEBUG !_!_! read " + dcount + " exported records");

                determineReadUpToCounters();
                dcount = m_clientOverFlow.size();
                processClientIdOverFlow();

                System.out.println("!_!_! DEBUG !_!_! processed " + (dcount - m_clientOverFlow.size()) + " client overflow txid records");
                System.out.println("!_!_! DEBUG !_!_! overflow size is now " + m_clientOverFlow.size());

                more_txnids = readEnoughClientRecords();
                if (!more_txnids) {
                    System.out.println("No more client txn IDs");
                }

                if (matchClientTxnIds(vfout))
                {
                    emptyRemovalCycles = 0;
                }
                else if (++emptyRemovalCycles >= 20)
                {
                    System.err.println("ERROR: 20 check cycles failed to match client tx ids with exported tx id -- bailing out");
                    dumpUnmatchedSituation();
                    System.exit(1);
                }

                printTxCountByPartition();

                if (!more_rows || !more_txnids)
                {
                    if (more_rows && ! m_clientTxnIds.isEmpty())
                    {
                        quit = false;
                    }
                    else
                    {
                        quit = true;
                    }
                }
            }
        }
        if (more_rows || more_txnids)
        {
            System.out.println("Something wasn't drained");
            System.out.println("client txns remaining: " + m_clientTxnIds.size());
            System.out.println("Export rows remaining: ");
            int total = 0;
            for (int i = 0; i < m_partitions; i++)
            {
                total += m_rowTxnIds.get(i).size();
                System.out.println("\tpartition: " + i + ", size: " + m_rowTxnIds.get(i).size());
            }
            if (total != 0 && m_clientTxnIds.size() != 0)
            {
                System.out.println("THIS IS A REAL ERROR?!");
            }
        }
    }

    /**
     * It attempts to read enough client records to match the records read
     * from the exported file.
     *
     * @return true is there are more client to be read, or false if not
     * @throws IOException
     * @throws ValidationErr
     */
    private boolean readEnoughClientRecords() throws IOException, ValidationErr {
        final int maxOverFlow = m_clientIndexes.size() * 1000;
        final int overFlowSize = m_clientOverFlow.size();

        int dcount = 0;
        long [] rec = new long [3];

        for (int partId: m_readUpTo.keySet()) {

            int upTo = (int)m_readUpTo.get(partId).get();
            if (upTo > 0 && overFlowSize < maxOverFlow) {
                upTo += 1000;
            }

            boolean keepReadingBecauseItIsTheLastFile = false;

            INNER: for( int i = 0; i < upTo || keepReadingBecauseItIsTheLastFile; ++i) {

                String trace = readNextClientFileLine(partId);

                keepReadingBecauseItIsTheLastFile =
                        Boolean.FALSE.equals(m_clientComplete.get(partId));

                if ( trace == null) {
                    m_readUpTo.get(partId).set(0);
                    System.out.println("No more client txn IDs for partition id " + partId);
                    break INNER;
                }

                int recColumns = splitClientTrace(trace, rec);

                if (recColumns == rec.length) {
                    long rowid = rec[0];
                    long txid = rec[1];
                    long ts = rec[2];

                    if (txid >= 0) {
                        m_clientTxnIds.put(txid,ts);
                        countDownReadUpTo(txid);
                    } else {
                        m_clientTxnIdOrphans.add(rowid);
                    }
                    dcount++;
                } else if (trace != null) {
                    System.out.println("WARN read malformed trace " + trace);
                }
            }
        }
        System.out.println("!_!_! DEBUG !_!_! read " + dcount + " client txid records");

        // read the client records that do not have associated tx (invocations with failed status codes)
        readOrphanedClientRecords();

        return haveNotReadAllClientRecords();
    }

    /**
     * tests whether or not all client records have been read
     * @return true is there are more client to be read, or false if not
     */
    private boolean haveNotReadAllClientRecords() {
        int doneCount = 0;
        for (Map.Entry<Integer, Boolean> e: m_clientComplete.entrySet()) {
            if ( Boolean.TRUE.equals(e.getValue())) ++doneCount;
        }
        return doneCount == 0 || doneCount != m_clientIndexes.size();
    }

    /**
     * Read the client records that do not have associated tx (invocations with failed status codes).
     * Delete the client generated files once they are read
     * @throws IOException
     */
    private void readOrphanedClientRecords() throws IOException {
        File baseDH = new File(m_clientPath, "-1");
        if (   !baseDH.exists()
            || !baseDH.isDirectory()
            || !baseDH.canRead()
            || !baseDH.canExecute()
            || !baseDH.canWrite()) return;

        File [] files = baseDH.listFiles(clientFileNameFilter);
        if (files.length == 0) return;

        Arrays.sort(files, clientFileNameComparator);

        long [] rec = new long [3];
        for (File f: files) {
            try (BufferedReader in = new BufferedReader(
                    new InputStreamReader(new FileInputStream(f)))) {
                String line = in.readLine();
                while (line != null) {
                    int recColumns = splitClientTrace(line, rec);
                    if (recColumns == rec.length) {
                        long rowid = rec[0];
                        long txid = rec[1];

                        if (txid < 0) {
                            m_clientTxnIdOrphans.add(rowid);
                        }
                    } else if (line != null) {
                        System.out.println("WARN read malformed trace " + line + " in " + f);
                    }
                    line = in.readLine();
                }
            } finally {
                f.delete();
            }
        }
    }

    private PrintWriter gzipWriterTo( String fileName) throws IOException
    {
        File fh = new File(fileName);
        if (fh.exists() && fh.isFile() && fh.canRead() && fh.canWrite())
        {
            fh.delete();
        }
        PrintWriter out =
                new PrintWriter(
                        new OutputStreamWriter(
                                new GZIPOutputStream(
                                        new FileOutputStream(fh), 16384)));
        return out;
    }

    private PrintWriter gzipWriterTo( File fh) throws IOException
    {
        if (fh.exists() && fh.isFile() && fh.canRead() && fh.canWrite())
        {
            fh.delete();
        }
        PrintWriter out =
                new PrintWriter(
                        new OutputStreamWriter(
                                new GZIPOutputStream(
                                        new FileOutputStream(fh), 16384)));
        return out;
    }

    /**
     * Verifies the skinny version of the exported table. By skinny it means that it contains the
     * bare minimum of columns (just enough for the purpose of transaction verification)
     *
     * @throws Exception
     */
    void verifySkinny() throws Exception
    {

        long ttlVerified = 0;

        //checkForMoreExportFiles();
        Pair<BufferedReader, Runnable> csvPair = openNextExportFile();
        BufferedReader csv = csvPair.getFirst();

        //checkForMoreClientFiles();
        String row;
        long [] rowValues = new long [8]; // [6] txnId [7] rowId
        boolean quit = false;
        boolean more_rows = true;
        boolean more_txnids = true;
        boolean expect_export_eof = false;
        int emptyRemovalCycles = 0;

        try (
                PrintWriter txout = gzipWriterTo("read-exported-transactions.gz");
                PrintWriter vfout = gzipWriterTo("verified-exported-transactions.gz")
        ) {
            while (!quit)
            {
                markCheckedUpTo();
                int dcount = 0;
                while ((dcount < 50000 || !more_txnids) && more_rows)
                {
                    row = csv.readLine();
                    if (row == null)
                    {
                        expect_export_eof = false;
                        csvPair.getSecond().run();
                        csvPair = openNextExportFile();
                        if (csvPair == null)
                        {
                            System.out.println("No more export rows");
                            more_rows = false;
                            break;
                        }
                        else
                        {
                            csv = csvPair.getFirst();
                            row = csv.readLine();
                        }
                    }
                    else if (expect_export_eof)
                    {
                        throw new ValidationErr("previously logged row had unexpected number of columns");
                    }

                    int columnCount = splitCSV(row, rowValues);
                    expect_export_eof = columnCount < rowValues.length;
                    dcount++;

                    if (expect_export_eof) {
                        System.err.println(
                                "ERROR: Unexpected number of columns for the following row:\n\t" +
                                 row
                                 );
                        continue;
                    }
                    /*
                     * client dude has only confirmed tx id, on asynch writer exceptions we
                     * writer row id for which we don't have confirmed commit, and thus use
                     * rows' own tx id for verification
                     */
                    if (++ttlVerified % VALIDATION_REPORT_INTERVAL == 0) {
                        System.out.println("Verified " + ttlVerified + " rows.");
                    }

                    int partition =  Integer.MAX_VALUE;
                    if (rowValues[3] < Integer.MAX_VALUE)
                    {
                        partition = (int)rowValues[3];
                    }
                    long rowTxnId = rowValues[6];
                    long rowId = rowValues[7];

                    if (TxnEgo.getPartitionId(rowTxnId) != partition) {
                        System.err.println("ERROR: mismatched exported partition for txid " + rowTxnId +
                                ", tx says it belongs to " + TxnEgo.getPartitionId(rowTxnId) +
                                ", while export record says " + partition);
                        partition = TxnEgo.getPartitionId(rowTxnId);
                    }

                    txout.printf("%d:%d\n", rowTxnId, rowId);

                    if (! m_rowTxnIds.containsKey(partition)) {
                        System.err.println("ERROR: unknow partition " + partition + " in txnid " + rowTxnId);
                        continue;
                    }

                    Long previous = m_rowTxnIds.get(partition).put(rowTxnId,rowId);
                    if (previous != null)
                    {
                        System.out.println("WARN Duplicate TXN ID in export stream: " + rowTxnId);
                    }
                    else
                    {
                        //System.out.println("Added txnId: " + rowTxnId + " to outstanding export");
                    }
                }

                System.out.println("\n!_!_! DEBUG !_!_! read " + dcount + " exported records");

                determineReadUpToCounters();
                dcount = m_clientOverFlow.size();
                processClientIdOverFlow();

                System.out.println("!_!_! DEBUG !_!_! processed " + (dcount - m_clientOverFlow.size()) + " client overflow txid records");
                System.out.println("!_!_! DEBUG !_!_! overflow size is now " + m_clientOverFlow.size());

                more_txnids = readEnoughClientRecords();
                if (!more_txnids) {
                    System.out.println("No more client txn IDs");
                }

                if (matchClientTxnIds(vfout))
                {
                    emptyRemovalCycles = 0;
                }
                else if (++emptyRemovalCycles >= 20)
                {
                    System.err.println("ERROR: 20 check cycles failed to match client tx ids with exported tx id -- bailing out");
                    dumpUnmatchedSituation();
                    System.exit(1);
                }

                printTxCountByPartition();

                if (!more_rows || !more_txnids)
                {
                    if (more_rows && ! m_clientTxnIds.isEmpty())
                    {
                        quit = false;
                    }
                    else
                    {
                        quit = true;
                    }
                }
            }
        }

    }

    private void printTxCountByPartition() {
        StringBuilder sb = new StringBuilder(512).append("partition TxCounts: ");
        int cnt = 0;
        for (Map<Long,Long> part: m_rowTxnIds.values()) {
            if( cnt++ > 0) sb.append(", ");
            sb.append(part.size());
        }
        System.out.println("\n==================================================================");
        System.out.println(sb.toString());
        System.out.println("client tx id list size is " + m_clientTxnIds.size());
        int [] txByPart    = new int [m_partitions]; Arrays.fill(txByPart, 0);
        int [] staleByPart = new int [m_partitions]; Arrays.fill(staleByPart, 0);
        for( Long tx: m_clientTxnIds.keySet())
        {
            int partid = TxnEgo.getPartitionId(tx);
            ++txByPart[partid];
            if (tx < m_maxPartTxId.get(partid)) ++staleByPart[partid];
        }
        sb.delete(0, sb.length()).append(" -- ");
        for (int i = 0; i < m_partitions; ++i)
        {
            if (i>0) sb.append(", ");
            sb.append(txByPart[i]).append("[").append(staleByPart[i]).append("]");
        }
        System.out.println(sb.toString());
        System.out.println("overflow txid id list size is " + m_clientOverFlow.size());
        Arrays.fill(txByPart, 0);
        Arrays.fill(staleByPart, 0);
        for( Long tx: m_clientOverFlow)
        {
            int partid = TxnEgo.getPartitionId(tx);
            ++txByPart[partid];
            if (tx < m_maxPartTxId.get(partid)) ++staleByPart[partid];
        }
        sb.delete(0, sb.length()).append(" -- ");
        for (int i = 0; i < m_partitions; ++i)
        {
            if (i>0) sb.append(", ");
            sb.append(txByPart[i]).append("[").append(staleByPart[i]).append("]");
        }
        System.out.println(sb.toString());
        System.out.println("orphaned client row id list size is " + m_clientTxnIdOrphans.size());
        System.out.println("stale client txid id list size is " + m_staleTxnIds.size());
        System.out.println("==================================================================\n");
    }

    void printClientTxIds() {
        SimpleDateFormat dfmt = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSZ");

        TreeMap<Long, Long> sortedByDate = new TreeMap<Long,Long>();
        Iterator<Map.Entry<Long, Long>> itr = m_clientTxnIds.entrySet().iterator();
        while (itr.hasNext()) {
            Map.Entry<Long, Long> entry = itr.next();
            sortedByDate.put(entry.getValue(), entry.getKey());
            itr.remove();
        }

        System.out.println("\n==================================================================");
        System.out.println("List of remaining committed but unmatched client transactions:");
        for (Map.Entry<Long,Long> entry: sortedByDate.entrySet()) {
            String ts = dfmt.format(new Date(entry.getKey()));
            long txid = entry.getValue();
            int partid = TxnEgo.getPartitionId(txid);
            System.out.println(ts+ ", partition: " + partid + ", txid: " + txid);
        }
        System.out.println("==================================================================\n");
    }

    /**
     * Adds all the discovered user ssh private keys to jSch session
     * @param file the user directory which contains the private keys
     * @throws Exception
     */
    private void loadAllPrivateKeys(File file) throws Exception {
        if (file.isDirectory()) {
            for (File f : file.listFiles()) {
                loadAllPrivateKeys(f);
            }
        } else if (file.isFile() && file.canRead()) {
            FileReader fr = new FileReader(file);
            BufferedReader br = new BufferedReader(fr);
            final String firstLine = br.readLine();
            if (firstLine != null && firstLine.contains("PRIVATE KEY")) {
                m_jsch.addIdentity(file.getAbsolutePath());
            }
        }
    }

    private void touchActiveTracker(RemoteHost rh) throws Exception
    {
        final String trackerFileName = rh.path + "/" + TRACKER_FILENAME;
        final ByteArrayInputStream bis = new ByteArrayInputStream(
                new String("__DUMMY__").getBytes(Constants.UTF8ENCODING)
                );
        rh.channel.put(bis,trackerFileName);
    }

    @SuppressWarnings("unchecked")
    private void checkForMoreFilesRemote(Comparator<String> comparator) throws Exception
    {
        int onDoneRetries = 6;
        long start_time = System.currentTimeMillis();
        while (m_exportFiles.isEmpty())
        {
            /*
             * Collect the list of remote files at each node
             * Sort the list from each node
             */
            int activeFound = 0;
            List<Pair<ChannelSftp, List<String>>> pathsFromAllNodes = new ArrayList<Pair<ChannelSftp, List<String>>>();
            for (RemoteHost rh : m_hosts) {

                Vector<LsEntry> files = rh.channel.ls(rh.path);
                List<String> paths = new ArrayList<String>();

                final int trackerModifyTime = rh.channel.stat(rh.path + "/" + TRACKER_FILENAME).getMTime();

                boolean activeInRemote = false;
                boolean filesInRemote = false;

                for (LsEntry entry : files) {
                    activeInRemote = activeInRemote || entry.getFilename().trim().toLowerCase().startsWith("active");
                    filesInRemote = filesInRemote || entry.getFilename().trim().toLowerCase().startsWith("active");

                    if (    !entry.getFilename().equals(".") &&
                            !entry.getFilename().equals("..") &&
                            !entry.getAttrs().isDir())
                    {
                        final String entryFileName = rh.path + "/" + entry.getFilename();
                        final int entryModifyTime = entry.getAttrs().getMTime();

                        if (!entry.getFilename().contains("active"))
                        {
                            Matcher mtc = EXPORT_FILENAME_REGEXP.matcher(entry.getFilename());
                            if (mtc.matches()) {
                                paths.add(entryFileName);
                                activeInRemote = activeInRemote || entryModifyTime > trackerModifyTime;
                                filesInRemote = true;
                            } else {
                                System.err.println(
                                        "ERROR: " + entryFileName +
                                        " does not match expected export file name pattern");
                            }
                        }
                        else if (entry.getFilename().trim().toLowerCase().startsWith("active-"))
                        {
                            if ((trackerModifyTime - entryModifyTime) > 120)
                            {
                                final String renamed = rh.path + "/" + entry.getFilename().substring("active-".length());
                                rh.channel.rename(entryFileName, renamed);
                                paths.add(renamed);
                            }
                        }
                    }
                }

                touchActiveTracker(rh);

                rh.activeSeen = rh.activeSeen || activeInRemote;
                rh.fileSeen = rh.fileSeen || filesInRemote;

                if( activeInRemote) activeFound++;

                Collections.sort(paths, comparator);
                if (!paths.isEmpty()) pathsFromAllNodes.add(Pair.of(rh.channel, paths));
            }

            if (!m_clientComplete.isEmpty()) {
                printExportFileSituation(pathsFromAllNodes, activeFound);
            }

            if( pathsFromAllNodes.isEmpty() && activeFound == 0 && allActiveSeen())
            {
                if (--onDoneRetries <= 0) return;
                Thread.sleep(5000);
            }

            // add them to m_exportFiles as ordered by the comparator
            TreeMap<String, Pair<ChannelSftp,String>> hadPaths =
                    new TreeMap<String, Pair<ChannelSftp, String>>(comparator);

            for (Pair<ChannelSftp, List<String>> p : pathsFromAllNodes)
            {
                final ChannelSftp c = p.getFirst();
                for (String path: p.getSecond())
                {
                    hadPaths.put( path, Pair.of(c, path));
                }
            }

            boolean hadOne = !hadPaths.isEmpty();

            Iterator<Map.Entry<String, Pair<ChannelSftp, String>>> itr =
                    hadPaths.entrySet().iterator();

            while (itr.hasNext())
            {
                Map.Entry<String, Pair<ChannelSftp, String>> entry = itr.next();
                m_exportFiles.offer(entry.getValue());
                itr.remove();
            }

            long now = System.currentTimeMillis();
            if ((now - start_time) > FILE_TIMEOUT_MS)
            {
                throw new ValidationErr("Timed out waiting on new files.\n" +
                        "This indicates a mismatch in the transaction streams between the client logs and the export data or the death of something important.",
                        null, null);
            }
            else if (!hadOne)
            {
                Thread.sleep(1200);
            }
        }
    }

    /**
     * In situations where enough exported records are read without client transaction
     * matches, this procedure prints for each partition which are its recorded but unmatched
     * client and exported transaction ids
     */
    void dumpUnmatchedSituation() {
        File unmatchedDH = new File("unmatched");
        unmatchedDH.mkdir();
        if (   !unmatchedDH.exists()
            || !unmatchedDH.isDirectory()
            || !unmatchedDH.canRead()
            || !unmatchedDH.canExecute()
            || !unmatchedDH.canWrite()) {
            System.err.println("cannot access/create the unmatched directory");
            return;
        }

        TreeMap<Integer,File> partFHs = new TreeMap<>();

        for (int partid: m_clientIndexes.keySet()) {
            File partDH = new File(unmatchedDH,Integer.toString(partid));
            partDH.mkdir();
            partFHs.put(partid,partDH);
        }

        for (int partid: m_clientIndexes.keySet()) {

            int excnt = 0;
            int clcnt = 0;

            File clientFH = new File(partFHs.get(partid),"recorded.gz");
            File exportFH = new File(partFHs.get(partid),"exported.gz");

            try (
                    PrintWriter clout = gzipWriterTo(clientFH);
                    PrintWriter exout = gzipWriterTo(exportFH);
            ) {
                Iterator<Map.Entry<Long, Long>> itr = m_clientTxnIds.entrySet().iterator();
                while (itr.hasNext()) {
                    Map.Entry<Long, Long> e = itr.next();
                    if (TxnEgo.getPartitionId(e.getKey()) == partid) {
                        clout.printf("%d:%d\n", e.getKey(),e.getValue());
                        itr.remove();
                        ++clcnt;
                    }
                }

                itr = m_rowTxnIds.get(partid).entrySet().iterator();
                while (itr.hasNext()) {
                    Map.Entry<Long, Long> e = itr.next();
                    exout.printf("%d:%d\n", e.getKey(),e.getValue());
                    itr.remove();
                    ++excnt;
                }
            } catch (IOException e) {
                throw new RuntimeException("Failed to dump unmatched for partition " + partid, e);
            }

            if (clcnt == 0) {
                clientFH.delete();
            }
            if (excnt == 0) {
                exportFH.delete();
            }
            if (clcnt == 0 && excnt == 0) {
                partFHs.get(partid).delete();
            }
        }
    }

    /**
     * Prints out what its latest exported file listing probe contains.
     * @param pathsFromAllNodes a list of lists where the first dimension is the node, and the
     *            second is the list of probed export files found on that node
     * @param activeFound how many active export files where found id the probe
     */
    private void printExportFileSituation(List<Pair<ChannelSftp, List<String>>> pathsFromAllNodes, int activeFound) {
        System.out.println("\n================= E X P O R T  F I L E S  S I T U A T I O N ======================");
        System.out.println("On                      " + new Date());
        System.out.println("Active Found Count is   " + activeFound);
        System.out.println("All active seen flag is " + allActiveSeen());
        if (!allActiveSeen()) {
            for (RemoteHost host: m_hosts) {
                String hn = "(unknown)";
                try {
                    hn = host.channel.getSession().getHost();
                } catch (JSchException ignoreIt) {}
                if (!host.activeSeen && host.fileSeen) {
                    System.out.println("\tno activity was detected on " + hn);
                }
            }
        }
        for (Pair<ChannelSftp, List<String>> p: pathsFromAllNodes) {
            String hn = "(unknown)";
            try {
                hn = p.getFirst().getSession().getHost();
            } catch (JSchException ignoreIt) {}
            System.out.println("On "+hn+" I found the following files");
            for (String f: p.getSecond()) {
                System.out.println("\t" + f);
            }
        }
        System.out.println("==================================================================================\n");
    }


    private boolean sameFiles( File [] a, File [] b, Comparator<File> comparator) {
        if( a == null || b == null) {
            return b == a;
        } else if ( a.length != b.length) {
            return false;
        }
        Arrays.sort(a, comparator);
        Arrays.sort(b, comparator);
        return Arrays.equals(a, b);
    }

    private File[] checkForMoreFiles(File path, File[] files, FileFilter acceptor,
                                   Comparator<File> comparator) throws ValidationErr
    {
        File [] oldFiles = files;
        int emptyRetries = 50;
        long start_time = System.currentTimeMillis();

        while (sameFiles(files, oldFiles, comparator) || (files.length == 0 && emptyRetries >=0))
        {
            files = path.listFiles(acceptor);
            emptyRetries = (files.length > 0 ? 50 : emptyRetries - 1);
            m_clientlogSeen = m_clientlogSeen || files.length > 0;
            long now = System.currentTimeMillis();
            if ((now - start_time) > FILE_TIMEOUT_MS)
            {
                Arrays.sort(files, comparator);
                System.err.println("ERROR Polling for client files timed out, the current list of files is:");
                for (File f: files) {
                    System.err.println("\t"+ f);
                }

                throw new ValidationErr("Timed out waiting on new files in " + path.getName()+ ".\n" +
                                        "This indicates a mismatch in the transaction streams between the client logs and the export data or the death of something important.",
                                        null, null);
            } else {
                try {
                    Thread.sleep(1200);
                } catch (InterruptedException ignoreIt) {
                }
            }
        }
        Arrays.sort(files, comparator);
        return files;
    }

    private void checkForMoreExportFiles() throws Exception
    {
        Comparator<String> comparator = new Comparator<String>()
        {
            @Override
            public int compare(String f1, String f2)
            {
                f1 = f1.substring(f1.lastIndexOf('/') + 1);
                f2 = f2.substring(f2.lastIndexOf('/') + 1);

                Matcher m1 = EXPORT_FILENAME_REGEXP.matcher(f1);
                Matcher m2 = EXPORT_FILENAME_REGEXP.matcher(f2);

                if (m1.matches() && !m2.matches()) return -1;
                else if (m2.matches() && !m1.matches()) return 1;

                long first_ts = Long.parseLong(m1.group(2));
                long second_ts = Long.parseLong(m2.group(2));

                if (first_ts != second_ts)
                {
                    return (int)(first_ts - second_ts);
                }
                else
                {
                    long first_txnid = Long.parseLong(m1.group(1));
                    long second_txnid = Long.parseLong(m2.group(1));

                    if (first_txnid < second_txnid)
                    {
                        return -1;
                    }
                    else if (first_txnid > second_txnid)
                    {
                        return 1;
                    }
                    else
                    {
                        return 0;
                    }
                }
            }
        };

        checkForMoreFilesRemote(comparator);
        for (Pair<ChannelSftp, String> p : m_exportFiles)
        {
            System.out.println("" + p.getFirst().getSession().getHost() + " : " + p.getSecond());
        }
    }

    private static final FileFilter clientFileNameFilter = new FileFilter() {
        @Override
        public boolean accept(File pathname) {
            return pathname.getName().contains("dude") && !pathname.getName().startsWith("active-");
        }
    };

    private static final Comparator<File> clientFileNameComparator = new Comparator<File>() {
        @Override
        public int compare(File f1, File f2) {
            long first = Long.parseLong(f1.getName().split("-")[0]);
            long second = Long.parseLong(f2.getName().split("-")[0]);
            return (int)(first - second);
        }
    };

    private File[] checkForMoreClientFiles(int partId) throws ValidationErr{

        if (m_clientComplete.containsKey(partId)) return new File[0];

        File clientPath = m_clientPaths.get(partId);
        if (clientPath == null) {
            clientPath = new File(m_clientPath, Integer.toString(partId));
            if (   !clientPath.exists()
                || !clientPath.isDirectory()
                || !clientPath.canRead()
                || !clientPath.canExecute()
            ) {
                throw new ValidationErr("Cannot access directory " + clientPath);
            }
            m_clientPaths.put(partId, clientPath);
        }

        File [] clientFiles = m_clientFiles.get(partId);
        if (clientFiles == null) {
            clientFiles = new File[0];
            m_clientFiles.put(partId, clientFiles);
        }

        clientFiles = checkForMoreFiles(
                clientPath,
                clientFiles,
                clientFileNameFilter,
                clientFileNameComparator
                );

        m_clientFiles.put(partId, clientFiles);
        return clientFiles;
    }

    Pair<BufferedReader,Runnable> openNextExportFile() throws Exception
    {
        if (m_exportFiles.isEmpty())
        {
            checkForMoreExportFiles();
        }
        Pair<ChannelSftp, String> remotePair = m_exportFiles.poll();
        if (remotePair == null) return null;
        final ChannelSftp channel = remotePair.getFirst();
        final String path = remotePair.getSecond();
        System.out.println(
                "INFO export: Opening export file: " + channel.getSession().getHost() + "@" + path);
        final BufferedReader reader = new BufferedReader(
                new InputStreamReader(channel.get(path)), 4096 * 32
                );
        Runnable r = new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    reader.close();
                    channel.rm(path);
                } catch (Exception e)
                {
                    Throwables.propagate(e);
                }
            }
        };

        return Pair.of(reader,r);
    }

    private BufferedReader openNextClientFile(int partId) throws IOException, ValidationErr {

        Integer clientIndex = m_clientIndexes.get(partId);
        if (clientIndex == null) {
            clientIndex = 0;
            m_clientIndexes.put(partId, clientIndex);
        }

        File [] clientFiles = m_clientFiles.get(partId);
        if (clientFiles == null) {
            clientFiles = new File[0];
            m_clientFiles.put(partId, clientFiles);
        }

        if (clientIndex == clientFiles.length) {

            if (m_clientComplete.containsKey(partId)) return null;

            clientFiles = checkForMoreClientFiles(partId);

            if (clientFiles.length == 0) {
                m_clientComplete.put(partId,true);
                return null;
            }
            clientIndex = 0;
        }

        File clientFile = clientFiles[clientIndex];
        System.out.println(
                "INFO clientlog: Opening client file: " + clientFile.getName()
              + " for partition id " + partId
              );

        if (clientFile.getName().trim().toLowerCase().endsWith("-last")) {
            m_clientComplete.put(partId,false);
        }

        BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(clientFile)));
        m_clientIndexes.put(partId, clientIndex + 1);

        return reader;
    }

    private String readNextClientFileLine(int partId) throws IOException, ValidationErr {
        BufferedReader reader = m_clientReaders.get(partId);
        String line = null;
        do {
            if (reader == null) {

                reader = openNextClientFile(partId);

                if (reader == null) {
                    if (m_clientComplete.containsKey(partId)) {
                        m_clientComplete.put(partId, true);
                    }
                    return null;
                }

                m_clientReaders.put(partId, reader);
            }

            line = reader.readLine();
            if (line == null) {
                try { reader.close(); } catch (Exception ignoreIt) {};

                int index = m_clientIndexes.get(partId) - 1;
                m_clientFiles.get(partId)[index].delete();
                m_clientReaders.remove(partId);

                reader = null;
            }

        } while (line == null);
        return line;
    }

    private boolean allActiveSeen()
    {
        boolean seen = true;
        for (RemoteHost host: m_hosts)
        {
            seen = seen && (host.activeSeen || !host.fileSeen);
        }
        return seen;
    }

    private void determineReadUpToCounters()
    {
        for (int i = 0; i < m_partitions; ++i)
        {
            long partitionReadUpToCount =
                    m_rowTxnIds.get(i).size() - m_checkedUpTo.get(i);
            m_readUpTo.get(i).set(partitionReadUpToCount);
        }
    }

    private void markCheckedUpTo()
    {
        for (int partid = 0; partid < m_partitions; ++partid)
        {
            int mark = m_rowTxnIds.get(partid).size();
            m_checkedUpTo.put(partid, mark);
        }
    }

    private void countDownReadUpTo( long txid)
    {
        int partid = TxnEgo.getPartitionId(txid);
        if (m_readUpTo.get(partid) == null)
        {
            System.out.println("WARN: could find countdown for partition " + partid);
            return;
        }
        if (m_readUpTo.get(partid).decrementAndGet() <= 0)
        {
            m_clientOverFlow.add(txid);
        }
    }

    private boolean matchClientTxnIds(PrintWriter writer)
    {
        Collections.sort(m_clientTxnIdOrphans);

        int matchCount = 0;
        int staleCount = 0;
        int dupStaleCount = 0;

        Iterator<Long> staleitr = m_staleTxnIds.iterator();
        while (staleitr.hasNext())
        {
            long staletx = staleitr.next();
            if (m_clientTxnIds.remove(staletx) != null)
            {
                ++matchCount;
                staleitr.remove();

                if (writer != null) writer.println(staletx);

                System.out.println(" *** Resurfaced stale tx " + staletx + " found in the client tx list");
            }
        }
        for (int i = 0; i < m_partitions; ++i)
        {
            Iterator<Map.Entry<Long, Long>> txitr = m_rowTxnIds.get(i).entrySet().iterator();
            while (txitr.hasNext())
            {
                Map.Entry<Long, Long> e = txitr.next();

                if (m_clientTxnIds.remove(e.getKey()) != null)
                {
                    txitr.remove();
                    ++matchCount;

                    if (writer != null) writer.println(e.getKey());

                    m_clientOverFlow.remove(e.getKey());
                    if (e.getKey() > m_maxPartTxId.get(i))
                    {
                        m_maxPartTxId.put(i, e.getKey());
                    }
                    if (m_staleTxnIds.remove(e.getKey()))
                    {
                        System.out.println(" *** Matched stale tx " + e.getKey() + " found in the client tx list");
                    }
                }
                else if (e.getKey() <= m_maxPartTxId.get(i))
                {
                    txitr.remove();
                    if (!m_staleTxnIds.add(e.getKey()))
                    {
                        ++dupStaleCount;
                    }
                    ++staleCount;
                }
                else
                {
                    long bsidx = Collections.binarySearch(m_clientTxnIdOrphans, e.getValue());
                    if( bsidx >= 0)
                    {
                        System.out.println(" *** Found unrecorded txid " + e.getKey() + " for rowId " + e.getValue());

                        if (writer != null) writer.println(e.getKey());

                        txitr.remove();
                        m_clientTxnIdOrphans.remove(bsidx);
                        ++matchCount;
                    }
                }
            }
        }

        System.out.println("!_!_! DEBUG !_!_! *MATCHED* " + matchCount
                + " exported records, with *STALE* " + staleCount + " of which  "
                + dupStaleCount + " are duplicate"
                );

        return (matchCount + staleCount) > 0;
    }

    private void processClientIdOverFlow()
    {
        Iterator<Long> ovfitr = m_clientOverFlow.iterator();
        while (ovfitr.hasNext())
        {
            long txnId = ovfitr.next();
            int partid = TxnEgo.getPartitionId(txnId);

            if (m_readUpTo.get(partid).decrementAndGet() >= 0)
            {
                ovfitr.remove();
            }
        }
    }

    public static ValidationErr verifyRow(String[] row) throws ValidationErr
    {
        if (row.length < 29)
        {
            System.err.println("ERROR: Unexpected number of columns for the following row:\n\t Expected 29, Found: "
                    + row.length + "Row:" + Arrays.toString(row));
            return new ValidationErr("number of columns", row.length, 29);
        }

        int col = 5; // col offset is always pre-incremented.
        Long txnid = Long.parseLong(row[++col]); // col 6
        Long rowid = Long.parseLong(row[++col]); // col 7
        // matches VoltProcedure.getSeededRandomNumberGenerator()
        Random prng = new Random(txnid);
        SampleRecord valid = new SampleRecord(rowid, prng);

        // col 8
        Byte rowid_group = Byte.parseByte(row[++col]);
        if (rowid_group != valid.rowid_group)
            return error("rowid_group invalid", rowid_group, valid.rowid_group);

        // col 9
        Byte type_null_tinyint = row[++col].equals("NULL") ? null : Byte.valueOf(row[col]);
        if ( (!(type_null_tinyint == null && valid.type_null_tinyint == null)) &&
             (!type_null_tinyint.equals(valid.type_null_tinyint)) )
            return error("type_not_null_tinyint", type_null_tinyint, valid.type_null_tinyint);

        // col 10
        Byte type_not_null_tinyint = Byte.valueOf(row[++col]);
        if (!type_not_null_tinyint.equals(valid.type_not_null_tinyint))
            return error("type_not_null_tinyint", type_not_null_tinyint, valid.type_not_null_tinyint);

        // col 11
        Short type_null_smallint = row[++col].equals("NULL") ? null : Short.valueOf(row[col]);
        if ( (!(type_null_smallint == null && valid.type_null_smallint == null)) &&
             (!type_null_smallint.equals(valid.type_null_smallint)) )
            return error("type_null_smallint", type_null_smallint, valid.type_null_smallint);

        // col 12
        Short type_not_null_smallint = Short.valueOf(row[++col]);
        if (!type_not_null_smallint.equals(valid.type_not_null_smallint))
            return error("type_null_smallint", type_not_null_smallint, valid.type_not_null_smallint);

        // col 13
        Integer type_null_integer = row[++col].equals("NULL") ? null : Integer.valueOf(row[col]);
        if ( (!(type_null_integer == null && valid.type_null_integer == null)) &&
             (!type_null_integer.equals(valid.type_null_integer)) )
            return error("type_null_integer", type_null_integer, valid.type_null_integer);

        // col 14
        Integer type_not_null_integer = Integer.valueOf(row[++col]);
        if (!type_not_null_integer.equals(valid.type_not_null_integer))
            return error("type_not_null_integer", type_not_null_integer, valid.type_not_null_integer);

        // col 15
        Long type_null_bigint = row[++col].equals("NULL") ? null : Long.valueOf(row[col]);
        if ( (!(type_null_bigint == null && valid.type_null_bigint == null)) &&
             (!type_null_bigint.equals(valid.type_null_bigint)) )
            return error("type_null_bigint", type_null_bigint, valid.type_null_bigint);

        // col 16
        Long type_not_null_bigint = Long.valueOf(row[++col]);
        if (!type_not_null_bigint.equals(valid.type_not_null_bigint))
            return error("type_not_null_bigint", type_not_null_bigint, valid.type_not_null_bigint);

        // The ExportToFileClient truncates microseconds. Construct a TimestampType here
        // that also truncates microseconds.
        TimestampType type_null_timestamp;
        if (row[++col].equals("NULL")) {  // col 17
            type_null_timestamp = null;
        } else {
            TimestampType tmp = new TimestampType(row[col]);
            type_null_timestamp = new TimestampType(tmp.asApproximateJavaDate());
        }

        if ( (!(type_null_timestamp == null && valid.type_null_timestamp == null)) &&
             (!type_null_timestamp.equals(valid.type_null_timestamp)) )
        {
            System.out.println("CSV value: " + row[col]);
            System.out.println("EXP value: " + valid.type_null_timestamp.toString());
            System.out.println("ACT value: " + type_null_timestamp.toString());
            return error("type_null_timestamp", type_null_timestamp, valid.type_null_timestamp);
        }

        // col 18
        TimestampType type_not_null_timestamp = new TimestampType(row[++col]);
        if (!type_not_null_timestamp.equals(valid.type_not_null_timestamp))
            return error("type_null_timestamp", type_not_null_timestamp, valid.type_not_null_timestamp);

        // col 19
        BigDecimal type_null_decimal = row[++col].equals("NULL") ? null : new BigDecimal(row[col]);
        if ( (!(type_null_decimal == null && valid.type_null_decimal == null)) &&
             (!type_null_decimal.equals(valid.type_null_decimal)) )
            return error("type_null_decimal", type_null_decimal, valid.type_null_decimal);

        // col 20
        BigDecimal type_not_null_decimal = new BigDecimal(row[++col]);
        if (!type_not_null_decimal.equals(valid.type_not_null_decimal))
            return error("type_not_null_decimal", type_not_null_decimal, valid.type_not_null_decimal);

        // col 21
        Double type_null_float = row[++col].equals("NULL") ? null : Double.valueOf(row[col]);
        if ( (!(type_null_float == null && valid.type_null_float == null)) &&
             (!type_null_float.equals(valid.type_null_float)) )
        {
            System.out.println("CSV value: " + row[col]);
            System.out.println("EXP value: " + valid.type_null_float);
            System.out.println("ACT value: " + type_null_float);
            System.out.println("valueOf():" + Double.valueOf("-2155882919525625344.000000000000"));
            System.out.flush();
            return error("type_null_float", type_null_float, valid.type_null_float);
        }

        // col 22
        Double type_not_null_float = Double.valueOf(row[++col]);
        if (!type_not_null_float.equals(valid.type_not_null_float))
            return error("type_not_null_float", type_not_null_float, valid.type_not_null_float);

        // col 23
        String type_null_varchar25 = row[++col].equals("NULL") ? null : row[col];
        if (!(type_null_varchar25 == valid.type_null_varchar25 ||
              type_null_varchar25.equals(valid.type_null_varchar25)))
            return error("type_null_varchar25", type_null_varchar25, valid.type_null_varchar25);

        // col 24
        String type_not_null_varchar25 = row[++col];
        if (!type_not_null_varchar25.equals(valid.type_not_null_varchar25))
            return error("type_not_null_varchar25", type_not_null_varchar25, valid.type_not_null_varchar25);

        // col 25
        String type_null_varchar128 = row[++col].equals("NULL") ? null : row[col];
        if (!(type_null_varchar128 == valid.type_null_varchar128 ||
              type_null_varchar128.equals(valid.type_null_varchar128)))
            return error("type_null_varchar128", type_null_varchar128, valid.type_null_varchar128);

        // col 26
        String type_not_null_varchar128 = row[++col];
        if (!type_not_null_varchar128.equals(valid.type_not_null_varchar128))
            return error("type_not_null_varchar128", type_not_null_varchar128, valid.type_not_null_varchar128);

        // col 27
        String type_null_varchar1024 = row[++col].equals("NULL") ? null : row[col];
        if (!(type_null_varchar1024 == valid.type_null_varchar1024 ||
              type_null_varchar1024.equals(valid.type_null_varchar1024)))
            return error("type_null_varchar1024", type_null_varchar1024, valid.type_null_varchar1024);

        // col 28
        String type_not_null_varchar1024 = row[++col];
        if (!type_not_null_varchar1024.equals(valid.type_not_null_varchar1024))
            return error("type_not_null_varchar1024", type_not_null_varchar1024, valid.type_not_null_varchar1024);

        return null;
    }

    public static ValidationErr error(String msg, Object val, Object exp) throws ValidationErr {
        System.err.println("ERROR: " + msg + "Value:" + val + "\nExpected:" + exp);
        return new ValidationErr(msg, val, exp);
    }

    int m_partitions = 0;
    HashMap<Integer, TreeMap<Long,Long>> m_rowTxnIds = new HashMap<>();

    HashMap<Integer,Long> m_maxPartTxId = new HashMap<>();

    TreeMap<Long,Long> m_clientTxnIds = new TreeMap<>();
    ArrayList<Long> m_clientTxnIdOrphans = new ArrayList<>();
    HashMap<Integer,AtomicLong> m_readUpTo = new HashMap<>();
    HashMap<Integer,Integer> m_checkedUpTo = new HashMap<>();
    TreeSet<Long> m_clientOverFlow = new TreeSet<>();
    TreeSet<Long> m_staleTxnIds = new TreeSet<>();

    Queue<Pair<ChannelSftp, String>> m_exportFiles = new ArrayDeque<>();

    File m_clientPath = null;

    NavigableMap<Integer,File> m_clientPaths = new TreeMap<>();
    NavigableMap<Integer,File[]> m_clientFiles = new TreeMap<>();
    NavigableMap<Integer,Integer> m_clientIndexes = new TreeMap<>();
    NavigableMap<Integer, BufferedReader> m_clientReaders = new TreeMap<>();
    NavigableMap<Integer, Boolean> m_clientComplete = new TreeMap<>();

    boolean m_clientlogSeen = false;

    static {
        VoltDB.setDefaultTimezone();
    }

    public static void main(String[] args) throws Exception {
        ExportOnServerVerifier verifier = new ExportOnServerVerifier();
        try
        {
            boolean skinny = verifier.verifySetup(args);

            if (skinny) verifier.verifySkinny();
            else verifier.verifyFat();

            if (verifier.m_clientTxnIds.size() > 0)
            {
                System.err.println("ERROR: not all client txids were matched against exported txids");
                verifier.printClientTxIds();
                System.exit(1);
            }
        }
        catch(IOException e) {
            e.printStackTrace(System.err);
            System.exit(-1);
        }
        catch (ValidationErr e ) {
            System.err.println("Validation error: " + e.toString());
            System.exit(-1);
        }
        System.exit(0);
    }

    public static class RoughCSVTokenizer {

        private RoughCSVTokenizer() {
        }

        private static void moveToBuffer(List<String> resultBuffer, StringBuilder buf) {
            resultBuffer.add(buf.toString());
            buf.delete(0, buf.length());
        }

        public static String[] tokenize(String csv) {
            List<String> resultBuffer = new java.util.ArrayList<String>();

            if (csv != null) {
                int z = csv.length();
                Character openingQuote = null;
                boolean trimSpace = false;
                StringBuilder buf = new StringBuilder();

                for (int i = 0; i < z; ++i) {
                    char c = csv.charAt(i);
                    trimSpace = trimSpace && Character.isWhitespace(c);
                    if (c == '"' || c == '\'') {
                        if (openingQuote == null) {
                            openingQuote = c;
                            int bi = 0;
                            while (bi < buf.length()) {
                                if (Character.isWhitespace(buf.charAt(bi))) {
                                    buf.deleteCharAt(bi);
                                } else {
                                    bi++;
                                }
                            }
                        }
                        else if (openingQuote == c ) {
                            openingQuote = null;
                            trimSpace = true;
                        }
                    }
                    else if (c == '\\') {
                        if ((z > i + 1)
                            && ((csv.charAt(i + 1) == '"')
                                || (csv.charAt(i + 1) == '\\'))) {
                            buf.append(csv.charAt(i + 1));
                            ++i;
                        } else {
                            buf.append("\\");
                        }
                    } else {
                        if (openingQuote != null) {
                            buf.append(c);
                        } else {
                            if (c == ',') {
                                moveToBuffer(resultBuffer, buf);
                            } else {
                                if (!trimSpace) buf.append(c);
                            }
                        }
                    }
                }
                moveToBuffer(resultBuffer, buf);
            }

            String[] result = new String[resultBuffer.size()];
            return resultBuffer.toArray(result);
        }
    }
}
TOP

Related Classes of genqa.ExportOnServerVerifier

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.