Github user hanm commented on a diff in the pull request: https://github.com/apache/zookeeper/pull/615#discussion_r215129442 --- Diff: src/java/main/org/apache/zookeeper/server/util/LogChopper.java --- @@ -0,0 +1,152 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.zookeeper.server.util; + +import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.server.persistence.FileHeader; +import org.apache.zookeeper.server.persistence.FileTxnLog; +import org.apache.zookeeper.txn.TxnHeader; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.Adler32; +import java.util.zip.Checksum; + +/** + * this class will chop the log at the specified zxid + */ +public class LogChopper { + public static void main(String args[]) { + if (args.length != 3) { + System.out.println("Usage: LogChopper zxid_to_chop_to txn_log_to_chop chopped_filename"); + System.out.println(" this program will read the txn_log_to_chop file and copy all the transactions"); + System.out.println(" from it up to (and including) the given zxid into chopped_filename."); + System.exit(1); + } + long zxid = Long.decode(args[0]); + String txnLog = args[1]; + String choppedLog = args[2]; + + int rc = 2; + try ( + InputStream is = new BufferedInputStream(new FileInputStream(txnLog)); + OutputStream os = new BufferedOutputStream(new FileOutputStream(choppedLog)) + ) { + if (chop(is, os, zxid)) { + rc = 0; + } + } catch (Exception e) { + System.out.println("Got exception: " + e.getMessage()); + } + System.exit(rc); + } + + public static boolean chop(InputStream is, OutputStream os, long zxid) throws IOException { + BinaryInputArchive logStream = BinaryInputArchive.getArchive(is); + BinaryOutputArchive choppedStream = BinaryOutputArchive.getArchive(os); + FileHeader fhdr = new FileHeader(); + fhdr.deserialize(logStream, "fileheader"); + + if (fhdr.getMagic() != FileTxnLog.TXNLOG_MAGIC) { + System.err.println("Invalid magic number in txn log file"); + return false; + } + System.out.println("ZooKeeper Transactional Log File with dbid " + + fhdr.getDbid() + " txnlog format version " + + fhdr.getVersion()); + + fhdr.serialize(choppedStream, "fileheader"); + int count = 0; + boolean hasZxid = false; + long previousZxid = -1; + while (true) { + long crcValue; + byte[] bytes; + try { + crcValue = logStream.readLong("crcvalue"); + + bytes = logStream.readBuffer("txnEntry"); + } catch (EOFException e) { + System.out.println("EOF reached after " + count + " txns."); + // returning false because nothing was chopped + return false; + } + if (bytes.length == 0) { + // Since we preallocate, we define EOF to be an + // empty transaction + System.out.println("EOF reached after " + count + " txns."); + // returning false because nothing was chopped + return false; + } + + Checksum crc = new Adler32(); + crc.update(bytes, 0, bytes.length); + if (crcValue != crc.getValue()) { + throw new IOException("CRC doesn't match " + crcValue + + " vs " + crc.getValue()); + } + TxnHeader hdr = new TxnHeader(); + Record txn = SerializeUtils.deserializeTxn(bytes, hdr); + if (logStream.readByte("EOR") != 'B') { + System.out.println("Last transaction was partial."); + throw new EOFException("Last transaction was partial."); + } + + long txnZxid = hdr.getZxid(); + if (txnZxid == zxid) { + hasZxid = true; + } + + // logging the gap to make the inconsistency investigation easier + if (previousZxid != -1 && txnZxid != previousZxid + 1) { + long txnEpoch = ZxidUtils.getEpochFromZxid(txnZxid); + long txnCounter = ZxidUtils.getCounterFromZxid(txnZxid); + long previousEpoch = ZxidUtils.getEpochFromZxid(previousZxid); + if (txnEpoch == previousEpoch || txnCounter != 1) { + System.out.println( + String.format("There is gap between %x and %x", + previousZxid, txnZxid)); + } + } + previousZxid = txnZxid; + + if (hdr.getZxid() > zxid) { --- End diff -- `hdr.getZxid()` is previously assigned to `txnZxid`, so maybe use `txnZxid` here instead? Also, would it be better to move this check before the gap detection logic to terminate earlier?
---