Author: mreutegg
Date: Thu Apr 20 14:16:25 2017
New Revision: 1792067

URL: http://svn.apache.org/viewvc?rev=1792067&view=rev
Log:
OAK-6109: Revision GC command in oak-run

Added:
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/RevisionsCommand.java
   (with props)
    
jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/RevisionsCommandTest.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java
    
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Utils.java

Modified: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java?rev=1792067&r1=1792066&r2=1792067&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/AvailableModes.java
 Thu Apr 20 14:16:25 2017
@@ -42,6 +42,7 @@ public final class AvailableModes {
             .put("history", new HistoryCommand())
             .put(IndexCommand.INDEX, new IndexCommand())
             .put(PersistentCacheCommand.PERSISTENTCACHE, new 
PersistentCacheCommand())
+            .put("revisions", new RevisionsCommand())
             .put("recovery", new RecoveryCommand())
             .put("repair", new RepairCommand())
             .put("resetclusterid", new ResetClusterIdCommand())

Added: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/RevisionsCommand.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/RevisionsCommand.java?rev=1792067&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/RevisionsCommand.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/RevisionsCommand.java
 Thu Apr 20 14:16:25 2017
@@ -0,0 +1,169 @@
+/*
+ * 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.run;
+
+import com.google.common.io.Closer;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+import joptsimple.OptionSpec;
+
+import org.apache.jackrabbit.oak.commons.TimeDurationFormatter;
+import org.apache.jackrabbit.oak.run.commons.Command;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.VersionGCOptions;
+import org.apache.jackrabbit.oak.plugins.document.VersionGarbageCollector;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+
+import static 
org.apache.jackrabbit.oak.plugins.document.util.Utils.timestampToString;
+
+/**
+ * Gives information about current node revisions state.
+ */
+public class RevisionsCommand implements Command {
+
+    private static class RevisionsOptions extends Utils.NodeStoreOptions {
+
+        public static final String CMD_INFO = "info";
+        public static final String CMD_COLLECT = "collect";
+        public static final String CMD_RESET = "reset";
+
+        public final OptionSpec<?> once;
+        public final OptionSpec<Integer> limit;
+        public final OptionSpec<Long> olderThan;
+
+        RevisionsOptions(String usage) {
+            super(usage);
+            once = parser.accepts("once", "only 1 iteration");
+            limit = parser
+                    .accepts("limit", "collect at most limit 
documents").withRequiredArg()
+                    .ofType(Integer.class).defaultsTo(-1);
+            olderThan = parser
+                    .accepts("olderThan", "collect only docs older than n 
seconds").withRequiredArg()
+                    .ofType(Long.class).defaultsTo(TimeUnit.DAYS.toSeconds(1));
+        }
+
+        public RevisionsOptions parse(String[] args) {
+            super.parse(args);
+            return this;
+        }
+
+        public String getSubCmd() {
+            List<String> args = getOtherArgs();
+            if (args.size() > 0) {
+                return args.get(0);
+            }
+            return "info";
+        }
+
+        public boolean runOnce() {
+            return options.has(once);
+        }
+
+        public int getLimit() {
+            return limit.value(options);
+        }
+
+        public long getOlderThan() {
+            return olderThan.value(options);
+        }
+    }
+
+    @Override
+    public void execute(String... args) throws Exception {
+        Closer closer = Closer.create();
+        RevisionsOptions options = new RevisionsOptions("revisions 
mongodb://host:port/database <subcmd> [options]\n"
+                + "where subcmd is one of\n"
+                + "  info     give information about the revisions state 
without performing any modifications\n"
+                + "  collect  perform garbage collection.\n"
+                + "  reset    clear all persisted metadata.\n"
+                + "the following options are recognized:\n"
+                + "  --limit n      collect at most n documents\n"
+                + "  --olderThan n  collect only documents older than n 
seconds\n"
+                + "  --once         run at maximum one iteration\n"
+        ).parse(args);
+
+        try {
+            String subCmd = options.getSubCmd();
+            NodeStore store = Utils.bootstrapNodeStore(options, closer);
+            if (!(store instanceof DocumentNodeStore)) {
+                System.err.println("revisions mode only available for 
DocumentNodeStore");
+                System.exit(1);
+            }
+            DocumentNodeStore dns = (DocumentNodeStore) store;
+            VersionGarbageCollector gc = dns.getVersionGarbageCollector();
+
+            VersionGCOptions gcOptions = gc.getOptions();
+            if (options.runOnce()) {
+                gcOptions = gcOptions.withMaxIterations(1);
+            }
+            if (options.getLimit() >= 0) {
+                gcOptions = gcOptions.withCollectLimit(options.getLimit());
+            }
+            gc.setOptions(gcOptions);
+
+            if (RevisionsOptions.CMD_INFO.equals(subCmd)) {
+                System.out.println("retrieving gc info");
+                VersionGarbageCollector.VersionGCInfo info = 
gc.getInfo(options.getOlderThan(), TimeUnit.SECONDS);
+
+                System.out.printf(Locale.US, "%21s  %s%n", "Last Successful 
Run:",
+                        info.lastSuccess > 0? fmtTimestamp(info.lastSuccess) : 
"<unknown>");
+                System.out.printf(Locale.US, "%21s  %s%n", "Oldest Revision:",
+                        fmtTimestamp(info.oldestRevisionEstimate));
+                System.out.printf(Locale.US, "%21s  %d%n", "Delete 
Candidates:",
+                        info.revisionsCandidateCount);
+                System.out.printf(Locale.US, "%21s  %d%n", "Collect Limit:",
+                        info.collectLimit);
+                System.out.printf(Locale.US, "%21s  %s%n", "Collect Interval:",
+                        fmtDuration(info.recommendedCleanupInterval));
+                System.out.printf(Locale.US, "%21s  %s%n", "Collect Before:",
+                        fmtTimestamp(info.recommendedCleanupTimestamp));
+                System.out.printf(Locale.US, "%21s  %d%n", "Iterations 
Estimate:",
+                        info.estimatedIterations);
+            }
+            else if (RevisionsOptions.CMD_COLLECT.equals(subCmd)) {
+                long started = System.currentTimeMillis();
+                System.out.println("starting gc collect");
+                VersionGarbageCollector.VersionGCStats stats = 
gc.gc(options.getOlderThan(), TimeUnit.SECONDS);
+                long ended = System.currentTimeMillis();
+                System.out.printf(Locale.US, "%21s  %s%n", "Started:", 
fmtTimestamp(started));
+                System.out.printf(Locale.US, "%21s  %s%n", "Ended:", 
fmtTimestamp(ended));
+                System.out.printf(Locale.US, "%21s  %s%n", "Duration:", 
fmtDuration(ended - started));
+                System.out.printf(Locale.US, "%21s  %s%n", "Stats:", 
stats.toString());
+            }
+            else if (RevisionsOptions.CMD_RESET.equals(subCmd)) {
+                System.out.println("resetting recommendations and statistics");
+                gc.reset();
+            }
+            else {
+                System.err.println("unknown revisions command: " + subCmd);
+            }
+        } catch (Throwable e) {
+            throw closer.rethrow(e);
+        } finally {
+            closer.close();
+        }
+    }
+
+    private String fmtTimestamp(long ts) {
+        return timestampToString(ts);
+    }
+
+    private String fmtDuration(long ts) {
+        return TimeDurationFormatter.forLogging().format(ts, 
TimeUnit.MILLISECONDS);
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/RevisionsCommand.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Utils.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Utils.java?rev=1792067&r1=1792066&r2=1792067&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Utils.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Utils.java
 Thu Apr 20 14:16:25 2017
@@ -24,6 +24,8 @@ import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.List;
@@ -58,39 +60,82 @@ import joptsimple.OptionSet;
 import joptsimple.OptionSpec;
 
 class Utils {
-    
+
     private static final long MB = 1024 * 1024;
 
-    public static NodeStore bootstrapNodeStore(String[] args, Closer closer, 
String h) throws IOException {
-        //TODO add support for other NodeStore flags
-        OptionParser parser = new OptionParser();
-        OptionSpec<Integer> clusterId = parser
-                .accepts("clusterId", "MongoMK clusterId").withRequiredArg()
-                .ofType(Integer.class).defaultsTo(0);
-        OptionSpec<Void> disableBranchesSpec = parser.
-                accepts("disableBranches", "disable branches");    
-        OptionSpec<Integer> cacheSizeSpec = parser.
-                accepts("cacheSize", "cache size").withRequiredArg().
-                ofType(Integer.class).defaultsTo(0);         
-        OptionSpec<?> help = parser.acceptsAll(asList("h", "?", "help"),
-                "show help").forHelp();
-        OptionSpec<String> nonOption = parser
-                .nonOptions(h);
+    public static class NodeStoreOptions {
 
-        OptionSet options = parser.parse(args);
-        List<String> nonOptions = nonOption.values(options);
+        public final OptionParser parser;
+        public final OptionSpec<Integer> clusterId;
+        public final OptionSpec<Void> disableBranchesSpec;
+        public final OptionSpec<Integer> cacheSizeSpec;
+        public final OptionSpec<?> help;
+        public final OptionSpec<String> nonOption;
+
+        protected OptionSet options;
+
+        public NodeStoreOptions(String usage) {
+            parser = new OptionParser();
+            clusterId = parser
+                    .accepts("clusterId", "MongoMK 
clusterId").withRequiredArg()
+                    .ofType(Integer.class).defaultsTo(0);
+            disableBranchesSpec = parser.
+                    accepts("disableBranches", "disable branches");
+            cacheSizeSpec = parser.
+                    accepts("cacheSize", "cache size").withRequiredArg().
+                    ofType(Integer.class).defaultsTo(0);
+            help = parser.acceptsAll(asList("h", "?", "help"),"show 
help").forHelp();
+            nonOption = parser.nonOptions(usage);
+        }
+
+        public NodeStoreOptions parse(String[] args) {
+            assert(options == null);
+            options = parser.parse(args);
+            return this;
+        }
+
+        public void printHelpOn(OutputStream sink) throws IOException {
+            parser.printHelpOn(sink);
+            System.exit(2);
+        }
+
+        public String getStoreArg() {
+            List<String> nonOptions = nonOption.values(options);
+            return nonOptions.size() > 0? nonOptions.get(0) : "";
+        }
+
+        public List<String> getOtherArgs() {
+            List<String> args = new 
ArrayList<String>(nonOption.values(options));
+            if (args.size() > 0) {
+                args.remove(0);
+            }
+            return args;
+        }
+
+        public int getClusterId() {
+            return clusterId.value(options);
+        }
 
-        if (options.has(help)) {
-            parser.printHelpOn(System.out);
-            System.exit(0);
+        public boolean disableBranchesSpec() {
+            return options.has(disableBranchesSpec);
         }
 
-        if (nonOptions.isEmpty()) {
-            parser.printHelpOn(System.err);
+        public int getCacheSize() {
+            return cacheSizeSpec.value(options);
+        }
+    }
+
+    public static NodeStore bootstrapNodeStore(String[] args, Closer closer, 
String h) throws IOException {
+        return bootstrapNodeStore(new NodeStoreOptions(h).parse(args), closer);
+    }
+
+    public static NodeStore bootstrapNodeStore(NodeStoreOptions options, 
Closer closer) throws IOException {
+        String src = options.getStoreArg();
+        if (src == null || src.length() == 0) {
+            options.printHelpOn(System.err);
             System.exit(1);
         }
 
-        String src = nonOptions.get(0);
         if (src.startsWith(MongoURI.MONGODB_PREFIX)) {
             MongoClientURI uri = new MongoClientURI(src);
             if (uri.getDatabase() == null) {
@@ -102,13 +147,13 @@ class Utils {
             closer.register(asCloseable(mongo));
             DocumentMK.Builder builder = new DocumentMK.Builder();
             builder.
-                setMongoDB(mongo.getDB()).
-                setLeaseCheck(false).
-                setClusterId(clusterId.value(options));
-            if (options.has(disableBranchesSpec)) {
+                    setMongoDB(mongo.getDB()).
+                    setLeaseCheck(false).
+                    setClusterId(options.getClusterId());
+            if (options.disableBranchesSpec()) {
                 builder.disableBranches();
             }
-            int cacheSize = cacheSizeSpec.value(options);
+            int cacheSize = options.getCacheSize();
             if (cacheSize != 0) {
                 builder.memoryCacheSize(cacheSize * MB);
             }
@@ -225,4 +270,4 @@ class Utils {
         }
         return props;
     }
-}
+}
\ No newline at end of file

Added: 
jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/RevisionsCommandTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/RevisionsCommandTest.java?rev=1792067&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/RevisionsCommandTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/RevisionsCommandTest.java
 Thu Apr 20 14:16:25 2017
@@ -0,0 +1,143 @@
+/*
+ * 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.run;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.jackrabbit.oak.plugins.document.Collection;
+import org.apache.jackrabbit.oak.plugins.document.Document;
+import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.MongoConnectionFactory;
+import org.apache.jackrabbit.oak.plugins.document.MongoUtils;
+import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+public class RevisionsCommandTest {
+
+    @Rule
+    public MongoConnectionFactory connectionFactory = new 
MongoConnectionFactory();
+
+    @Rule
+    public DocumentMKBuilderProvider builderProvider = new 
DocumentMKBuilderProvider();
+
+    private DocumentNodeStore ns;
+
+    @BeforeClass
+    public static void assumeMongoDB() {
+        assumeTrue(MongoUtils.isAvailable());
+    }
+
+    @Before
+    public void before() {
+        ns = createDocumentNodeStore();
+    }
+
+    @Test
+    public void info() throws Exception {
+        ns.getVersionGarbageCollector().gc(1, TimeUnit.HOURS);
+        ns.dispose();
+
+        String output = captureSystemOut(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    new RevisionsCommand().execute(MongoUtils.URL, "info");
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+        assertTrue(output.contains("Last Successful Run"));
+    }
+
+    @Test
+    public void reset() throws Exception {
+        ns.getVersionGarbageCollector().gc(1, TimeUnit.HOURS);
+
+        Document doc = ns.getDocumentStore().find(Collection.SETTINGS, 
"versionGC");
+        assertNotNull(doc);
+
+        ns.dispose();
+
+        String output = captureSystemOut(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    new RevisionsCommand().execute(MongoUtils.URL, "reset");
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+        assertTrue(output.contains("resetting recommendations and 
statistics"));
+
+        MongoConnection c = connectionFactory.getConnection();
+        ns = builderProvider.newBuilder().setMongoDB(c.getDB()).getNodeStore();
+        doc = ns.getDocumentStore().find(Collection.SETTINGS, "versionGC");
+        assertNull(doc);
+    }
+
+    @Test
+    public void collect() throws Exception {
+        ns.dispose();
+
+        String output = captureSystemOut(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    new RevisionsCommand().execute(MongoUtils.URL, "collect");
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+        assertTrue(output.contains("starting gc collect"));
+    }
+
+    private DocumentNodeStore createDocumentNodeStore() {
+        MongoConnection c = connectionFactory.getConnection();
+        MongoUtils.dropCollections(c.getDB().getName());
+        return 
builderProvider.newBuilder().setMongoDB(c.getDB()).getNodeStore();
+    }
+
+    private String captureSystemOut(Runnable r) {
+        PrintStream old = System.out;
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            PrintStream ps = new PrintStream(baos);
+            System.setOut(ps);
+            r.run();
+            System.out.flush();
+            return baos.toString();
+        } finally {
+            System.setOut(old);
+        }
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/RevisionsCommandTest.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to