You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by md...@apache.org on 2015/10/27 19:49:36 UTC
svn commit: r1710862 - in /jackrabbit/oak/trunk:
oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/
oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/
oak-run/ oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/s...
Author: mduerig
Date: Tue Oct 27 18:49:35 2015
New Revision: 1710862
URL: http://svn.apache.org/viewvc?rev=1710862&view=rev
Log:
OAK-3560: Tooling for writing segment graphs to a file
Add graph run mode to oak-run dumping a file store segment graph to a text file in Guess GDF format
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarReader.java
jackrabbit/oak/trunk/oak-run/README.md
jackrabbit/oak/trunk/oak-run/pom.xml
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/segment/FileStoreHelper.java
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java?rev=1710862&r1=1710861&r2=1710862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java Tue Oct 27 18:49:35 2015
@@ -36,6 +36,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
+import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import com.google.common.base.Charsets;
@@ -273,6 +274,30 @@ public class Segment {
<< RECORD_ALIGN_BITS;
}
+ /**
+ * Returns the segment meta data of this segment or {@code null} if none is present.
+ * <p>
+ * The segment meta data is a string of the format {@code "{wid=W,sno=S,gc=G,t=T}"}
+ * where:
+ * <ul>
+ * <li>{@code W} is the writer id {@code wid}, </li>
+ * <li>{@code S} is a unique, increasing sequence number corresponding to the allocation order
+ * of the segments in this store, </li>
+ * <li>{@code G} is the garbage collection generation (i.e. the number of compaction cycles
+ * that have been run),</li>
+ * <li>{@code T} is a time stamp according to {@link System#currentTimeMillis()}.</li>
+ * </ul>
+ * @return the segment meta data
+ */
+ @CheckForNull
+ public String getSegmentInfo() {
+ if (getRootCount() == 0) {
+ return null;
+ } else {
+ return readString(getRootOffset(0));
+ }
+ }
+
SegmentId getRefId(int index) {
if (refids == null || index >= refids.length) {
String type = "data";
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java?rev=1710862&r1=1710861&r2=1710862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java Tue Oct 27 18:49:35 2015
@@ -1357,6 +1357,19 @@ public class FileStore implements Segmen
super.setRevision(revision);
}
+ /**
+ * Build the graph of segments reachable from an initial set of segments
+ * @param referencedIds the initial set of segments
+ * @throws IOException
+ */
+ public Map<UUID, Set<UUID>> getSegmentGraph(Set<UUID> referencedIds) throws IOException {
+ Map<UUID, Set<UUID>> graph = newHashMap();
+ for (TarReader reader : super.readers) {
+ graph.putAll(reader.getReferenceGraph(referencedIds));
+ }
+ return graph;
+ }
+
@Override
public boolean setHead(SegmentNodeState base, SegmentNodeState head) {
throw new UnsupportedOperationException("Read Only Store");
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarReader.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarReader.java?rev=1710862&r1=1710861&r2=1710862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarReader.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/TarReader.java Tue Oct 27 18:49:35 2015
@@ -36,6 +36,7 @@ import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -46,6 +47,9 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
import org.apache.commons.io.FileUtils;
import org.apache.jackrabbit.oak.plugins.segment.CompactionMap;
import org.slf4j.Logger;
@@ -630,6 +634,75 @@ class TarReader implements Closeable {
return -1;
}
+ @Nonnull
+ private TarEntry[] getEntries() {
+ TarEntry[] entries = new TarEntry[index.remaining() / 24];
+ int position = index.position();
+ for (int i = 0; position < index.limit(); i++) {
+ entries[i] = new TarEntry(
+ index.getLong(position),
+ index.getLong(position + 8),
+ index.getInt(position + 16),
+ index.getInt(position + 20));
+ position += 24;
+ }
+ Arrays.sort(entries, TarEntry.OFFSET_ORDER);
+ return entries;
+ }
+
+ @CheckForNull
+ private List<UUID> getReferences(TarEntry entry, UUID id, Map<UUID, List<UUID>> graph) throws IOException {
+ if (graph != null) {
+ return graph.get(id);
+ } else {
+ // a pre-compiled graph is not available, so read the
+ // references directly from this segment
+ ByteBuffer segment = access.read(
+ entry.offset(),
+ Math.min(entry.size(), 16 * 256));
+ int pos = segment.position();
+ int refCount = segment.get(pos + REF_COUNT_OFFSET) & 0xff;
+ int refEnd = pos + 16 * (refCount + 1);
+ List<UUID> refIds = newArrayList();
+ for (int refPos = pos + 16; refPos < refEnd; refPos += 16) {
+ refIds.add(new UUID(
+ segment.getLong(refPos),
+ segment.getLong(refPos + 8)));
+ }
+ return refIds;
+ }
+ }
+
+ /**
+ * Build the graph of segments reachable from an initial set of segments
+ * @param referencedIds the initial set of segments
+ * @throws IOException
+ */
+ Map<UUID, Set<UUID>> getReferenceGraph(Set<UUID> referencedIds) throws IOException {
+ Map<UUID, List<UUID>> graph = getGraph();
+ Map<UUID, Set<UUID>> refGraph = newHashMap();
+
+ TarEntry[] entries = getEntries();
+ for (int i = entries.length - 1; i >= 0; i--) {
+ TarEntry entry = entries[i];
+ UUID id = new UUID(entry.msb(), entry.lsb());
+ if (!referencedIds.remove(id)) {
+ // this segment is not referenced anywhere
+ entries[i] = null;
+ } else {
+ if (isDataSegmentId(entry.lsb())) {
+ // this is a referenced data segment, so follow the graph
+ List<UUID> refIds = getReferences(entry, id, graph);
+ if (refIds != null) {
+ refGraph.put(id, new HashSet<UUID>(refIds));
+ referencedIds.addAll(refIds);
+ }
+ }
+ }
+ }
+ return refGraph;
+ }
+
/**
* Garbage collects segments in this file. First it collects the set of
* segments that are referenced / reachable, then (if more than 25% is
@@ -652,52 +725,25 @@ class TarReader implements Closeable {
Set<UUID> cleaned = newHashSet();
Map<UUID, List<UUID>> graph = getGraph();
-
- TarEntry[] sorted = new TarEntry[index.remaining() / 24];
- int position = index.position();
- for (int i = 0; position < index.limit(); i++) {
- sorted[i] = new TarEntry(
- index.getLong(position),
- index.getLong(position + 8),
- index.getInt(position + 16),
- index.getInt(position + 20));
- position += 24;
- }
- Arrays.sort(sorted, TarEntry.OFFSET_ORDER);
+ TarEntry[] entries = getEntries();
int size = 0;
int count = 0;
- for (int i = sorted.length - 1; i >= 0; i--) {
- TarEntry entry = sorted[i];
+ for (int i = entries.length - 1; i >= 0; i--) {
+ TarEntry entry = entries[i];
UUID id = new UUID(entry.msb(), entry.lsb());
if (!referencedIds.remove(id)) {
// this segment is not referenced anywhere
cleaned.add(id);
- sorted[i] = null;
+ entries[i] = null;
} else {
size += getEntrySize(entry.size());
count += 1;
if (isDataSegmentId(entry.lsb())) {
// this is a referenced data segment, so follow the graph
- if (graph != null) {
- List<UUID> refids = graph.get(id);
- if (refids != null) {
- referencedIds.addAll(refids);
- }
- } else {
- // a pre-compiled graph is not available, so read the
- // references directly from this segment
- ByteBuffer segment = access.read(
- entry.offset(),
- Math.min(entry.size(), 16 * 256));
- int pos = segment.position();
- int refcount = segment.get(pos + REF_COUNT_OFFSET) & 0xff;
- int refend = pos + 16 * (refcount + 1);
- for (int refpos = pos + 16; refpos < refend; refpos += 16) {
- referencedIds.add(new UUID(
- segment.getLong(refpos),
- segment.getLong(refpos + 8)));
- }
+ List<UUID> refIds = getReferences(entry, id, graph);
+ if (refIds != null) {
+ referencedIds.addAll(refIds);
}
}
}
@@ -733,7 +779,7 @@ class TarReader implements Closeable {
log.debug("Writing new generation {}", newFile.getName());
TarWriter writer = new TarWriter(newFile);
- for (TarEntry entry : sorted) {
+ for (TarEntry entry : entries) {
if (entry != null) {
byte[] data = new byte[entry.size()];
access.read(entry.offset(), entry.size()).get(data);
Modified: jackrabbit/oak/trunk/oak-run/README.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/README.md?rev=1710862&r1=1710861&r2=1710862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-run/README.md (original)
+++ jackrabbit/oak/trunk/oak-run/README.md Tue Oct 27 18:49:35 2015
@@ -14,6 +14,7 @@ The following runmodes are currently ava
* server : Run the Oak Server.
* console : Start an interactive console.
* explore : Starts a GUI browser based on java swing.
+ * graph : Export the segment graph of a segment store to a file.
* check : Check the FileStore for inconsistencies
* primary : Run a TarMK Cold Standby primary instance
* standby : Run a TarMK Cold Standby standby instance
@@ -95,6 +96,29 @@ browsing of an existing oak repository.
$ java -jar oak-run-*.jar explore /path/to/oak/repository [skip-size-check]
+Graph
+-----
+
+The 'graph' mode export the segment graph of a file store to a text file in the
+[Guess GDF format](https://gephi.github.io/users/supported-graph-formats/gdf-format/),
+which is easily imported into [Gephi](https://gephi.github.io).
+
+As the GDF format only supports integer values but the segment time stamps are encoded as long
+values an optional 'epoch' argument can be specified. If no epoch is given on the command line
+the start of the day of the last modified date of the 'journal.log' is used. The epoch specifies
+a negative offset translating all timestamps into a valid int range.
+
+ $ java -jar oak-run-*.jar graph [File] <options>
+
+ [File] -- Path to segment store (required)
+
+ Option Description
+ ------ -----------
+ --epoch <Long> Epoch of the segment time stamps
+ (derived from journal.log if not
+ given)
+ --output <File> Output file (default: segments.gdf)
+
Check
-----
Modified: jackrabbit/oak/trunk/oak-run/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/pom.xml?rev=1710862&r1=1710861&r2=1710862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-run/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-run/pom.xml Tue Oct 27 18:49:35 2015
@@ -361,6 +361,11 @@
<version>1.0.6</version>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.2.4</version>
+ </dependency>
<dependency>
<groupId>org.apache.tika</groupId>
Modified: jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/segment/FileStoreHelper.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/segment/FileStoreHelper.java?rev=1710862&r1=1710861&r2=1710862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/segment/FileStoreHelper.java (original)
+++ jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/segment/FileStoreHelper.java Tue Oct 27 18:49:35 2015
@@ -18,27 +18,40 @@
*/
package org.apache.jackrabbit.oak.plugins.segment;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.Collections.reverseOrder;
+import static java.util.Collections.singleton;
import static java.util.Collections.sort;
+import static org.apache.jackrabbit.oak.plugins.segment.SegmentId.isDataSegmentId;
import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayDeque;
+import java.util.Date;
import java.util.Deque;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.plugins.segment.file.FileStore;
+import org.apache.jackrabbit.oak.plugins.segment.file.FileStore.ReadOnlyStore;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
-public class FileStoreHelper {
+public final class FileStoreHelper {
- public final static String newline = "\n";
+ public static final String newline = "\n";
private FileStoreHelper() {
}
@@ -94,4 +107,120 @@ public class FileStoreHelper {
}
}
+ /**
+ * Write the segment graph of a file store to a stream.
+ * <p>
+ * The graph is written in
+ * <a href="https://gephi.github.io/users/supported-graph-formats/gdf-format/">the Guess GDF format</a>,
+ * which is easily imported into <a href="https://gephi.github.io/">Gephi</a>.
+ * As GDF only supports integers but the segment time stamps are encoded as long
+ * the {@code epoch} argument is used as a negative offset translating all timestamps
+ * into a valid int range.
+ *
+ * @param fileStore file store to graph
+ * @param out stream to write the graph to
+ * @param epoch epoch (in milliseconds)
+ * @throws Exception
+ */
+ public static void writeSegmentGraph(ReadOnlyStore fileStore, OutputStream out, Date epoch) throws Exception {
+ PrintWriter writer = new PrintWriter(out);
+ try {
+ SegmentNodeState root = fileStore.getHead();
+
+ // Segment graph starting from the segment containing root
+ Map<UUID, Set<UUID>> segmentGraph = fileStore.getSegmentGraph(new HashSet<UUID>(singleton(root.getRecordId().asUUID())));
+
+ // All segment in the segment graph
+ Set<UUID> segments = newHashSet();
+ segments.addAll(segmentGraph.keySet());
+ for (Set<UUID> tos : segmentGraph.values()) {
+ segments.addAll(tos);
+ }
+
+ // Graph of segments containing the head state
+ Map<UUID, Set<UUID>> headGraph = newHashMap();
+ collectSegments(root, headGraph);
+
+ // All segments containing the head state
+ Set<UUID> headSegments = newHashSet();
+ for (Entry<UUID, Set<UUID>> entry : headGraph.entrySet()) {
+ headSegments.add(entry.getKey());
+ headSegments.addAll(entry.getValue());
+ }
+
+ writer.write("nodedef>name VARCHAR, label VARCHAR, type VARCHAR, wid VARCHAR, gc INT, t INT, head BOOLEAN\n");
+ for (UUID segment : segments) {
+ writeNode(segment, writer, headSegments.contains(segment), epoch, fileStore.getTracker());
+ }
+
+ writer.write("edgedef>node1 VARCHAR, node2 VARCHAR, head BOOLEAN\n");
+ for (Entry<UUID, Set<UUID>> edge : segmentGraph.entrySet()) {
+ UUID from = edge.getKey();
+ for (UUID to : edge.getValue()) {
+ Set<UUID> he = headGraph.get(from);
+ boolean inHead = he != null && he.contains(to);
+ writer.write(from + "," + to + "," + inHead + "\n");
+ }
+ }
+ } finally {
+ writer.close();
+ }
+ }
+
+ private static void collectSegments(SegmentNodeState root, Map<UUID, Set<UUID>> graph) {
+ UUID nodeId = root.getRecordId().asUUID();
+ Set<UUID> refs = graph.get(nodeId);
+ if (refs == null) {
+ refs = newHashSet();
+ graph.put(nodeId, refs);
+ }
+
+ for (PropertyState propertyState : root.getProperties()) {
+ if (propertyState instanceof SegmentPropertyState) {
+ SegmentPropertyState sps = (SegmentPropertyState) propertyState;
+ refs.add(sps.getRecordId().getSegmentId().asUUID());
+ }
+
+ }
+
+ for (ChildNodeEntry childNodeEntry : root.getChildNodeEntries()) {
+ if (childNodeEntry.getNodeState() instanceof SegmentNodeState) {
+ SegmentNodeState child = (SegmentNodeState) childNodeEntry.getNodeState();
+ refs.add(child.getRecordId().getSegmentId().asUUID());
+ collectSegments(child, graph);
+ }
+ }
+ }
+
+ private static void writeNode(UUID node, PrintWriter writer, boolean inHead, Date epoch, SegmentTracker tracker) {
+ JsonObject sInfo = getSegmentInfo(node, tracker);
+ if (sInfo == null) {
+ writer.write(node + ",b,bulk,b,-1,-1," + inHead + "\n");
+ } else {
+ long t = sInfo.get("t").getAsLong();
+ long ts = t - epoch.getTime();
+ checkArgument(ts >= Integer.MIN_VALUE && ts <= Integer.MAX_VALUE,
+ "Time stamp (" + new Date(t) + ") not in epoch (" +
+ new Date(epoch.getTime() + Integer.MIN_VALUE) + " - " +
+ new Date(epoch.getTime() + Integer.MAX_VALUE) + ")");
+ writer.write(node +
+ "," + sInfo.get("sno").getAsString() +
+ ",data" +
+ "," + sInfo.get("wid").getAsString() +
+ "," + sInfo.get("gc").getAsString() +
+ "," + ts +
+ "," + inHead + "\n");
+ }
+ }
+
+ private static JsonObject getSegmentInfo(UUID node, SegmentTracker tracker) {
+ if (isDataSegmentId(node.getLeastSignificantBits())) {
+ SegmentId id = tracker.getSegmentId(node.getMostSignificantBits(), node.getLeastSignificantBits());
+ String info = id.getSegment().getSegmentInfo();
+ return info == null ? null : new JsonParser().parse(info).getAsJsonObject();
+ } else {
+ return null;
+ }
+ }
+
}
Modified: jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java?rev=1710862&r1=1710861&r2=1710862&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java (original)
+++ jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java Tue Oct 27 18:49:35 2015
@@ -27,6 +27,7 @@ import static org.slf4j.LoggerFactory.ge
import java.io.Closeable;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
@@ -34,6 +35,7 @@ import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -67,6 +69,7 @@ import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.time.DateUtils;
import org.apache.jackrabbit.oak.Oak;
import org.apache.jackrabbit.oak.api.ContentRepository;
import org.apache.jackrabbit.oak.benchmark.BenchmarkRunner;
@@ -95,6 +98,7 @@ import org.apache.jackrabbit.oak.plugins
import org.apache.jackrabbit.oak.plugins.document.util.MapDBMapFactory;
import org.apache.jackrabbit.oak.plugins.document.util.MapFactory;
import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection;
+import org.apache.jackrabbit.oak.plugins.segment.FileStoreHelper;
import org.apache.jackrabbit.oak.plugins.segment.RecordId;
import org.apache.jackrabbit.oak.plugins.segment.RecordUsageAnalyser;
import org.apache.jackrabbit.oak.plugins.segment.Segment;
@@ -105,6 +109,7 @@ import org.apache.jackrabbit.oak.plugins
import org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStrategy;
import org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStrategy.CleanupType;
import org.apache.jackrabbit.oak.plugins.segment.file.FileStore;
+import org.apache.jackrabbit.oak.plugins.segment.file.FileStore.ReadOnlyStore;
import org.apache.jackrabbit.oak.plugins.segment.file.JournalReader;
import org.apache.jackrabbit.oak.plugins.segment.standby.client.StandbyClient;
import org.apache.jackrabbit.oak.plugins.segment.standby.server.StandbyServer;
@@ -165,6 +170,9 @@ public final class Main {
case DEBUG:
debug(args);
break;
+ case GRAPH:
+ graph(args);
+ break;
case CHECK:
check(args);
break;
@@ -777,6 +785,53 @@ public final class Main {
}
}
+ private static void graph(String[] args) throws Exception {
+ OptionParser parser = new OptionParser();
+ OptionSpec<File> directoryArg = parser.nonOptions(
+ "Path to segment store (required)").ofType(File.class);
+ OptionSpec<File> outFileArg = parser.accepts(
+ "output", "Output file").withRequiredArg().ofType(File.class)
+ .defaultsTo(new File("segments.gdf"));
+ OptionSpec<Long> epochArg = parser.accepts(
+ "epoch", "Epoch of the segment time stamps (derived from journal.log if not given)")
+ .withRequiredArg().ofType(Long.class);
+ OptionSet options = parser.parse(args);
+
+ File directory = directoryArg.value(options);
+ if (directory == null) {
+ System.err.println("Dump the segment graph to a file. Usage: graph [File] <options>");
+ parser.printHelpOn(System.err);
+ System.exit(-1);
+ }
+ if (!isValidFileStore(directory.getPath())) {
+ System.err.println("Invalid FileStore directory " + directory);
+ System.exit(1);
+ }
+
+ File outFile = outFileArg.value(options);
+ Date epoch;
+ if (options.has(epochArg)) {
+ epoch = new Date(epochArg.value(options));
+ } else {
+ epoch = new Date(new File(directory, "journal.log").lastModified());
+ epoch = DateUtils.setHours(epoch, 0);
+ epoch = DateUtils.setMinutes(epoch, 0);
+ epoch = DateUtils.setSeconds(epoch, 0);
+ epoch = DateUtils.setMilliseconds(epoch, 0);
+ }
+
+ System.out.println("Opening file store at " + directory);
+ ReadOnlyStore fileStore = new ReadOnlyStore(directory);
+
+ if (outFile.exists()) {
+ outFile.delete();
+ }
+
+ System.out.println("Setting epoch to " + epoch);
+ System.out.println("Writing graph to " + outFile);
+ FileStoreHelper.writeSegmentGraph(fileStore, new FileOutputStream(outFile), epoch);
+ }
+
private static void check(String[] args) throws IOException {
OptionParser parser = new OptionParser();
ArgumentAcceptingOptionSpec<String> path = parser.accepts(
@@ -1191,6 +1246,7 @@ public final class Main {
BENCHMARK("benchmark"),
CONSOLE("console"),
DEBUG("debug"),
+ GRAPH("graph"),
CHECK("check"),
COMPACT("compact"),
SERVER("server"),