blob: ce138c0a1b61117e81b2dc5e78807b79e6784d3c [file] [log] [blame]
/*
* 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 static java.util.Arrays.asList;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.jackrabbit.oak.run.commons.Command;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;
import org.h2.mvstore.type.StringDataType;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
public class PersistentCacheCommand implements Command {
public final static String PERSISTENTCACHE = "persistentcache";
@SuppressWarnings("unchecked")
@Override
public void execute(String... args) throws Exception {
OptionParser parser = new OptionParser();
OptionSpec<String> pathSpec = parser.accepts("path",
"only list entries starting with this path prefix").
withOptionalArg().defaultsTo("/");
OptionSpec<String> revisionSpec = parser.accepts("revision",
"only list revisions that start with this prefix").
withRequiredArg().defaultsTo("");
OptionSpec<String> mapSpec = parser.accepts("map",
"only print contents of this map").
withRequiredArg().defaultsTo("");
OptionSpec<Void> valuesSpec = parser.accepts("values",
"print values, not just keys and value lengths");
OptionSpec<Void> rawSpec = parser.accepts("raw",
"print raw data (tab separated map name, key, length, value)");
OptionSpec<String> outSpec = parser.accepts("out",
"print to this file instead of stdout").
withRequiredArg().defaultsTo("");
OptionSpec<?> helpSpec = parser.acceptsAll(
asList("h", "?", "help"), "show help").forHelp();
OptionSet options = parser.parse(args);
parser.nonOptions(
"persistent cache file (required)").ofType(File.class);
if (options.has(helpSpec)
|| options.nonOptionArguments().isEmpty()) {
System.out.println("Mode: " + PERSISTENTCACHE);
System.out.println("Map names and statistic are listed if just the file name is specified.");
System.out.println("To list all keys, just specify '/' and the file name.");
System.out.println("To dump multiples files in one go, add multiple file names.");
System.out.println("Files are accessed in read-only mode; " +
"to analyze a running system you need to copy the cache file first.");
System.out.println("Output format is CSV (',' replaced with '#')");
System.out.println("To import in H2, use: " +
"create table cache as select * from csvread('cache.csv', null, 'fieldDelimiter=')");
System.out.println();
parser.printHelpOn(System.out);
return;
}
String path = pathSpec.value(options);
String revision = revisionSpec.value(options);
String map = mapSpec.value(options);
boolean values = options.has(valuesSpec);
boolean raw = options.has(rawSpec);
String out = outSpec.value(options);
PrintWriter write = new PrintWriter(System.out);
if (out.length() > 0) {
write = new PrintWriter(new BufferedWriter(new FileWriter(out)));
}
for (String fileName : ((List<String>) options.nonOptionArguments())) {
dump(write, path, revision, map, fileName, values, raw);
}
write.flush();
}
static void dump(PrintWriter write, String path, String revision,
String map, String fileName, boolean values, boolean raw) {
MVStore s = new MVStore.Builder().readOnly().
fileName(fileName).open();
Map<String, String> meta = s.getMetaMap();
boolean statsOnly = "".equalsIgnoreCase(map) &&
"".equals(revision) &&
"".equals(path);
if (!statsOnly) {
if (raw) {
write.println("map" + "\t" + "key" + "\t" + "length" + "\t" + "value");
} else if (values) {
write.println("map,path,revision,p2,length,value");
} else {
write.println("map,path,revision,p2,length");
}
}
for (String n : meta.keySet()) {
if (n.startsWith("name.")) {
String mapName = n.substring(5, n.length());
if (map.length() > 0 && !map.equalsIgnoreCase(mapName)) {
continue;
}
MVMap.Builder<String, String> b =
new MVMap.Builder<String, String>().
keyType(StringDataType.INSTANCE).valueType(
StringDataType.INSTANCE);
MVMap<String, String> m = s.openMap(mapName, b);
if (statsOnly) {
statistics(write, m);
} else if (raw) {
dumpRaw(write, m);
} else {
dump(write, m, path, revision, values);
}
}
}
s.close();
}
static void statistics(PrintWriter write, MVMap<String, String> m) {
write.println("map: " + m.getName().toLowerCase());
write.println("entryCount: " + m.sizeAsLong());
long keyLen = 0, valueLen = 0;
for (Entry<String, String> e : m.entrySet()) {
String k = e.getKey();
String v = e.getValue();
keyLen += k.length();
valueLen += v.length();
}
write.println("keyLen: " + keyLen);
write.println("valueLen: " + valueLen);
write.println();
}
static void dumpRaw(PrintWriter write, MVMap<String, String> m) {
String mapName = m.getName().toLowerCase();
// map key value length
for (Entry<String, String> e : m.entrySet()) {
String key = e.getKey();
String value = e.getValue();
write.println(mapName + "\t" + key + "\t" + value.length() + "\t" + value);
}
}
static void dump(PrintWriter write, MVMap<String, String> m, String path,
String revision, boolean values) {
String mapName = m.getName().toLowerCase();
// map,path,revision,p2,value,length
for (Entry<String, String> e : m.entrySet()) {
String key = e.getKey();
int slash = key.indexOf('/');
String r2 = "";
if (!key.startsWith("/") && slash > 0) {
r2 = key.substring(0, slash).replace(',', '#');
key = key.substring(slash);
}
if (!"/".equals(path) && !key.startsWith(path)) {
continue;
}
int lastAt = key.lastIndexOf('@');
String rev = "";
if (r2.endsWith(":p")) {
// prev_document, for example 0:p/r155a16928cd-0-1/0
// format: ..p<path>r<revision>/<number>
// we set r2 to <number>
rev = key.substring(key.lastIndexOf('r'));
key = key.substring(0, key.length() - rev.length());
r2 = rev.substring(rev.lastIndexOf('/') + 1);
rev = rev.substring(0, rev.lastIndexOf('/'));
}
if (lastAt > 0) {
rev = key.substring(lastAt + 1).replace(',', '#');
key = key.substring(0, lastAt);
}
if (!"".equals(revision) && !rev.startsWith(revision)) {
continue;
}
String v = e.getValue();
if (values) {
v = v.length() + "," + v.replace(',', '#');
} else {
v = "" + v.length();
}
key = key.replace(',', '#');
write.println(mapName + "," + key + "," + rev + "," + r2 + "," + v);
}
}
}