package org.eclipse.jdt.internal.core.nd.db;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.internal.core.nd.ITypeFactory;
import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
public class MemoryStats {
public static final int TOTAL_MALLOC_POOLS = 64;
public static final int SIZE = TOTAL_MALLOC_POOLS * PoolStats.RECORD_SIZE;
private Map<Integer, PoolStats> stats = new HashMap<>();
public final long address;
private Chunk db;
public static final class PoolStats {
public static int POOL_ID_OFFSET = 0;
public static int NUM_ALLOCATIONS_OFFSET = POOL_ID_OFFSET + Database.SHORT_SIZE;
public static int TOTAL_SIZE_OFFSET = NUM_ALLOCATIONS_OFFSET + Database.LONG_SIZE;
public static final int RECORD_SIZE = TOTAL_SIZE_OFFSET + Database.LONG_SIZE;
short poolId;
long numAllocations;
long totalSize;
long address;
public PoolStats(Chunk db, long address) {
this.address = address;
this.poolId = db.getShort(POOL_ID_OFFSET + address);
this.numAllocations = db.getLong(NUM_ALLOCATIONS_OFFSET + address);
this.totalSize = db.getLong(TOTAL_SIZE_OFFSET + address);
}
public void setAllocations(Chunk db, long numAllocations) {
this.numAllocations = numAllocations;
db.putLong(this.address + NUM_ALLOCATIONS_OFFSET, numAllocations);
}
public void setTotalSize(Chunk db, long totalSize) {
this.totalSize = totalSize;
db.putLong(this.address + TOTAL_SIZE_OFFSET, totalSize);
}
public void setPoolId(Chunk db, short poolId) {
this.poolId = poolId;
db.putShort(this.address + POOL_ID_OFFSET, poolId);
}
public long getNumAllocations() {
return this.numAllocations;
}
public short getPoolId() {
return this.poolId;
}
public long getTotalSize() {
return this.totalSize;
}
}
public MemoryStats(Chunk db, long address) {
this.db = db;
this.address = address;
}
public void printMemoryStats(NdNodeTypeRegistry<?> nodeRegistry) {
StringBuilder builder = new StringBuilder();
for (PoolStats next : getSortedPools()) {
builder.append(getPoolName(nodeRegistry, next.poolId));
builder.append(" ");
builder.append(next.numAllocations);
builder.append(" allocations, ");
builder.append(Database.formatByteString(next.totalSize));
builder.append("\n");
}
System.out.println(builder.toString());
}
private String getPoolName(NdNodeTypeRegistry<?> registry, int poolId) {
switch (poolId) {
case Database.POOL_MISC: return "Miscellaneous";
case Database.POOL_BTREE: return "B-Trees";
case Database.POOL_DB_PROPERTIES: return "DB Properties";
case Database.POOL_STRING_LONG: return "Long Strings";
case Database.POOL_STRING_SHORT: return "Short Strings";
case Database.POOL_LINKED_LIST: return "Linked Lists";
case Database.POOL_STRING_SET: return "String Sets";
case Database.POOL_GROWABLE_ARRAY: return "Growable Arrays";
default:
if (poolId >= Database.POOL_FIRST_NODE_TYPE) {
ITypeFactory<?> type = registry.getClassForType((short)(poolId - Database.POOL_FIRST_NODE_TYPE));
if (type != null) {
return type.getElementClass().getSimpleName();
}
}
return "Unknown memory pool " + poolId;
}
}
public Collection<PoolStats> getPools() {
return this.stats.values();
}
public List<PoolStats> getSortedPools() {
List<PoolStats> unsorted = new ArrayList<>();
unsorted.addAll(getPools());
Collections.sort(unsorted, new Comparator<PoolStats>() {
@Override
public int compare(PoolStats o1, PoolStats o2) {
return Long.signum(o2.totalSize - o1.totalSize);
}
});
return unsorted;
}
public void recordMalloc(short poolId, long size) {
PoolStats toRecord = getPoolStats(poolId);
toRecord.setAllocations(this.db, toRecord.numAllocations + 1);
toRecord.setTotalSize(this.db, toRecord.totalSize + size);
}
private PoolStats getPoolStats(short poolId) {
if (this.stats.isEmpty()) {
refresh();
}
PoolStats result = this.stats.get((int)poolId);
if (result == null) {
if (this.stats.size() >= TOTAL_MALLOC_POOLS) {
throw new IndexException("Too many malloc pools. Please increase the size of TOTAL_MALLOC_POOLS.");
}
int idx = 0;
for (;;idx++) {
PoolStats nextPool = readPool(idx);
if (idx > 0 && nextPool.poolId == 0) {
break;
}
if (nextPool.poolId == poolId) {
throw new IllegalStateException("The stats were out of sync with the database.");
}
if (nextPool.poolId > poolId) {
break;
}
}
int lastIdx = idx;
for (;;lastIdx++) {
PoolStats nextPool = readPool(lastIdx);
if (lastIdx > 0 && nextPool.poolId == 0) {
break;
}
}
for (int shiftIdx = lastIdx; shiftIdx > idx; shiftIdx--) {
PoolStats writeTo = readPool(shiftIdx);
PoolStats readFrom = readPool(shiftIdx - 1);
writeTo.setAllocations(this.db, readFrom.numAllocations);
writeTo.setTotalSize(this.db, readFrom.totalSize);
writeTo.setPoolId(this.db, readFrom.poolId);
}
result = readPool(idx);
result.setAllocations(this.db, 0);
result.setTotalSize(this.db, 0);
result.setPoolId(this.db, poolId);
refresh();
result = this.stats.get((int)poolId);
}
return result;
}
private List<PoolStats> loadStats() {
List<PoolStats> result = new ArrayList<>();
for (int idx = 0; idx < TOTAL_MALLOC_POOLS; idx++) {
PoolStats next = readPool(idx);
if (idx > 0 && next.poolId == 0) {
break;
}
result.add(next);
}
return result;
}
public void refresh() {
this.stats.clear();
for (PoolStats next : loadStats()) {
this.stats.put((int)next.poolId, next);
}
}
public PoolStats readPool(int idx) {
return new PoolStats(this.db, this.address + idx * PoolStats.RECORD_SIZE);
}
public void recordFree(short poolId, long size) {
PoolStats toRecord = getPoolStats(poolId);
if (toRecord.numAllocations <= 0 || toRecord.totalSize < size) {
throw new IndexException("Attempted to free more memory from pool " + poolId + " than was ever allocated");
}
toRecord.setAllocations(this.db, toRecord.numAllocations - 1);
toRecord.setTotalSize(this.db, toRecord.totalSize - size);
}
}