/******************************************************************************* * Copyright (c) 2016 Google, Inc and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Stefan Xenos (Google) - Initial implementation *******************************************************************************/ 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; /** The size of the statistics for a single malloc pool */ 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(" "); //$NON-NLS-1$ builder.append(next.numAllocations); builder.append(" allocations, "); //$NON-NLS-1$ builder.append(next.totalSize); builder.append(" bytes\n"); //$NON-NLS-1$ } System.out.println(builder.toString()); } private String getPoolName(NdNodeTypeRegistry<?> registry, int poolId) { switch (poolId) { case Database.POOL_MISC: return "Miscellaneous"; //$NON-NLS-1$ case Database.POOL_BTREE: return "B-Trees"; //$NON-NLS-1$ case Database.POOL_DB_PROPERTIES: return "DB Properties"; //$NON-NLS-1$ case Database.POOL_STRING_LONG: return "Long Strings"; //$NON-NLS-1$ case Database.POOL_STRING_SHORT: return "Short Strings"; //$NON-NLS-1$ case Database.POOL_LINKED_LIST: return "Linked Lists"; //$NON-NLS-1$ case Database.POOL_STRING_SET: return "String Sets"; //$NON-NLS-1$ case Database.POOL_GROWABLE_ARRAY: return "Growable Arrays"; //$NON-NLS-1$ 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; //$NON-NLS-1$ } } 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."); //$NON-NLS-1$ } // Find the insertion position 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."); //$NON-NLS-1$ } if (nextPool.poolId > poolId) { break; } } // Find the last pool position int lastIdx = idx; for (;;lastIdx++) { PoolStats nextPool = readPool(lastIdx); if (lastIdx > 0 && nextPool.poolId == 0) { break; } } // Shift all the pools to make room 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"); //$NON-NLS-1$//$NON-NLS-2$ } toRecord.setAllocations(this.db, toRecord.numAllocations - 1); toRecord.setTotalSize(this.db, toRecord.totalSize - size); } }