/*
* Copyright 2014 WANdisco
*
* WANdisco 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 c5db.log;
import c5db.interfaces.log.SequentialEntryCodec;
import c5db.log.generated.OLogHeader;
import c5db.replication.generated.QuorumConfigurationMessage;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.channels.Channels;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;
import static c5db.log.EntryEncodingUtil.decodeAndCheckCrc;
import static c5db.log.LogPersistenceService.BytePersistence;
import static c5db.log.LogPersistenceService.PersistenceReader;
public class CatOLog {
// TODO replace formatting constants with command line parameters
private static final int HEX_ADDRESS_DIGITS = 8;
private static final int LONG_DIGITS = 8;
private static final int INT_DIGITS = 8;
/**
* Output to System.out the contents of an OLog file, with one entry on each line.
*
* @param args Accepts only one argument, the name of the log file.
* @throws IOException
*/
public static void main(String args[]) throws IOException {
if (args.length != 1) {
System.err.println("Usage: CatOLog filename");
System.exit(-1);
}
File inputLogFile = new File(args[0]);
if (!inputLogFile.exists() || inputLogFile.isDirectory()) {
System.err.println("File does not exist, or is a directory");
System.exit(-1);
}
describeLogFileToOutput(inputLogFile, System.out);
}
private static void describeLogFileToOutput(File inputLogFile, PrintStream out) throws IOException {
openFileAndParseEntries(inputLogFile,
(header, validCrc) ->
out.println(formatLogHeader(header, validCrc)),
(address, entry) -> {
out.print(toHex(address) + ": ");
out.println(formatEntry(entry));
});
}
private static final SequentialEntryCodec<OLogEntryDescription> CODEC = new OLogEntryDescription.Codec();
private static void openFileAndParseEntries(File inputLogFile,
HeaderWithCrcValidity doWithHeader,
EntryWithAddress doForEach) throws IOException {
try (BytePersistence persistence = new FilePersistence(inputLogFile.toPath());
PersistenceReader reader = persistence.getReader();
InputStream inputStream = Channels.newInputStream(reader)) {
decodeAndUseLogHeader(inputStream, doWithHeader);
//noinspection InfiniteLoopStatement
do {
long address = reader.position();
OLogEntryDescription entry = CODEC.decode(inputStream);
doForEach.accept(address, entry);
} while (true);
} catch (EOFException ignore) {
}
}
private interface EntryWithAddress {
void accept(long address, OLogEntryDescription entry);
}
private interface HeaderWithCrcValidity {
void accept(OLogHeader header, boolean validCrc);
}
private static String toHex(long address) {
return String.join(" ",
Splitter
.fixedLength(4)
.split(String.format("%0" + HEX_ADDRESS_DIGITS + "x", address)));
}
private static String formatLogHeader(OLogHeader header, boolean validCrc) {
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb, Locale.US);
formatter.format("HEADER [base term: %" + LONG_DIGITS + "d]", header.getBaseTerm());
formatter.format(" [base seq: %" + LONG_DIGITS + "d]", header.getBaseSeqNum());
formatter.format(" [base config: ");
formatConfiguration(formatter, header.getBaseConfiguration());
formatter.format("]");
if (!validCrc) {
formatter.format(" <invalid log header CRC>");
}
return formatter.toString();
}
private static String formatEntry(OLogEntryDescription entry) {
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb, Locale.US);
formatter.format(" [term: %" + LONG_DIGITS + "d]", entry.getElectionTerm());
formatter.format(" [seq: %" + LONG_DIGITS + "d]", entry.getSeqNum());
formatContent(formatter, entry);
if (!entry.isHeaderCrcValid()) {
formatter.format(" <invalid header CRC>");
}
if (!entry.isContentCrcValid()) {
formatter.format(" <invalid content CRC>");
}
return formatter.toString();
}
private static void formatContent(Formatter formatter, OLogEntryDescription entry) {
switch (entry.getType()) {
case QUORUM_CONFIGURATION:
formatter.format(" [quorum configuration: ");
formatConfiguration(formatter, entry.getQuorumConfiguration().toProtostuff());
formatter.format("]");
break;
case DATA:
formatter.format(" [content length: %" + INT_DIGITS + "d]", entry.getContentLength());
break;
default:
throw new AssertionError("Unhandled enum value in CatOLog#formatContent");
}
}
private static void formatConfiguration(Formatter formatter, QuorumConfigurationMessage message) {
formatter.format("(");
if (message.getTransitional()) {
formatPeerIdList(formatter, message.getPrevPeersList());
formatter.format(" -> ");
formatPeerIdList(formatter, message.getNextPeersList());
} else {
formatPeerIdList(formatter, message.getAllPeersList());
}
formatter.format(")");
}
private static void formatPeerIdList(Formatter formatter, List<Long> peerIdList) {
Joiner joiner = Joiner.on(", ");
formatter.format(joiner.join(peerIdList));
}
private static void decodeAndUseLogHeader(InputStream inputStream, HeaderWithCrcValidity doWithHeader)
throws IOException {
OLogHeader header;
boolean validCrc = true;
try {
header = decodeAndCheckCrc(inputStream, OLogHeader.getSchema());
} catch (EntryEncodingUtil.CrcError e) {
validCrc = false;
header = OLogHeader.getSchema().newMessage();
}
doWithHeader.accept(header, validCrc);
}
}