Author: mduerig
Date: Thu Feb 12 09:44:46 2015
New Revision: 1659183
URL: http://svn.apache.org/r1659183
Log:
OAK-2504: oak-run debug should list a breakdown of space usage per record type
Initial implementation
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordUsageAnalyser.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/RecordUsageAnalyserTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordType.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordType.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordType.java?rev=1659183&r1=1659182&r2=1659183&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordType.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordType.java
Thu Feb 12 09:44:46 2015
@@ -19,7 +19,7 @@ package org.apache.jackrabbit.oak.plugin
/**
* The type of a record in a segment.
*/
-enum RecordType {
+public enum RecordType {
/**
* A leaf of a map (which is a HAMT tree). This contains
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordUsageAnalyser.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordUsageAnalyser.java?rev=1659183&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordUsageAnalyser.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordUsageAnalyser.java
Thu Feb 12 09:44:46 2015
@@ -0,0 +1,331 @@
+/*
+ * 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.base.Preconditions.checkArgument;
+import static com.google.common.collect.Sets.newHashSet;
+import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
+import static org.apache.jackrabbit.oak.api.Type.BINARY;
+import static org.apache.jackrabbit.oak.plugins.segment.ListRecord.LEVEL_SIZE;
+import static org.apache.jackrabbit.oak.plugins.segment.Segment.MEDIUM_LIMIT;
+import static
org.apache.jackrabbit.oak.plugins.segment.Segment.RECORD_ID_BYTES;
+import static org.apache.jackrabbit.oak.plugins.segment.Segment.SMALL_LIMIT;
+import static
org.apache.jackrabbit.oak.plugins.segment.SegmentWriter.BLOCK_SIZE;
+import static
org.apache.jackrabbit.oak.plugins.segment.Template.MANY_CHILD_NODES;
+import static
org.apache.jackrabbit.oak.plugins.segment.Template.ZERO_CHILD_NODES;
+
+import java.util.Formatter;
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * This utility breaks down space usage per record type.
+ * It accounts for value sharing. That is, an instance
+ * of this class will remember which records it has seen
+ * already and not count those again. Only the effective
+ * space taken by the records is taken into account. Slack
+ * space from aligning records is not accounted for.
+ */
+public class RecordUsageAnalyser {
+ private final Set<RecordId> seenIds = newHashSet();
+
+ private int mapSize; // leaf and branch
+ private int listSize; // list and bucket
+ private int valueSize; // inlined values
+ private int templateSize; // template
+ private int nodeSize; // node
+
+ public int getMapSize() {
+ return mapSize;
+ }
+
+ public int getListSize() {
+ return listSize;
+ }
+
+ public int getValueSize() {
+ return valueSize;
+ }
+
+ public int getTemplateSize() {
+ return templateSize;
+ }
+
+ public int getNodeSize() {
+ return nodeSize;
+ }
+
+ public void analyseNode(RecordId nodeId) {
+ if (seenIds.add(nodeId)) {
+ Segment segment = nodeId.getSegment();
+ int offset = nodeId.getOffset();
+ RecordId templateId = segment.readRecordId(offset);
+ analyseTemplate(templateId);
+
+ Template template = segment.readTemplate(templateId);
+
+ // Recurses into child nodes in this segment
+ if (template.getChildName() == MANY_CHILD_NODES) {
+ RecordId childMapId = segment.readRecordId(offset +
RECORD_ID_BYTES);
+ MapRecord childMap = segment.readMap(childMapId);
+ analyseMap(childMapId, childMap);
+ for (ChildNodeEntry childNodeEntry : childMap.getEntries()) {
+ NodeState child = childNodeEntry.getNodeState();
+ if (child instanceof SegmentNodeState) {
+ RecordId childId = ((SegmentNodeState)
child).getRecordId();
+ analyseNode(childId);
+ }
+ }
+ } else if (template.getChildName() != ZERO_CHILD_NODES) {
+ RecordId childId = segment.readRecordId(offset +
RECORD_ID_BYTES);
+ analyseNode(childId);
+ }
+
+ // Recurse into properties
+ int ids = template.getChildName() == ZERO_CHILD_NODES ? 1 : 2;
+ nodeSize += ids * RECORD_ID_BYTES;
+ PropertyTemplate[] propertyTemplates =
template.getPropertyTemplates();
+ for (PropertyTemplate propertyTemplate : propertyTemplates) {
+ nodeSize += RECORD_ID_BYTES;
+ RecordId propertyId = segment.readRecordId(offset + ids++ *
RECORD_ID_BYTES);
+ analyseProperty(propertyId, propertyTemplate);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ Formatter formatter = new Formatter(sb);
+ formatter.format(
+ "%s in maps (leaf and branch records)%n",
+ byteCountToDisplaySize(mapSize));
+ formatter.format(
+ "%s in lists (list and bucket records)%n",
+ byteCountToDisplaySize(listSize));
+ formatter.format(
+ "%s in values (value and block records)%n",
+ byteCountToDisplaySize(valueSize));
+ formatter.format(
+ "%s in templates (template records)%n",
+ byteCountToDisplaySize(templateSize));
+ formatter.format(
+ "%s in nodes (node records)%n",
+ byteCountToDisplaySize(nodeSize));
+ return sb.toString();
+ }
+
+ private void analyseTemplate(RecordId templateId) {
+ if (seenIds.add(templateId)) {
+ Segment segment = templateId.getSegment();
+ int size = 0;
+ int offset = templateId.getOffset();
+ int head = segment.readInt(offset + size);
+ boolean hasPrimaryType = (head & (1 << 31)) != 0;
+ boolean hasMixinTypes = (head & (1 << 30)) != 0;
+ boolean zeroChildNodes = (head & (1 << 29)) != 0;
+ boolean manyChildNodes = (head & (1 << 28)) != 0;
+ int mixinCount = (head >> 18) & ((1 << 10) - 1);
+ int propertyCount = head & ((1 << 18) - 1);
+ size += 4;
+
+ if (hasPrimaryType) {
+ RecordId primaryId = segment.readRecordId(offset + size);
+ analyseString(primaryId);
+ size += Segment.RECORD_ID_BYTES;
+ }
+
+ if (hasMixinTypes) {
+ for (int i = 0; i < mixinCount; i++) {
+ RecordId mixinId = segment.readRecordId(offset + size);
+ analyseString(mixinId);
+ size += Segment.RECORD_ID_BYTES;
+ }
+ }
+
+ if (!zeroChildNodes && !manyChildNodes) {
+ RecordId childNameId = segment.readRecordId(offset + size);
+ analyseString(childNameId);
+ size += Segment.RECORD_ID_BYTES;
+ }
+
+ for (int i = 0; i < propertyCount; i++) {
+ RecordId propertyNameId = segment.readRecordId(offset + size);
+ size += Segment.RECORD_ID_BYTES;
+ size++; // type
+ analyseString(propertyNameId);
+ }
+ templateSize += size;
+ }
+ }
+
+ private void analyseMap(RecordId mapId, MapRecord map) {
+ if (seenIds.add(mapId)) {
+ if (map.isDiff()) {
+ analyseDiff(mapId, map);
+ } else if (map.isLeaf()) {
+ analyseLeaf(map);
+ } else {
+ analyseBranch(map);
+ }
+ }
+ }
+
+ private void analyseDiff(RecordId mapId, MapRecord map) {
+ mapSize += 4; // -1
+ mapSize += 4; // hash of changed key
+ mapSize += RECORD_ID_BYTES; // key
+ mapSize += RECORD_ID_BYTES; // value
+ mapSize += RECORD_ID_BYTES; // base
+
+ RecordId baseId = mapId.getSegment().readRecordId(
+ mapId.getOffset() + 8 + 2 * RECORD_ID_BYTES);
+ analyseMap(baseId, new MapRecord(baseId));
+ }
+
+ private void analyseLeaf(MapRecord map) {
+ mapSize += 4; // size
+ mapSize += map.size() * 4; // key hashes
+
+ for (MapEntry entry : map.getEntries()) {
+ mapSize += 2 * RECORD_ID_BYTES; // key value pairs
+ analyseString(entry.getKey());
+ }
+ }
+
+ private void analyseBranch(MapRecord map) {
+ mapSize += 4; // level/size
+ mapSize += 4; // bitmap
+ for (MapRecord bucket : map.getBuckets()) {
+ if (bucket != null) {
+ mapSize += RECORD_ID_BYTES;
+ analyseMap(bucket.getRecordId(), bucket);
+ }
+ }
+ }
+
+ private void analyseProperty(RecordId propertyId, PropertyTemplate
template) {
+ if (!seenIds.contains(propertyId)) {
+ Segment segment = propertyId.getSegment();
+ int offset = propertyId.getOffset();
+ Type<?> type = template.getType();
+
+ if (type.isArray()) {
+ seenIds.add(propertyId);
+ int size = segment.readInt(offset);
+ valueSize += 4;
+
+ if (size > 0) {
+ RecordId listId = segment.readRecordId(offset + 4);
+ valueSize += RECORD_ID_BYTES;
+ for (RecordId valueId : new ListRecord(listId,
size).getEntries()) {
+ analyseValue(valueId, type.getBaseType());
+ }
+ analyseList(listId, size);
+ }
+ } else {
+ analyseValue(propertyId, type);
+ }
+ }
+ }
+
+ private void analyseValue(RecordId valueId, Type<?> type) {
+ checkArgument(!type.isArray());
+ if (type == BINARY) {
+ analyseBlob(valueId);
+ } else {
+ analyseString(valueId);
+ }
+ }
+
+ private void analyseBlob(RecordId blobId) {
+ if (seenIds.add(blobId)) {
+ Segment segment = blobId.getSegment();
+ int offset = blobId.getOffset();
+ byte head = segment.readByte(offset);
+ if ((head & 0x80) == 0x00) {
+ // 0xxx xxxx: small value
+ valueSize += (1 + head);
+ } else if ((head & 0xc0) == 0x80) {
+ // 10xx xxxx: medium value
+ int length = (segment.readShort(offset) & 0x3fff) +
SMALL_LIMIT;
+ valueSize += (2 + length);
+ } else if ((head & 0xe0) == 0xc0) {
+ // 110x xxxx: long value
+ long length = (segment.readLong(offset) & 0x1fffffffffffffffL)
+ MEDIUM_LIMIT;
+ int size = (int) ((length + BLOCK_SIZE - 1) / BLOCK_SIZE);
+ RecordId listId = segment.readRecordId(offset + 8);
+ analyseList(listId, size);
+ valueSize += (8 + RECORD_ID_BYTES + length);
+ } else if ((head & 0xf0) == 0xe0) {
+ // 1110 xxxx: external value
+ int length = (head & 0x0f) << 8 | (segment.readByte(offset +
1) & 0xff);
+ valueSize += (2 + length);
+ } else {
+ throw new IllegalStateException(String.format(
+ "Unexpected value record type: %02x", head & 0xff));
+ }
+ }
+ }
+
+ private void analyseString(RecordId stringId) {
+ if (seenIds.add(stringId)) {
+ Segment segment = stringId.getSegment();
+ int offset = stringId.getOffset();
+
+ long length = segment.readLength(offset);
+ if (length < Segment.SMALL_LIMIT) {
+ valueSize += (1 + length);
+ } else if (length < Segment.MEDIUM_LIMIT) {
+ valueSize += (2 + length);
+ } else if (length < Integer.MAX_VALUE) {
+ int size = (int) ((length + BLOCK_SIZE - 1) / BLOCK_SIZE);
+ RecordId listId = segment.readRecordId(offset + 8);
+ analyseList(listId, size);
+ valueSize += (8 + RECORD_ID_BYTES + length);
+ } else {
+ throw new IllegalStateException("String is too long: " +
length);
+ }
+ }
+ }
+
+ private void analyseList(RecordId listId, int size) {
+ if (seenIds.add(listId)) {
+ listSize += noOfListSlots(size) * RECORD_ID_BYTES;
+ }
+ }
+
+ private static int noOfListSlots(int size) {
+ if (size <= LEVEL_SIZE) {
+ return size;
+ } else {
+ int fullBuckets = size / LEVEL_SIZE;
+ if (size % LEVEL_SIZE > 1) {
+ return size + noOfListSlots(fullBuckets + 1);
+ } else {
+ return size + noOfListSlots(fullBuckets);
+ }
+ }
+ }
+
+}
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java?rev=1659183&r1=1659182&r2=1659183&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java
Thu Feb 12 09:44:46 2015
@@ -16,6 +16,7 @@
*/
package org.apache.jackrabbit.oak.plugins.segment;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndexes;
import static com.google.common.base.Preconditions.checkState;
@@ -34,13 +35,12 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
+import com.google.common.base.Charsets;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.plugins.blob.ReferenceCollector;
import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
-import com.google.common.base.Charsets;
-
/**
* A list of records.
* <p>
@@ -187,6 +187,23 @@ public class Segment {
return (data.get(REF_COUNT_OFFSET) & 0xff) + 1;
}
+ public int getRootCount() {
+ return data.getShort(ROOT_COUNT_OFFSET) & 0xffff;
+ }
+
+ public RecordType getRootType(int index) {
+ int refCount = getRefCount();
+ checkArgument(index < getRootCount());
+ return RecordType.values()[data.get(data.position() + refCount * 16 +
index * 3) & 0xff];
+ }
+
+ public int getRootOffset(int index) {
+ int refCount = getRefCount();
+ checkArgument(index < getRootCount());
+ return (data.getShort(data.position() + refCount * 16 + index * 3 + 1)
& 0xffff)
+ << RECORD_ALIGN_BITS;
+ }
+
SegmentId getRefId(int index) {
if (refids == null || index >= refids.length) {
String type = "data";
Added:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/RecordUsageAnalyserTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/RecordUsageAnalyserTest.java?rev=1659183&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/RecordUsageAnalyserTest.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/RecordUsageAnalyserTest.java
Thu Feb 12 09:44:46 2015
@@ -0,0 +1,273 @@
+/*
+ * 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 java.util.Collections.nCopies;
+import static org.apache.jackrabbit.oak.api.Type.LONGS;
+import static org.apache.jackrabbit.oak.api.Type.NAME;
+import static org.apache.jackrabbit.oak.api.Type.NAMES;
+import static org.apache.jackrabbit.oak.api.Type.STRINGS;
+import static
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.apache.jackrabbit.oak.plugins.segment.ListRecord.LEVEL_SIZE;
+import static org.apache.jackrabbit.oak.plugins.segment.Segment.MEDIUM_LIMIT;
+import static org.apache.jackrabbit.oak.plugins.segment.Segment.SMALL_LIMIT;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Random;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import org.apache.jackrabbit.oak.plugins.memory.ArrayBasedBlob;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RecordUsageAnalyserTest {
+ private SegmentStore store;
+ private SegmentWriter writer;
+ private RecordUsageAnalyser analyser = new RecordUsageAnalyser();
+
+ @Before
+ public void setup() {
+ store = mock(SegmentStore.class);
+ SegmentTracker tracker = new SegmentTracker(store);
+ when(store.getTracker()).thenReturn(tracker);
+ writer = new SegmentWriter(store, store.getTracker());
+ analyser = new RecordUsageAnalyser();
+ }
+
+ @Test
+ public void emptyNode() {
+ SegmentNodeState node = writer.writeNode(EMPTY_NODE);
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 0, 0, 4, 3);
+ }
+
+ @Test
+ public void nodeWithInt() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("one", 1);
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 0, 6, 8, 6);
+ }
+
+ @Test
+ public void nodeWithString() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("two", "222");
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 0, 8, 8, 6);
+ }
+
+ @Test
+ public void nodeWithMediumString() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("medium", Strings.repeat("a", SMALL_LIMIT + 1));
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 0, 138, 8, 6);
+ }
+
+ @Test
+ public void nodeWithLargeString() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("large", Strings.repeat("b", MEDIUM_LIMIT + 1));
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 15, 16530, 8, 6);
+ }
+
+ @Test
+ public void nodeWithSameString() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("two", "two");
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 0, 4, 8, 6);
+ }
+
+ @Test
+ public void nodeWithInts() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("multi", ImmutableList.of(1L, 2L, 3L, 4L), LONGS);
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 12, 21, 8, 6);
+ }
+
+ @Test
+ public void nodeWithManyInts() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("multi", nCopies(LEVEL_SIZE + 1, 1L), LONGS);
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 771, 15, 8, 6);
+ }
+
+ @Test
+ public void nodeWithManyIntsAndOne() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("multi", nCopies(LEVEL_SIZE + 2, 1L), LONGS);
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 777, 15, 8, 6);
+ }
+
+ @Test
+ public void nodeWithStrings() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("multi", ImmutableList.of("one", "one", "two",
"two", "three"), STRINGS);
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 15, 27, 8, 6);
+ }
+
+ @Test
+ public void nodeWithBlob() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("blob", new ArrayBasedBlob(new byte[]{1, 2, 3,
4}));
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 0, 10, 8, 6);
+ }
+
+ @Test
+ public void nodeWithMediumBlob() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+
+ byte[] bytes = new byte[SMALL_LIMIT + 1];
+ new Random().nextBytes(bytes);
+ builder.setProperty("mediumBlob", new ArrayBasedBlob(bytes));
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 0, 142, 8, 6);
+ }
+
+ @Test
+ public void nodeWithLargeBlob() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+
+ byte[] bytes = new byte[MEDIUM_LIMIT + 1];
+ new Random().nextBytes(bytes);
+ builder.setProperty("largeBlob", new ArrayBasedBlob(bytes));
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 15, 16534, 8, 6);
+ }
+
+ @Test
+ public void nodeWithPrimaryType() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("jcr:primaryType", "type", NAME);
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 0, 5, 7, 3);
+ }
+
+ @Test
+ public void nodeWithMixinTypes() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setProperty("jcr:mixinTypes", ImmutableList.of("type1",
"type2"), NAMES);
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 0, 12, 10, 3);
+ }
+
+ @Test
+ public void singleChild() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setChildNode("child");
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 0, 0, 6, 11, 9);
+ }
+
+ @Test
+ public void multiChild() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setChildNode("child1");
+ builder.setChildNode("child2");
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 24, 0, 14, 8, 12);
+ }
+
+ @Test
+ public void manyChild() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ for (int k = 0; k < MapRecord.BUCKETS_PER_LEVEL + 1; k++) {
+ builder.setChildNode("child" + k);
+ }
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 457, 0, 254, 8, 105);
+ }
+
+ @Test
+ public void changedChild() {
+ NodeBuilder builder = EMPTY_NODE.builder();
+ builder.setChildNode("child1");
+ builder.setChildNode("child2");
+
+ SegmentNodeState node = writer.writeNode(builder.getNodeState());
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 24, 0, 14, 8, 12);
+
+ builder = node.builder();
+ builder.child("child1").setProperty("p", "q");
+
+
when(store.containsSegment(node.getRecordId().getSegmentId())).thenReturn(true);
+ node = (SegmentNodeState) builder.getNodeState();
+
+ analyser.analyseNode(node.getRecordId());
+ assertSizes(analyser, 41, 0, 18, 16, 24);
+ }
+
+ private static void assertSizes(RecordUsageAnalyser analyser,
+ int maps, int lists, int values, int templates, int nodes) {
+ assertEquals("maps sizes mismatch", maps, analyser.getMapSize());
+ assertEquals("lists sizes mismatch", lists, analyser.getListSize());
+ assertEquals("value sizes mismatch", values, analyser.getValueSize());
+ assertEquals("template sizes mismatch", templates,
analyser.getTemplateSize());
+ assertEquals("nodes sizes mismatch", nodes, analyser.getNodeSize());
+ }
+
+}
Modified:
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java?rev=1659183&r1=1659182&r2=1659183&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
(original)
+++
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
Thu Feb 12 09:44:46 2015
@@ -18,7 +18,9 @@ package org.apache.jackrabbit.oak.run;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.Arrays.asList;
+import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
import static org.apache.jackrabbit.oak.checkpoint.Checkpoints.CP;
+import static org.apache.jackrabbit.oak.plugins.segment.RecordType.NODE;
import static
org.apache.jackrabbit.oak.plugins.segment.file.tooling.ConsistencyChecker.checkConsistency;
import java.io.Closeable;
@@ -56,12 +58,10 @@ import com.google.common.util.concurrent
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoURI;
-
import joptsimple.ArgumentAcceptingOptionSpec;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
-
import org.apache.jackrabbit.core.RepositoryContext;
import org.apache.jackrabbit.core.config.RepositoryConfig;
import org.apache.jackrabbit.oak.Oak;
@@ -90,6 +90,7 @@ import org.apache.jackrabbit.oak.plugins
import org.apache.jackrabbit.oak.plugins.document.util.MapFactory;
import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection;
import org.apache.jackrabbit.oak.plugins.segment.RecordId;
+import org.apache.jackrabbit.oak.plugins.segment.RecordUsageAnalyser;
import org.apache.jackrabbit.oak.plugins.segment.Segment;
import org.apache.jackrabbit.oak.plugins.segment.SegmentId;
import org.apache.jackrabbit.oak.plugins.segment.SegmentNodeState;
@@ -805,20 +806,21 @@ public class Main {
}
}
- private static void debugFileStore(FileStore store){
-
+ private static void debugFileStore(FileStore store) {
Map<SegmentId, List<SegmentId>> idmap = Maps.newHashMap();
-
int dataCount = 0;
long dataSize = 0;
int bulkCount = 0;
long bulkSize = 0;
+ RecordUsageAnalyser analyser = new RecordUsageAnalyser();
+
for (SegmentId id : store.getSegmentIds()) {
if (id.isDataSegmentId()) {
Segment segment = id.getSegment();
dataCount++;
dataSize += segment.size();
idmap.put(id, segment.getReferencedIds());
+ analyseSegment(segment, analyser);
} else if (id.isBulkSegmentId()) {
bulkCount++;
bulkSize += id.getSegment().size();
@@ -827,11 +829,12 @@ public class Main {
}
System.out.println("Total size:");
System.out.format(
- "%6dMB in %6d data segments%n",
- dataSize / (1024 * 1024), dataCount);
+ "%s in %6d data segments%n",
+ byteCountToDisplaySize(dataSize), dataCount);
System.out.format(
- "%6dMB in %6d bulk segments%n",
- bulkSize / (1024 * 1024), bulkCount);
+ "%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();
@@ -855,13 +858,22 @@ public class Main {
bulkSize += id.getSegment().size();
}
}
- System.out.println("Available for garbage collection:");
+ System.out.println("\nAvailable for garbage collection:");
System.out.format(
- "%6dMB in %6d data segments%n",
- dataSize / (1024 * 1024), dataCount);
+ "%s in %6d data segments%n",
+ byteCountToDisplaySize(dataSize), dataCount);
System.out.format(
- "%6dMB in %6d bulk segments%n",
- bulkSize / (1024 * 1024), bulkCount);
+ "%s in %6d bulk segments%n",
+ byteCountToDisplaySize(bulkSize), bulkCount);
+ }
+
+ private static void analyseSegment(Segment segment, RecordUsageAnalyser
analyser) {
+ for (int k = 0; k < segment.getRootCount(); k++) {
+ if (segment.getRootType(k) == NODE) {
+ RecordId nodeId = new RecordId(segment.getSegmentId(),
segment.getRootOffset(k));
+ analyser.analyseNode(nodeId);
+ }
+ }
}
/**