| /* |
| * 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; |
| |
| import static com.google.common.collect.Sets.newHashSet; |
| import static org.apache.commons.io.FileUtils.byteCountToDisplaySize; |
| |
| import java.util.Formatter; |
| import java.util.Set; |
| |
| import org.jetbrains.annotations.NotNull; |
| |
| /** |
| * 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 extends SegmentParser { |
| private final RecordIdSet seenIds = new RecordIdSet(); |
| private final Set<String> deadLinks = newHashSet(); |
| |
| private long mapSize; // leaf and branch |
| private long listSize; // list and bucket |
| private long valueSize; // inlined values |
| private long templateSize; // template |
| private long nodeSize; // node |
| |
| private long mapCount; |
| private long listCount; |
| private long propertyCount; |
| private long smallBlobCount; |
| private long mediumBlobCount; |
| private long longBlobCount; |
| private long externalBlobCount; |
| private long smallStringCount; |
| private long mediumStringCount; |
| private long longStringCount; |
| private long templateCount; |
| private long nodeCount; |
| |
| public RecordUsageAnalyser(@NotNull SegmentReader reader) { |
| super(reader); |
| } |
| |
| /** |
| * @return number of bytes in {@link RecordType#LEAF leaf} and {@link RecordType#BRANCH branch} |
| * records. |
| */ |
| public long getMapSize() { |
| return mapSize; |
| } |
| |
| /** |
| * @return number of bytes in {@link RecordType#LIST list} and {@link RecordType#BUCKET bucket} |
| * records. |
| */ |
| public long getListSize() { |
| return listSize; |
| } |
| |
| /** |
| * @return number of bytes in inlined values (strings and blobs) |
| */ |
| public long getValueSize() { |
| return valueSize; |
| } |
| |
| /** |
| * @return number of bytes in {@link RecordType#TEMPLATE template} records. |
| */ |
| public long getTemplateSize() { |
| return templateSize; |
| } |
| |
| /** |
| * @return number of bytes in {@link RecordType#NODE node} records. |
| */ |
| public long getNodeSize() { |
| return nodeSize; |
| } |
| |
| /** |
| * @return number of maps |
| */ |
| public long getMapCount() { |
| return mapCount; |
| } |
| |
| /** |
| * @return number of lists |
| */ |
| public long getListCount() { |
| return listCount; |
| } |
| |
| /** |
| * @return number of properties |
| */ |
| public long getPropertyCount() { |
| return propertyCount; |
| } |
| |
| /** |
| * @return number of {@link Segment#SMALL_LIMIT small} blobs. |
| * |
| */ |
| public long getSmallBlobCount() { |
| return smallBlobCount; |
| } |
| |
| /** |
| * @return number of {@link Segment#MEDIUM_LIMIT medium} blobs. |
| * |
| */ |
| public long getMediumBlobCount() { |
| return mediumBlobCount; |
| } |
| |
| /** |
| * @return number of long blobs. |
| * |
| */ |
| public long getLongBlobCount() { |
| return longBlobCount; |
| } |
| |
| /** |
| * @return number of external blobs. |
| * |
| */ |
| public long getExternalBlobCount() { |
| return externalBlobCount; |
| } |
| |
| /** |
| * @return number of {@link Segment#SMALL_LIMIT small} strings. |
| * |
| */ |
| public long getSmallStringCount() { |
| return smallStringCount; |
| } |
| |
| /** |
| * @return number of {@link Segment#MEDIUM_LIMIT medium} strings. |
| * |
| */ |
| public long getMediumStringCount() { |
| return mediumStringCount; |
| } |
| |
| /** |
| * @return number of long strings. |
| * |
| */ |
| public long getLongStringCount() { |
| return longStringCount; |
| } |
| |
| /** |
| * @return number of templates. |
| */ |
| public long getTemplateCount() { |
| return templateCount; |
| } |
| |
| /** |
| * @return number of nodes. |
| */ |
| public long getNodeCount() { |
| return nodeCount; |
| } |
| |
| public void analyseNode(RecordId nodeId) { |
| onNode(null, nodeId); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| @SuppressWarnings("resource") |
| Formatter formatter = new Formatter(sb); |
| formatter.format( |
| "%s in maps (%s leaf and branch records)%n", |
| byteCountToDisplaySize(mapSize), mapCount); |
| formatter.format( |
| "%s in lists (%s list and bucket records)%n", |
| byteCountToDisplaySize(listSize), listCount); |
| formatter.format( |
| "%s in values (value and block records of %s properties, " + |
| "%s/%s/%s/%s small/medium/long/external blobs, %s/%s/%s small/medium/long strings)%n", |
| byteCountToDisplaySize(valueSize), propertyCount, |
| smallBlobCount, mediumBlobCount, longBlobCount, externalBlobCount, |
| smallStringCount, mediumStringCount, longStringCount); |
| formatter.format( |
| "%s in templates (%s template records)%n", |
| byteCountToDisplaySize(templateSize), templateCount); |
| formatter.format( |
| "%s in nodes (%s node records)%n", |
| byteCountToDisplaySize(nodeSize), nodeCount); |
| formatter.format("links to non existing segments: %s", deadLinks); |
| return sb.toString(); |
| } |
| |
| @Override |
| protected void onNode(RecordId parentId, RecordId nodeId) { |
| try { |
| if (seenIds.addIfNotPresent(nodeId)) { |
| NodeInfo info = parseNode(nodeId); |
| this.nodeCount++; |
| this.nodeSize += info.size; |
| } |
| } catch (SegmentNotFoundException snfe) { |
| deadLinks.add(snfe.getSegmentId()); |
| } |
| } |
| |
| @Override |
| protected void onTemplate(RecordId parentId, RecordId templateId) { |
| try { |
| if (seenIds.addIfNotPresent(templateId)) { |
| TemplateInfo info = parseTemplate(templateId); |
| this.templateCount++; |
| this.templateSize += info.size; |
| } |
| } catch (SegmentNotFoundException snfe) { |
| deadLinks.add(snfe.getSegmentId()); |
| } |
| } |
| |
| @Override |
| protected void onMapDiff(RecordId parentId, RecordId mapId, MapRecord map) { |
| try { |
| if (seenIds.addIfNotPresent(mapId)) { |
| MapInfo info = parseMapDiff(mapId, map); |
| this.mapCount++; |
| this.mapSize += info.size; |
| } |
| } catch (SegmentNotFoundException snfe) { |
| deadLinks.add(snfe.getSegmentId()); |
| } |
| } |
| |
| @Override |
| protected void onMapLeaf(RecordId parentId, RecordId mapId, MapRecord map) { |
| try { |
| if (seenIds.addIfNotPresent(mapId)) { |
| MapInfo info = parseMapLeaf(mapId, map); |
| this.mapCount++; |
| this.mapSize += info.size; |
| } |
| } catch (SegmentNotFoundException snfe) { |
| deadLinks.add(snfe.getSegmentId()); |
| } |
| } |
| |
| @Override |
| protected void onMapBranch(RecordId parentId, RecordId mapId, MapRecord map) { |
| try { |
| if (seenIds.addIfNotPresent(mapId)) { |
| MapInfo info = parseMapBranch(mapId, map); |
| this.mapCount++; |
| this.mapSize += info.size; |
| } |
| } catch (SegmentNotFoundException snfe) { |
| deadLinks.add(snfe.getSegmentId()); |
| } |
| } |
| |
| @Override |
| protected void onProperty(RecordId parentId, RecordId propertyId, PropertyTemplate template) { |
| try { |
| if (!seenIds.contains(propertyId)) { |
| PropertyInfo info = parseProperty(parentId, propertyId, template); |
| this.propertyCount++; |
| this.valueSize += info.size; |
| seenIds.addIfNotPresent(propertyId); |
| } |
| } catch (SegmentNotFoundException snfe) { |
| deadLinks.add(snfe.getSegmentId()); |
| } |
| } |
| |
| @Override |
| protected void onBlob(RecordId parentId, RecordId blobId) { |
| try { |
| if (seenIds.addIfNotPresent(blobId)) { |
| BlobInfo info = parseBlob(blobId); |
| this.valueSize += info.size; |
| switch (info.blobType) { |
| case SMALL: |
| this.smallBlobCount++; |
| break; |
| case MEDIUM: |
| this.mediumBlobCount++; |
| break; |
| case LONG: |
| this.longBlobCount++; |
| break; |
| case EXTERNAL: |
| this.externalBlobCount++; |
| break; |
| } |
| } |
| } catch (SegmentNotFoundException snfe) { |
| deadLinks.add(snfe.getSegmentId()); |
| } |
| } |
| |
| @Override |
| protected void onString(RecordId parentId, RecordId stringId) { |
| try { |
| if (seenIds.addIfNotPresent(stringId)) { |
| BlobInfo info = parseString(stringId); |
| this.valueSize += info.size; |
| switch (info.blobType) { |
| case SMALL: |
| this.smallStringCount++; |
| break; |
| case MEDIUM: |
| this.mediumStringCount++; |
| break; |
| case LONG: |
| this.longStringCount++; |
| break; |
| case EXTERNAL: |
| throw new IllegalStateException("String is too long: " + info.size); |
| } |
| } |
| } catch (SegmentNotFoundException snfe) { |
| deadLinks.add(snfe.getSegmentId()); |
| } |
| } |
| |
| @Override |
| protected void onList(RecordId parentId, RecordId listId, int count) { |
| try { |
| if (seenIds.addIfNotPresent(listId)) { |
| ListInfo info = parseList(parentId, listId, count); |
| this.listCount++; |
| this.listSize += info.size; |
| } |
| } catch (SegmentNotFoundException snfe) { |
| deadLinks.add(snfe.getSegmentId()); |
| } |
| } |
| |
| } |