You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ratis.apache.org by lj...@apache.org on 2020/01/31 09:02:30 UTC

[incubator-ratis] branch master updated: RATIS-755. Add a log dump command line utility inside ratis. Contributed by Mukul Kumar Singh.

This is an automated email from the ASF dual-hosted git repository.

ljain pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-ratis.git


The following commit(s) were added to refs/heads/master by this push:
     new 5e858b5  RATIS-755. Add a log dump command line utility inside ratis. Contributed by Mukul Kumar Singh.
5e858b5 is described below

commit 5e858b53fe5cd31c18c195fb92e7318e6f2d8318
Author: Lokesh Jain <lj...@apache.org>
AuthorDate: Fri Jan 31 14:32:19 2020 +0530

    RATIS-755. Add a log dump command line utility inside ratis. Contributed by Mukul Kumar Singh.
---
 pom.xml                                            |   1 +
 .../apache/ratis/server/impl/ServerProtoUtils.java |  23 +++--
 .../ratis/server/raftlog/segmented/LogSegment.java |   4 +-
 .../server/raftlog/segmented/SegmentedRaftLog.java |   2 +-
 .../raftlog/segmented/SegmentedRaftLogWorker.java  |   5 +-
 .../ratis/server/storage/RaftStorageDirectory.java |  37 +++++---
 ratis-tools/pom.xml                                | 100 ++++++++++++++++++++
 .../org/apache/ratis/tools/ArithmeticLogDump.java  |  51 ++++++++++
 .../org/apache/ratis/tools/DefaultLogDump.java     |  42 +++++++++
 .../java/org/apache/ratis/tools/ParseRatisLog.java | 104 +++++++++++++++++++++
 .../apache/ratis/tools/TestArithmeticLogDump.java  |  90 ++++++++++++++++++
 11 files changed, 430 insertions(+), 29 deletions(-)

diff --git a/pom.xml b/pom.xml
index 79f5d46..49cabeb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,6 +57,7 @@
     <module>ratis-replicated-map</module>
     <module>ratis-logservice</module>
     <module>ratis-metrics</module>
+    <module>ratis-tools</module>
   </modules>
 
   <pluginRepositories>
diff --git a/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerProtoUtils.java b/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerProtoUtils.java
index 0f69f89..0bfb342 100644
--- a/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerProtoUtils.java
+++ b/ratis-server/src/main/java/org/apache/ratis/server/impl/ServerProtoUtils.java
@@ -26,7 +26,6 @@ import org.apache.ratis.protocol.RaftGroupMemberId;
 import org.apache.ratis.protocol.RaftPeer;
 import org.apache.ratis.protocol.RaftPeerId;
 import org.apache.ratis.server.protocol.TermIndex;
-import org.apache.ratis.statemachine.StateMachine;
 import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
 import org.apache.ratis.util.Preconditions;
 import org.apache.ratis.util.ProtoUtils;
@@ -35,6 +34,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import static org.apache.ratis.server.impl.RaftServerConstants.DEFAULT_CALLID;
@@ -69,26 +69,31 @@ public interface ServerProtoUtils {
     return toLogEntryString(entry, null);
   }
 
-  static String toStateMachineLogEntryString(StateMachineLogEntryProto smLog, StateMachine stateMachine) {
-    if (stateMachine != null) {
-      return stateMachine.toStateMachineLogEntryString(smLog);
-    }
+  static String toStateMachineLogEntryString(StateMachineLogEntryProto smLog,
+                                             Function<StateMachineLogEntryProto, String> function) {
     final ByteString clientId = smLog.getClientId();
-    return (clientId.isEmpty() ? "<empty clientId>" : ClientId.valueOf(clientId)) + ", cid=" + smLog.getCallId();
+    String callIdString = (clientId.isEmpty() ? "<empty clientId>" : ClientId.valueOf(clientId))
+        + ", cid=" + smLog.getCallId();
+
+    String smString = "";
+    if (function != null) {
+      smString = "\n\t State Machine: " + function.apply(smLog);
+    }
+    return callIdString + smString;
   }
 
 
   static String toLogEntryString(LogEntryProto entry,
-                                 StateMachine stateMachine) {
+                                 Function<StateMachineLogEntryProto, String> function) {
     if (entry == null) {
       return null;
     }
     final String s;
     if (entry.hasStateMachineLogEntry()) {
-      s = ", " + toStateMachineLogEntryString(entry.getStateMachineLogEntry(), stateMachine);
+      s = ", " + toStateMachineLogEntryString(entry.getStateMachineLogEntry(), function);
     } else if (entry.hasMetadataEntry()) {
       final MetadataProto metadata = entry.getMetadataEntry();
-      s = "(c" + metadata.getCommitIndex() + ")";
+      s = "(c:" + metadata.getCommitIndex() + ")";
     } else {
       s = "";
     }
diff --git a/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/LogSegment.java b/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/LogSegment.java
index 19f997d..34552c2 100644
--- a/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/LogSegment.java
+++ b/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/LogSegment.java
@@ -50,7 +50,7 @@ import java.util.function.Consumer;
  *
  * This class will be protected by the {@link SegmentedRaftLog}'s read-write lock.
  */
-class LogSegment implements Comparable<Long> {
+public class LogSegment implements Comparable<Long> {
   static final Logger LOG = LoggerFactory.getLogger(LogSegment.class);
 
   static long getEntrySize(LogEntryProto entry) {
@@ -89,7 +89,7 @@ class LogSegment implements Comparable<Long> {
     return new LogSegment(storage, false, start, end, raftLogMetrics);
   }
 
-  private static int readSegmentFile(File file, long start, long end,
+  public static int readSegmentFile(File file, long start, long end,
       boolean isOpen, CorruptionPolicy corruptionPolicy,
       RaftLogMetrics raftLogMetrics, Consumer<LogEntryProto> entryConsumer) throws
       IOException {
diff --git a/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/SegmentedRaftLog.java b/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/SegmentedRaftLog.java
index eb91182..717c517 100644
--- a/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/SegmentedRaftLog.java
+++ b/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/SegmentedRaftLog.java
@@ -528,6 +528,6 @@ public class SegmentedRaftLog extends RaftLog {
 
   @Override
   public String toLogEntryString(LogEntryProto logEntry) {
-    return ServerProtoUtils.toLogEntryString(logEntry, stateMachine);
+    return ServerProtoUtils.toLogEntryString(logEntry, stateMachine::toStateMachineLogEntryString);
   }
 }
diff --git a/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/SegmentedRaftLogWorker.java b/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/SegmentedRaftLogWorker.java
index d632bfb..a0d471e 100644
--- a/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/SegmentedRaftLogWorker.java
+++ b/ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/SegmentedRaftLogWorker.java
@@ -455,7 +455,7 @@ class SegmentedRaftLogWorker implements Runnable {
           this.stateMachineFuture = stateMachine.writeStateMachineData(entry);
         } catch (Throwable e) {
           LOG.error(name + ": writeStateMachineData failed for index " + entry.getIndex()
-              + ", entry=" + ServerProtoUtils.toLogEntryString(entry, stateMachine), e);
+              + ", entry=" + ServerProtoUtils.toLogEntryString(entry, stateMachine::toStateMachineLogEntryString), e);
           throw e;
         }
       }
@@ -510,7 +510,8 @@ class SegmentedRaftLogWorker implements Runnable {
 
     @Override
     public String toString() {
-      return super.toString() + ": " + ServerProtoUtils.toLogEntryString(entry, stateMachine);
+      return super.toString() + ": " + ServerProtoUtils.
+          toLogEntryString(entry, stateMachine::toStateMachineLogEntryString);
     }
   }
 
diff --git a/ratis-server/src/main/java/org/apache/ratis/server/storage/RaftStorageDirectory.java b/ratis-server/src/main/java/org/apache/ratis/server/storage/RaftStorageDirectory.java
index 7a09442..5d57a20 100644
--- a/ratis-server/src/main/java/org/apache/ratis/server/storage/RaftStorageDirectory.java
+++ b/ratis-server/src/main/java/org/apache/ratis/server/storage/RaftStorageDirectory.java
@@ -18,6 +18,7 @@
 package org.apache.ratis.server.storage;
 
 import org.apache.ratis.server.impl.RaftServerConstants;
+import org.apache.ratis.server.raftlog.RaftLog;
 import org.apache.ratis.util.AtomicFileOutputStream;
 import org.apache.ratis.util.FileUtils;
 import org.slf4j.Logger;
@@ -38,7 +39,6 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import static java.nio.file.Files.newDirectoryStream;
-import static org.apache.ratis.server.impl.RaftServerConstants.INVALID_LOG_INDEX;
 
 public class RaftStorageDirectory {
   static final Logger LOG = LoggerFactory.getLogger(RaftStorageDirectory.class);
@@ -193,20 +193,9 @@ public class RaftStorageDirectory {
     try (DirectoryStream<Path> stream =
              Files.newDirectoryStream(getCurrentDir().toPath())) {
       for (Path path : stream) {
-        for (Pattern pattern : Arrays.asList(CLOSED_SEGMENT_REGEX, OPEN_SEGMENT_REGEX)) {
-          Matcher matcher = pattern.matcher(path.getFileName().toString());
-          if (matcher.matches()) {
-            if (pattern == OPEN_SEGMENT_REGEX && Files.size(path) == 0L) {
-              Files.delete(path);
-              LOG.info("Delete zero size file " + path);
-              break;
-            }
-            final long startIndex = Long.parseLong(matcher.group(1));
-            final long endIndex = matcher.groupCount() == 2 ?
-                Long.parseLong(matcher.group(2)) : INVALID_LOG_INDEX;
-            list.add(new LogPathAndIndex(path, startIndex, endIndex));
-            break;
-          }
+        LogPathAndIndex lpi = processOnePath(path);
+        if (lpi != null) {
+          list.add(lpi);
         }
       }
     }
@@ -214,6 +203,24 @@ public class RaftStorageDirectory {
     return list;
   }
 
+  public static LogPathAndIndex processOnePath(Path path) throws IOException {
+    for (Pattern pattern : Arrays.asList(CLOSED_SEGMENT_REGEX, OPEN_SEGMENT_REGEX)) {
+      Matcher matcher = pattern.matcher(path.getFileName().toString());
+      if (matcher.matches()) {
+        if (pattern == OPEN_SEGMENT_REGEX && Files.size(path) == 0L) {
+          Files.delete(path);
+          LOG.info("Delete zero size file " + path);
+          return null;
+        }
+        final long startIndex = Long.parseLong(matcher.group(1));
+        final long endIndex = matcher.groupCount() == 2 ?
+            Long.parseLong(matcher.group(2)) : RaftLog.INVALID_LOG_INDEX;
+        return new LogPathAndIndex(path, startIndex, endIndex);
+      }
+    }
+    return null;
+  }
+
   /**
    * Check to see if current/ directory is empty.
    */
diff --git a/ratis-tools/pom.xml b/ratis-tools/pom.xml
new file mode 100644
index 0000000..476c6c1
--- /dev/null
+++ b/ratis-tools/pom.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed 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. See accompanying LICENSE file.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>ratis</artifactId>
+    <groupId>org.apache.ratis</groupId>
+    <version>0.5.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>ratis-tools</artifactId>
+  <name>Apache Ratis Tools</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.ratis</groupId>
+      <artifactId>ratis-server</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.ratis</groupId>
+      <artifactId>ratis-proto</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.ratis</groupId>
+      <artifactId>ratis-common</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.ratis</groupId>
+      <artifactId>ratis-examples</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.ratis</groupId>
+      <artifactId>ratis-common</artifactId>
+      <scope>test</scope>
+      <type>test-jar</type>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.ratis</groupId>
+      <artifactId>ratis-server</artifactId>
+      <scope>test</scope>
+      <type>test-jar</type>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
+              <shadedArtifactAttached>true</shadedArtifactAttached>
+              <transformers>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                  <mainClass>org.apache.ratis.tools.ArithmeticLogDump</mainClass>
+                </transformer>
+              </transformers>
+              <filters>
+                <filter>
+                  <artifact>*:*</artifact>
+                  <excludes>
+                    <exlcude>META-INF/*.SF</exlcude>
+                    <exclude>META-INF/*.DSA</exclude>
+                    <exclude>META-INF/*.RSA</exclude>
+                  </excludes>
+                </filter>
+              </filters>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/ratis-tools/src/main/java/org/apache/ratis/tools/ArithmeticLogDump.java b/ratis-tools/src/main/java/org/apache/ratis/tools/ArithmeticLogDump.java
new file mode 100644
index 0000000..6cc9438
--- /dev/null
+++ b/ratis-tools/src/main/java/org/apache/ratis/tools/ArithmeticLogDump.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ratis.tools;
+
+import org.apache.ratis.examples.arithmetic.AssignmentMessage;
+import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
+
+import java.io.File;
+
+/**
+ * Utility to dump log segments for Arithmetic State Machine.
+ */
+public final class ArithmeticLogDump {
+  private ArithmeticLogDump() {
+
+  }
+
+  public static void main(String[] args) throws Exception {
+    String filePath = args[0];
+    System.out.println("file path is " + filePath);
+    File logFile = new File(filePath);
+
+    ParseRatisLog.Builder builder =  new ParseRatisLog.Builder();
+    ParseRatisLog prl = builder.setSegmentFile(logFile)
+        .setSMLogToString(ArithmeticLogDump::smToArithmeticLogString)
+        .build();
+
+    prl.dumpSegmentFile();
+  }
+
+  private static String smToArithmeticLogString(StateMachineLogEntryProto logEntryProto) {
+    AssignmentMessage message = new AssignmentMessage(logEntryProto.getLogData());
+    return message.toString();
+  }
+}
diff --git a/ratis-tools/src/main/java/org/apache/ratis/tools/DefaultLogDump.java b/ratis-tools/src/main/java/org/apache/ratis/tools/DefaultLogDump.java
new file mode 100644
index 0000000..451d205
--- /dev/null
+++ b/ratis-tools/src/main/java/org/apache/ratis/tools/DefaultLogDump.java
@@ -0,0 +1,42 @@
+/*
+ * 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.ratis.tools;
+
+import java.io.File;
+
+/**
+ * Default log dump tool to dump log entries for any state machine.
+ */
+public final class DefaultLogDump {
+  private DefaultLogDump() {
+
+  }
+
+  public static void main(String[] args) throws Exception {
+    String filePath = args[0];
+    System.out.println("file path is " + filePath);
+    File logFile = new File(filePath);
+
+    ParseRatisLog.Builder builder =  new ParseRatisLog.Builder();
+    ParseRatisLog prl = builder.setSegmentFile(logFile)
+        .build();
+
+    prl.dumpSegmentFile();
+  }
+}
diff --git a/ratis-tools/src/main/java/org/apache/ratis/tools/ParseRatisLog.java b/ratis-tools/src/main/java/org/apache/ratis/tools/ParseRatisLog.java
new file mode 100644
index 0000000..6ac3829
--- /dev/null
+++ b/ratis-tools/src/main/java/org/apache/ratis/tools/ParseRatisLog.java
@@ -0,0 +1,104 @@
+/*
+ * 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.ratis.tools;
+
+import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
+import org.apache.ratis.proto.RaftProtos.LogEntryProto;
+import org.apache.ratis.server.RaftServerConfigKeys;
+import org.apache.ratis.server.impl.ServerProtoUtils;
+import org.apache.ratis.server.raftlog.segmented.LogSegment;
+import org.apache.ratis.server.storage.RaftStorageDirectory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.function.Function;
+
+public final class ParseRatisLog {
+
+  private final File file;
+  private final Function<StateMachineLogEntryProto, String> smLogToString;
+
+  private long numConfEntries;
+  private long numMetadataEntries;
+  private long numStateMachineEntries;
+  private long numInvalidEntries;
+
+  private ParseRatisLog(File f , Function<StateMachineLogEntryProto, String> smLogToString) {
+    this.file = f;
+    this.smLogToString = smLogToString;
+    this.numConfEntries = 0;
+    this.numMetadataEntries = 0;
+    this.numStateMachineEntries = 0;
+    this.numInvalidEntries = 0;
+  }
+
+  public void dumpSegmentFile() throws IOException {
+    RaftStorageDirectory.LogPathAndIndex pi = RaftStorageDirectory.processOnePath(file.toPath());
+    if (pi == null) {
+      System.out.println("Invalid segment file");
+      return;
+    }
+
+    System.out.println("Processing Raft Log file: " + file.getAbsolutePath() + " size:" + file.length());
+    final int entryCount = LogSegment.readSegmentFile(file, pi.startIndex, pi.endIndex, pi.isOpen(),
+        RaftServerConfigKeys.Log.CorruptionPolicy.EXCEPTION, null, this::processLogEntry);
+    System.out.println("Num Total Entries: " + entryCount);
+    System.out.println("Num Conf Entries: " + numConfEntries);
+    System.out.println("Num Metadata Entries: " + numMetadataEntries);
+    System.out.println("Num StateMachineEntries Entries: " + numStateMachineEntries);
+    System.out.println("Num Invalid Entries: " + numInvalidEntries);
+  }
+
+
+  private void processLogEntry(LogEntryProto proto) {
+    if (proto.hasConfigurationEntry()) {
+      numConfEntries++;
+    } else if (proto.hasMetadataEntry()) {
+      numMetadataEntries++;
+    } else if (proto.hasStateMachineLogEntry()) {
+      numStateMachineEntries++;
+    } else {
+      System.out.println("Found invalid entry" + proto.toString());
+      numInvalidEntries++;
+    }
+
+    String str = ServerProtoUtils.toLogEntryString(proto, smLogToString);
+    System.out.println(str);
+  }
+
+  public static class Builder {
+    private File file = null;
+    private Function<StateMachineLogEntryProto, String> smLogToString = null;
+
+
+    public Builder setSegmentFile(File segmentFile) {
+      this.file = segmentFile;
+      return this;
+    }
+
+    public Builder setSMLogToString(Function<StateMachineLogEntryProto, String> smLogToStr) {
+      this.smLogToString = smLogToStr;
+      return this;
+    }
+
+    public ParseRatisLog build() {
+      return new ParseRatisLog(file, smLogToString);
+    }
+  }
+}
diff --git a/ratis-tools/src/test/java/org/apache/ratis/tools/TestArithmeticLogDump.java b/ratis-tools/src/test/java/org/apache/ratis/tools/TestArithmeticLogDump.java
new file mode 100644
index 0000000..dc215ac
--- /dev/null
+++ b/ratis-tools/src/test/java/org/apache/ratis/tools/TestArithmeticLogDump.java
@@ -0,0 +1,90 @@
+/**
+ * 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.ratis.tools;
+
+import org.apache.log4j.Level;
+import org.apache.ratis.BaseTest;
+import org.apache.ratis.MiniRaftCluster;
+import org.apache.ratis.RaftTestUtil;
+import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.server.RaftServerConfigKeys;
+import org.apache.ratis.server.impl.RaftServerImpl;
+import org.apache.ratis.server.simulation.MiniRaftClusterWithSimulatedRpc;
+import org.apache.ratis.server.storage.RaftStorageDirectory;
+import org.apache.ratis.statemachine.SimpleStateMachine4Testing;
+import org.apache.ratis.statemachine.StateMachine;
+import org.apache.ratis.util.Log4jUtils;
+import org.apache.ratis.util.TimeDuration;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class TestArithmeticLogDump extends BaseTest {
+  static {
+    Log4jUtils.setLogLevel(RaftServerImpl.LOG, Level.DEBUG);
+  }
+
+  public static final int NUM_SERVERS = 1;
+
+  protected static final RaftProperties properties = new RaftProperties();
+
+  private final MiniRaftClusterWithSimulatedRpc cluster = MiniRaftClusterWithSimulatedRpc
+      .FACTORY.newCluster(NUM_SERVERS, getProperties());
+
+  public RaftProperties getProperties() {
+    RaftServerConfigKeys.Rpc
+        .setSlownessTimeout(properties, TimeDuration.valueOf(1, TimeUnit.SECONDS));
+    properties.setClass(MiniRaftCluster.STATEMACHINE_CLASS_KEY,
+        SimpleStateMachine4Testing.class, StateMachine.class);
+    return properties;
+  }
+
+  @Before
+  public void setup() throws IOException {
+    Assert.assertNull(cluster.getLeader());
+    cluster.start();
+  }
+
+  @After
+  public void tearDown() {
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  @Test
+  public void testLogDump() throws Exception {
+    RaftServerImpl leaderServer = RaftTestUtil.waitForLeader(cluster);
+    List<RaftStorageDirectory.LogPathAndIndex> files =
+        leaderServer.getState().getStorage().getStorageDir().getLogSegmentFiles();
+    Assert.assertEquals(1, files.size());
+    cluster.shutdown();
+
+    ParseRatisLog.Builder builder =  new ParseRatisLog.Builder();
+    ParseRatisLog prl = builder.setSegmentFile(files.get(0).getPath().toFile())
+        .build();
+
+    prl.dumpSegmentFile();
+  }
+}