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