blob: 43315876897c3dd5b184a10367618d46653205e3 [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.segment.tool;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
import static org.apache.jackrabbit.oak.segment.RecordType.NODE;
import static org.apache.jackrabbit.oak.segment.tool.Utils.openReadOnlyFileStore;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import org.apache.jackrabbit.oak.segment.RecordId;
import org.apache.jackrabbit.oak.segment.RecordUsageAnalyser;
import org.apache.jackrabbit.oak.segment.Segment;
import org.apache.jackrabbit.oak.segment.SegmentId;
import org.apache.jackrabbit.oak.segment.file.ReadOnlyFileStore;
/**
* Print debugging information about a segment store.
*/
public class DebugStore {
/**
* Create a builder for the {@link DebugStore} command.
*
* @return an instance of {@link Builder}.
*/
public static Builder builder() {
return new Builder();
}
/**
* Collect options for the {@link DebugStore} command.
*/
public static class Builder {
private File path;
private Builder() {
// Prevent external instantiation
}
/**
* The path to an existing segment store. This parameter is required.
*
* @param path the path to an existing segment store.
* @return this builder.
*/
public Builder withPath(File path) {
this.path = checkNotNull(path);
return this;
}
/**
* Create an executable version of the {@link DebugStore} command.
*
* @return an instance of {@link Runnable}.
*/
public DebugStore build() {
checkNotNull(path);
return new DebugStore(this);
}
}
private final File path;
private DebugStore(Builder builder) {
this.path = builder.path;
}
public int run() {
try (ReadOnlyFileStore store = openReadOnlyFileStore(path)) {
debugFileStore(store);
return 0;
} catch (Exception e) {
e.printStackTrace(System.err);
return 1;
}
}
private static List<SegmentId> getReferencedSegmentIds(ReadOnlyFileStore store, Segment segment) {
List<SegmentId> result = new ArrayList<>();
for (int i = 0; i < segment.getReferencedSegmentIdCount(); i++) {
UUID uuid = segment.getReferencedSegmentId(i);
long msb = uuid.getMostSignificantBits();
long lsb = uuid.getLeastSignificantBits();
result.add(store.getSegmentIdProvider().newSegmentId(msb, lsb));
}
return result;
}
private static void debugFileStore(ReadOnlyFileStore store) {
Map<SegmentId, List<SegmentId>> idmap = Maps.newHashMap();
int dataCount = 0;
long dataSize = 0;
int bulkCount = 0;
long bulkSize = 0;
RecordUsageAnalyser analyser = new RecordUsageAnalyser(store.getReader());
for (SegmentId id : store.getSegmentIds()) {
if (id.isDataSegmentId()) {
Segment segment = id.getSegment();
dataCount++;
dataSize += segment.size();
idmap.put(id, getReferencedSegmentIds(store, segment));
analyseSegment(segment, analyser);
} else if (id.isBulkSegmentId()) {
bulkCount++;
bulkSize += id.getSegment().size();
idmap.put(id, Collections.<SegmentId>emptyList());
}
}
System.out.println("Total size:");
System.out.format("%s in %6d data segments%n", byteCountToDisplaySize(dataSize), dataCount);
System.out.format("%s in %6d bulk segments%n", byteCountToDisplaySize(bulkSize), bulkCount);
System.out.println(analyser.toString());
Set<SegmentId> garbage = newHashSet(idmap.keySet());
Queue<SegmentId> queue = Queues.newArrayDeque();
queue.add(store.getRevisions().getHead().getSegmentId());
while (!queue.isEmpty()) {
SegmentId id = queue.remove();
if (garbage.remove(id)) {
queue.addAll(idmap.get(id));
}
}
dataCount = 0;
dataSize = 0;
bulkCount = 0;
bulkSize = 0;
for (SegmentId id : garbage) {
if (id.isDataSegmentId()) {
dataCount++;
dataSize += id.getSegment().size();
} else if (id.isBulkSegmentId()) {
bulkCount++;
bulkSize += id.getSegment().size();
}
}
System.out.format("%nAvailable for garbage collection:%n");
System.out.format("%s in %6d data segments%n", byteCountToDisplaySize(dataSize), dataCount);
System.out.format("%s in %6d bulk segments%n", byteCountToDisplaySize(bulkSize), bulkCount);
}
private static void analyseSegment(final Segment segment, final RecordUsageAnalyser analyser) {
final List<RecordId> ids = newArrayList();
segment.forEachRecord((number, type, offset) -> {
if (type == NODE) {
ids.add(new RecordId(segment.getSegmentId(), number));
}
});
for (RecordId id : ids) {
try {
analyser.analyseNode(id);
} catch (Exception e) {
System.err.format("Error while processing node at %s", id);
e.printStackTrace(System.err);
}
}
}
}