Index: src/main/java/org/apache/jackrabbit/oak/explorer/Explorer.java =================================================================== --- src/main/java/org/apache/jackrabbit/oak/explorer/Explorer.java (revision 1713352) +++ src/main/java/org/apache/jackrabbit/oak/explorer/Explorer.java (working copy) @@ -239,6 +239,15 @@ } }); + JMenuItem menuPCM = new JMenuItem("Persisted Compaction Maps"); + menuPCM.setMnemonic(KeyEvent.VK_P); + menuPCM.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent ev) { + treePanel.printPCMInfo(); + } + }); + menuBar.add(menuReopen); menuBar.add(new JSeparator(JSeparator.VERTICAL)); menuBar.add(menuCompaction); @@ -249,6 +258,8 @@ menuBar.add(new JSeparator(JSeparator.VERTICAL)); menuBar.add(menuDiff); menuBar.add(new JSeparator(JSeparator.VERTICAL)); + menuBar.add(menuPCM); + menuBar.add(new JSeparator(JSeparator.VERTICAL)); frame.setJMenuBar(menuBar); frame.pack(); Index: src/main/java/org/apache/jackrabbit/oak/explorer/NodeStoreTree.java =================================================================== --- src/main/java/org/apache/jackrabbit/oak/explorer/NodeStoreTree.java (revision 1713352) +++ src/main/java/org/apache/jackrabbit/oak/explorer/NodeStoreTree.java (working copy) @@ -63,6 +63,7 @@ import org.apache.jackrabbit.oak.api.Blob; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.segment.PCMAnalyser; import org.apache.jackrabbit.oak.plugins.segment.RecordId; import org.apache.jackrabbit.oak.plugins.segment.SegmentBlob; import org.apache.jackrabbit.oak.plugins.segment.SegmentId; @@ -647,6 +648,20 @@ return true; } + public void printPCMInfo() { + PCMAnalyser rua = new PCMAnalyser(store); + if (rua.isEmpty()) { + setText("No persisted compaction map found."); + return; + } + StringBuilder sb = new StringBuilder(); + sb.append("Persisted compaction map info"); + sb.append(newline); + sb.append(newline); + sb.append(rua.toString()); + setText(sb.toString()); + } + private static class NamePathModel implements Comparable { private final FileStore store; Index: src/main/java/org/apache/jackrabbit/oak/plugins/segment/PCMAnalyser.java =================================================================== --- src/main/java/org/apache/jackrabbit/oak/plugins/segment/PCMAnalyser.java (revision 0) +++ src/main/java/org/apache/jackrabbit/oak/plugins/segment/PCMAnalyser.java (revision 0) @@ -0,0 +1,166 @@ +/* + * 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.plugins.segment; + +import static com.google.common.collect.Maps.newHashMap; +import static com.google.common.collect.Sets.newHashSet; +import static org.apache.jackrabbit.oak.plugins.segment.PersistedCompactionMap.PERSISTED_COMPACTION_MAP; + +import java.io.File; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.Formatter; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; + +import org.apache.jackrabbit.oak.plugins.segment.file.FileStore; + +public class PCMAnalyser extends RecordUsageAnalyser { + + /** + * Extracts persisted compaction map information. Returns a map keyed by the + * file name which contains all the pairs of compaction map record ids found + */ + public static Map>> readPCMHistory( + FileStore store) { + Map>> pcms = newHashMap(); + Map> index = store.getTarReaderIndex(); + for (String path : index.keySet()) { + Set segments = index.get(path); + String name = new File(path).getName(); + + for (UUID id : segments) { + Segment s = store.readSegment(new SegmentId(store.getTracker(), + id.getMostSignificantBits(), id + .getLeastSignificantBits())); + if (s.getSegmentId().isBulkSegmentId()) { + continue; + } + + for (int r = 0; r < s.getRootCount(); r++) { + RecordType t = s.getRootType(r); + if (t == RecordType.VALUE) { + RecordId nodeId = new RecordId(s.getSegmentId(), + s.getRootOffset(r)); + String v = Segment.readString(nodeId); + Entry pcm = parsePCMInfo(v, store); + + if (pcm != null) { + Set> pcmsByFile = pcms + .get(name); + if (pcmsByFile == null) { + pcmsByFile = newHashSet(); + pcms.put(name, pcmsByFile); + } + pcmsByFile.add(pcm); + pcmsByFile + .add(new SimpleImmutableEntry( + nodeId, null)); + } + } + } + } + } + return pcms; + } + + /** + * Extracts persisted compaction map information, if available, otherwise + * returs null + */ + public static Entry parsePCMInfo(String mapInfo, + FileStore store) { + if (mapInfo == null || !mapInfo.startsWith(PERSISTED_COMPACTION_MAP)) { + return null; + } + SegmentTracker tracker = store.getTracker(); + int idStartIndex = mapInfo.indexOf("id=") + 3; + int idEndIndex = mapInfo.indexOf(",", idStartIndex); + String id = mapInfo.substring(idStartIndex, idEndIndex); + RecordId rid = null; + try { + rid = RecordId.fromString(tracker, id); + } catch (IllegalArgumentException iae) { + // log a warn? + } + + int baseStartIndex = mapInfo.indexOf("baseId=") + 7; + String base = mapInfo.substring(baseStartIndex, mapInfo.length() - 1); + RecordId bid = null; + if (!"null".equals(base)) { + try { + bid = RecordId.fromString(tracker, base); + } catch (IllegalArgumentException iae) { + // log a warn? + } + } + return new SimpleImmutableEntry(rid, bid); + } + + private final Map>> pcms; + private final Set errors = newHashSet(); + + public PCMAnalyser(FileStore store) { + pcms = readPCMHistory(store); + for (Entry>> pcm : pcms + .entrySet()) { + for (Entry e : pcm.getValue()) { + try { + onPCM(e.getKey(), e.getValue()); + } catch (IllegalStateException ex) { + ex.printStackTrace(); + errors.add(ex.getMessage()); + } + } + } + + } + + public boolean isEmpty() { + return pcms.isEmpty(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + @SuppressWarnings("resource") + Formatter formatter = new Formatter(sb); + for (Entry>> pcm : pcms + .entrySet()) { + formatter.format("%s = %s%n", pcm.getKey(), pcm.getValue()); + } + formatter.format("%n"); + sb.append(super.toString()); + + formatter.format("%n"); + for (String e : errors) { + formatter.format("%s%n", e); + } + + return sb.toString(); + } + + private void onPCM(RecordId recordId, RecordId baseId) { + Segment s = recordId.getSegment(); + MapRecord map = s.readMap(recordId); + parseMap(null, recordId, map); + } + +}