/* This file is part of the db4o object database http://www.db4o.com
Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com
db4o is free software; you can redistribute it and/or modify it under
the terms of version 3 of the GNU General Public License as published
by the Free Software Foundation.
db4o is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see http://www.gnu.org/licenses/. */
package com.db4o.filestats;
import java.io.*;
import java.util.*;
import com.db4o.*;
import com.db4o.config.*;
import com.db4o.foundation.*;
import com.db4o.internal.*;
import com.db4o.internal.btree.*;
import com.db4o.internal.classindex.*;
import com.db4o.internal.collections.*;
import com.db4o.internal.fileheader.*;
import com.db4o.internal.freespace.*;
import com.db4o.internal.ids.*;
import com.db4o.internal.slots.*;
/**
* Collects database file usage statistics and prints them
* to the console.
* @sharpen.partial
*/
@decaf.Ignore(decaf.Platform.JDK11)
public class FileUsageStatsCollector {
private final Map<String, MiscCollector> MISC_COLLECTORS;
/**
* @sharpen.ignore
*/
@decaf.RemoveFirst(decaf.Platform.JDK11)
private void registerBigSetCollector() {
MISC_COLLECTORS.put(BigSet.class.getName(), new BigSetMiscCollector());
}
/**
* Usage: FileUsageStatsCollector <db path> [<collect gaps (true|false)>]
*/
public static void main(String[] args) {
String dbPath = args[0];
boolean collectSlots = args.length > 1 && "true".equals(args[1]);
System.out.println(dbPath + ": " + new File(dbPath).length());
FileUsageStats stats = runStats(dbPath, collectSlots);
System.out.println(stats);
}
public static FileUsageStats runStats(String dbPath) {
return runStats(dbPath, false);
}
public static FileUsageStats runStats(String dbPath, boolean collectSlots) {
return runStats(dbPath, collectSlots, Db4oEmbedded.newConfiguration());
}
public static FileUsageStats runStats(String dbPath, boolean collectSlots, EmbeddedConfiguration config) {
EmbeddedObjectContainer db = Db4oEmbedded.openFile(config, dbPath);
try {
return new FileUsageStatsCollector(db, collectSlots).collectStats();
}
finally {
db.close();
}
}
private final LocalObjectContainer _db;
private FileUsageStats _stats;
private BlockConverter _blockConverter;
private final SlotMap _slots;
public FileUsageStatsCollector(ObjectContainer db, boolean collectSlots) {
MISC_COLLECTORS = new HashMap<String, MiscCollector>();
registerBigSetCollector();
_db = (LocalObjectContainer) db;
byte blockSize = _db.blockSize();
_blockConverter = blockSize > 1 ? (BlockConverter)new BlockSizeBlockConverter(blockSize) : (BlockConverter)new DisabledBlockConverter();
_slots = collectSlots ? (SlotMap)new SlotMapImpl(_db.fileLength()) : (SlotMap)new NullSlotMap();
}
public FileUsageStats collectStats() {
_stats = new FileUsageStats(
_db.fileLength(),
fileHeaderUsage(),
idSystemUsage(),
freespace(),
classMetadataUsage(),
freespaceUsage(),
uuidUsage(),
_slots,
commitTimestampUsage());
Set<ClassNode> classRoots = ClassNode.buildHierarchy(_db.classCollection());
for (ClassNode classRoot : classRoots) {
collectClassSlots(classRoot.classMetadata());
collectClassStats(_stats, classRoot);
}
return _stats;
}
private long collectClassStats(FileUsageStats stats, ClassNode classNode) {
long subClassSlotUsage = 0;
for(ClassNode curSubClass : classNode.subClasses()) {
subClassSlotUsage += collectClassStats(stats, curSubClass);
}
ClassMetadata clazz = classNode.classMetadata();
long classIndexUsage = 0;
if(clazz.hasClassIndex()) {
classIndexUsage = bTreeUsage(((BTreeClassIndexStrategy)clazz.index()).btree());
}
long fieldIndexUsage = fieldIndexUsage(clazz);
InstanceUsage instanceUsage = classSlotUsage(clazz);
long totalSlotUsage = instanceUsage.slotUsage;
long ownSlotUsage = totalSlotUsage - subClassSlotUsage;
ClassUsageStats classStats = new ClassUsageStats(clazz.getName(), ownSlotUsage, classIndexUsage, fieldIndexUsage, instanceUsage.miscUsage, instanceUsage.numInstances);
stats.addClassStats(classStats);
return totalSlotUsage;
}
private long fieldIndexUsage(ClassMetadata classMetadata) {
final LongByRef usage = new LongByRef();
classMetadata.traverseDeclaredFields(new Procedure4<FieldMetadata>() {
public void apply(FieldMetadata field) {
if(field.isVirtual() || !field.hasIndex()) {
return;
}
usage.value += bTreeUsage(field.getIndex(_db.systemTransaction()));
}
});
return usage.value;
}
private long bTreeUsage(BTree btree) {
return bTreeUsage(_db, btree, _slots);
}
static long bTreeUsage(LocalObjectContainer db, BTree btree, SlotMap slotMap) {
return bTreeUsage(db.systemTransaction(), db.idSystem(), btree, slotMap);
}
private static long bTreeUsage(Transaction transaction, IdSystem idSystem, BTree btree, SlotMap slotMap) {
Iterator4<Integer> nodeIter = btree.allNodeIds(transaction);
Slot btreeSlot = idSystem.committedSlot(btree.getID());
slotMap.add(btreeSlot);
long usage = btreeSlot.length();
while(nodeIter.moveNext()) {
Integer curNodeId = nodeIter.current();
Slot slot = idSystem.committedSlot(curNodeId);
slotMap.add(slot);
usage += slot.length();
}
return usage;
}
private InstanceUsage classSlotUsage(ClassMetadata clazz) {
if(!clazz.hasClassIndex()) {
return new InstanceUsage(0, 0, 0);
}
final MiscCollector miscCollector = MISC_COLLECTORS.get(clazz.getName());
final LongByRef slotUsage = new LongByRef();
final LongByRef miscUsage = new LongByRef();
final IntByRef numInstances = new IntByRef();
BTreeClassIndexStrategy index = (BTreeClassIndexStrategy) clazz.index();
index.traverseIds(_db.systemTransaction(), new Visitor4<Integer>() {
public void visit(Integer id) {
numInstances.value++;
slotUsage.value += slotSizeForId(id);
if(miscCollector != null) {
miscUsage.value += miscCollector.collectFor(_db, id, _slots);
}
}
});
return new InstanceUsage(slotUsage.value, miscUsage.value, numInstances.value);
}
private void collectClassSlots(ClassMetadata clazz) {
if(!clazz.hasClassIndex()) {
return;
}
BTreeClassIndexStrategy index = (BTreeClassIndexStrategy) clazz.index();
index.traverseIds(_db.systemTransaction(), new Visitor4<Integer>() {
public void visit(Integer id) {
_slots.add(slot(id));
}
});
}
private long freespace() {
_db.freespaceManager().traverse(new Visitor4<Slot>() {
public void visit(Slot slot) {
_slots.add(slot);
}
});
return _db.freespaceManager().totalFreespace();
}
private long freespaceUsage() {
return freespaceUsage(_db.freespaceManager());
}
private long freespaceUsage(FreespaceManager fsm) {
if(fsm instanceof InMemoryFreespaceManager) {
return 0;
}
if(fsm instanceof BTreeFreespaceManager) {
return bTreeUsage((BTree)fieldValue(fsm, "_slotsByAddress")) + bTreeUsage((BTree)fieldValue(fsm, "_slotsByLength"));
}
if(fsm instanceof BlockAwareFreespaceManager) {
return freespaceUsage((FreespaceManager) fieldValue(fsm, "_delegate"));
}
throw new IllegalStateException("Unknown freespace manager: " + fsm);
}
private long idSystemUsage() {
final IntByRef usage = new IntByRef();
_db.idSystem().traverseOwnSlots(new Procedure4<Pair<Integer, Slot>>() {
@Override
public void apply(Pair<Integer, Slot> idSlot) {
Slot slot = idSlot.second;
usage.value += slot.length();
_slots.add(slot);
}
});
return usage.value;
}
private long classMetadataUsage() {
Slot classRepositorySlot = slot(_db.classCollection().getID());
_slots.add(classRepositorySlot);
long usage = classRepositorySlot.length();
Iterator4<Integer> classIdIter = _db.classCollection().ids();
while(classIdIter.moveNext()) {
int curClassId = classIdIter.current();
Slot classSlot = slot(curClassId);
_slots.add(classSlot);
usage += classSlot.length();
}
return usage;
}
private long fileHeaderUsage() {
int headerLength = _db.getFileHeader().length();
int usage = _blockConverter.blockAlignedBytes(headerLength);
FileHeaderVariablePart2 variablePart = (FileHeaderVariablePart2)fieldValue(_db.getFileHeader(), "_variablePart");
usage += _blockConverter.blockAlignedBytes(variablePart.marshalledLength());
_slots.add(new Slot(0, headerLength));
_slots.add(new Slot(variablePart.address(), variablePart.marshalledLength()));
return usage;
}
private long uuidUsage() {
if(_db.systemData().uuidIndexId() <= 0) {
return 0;
}
BTree index = _db.uUIDIndex().getIndex(_db.systemTransaction());
return index == null ? 0 : bTreeUsage(index);
}
private long commitTimestampUsage() {
LocalTransaction st = (LocalTransaction) _db.systemTransaction();
CommitTimestampSupport commitTimestampSupport = st.commitTimestampSupport();
if(commitTimestampSupport == null){
return 0;
}
BTree idToTimestampBtree = commitTimestampSupport.idToTimestamp();
long idToTimestampBTreeSize = idToTimestampBtree == null ? 0 : bTreeUsage(idToTimestampBtree);
BTree timestampToIdBtree = commitTimestampSupport.timestampToId();
long timestampToIdBTreeSize = timestampToIdBtree == null ? 0 : bTreeUsage(timestampToIdBtree);
return idToTimestampBTreeSize + timestampToIdBTreeSize;
}
private int slotSizeForId(int id) {
return slot(id).length();
}
private static <T> T fieldValue(Object parent, String fieldName) {
return (T) Reflection4.getFieldValue(parent, fieldName);
}
private static class InstanceUsage {
public final long slotUsage;
public final long miscUsage;
public final int numInstances;
public InstanceUsage(long slotUsage, long miscUsage, int numInstances) {
this.slotUsage = slotUsage;
this.miscUsage = miscUsage;
this.numInstances = numInstances;
}
}
private Slot slot(int id) {
return _db.idSystem().committedSlot(id);
}
}