Author: mduerig
Date: Tue Mar 27 14:34:31 2018
New Revision: 1827841

URL: http://svn.apache.org/viewvc?rev=1827841&view=rev
Log:
OAK-5655: TarMK: Analyse locality of reference
Utility for collecting IO traces for given read access patterns

Added:
    
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/
    
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/BreathFirstTrace.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracer.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/Trace.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/
    
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/
    
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/BreathFirstTraceTest.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracerRunner.java
    
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracerTest.java

Added: 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/BreathFirstTrace.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/BreathFirstTrace.java?rev=1827841&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/BreathFirstTrace.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/BreathFirstTrace.java
 Tue Mar 27 14:34:31 2018
@@ -0,0 +1,121 @@
+/*
+ * 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.jackrabbit.oak.segment.tool.iotrace;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Lists.newLinkedList;
+import static java.lang.String.valueOf;
+import static java.util.Collections.singleton;
+import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
+
+import java.io.Writer;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * A breath first traversal trace. 
+ * <p>
+ * When {@link Trace#run(NodeState) run} this trace performs a breath first 
traversal starting
+ * from the passed node up to a certain depth. It logs the current depth and 
the number of
+ * traversed nodes as additional {@link IOTracer#setContext(List) context}.
+ */
+public class BreathFirstTrace implements Trace {
+
+    /**
+     * The context specification of this trace.
+     * @see IOTracer#newIOTracer(Function, Writer, String)
+     */
+    @Nonnull
+    public static final String CONTEXT_SPEC = "depth,count";
+
+    private final int depth;
+
+    @Nonnull
+    private final String path;
+
+    @Nonnull
+    private final Consumer<List<String>> context;
+
+    @Nonnull
+    private final AtomicInteger nodeCount = new AtomicInteger();
+
+    /**
+     * Create a new instance of a breath first traversal trace.
+     * @param depth     maximal depth of the nodes to traverse
+     * @param path      path of the root node where to start traversing
+     * @param context   consumer to pass the additional context to
+     */
+    public BreathFirstTrace(int depth, @Nonnull String path, @Nonnull 
Consumer<List<String>> context) {
+        checkArgument(depth >= 0);
+
+        this.depth = depth;
+        this.path = path;
+        this.context = context;
+    }
+
+    @Override
+    public void run(@Nonnull NodeState node) {
+        updateContext(context, 0, nodeCount.incrementAndGet());
+        traverse(newLinkedList(singleton(getNode(node, path))), 0);
+    }
+
+    @Nonnull
+    private static NodeState getNode(@Nonnull NodeState root, @Nonnull String 
path) {
+        NodeState node = root;
+        for (String name : elements(path)) {
+            node = node.getChildNode(name);
+        }
+        return node;
+    }
+
+    private void traverse(Queue<NodeState> nodes, int depth) {
+        if (!nodes.isEmpty()) {
+            Queue<NodeState> children = newLinkedList();
+            while (!nodes.isEmpty()) {
+                NodeState head = nodes.poll();
+                assert head != null;
+                if (depth < this.depth) {
+                    head.getChildNodeEntries().forEach(
+                        cse -> {
+                            updateContext(context, depth + 1, 
nodeCount.incrementAndGet());
+                            NodeState child = cse.getNodeState();
+                            if (depth + 1 < this.depth) {
+                                // Only add to children queue if not at last 
level to save memory
+                                children.offer(child);
+                            }
+                        });
+                }
+            }
+            traverse(children, depth + 1);
+        }
+    }
+
+    private static void updateContext(@Nonnull Consumer<List<String>> context, 
int depth, int count) {
+        context.accept(ImmutableList.of(valueOf(depth), valueOf(count)));
+    }
+
+}

Added: 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracer.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracer.java?rev=1827841&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracer.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracer.java
 Tue Mar 27 14:34:31 2018
@@ -0,0 +1,205 @@
+/*
+ * 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.jackrabbit.oak.segment.tool.iotrace;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Queues.newConcurrentLinkedQueue;
+import static java.lang.String.join;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.Flushable;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.jackrabbit.oak.segment.file.FileStore;
+import org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitor;
+import org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitorAdapter;
+
+/**
+ * This utility class allows collecting IO traces of read accesses to segments
+ * caused by reading specific items.
+ * <p>
+ * An instance of {@link Trace} is used to specify a read pattern. Segment 
reads
+ * are recorded in CSV format:
+ <pre>
+ timestamp,file,segmentId,length,elapsed
+ 
1522147945084,data01415a.tar,f81378df-b3f8-4b25-0000-00000002c450,181328,171849
+ 
1522147945096,data01415a.tar,f81378df-b3f8-4b25-0000-00000002c450,181328,131272
+ 
1522147945097,data01415a.tar,f81378df-b3f8-4b25-0000-00000002c450,181328,142766
+ ...
+ </pre>
+ * {@link Trace} implementations can specify an additional context, which is 
recorded
+ * with each line of the CSV output. A context is simply a list of additional 
fields
+ * as specified during instantiation of an {@code IOTracer}.
+ */
+public class IOTracer {
+    @Nonnull
+    private final Function<IOMonitor, FileStore> fileStoreFactory;
+
+    @Nonnull
+    private final IOTraceMonitor ioMonitor;
+
+    private IOTracer(
+            @Nonnull Function<IOMonitor, FileStore> fileStoreFactory,
+            @Nonnull Writer output,
+            @Nonnull String contextSpec) {
+        this.fileStoreFactory = checkNotNull(fileStoreFactory);
+        ioMonitor = new IOTraceMonitor(checkNotNull(output), 
checkNotNull(contextSpec));
+    }
+
+    /**
+     * Create a new {@code IOTracer} instance.
+     * @param fileStoreFactory  A factory for creating a {@link FileStore} 
with the
+     *                          passed {@link IOMonitor} for monitoring 
segment IO.
+     * @param output            The target for the CSV formatted IO trace.
+     * @param contextSpec       The specification of additional context 
provided by
+     *                          the {@link Trace traces} being {@link 
IOTracer#collectTrace(Trace) run}.
+     *                          A trace consists of a comma separated list of 
values, which must match
+     *                          the list of values passed to {@link 
IOTracer#setContext(List)}.
+     * @return A new {@code IOTracer} instance.
+     */
+    @Nonnull
+    public static IOTracer newIOTracer(
+            @Nonnull Function<IOMonitor, FileStore> fileStoreFactory,
+            @Nonnull Writer output,
+            @Nonnull String contextSpec) {
+        return new IOTracer(fileStoreFactory, output, contextSpec);
+    }
+
+    /**
+     * Collect a IO trace.
+     * @param trace
+     */
+    public void collectTrace(@Nonnull Trace trace) {
+        checkNotNull(trace);
+        try (FileStore fileStore = 
checkNotNull(fileStoreFactory).apply(checkNotNull(ioMonitor))) {
+            trace.run(fileStore.getHead());
+        } finally {
+            ioMonitor.flush();
+        }
+    }
+
+    /**
+     * Set the {@code context} to be added to each line of the IOTrace going 
forward. The list
+     * of values needs to match the context specification passed to
+     * {@link IOTracer#newIOTracer(Function, Writer, String)}.
+     * @param context
+     */
+    public void setContext(@Nonnull List<String> context) {
+        ioMonitor.setContext(context);
+    }
+
+    private static class IOTraceMonitor extends IOMonitorAdapter implements 
Flushable {
+        @Nonnull
+        private final AtomicReference<List<String>> context =
+                new AtomicReference<>(ImmutableList.of());
+
+        @Nonnull
+        private final PrintWriter out;
+
+        @Nonnull
+        private final Lock ioLock = new ReentrantLock();
+
+        @Nonnull
+        private final ConcurrentLinkedQueue<IOEvent> ioEvents = 
newConcurrentLinkedQueue();
+
+        public IOTraceMonitor(@Nonnull Writer writer, @Nonnull String 
contextSpec) {
+            out = new PrintWriter(new BufferedWriter(writer));
+            out.println(IOEvent.getFields(contextSpec));
+        }
+
+        public void setContext(@Nonnull List<String> context) {
+            this.context.set(context);
+        }
+
+        @Override
+        public void afterSegmentRead(@Nonnull File file, long msb, long lsb, 
int length, long elapsed) {
+            ioEvents.add(new IOEvent(
+                    file.getName(), msb, lsb, length, elapsed, context.get()));
+            if (ioLock.tryLock()) {
+                try {
+                    flush();
+                } finally {
+                    ioLock.unlock();
+                }
+            }
+        }
+
+        @Override
+        public void flush() {
+            ioLock.lock();
+            try {
+                while (!ioEvents.isEmpty()) {
+                    out.println(Objects.toString(ioEvents.poll()));
+                }
+                out.flush();
+            } finally {
+                ioLock.unlock();
+            }
+        }
+
+        private static class IOEvent {
+            @Nonnull private final String fileName;
+            private final long msb;
+            private final long lsb;
+            private final int length;
+            private final long elapsed;
+            @Nonnull private final List<String> context;
+
+            private IOEvent(
+                    @Nonnull String fileName,
+                    long msb,
+                    long lsb,
+                    int length,
+                    long elapsed,
+                    @Nonnull List<String> context)
+            {
+                this.fileName = fileName;
+                this.msb = msb;
+                this.lsb = lsb;
+                this.length = length;
+                this.elapsed = elapsed;
+                this.context = context;
+            }
+
+            public static String getFields(@Nonnull String contextSpec) {
+                return "timestamp,file,segmentId,length,elapsed," + 
contextSpec;
+            }
+
+            @Override
+            public String toString() {
+                return System.currentTimeMillis() + "," +  fileName + "," +
+                        new UUID(msb, length) + "," + length + "," + elapsed + 
"," +
+                        join(",", context);
+            }
+        }
+    }
+}

Added: 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/Trace.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/Trace.java?rev=1827841&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/Trace.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/tool/iotrace/Trace.java
 Tue Mar 27 14:34:31 2018
@@ -0,0 +1,36 @@
+/*
+ * 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.jackrabbit.oak.segment.tool.iotrace;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * An instance of a {@code Trace} specifies a read pattern for tracing
+ * IO reads of segments with an {@link IOTracer} instance.
+ */
+interface Trace {
+
+    /**
+     * Run this trace on the passed {@code node}.
+     * @param node
+     */
+    void run(@Nonnull NodeState node);
+}

Added: 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/BreathFirstTraceTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/BreathFirstTraceTest.java?rev=1827841&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/BreathFirstTraceTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/BreathFirstTraceTest.java
 Tue Mar 27 14:34:31 2018
@@ -0,0 +1,83 @@
+/*
+ * 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.jackrabbit.oak.segment.tool.iotrace;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.Test;
+
+public class BreathFirstTraceTest {
+
+    @Nonnull
+    private static NodeState createTree(int depth) {
+        NodeBuilder root = EMPTY_NODE.builder();
+        NodeBuilder child = root;
+        for (int k = 0 ; k < depth; k++) {
+            child = child.setChildNode("node-" + k);
+        }
+        return root.getNodeState();
+    }
+
+    @Test
+    public void testTraverseEmptyTree() {
+        List<List<String>> trace = newArrayList();
+        new BreathFirstTrace(4, "/", trace::add).run(createTree(0));
+        assertEquals(1, trace.size());
+        assertEquals(ImmutableList.of("0", "1"), trace.get(0));
+    }
+
+    @Test
+    public void testTraverseDepth1Tree() {
+        List<List<String>> trace = newArrayList();
+        new BreathFirstTrace(4, "/", trace::add).run(createTree(1));
+        assertEquals(2, trace.size());
+        assertEquals(ImmutableList.of("0", "1"), trace.get(0));
+        assertEquals(ImmutableList.of("1", "2"), trace.get(1));
+    }
+
+    @Test
+    public void testTraverseDepth2Tree() {
+        List<List<String>> trace = newArrayList();
+        new BreathFirstTrace(4, "/", trace::add).run(createTree(2));
+        assertEquals(3, trace.size());
+        assertEquals(ImmutableList.of("0", "1"), trace.get(0));
+        assertEquals(ImmutableList.of("1", "2"), trace.get(1));
+        assertEquals(ImmutableList.of("2", "3"), trace.get(2));
+    }
+
+    @Test
+    public void testTraverseDepth3TreeWithLimit2() {
+        List<List<String>> trace = newArrayList();
+        new BreathFirstTrace(2, "/", trace::add).run(createTree(3));
+        assertEquals(3, trace.size());
+        assertEquals(ImmutableList.of("0", "1"), trace.get(0));
+        assertEquals(ImmutableList.of("1", "2"), trace.get(1));
+        assertEquals(ImmutableList.of("2", "3"), trace.get(2));
+    }
+
+}

Added: 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracerRunner.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracerRunner.java?rev=1827841&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracerRunner.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracerRunner.java
 Tue Mar 27 14:34:31 2018
@@ -0,0 +1,131 @@
+/*
+ * 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.jackrabbit.oak.segment.tool.iotrace;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.Boolean.parseBoolean;
+import static java.lang.Integer.getInteger;
+import static java.lang.String.format;
+import static java.lang.System.getProperty;
+import static 
org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder;
+import static 
org.apache.jackrabbit.oak.segment.tool.iotrace.IOTracer.newIOTracer;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.segment.file.FileStore;
+import org.apache.jackrabbit.oak.segment.file.FileStoreBuilder;
+import org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException;
+import org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitor;
+import org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitorAdapter;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * This test case can be used to collect {@link IOTracer io traces}. It is 
disabled
+ * by default and needs to be enabled via {@code -Dtest=IOTraceRunner}.
+ * <br>
+ * The test accepts the following properties:<br>
+ * {@code -Dinput=/path/to/segmentstore}. Required.<br>
+ * {@code -Doutput=/path/to/trace.cvs}. Default: {@code iotrace.csv}<br>
+ * {@code -Dmmap=true|false}. Default {@code true}<br>
+ * {@code -Dsegment-cache=n}. Default {@code 256}<br>
+ * {@code -Ddepth=n}. Default {@code 5}<br>
+ * {@code -Dpath=/path/to/start/node}. Default {@code /root}
+ * <p>
+ * FIXME OAK-5655: Turn this into a development tool and move to the right 
place.
+ */
+public class IOTracerRunner extends IOMonitorAdapter {
+    private static final boolean ENABLED =
+            IOTracerRunner.class.getSimpleName().equals(getProperty("test"));
+
+    private static final String OUTPUT = getProperty("output", "iotrace.csv");
+
+    private static final String INPUT = getProperty("segmentstore");
+
+    private static final boolean MMAP = parseBoolean(getProperty("mmap", 
"true"));
+
+    private static final int SEGMENT_CACHE = getInteger("segment-cache", 256);
+
+    private static final int DEPTH = getInteger("depth", 5);
+
+    private static final String PATH = getProperty("path", "/root");
+
+    @Before
+    public void setup() throws Exception {
+        assumeTrue(
+            format("Test disabled. Specify -Dtest=%s to enable it", 
IOTracerRunner.class.getSimpleName()),
+            ENABLED);
+    }
+
+    @Test
+    public void collectTrace() throws IOException, 
InvalidFileStoreVersionException {
+        checkArgument(INPUT != null, "No segment store directory specified");
+        System.out.println(format(
+                "Breath first traversing %d levels of %s starting at %s", 
DEPTH, INPUT, PATH));
+        System.out.println(
+                format("mmap=%b, segment cache=%d", MMAP, SEGMENT_CACHE));
+        System.out.println(format("Writing trace to %s", OUTPUT));
+
+        collectTrace(INPUT, MMAP, SEGMENT_CACHE, PATH, DEPTH, OUTPUT);
+    }
+
+    public void collectTrace(
+            @Nonnull String segmentStore,
+            boolean mmap,
+            int segmentCacheSize,
+            @Nonnull String path,
+            int depth,
+            @Nonnull String output)
+    throws IOException, InvalidFileStoreVersionException {
+        checkNotNull(segmentStore);
+        checkNotNull(path);
+        checkNotNull(output);
+
+        try (PrintWriter out = new PrintWriter(new BufferedWriter(new 
FileWriter(output, true)))) {
+            Function<IOMonitor, FileStore> factory = ioMonitor ->
+                    newFileStore(
+                            fileStoreBuilder(new File(segmentStore))
+                                    .withMemoryMapping(mmap)
+                                    .withSegmentCacheSize(segmentCacheSize)
+                                    .withIOMonitor(ioMonitor));
+
+            IOTracer ioTracer = newIOTracer(factory, out, 
BreathFirstTrace.CONTEXT_SPEC);
+            ioTracer.collectTrace(
+                    new BreathFirstTrace(depth, path, ioTracer::setContext));
+        }
+    }
+
+    @Nonnull
+    private static FileStore newFileStore(FileStoreBuilder fileStoreBuilder) {
+        try {
+            return fileStoreBuilder.build();
+        } catch (InvalidFileStoreVersionException | IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}

Added: 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracerTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracerTest.java?rev=1827841&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracerTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/tool/iotrace/IOTracerTest.java
 Tue Mar 27 14:34:31 2018
@@ -0,0 +1,133 @@
+/*
+ * 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.jackrabbit.oak.segment.tool.iotrace;
+
+import static java.lang.Integer.parseInt;
+import static java.lang.Long.parseLong;
+import static java.lang.System.currentTimeMillis;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static 
org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder;
+import static 
org.apache.jackrabbit.oak.segment.tool.iotrace.IOTracer.newIOTracer;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.oak.segment.SegmentNodeBuilder;
+import org.apache.jackrabbit.oak.segment.SegmentNodeState;
+import org.apache.jackrabbit.oak.segment.file.FileStore;
+import org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException;
+import org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitor;
+import org.apache.jackrabbit.oak.segment.spi.monitor.IOMonitorAdapter;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class IOTracerTest extends IOMonitorAdapter {
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder(new File("target"));
+
+    @Before
+    public void setup() throws IOException, InvalidFileStoreVersionException {
+        try (FileStore fileStore = fileStoreBuilder(folder.getRoot()).build()) 
{
+            SegmentNodeState currentHead = fileStore.getHead();
+            SegmentNodeBuilder root = currentHead.builder();
+            root.setChildNode("0a");
+            root.setChildNode("0b");
+            NodeBuilder builder = root.setChildNode("0c");
+
+            builder.setChildNode("0d");
+            builder.setChildNode("0e");
+            builder = builder.setChildNode("0f");
+
+            builder.setChildNode("0g");
+            builder.setChildNode("0h");
+            builder.setChildNode("0i").setChildNode("0j");
+            SegmentNodeState newHead = root.getNodeState();
+            fileStore.getRevisions().setHead(currentHead.getRecordId(), 
newHead.getRecordId());
+        }
+    }
+
+    @Nonnull
+    private FileStore createFileStore(IOMonitor ioMonitor) {
+        try {
+            return fileStoreBuilder(folder.getRoot())
+                    .withSegmentCacheSize(0)
+                    .withIOMonitor(ioMonitor).build();
+        } catch (InvalidFileStoreVersionException | IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Test
+    public void collectTrace() throws IOException, 
InvalidFileStoreVersionException {
+        try (StringWriter out = new StringWriter()) {
+            Function<IOMonitor, FileStore> factory = this::createFileStore;
+
+            IOTracer ioTracer = newIOTracer(factory, out, 
BreathFirstTrace.CONTEXT_SPEC);
+            ioTracer.collectTrace(
+                    new BreathFirstTrace(2, "/", ioTracer::setContext));
+
+            try (BufferedReader reader = new BufferedReader(new 
StringReader(out.toString()))) {
+                Optional<String> header = reader.lines().findFirst();
+                List<String[]> entries = reader.lines()
+                    .map(line -> line.split(","))
+                    .collect(toList());
+
+                assertTrue(header.isPresent());
+                
assertEquals("timestamp,file,segmentId,length,elapsed,depth,count", 
header.get());
+
+                long now = currentTimeMillis();
+                assertTrue("The timestamps of all entries must be in the past",
+                    entries.stream()
+                        .map(row -> parseLong(row[0]))  // ts
+                        .allMatch(ts -> ts <= now));
+
+                assertEquals("Expected depths 0, 1 and 2",
+                        ImmutableSet.of(0, 1, 2),
+                        entries.stream()
+                            .map(row -> parseInt(row[5])) // depth
+                            .distinct().collect(toSet()));
+
+                assertEquals("Expected max 10 nodes",
+                        Optional.of(true),
+                        entries.stream()
+                            .map(row -> parseInt(row[6])) // count
+                            .max(Comparator.naturalOrder())
+                            .map(max -> max <= 10));
+            }
+        }
+    }
+
+}


Reply via email to