1 /******************************************************************************* 2 * Copyright (c) 2016 Google, Inc and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Stefan Xenos (Google) - Initial implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.core.nd.db; 15 16 import java.util.ArrayList; 17 import java.util.Collection; 18 import java.util.Collections; 19 import java.util.Comparator; 20 import java.util.HashMap; 21 import java.util.List; 22 import java.util.Map; 23 24 import org.eclipse.jdt.internal.core.nd.ITypeFactory; 25 import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry; 26 27 public class MemoryStats { 28 public static final int TOTAL_MALLOC_POOLS = 64; 29 /** The size of the statistics for a single malloc pool */ 30 public static final int SIZE = TOTAL_MALLOC_POOLS * PoolStats.RECORD_SIZE; 31 32 private Map<Integer, PoolStats> stats = new HashMap<>(); 33 34 public final long address; 35 private Chunk db; 36 37 public static final class PoolStats { 38 public static int POOL_ID_OFFSET = 0; 39 public static int NUM_ALLOCATIONS_OFFSET = POOL_ID_OFFSET + Database.SHORT_SIZE; 40 public static int TOTAL_SIZE_OFFSET = NUM_ALLOCATIONS_OFFSET + Database.LONG_SIZE; 41 42 public static final int RECORD_SIZE = TOTAL_SIZE_OFFSET + Database.LONG_SIZE; 43 44 short poolId; 45 long numAllocations; 46 long totalSize; 47 long address; 48 PoolStats(Chunk db, long address)49 public PoolStats(Chunk db, long address) { 50 this.address = address; 51 this.poolId = db.getShort(POOL_ID_OFFSET + address); 52 this.numAllocations = db.getLong(NUM_ALLOCATIONS_OFFSET + address); 53 this.totalSize = db.getLong(TOTAL_SIZE_OFFSET + address); 54 } 55 setAllocations(Chunk db, long numAllocations)56 public void setAllocations(Chunk db, long numAllocations) { 57 this.numAllocations = numAllocations; 58 db.putLong(this.address + NUM_ALLOCATIONS_OFFSET, numAllocations); 59 } 60 setTotalSize(Chunk db, long totalSize)61 public void setTotalSize(Chunk db, long totalSize) { 62 this.totalSize = totalSize; 63 db.putLong(this.address + TOTAL_SIZE_OFFSET, totalSize); 64 } 65 setPoolId(Chunk db, short poolId)66 public void setPoolId(Chunk db, short poolId) { 67 this.poolId = poolId; 68 db.putShort(this.address + POOL_ID_OFFSET, poolId); 69 } 70 getNumAllocations()71 public long getNumAllocations() { 72 return this.numAllocations; 73 } 74 getPoolId()75 public short getPoolId() { 76 return this.poolId; 77 } 78 getTotalSize()79 public long getTotalSize() { 80 return this.totalSize; 81 } 82 } 83 MemoryStats(Chunk db, long address)84 public MemoryStats(Chunk db, long address) { 85 this.db = db; 86 this.address = address; 87 } 88 printMemoryStats(NdNodeTypeRegistry<?> nodeRegistry)89 public void printMemoryStats(NdNodeTypeRegistry<?> nodeRegistry) { 90 StringBuilder builder = new StringBuilder(); 91 for (PoolStats next : getSortedPools()) { 92 builder.append(getPoolName(nodeRegistry, next.poolId)); 93 builder.append(" "); //$NON-NLS-1$ 94 builder.append(next.numAllocations); 95 builder.append(" allocations, "); //$NON-NLS-1$ 96 builder.append(Database.formatByteString(next.totalSize)); 97 builder.append("\n"); //$NON-NLS-1$ 98 } 99 System.out.println(builder.toString()); 100 } 101 getPoolName(NdNodeTypeRegistry<?> registry, int poolId)102 private String getPoolName(NdNodeTypeRegistry<?> registry, int poolId) { 103 switch (poolId) { 104 case Database.POOL_MISC: return "Miscellaneous"; //$NON-NLS-1$ 105 case Database.POOL_BTREE: return "B-Trees"; //$NON-NLS-1$ 106 case Database.POOL_DB_PROPERTIES: return "DB Properties"; //$NON-NLS-1$ 107 case Database.POOL_STRING_LONG: return "Long Strings"; //$NON-NLS-1$ 108 case Database.POOL_STRING_SHORT: return "Short Strings"; //$NON-NLS-1$ 109 case Database.POOL_LINKED_LIST: return "Linked Lists"; //$NON-NLS-1$ 110 case Database.POOL_STRING_SET: return "String Sets"; //$NON-NLS-1$ 111 case Database.POOL_GROWABLE_ARRAY: return "Growable Arrays"; //$NON-NLS-1$ 112 default: 113 if (poolId >= Database.POOL_FIRST_NODE_TYPE) { 114 ITypeFactory<?> type = registry.getClassForType((short)(poolId - Database.POOL_FIRST_NODE_TYPE)); 115 116 if (type != null) { 117 return type.getElementClass().getSimpleName(); 118 } 119 } 120 return "Unknown memory pool " + poolId; //$NON-NLS-1$ 121 } 122 } 123 getPools()124 public Collection<PoolStats> getPools() { 125 return this.stats.values(); 126 } 127 getSortedPools()128 public List<PoolStats> getSortedPools() { 129 List<PoolStats> unsorted = new ArrayList<>(); 130 unsorted.addAll(getPools()); 131 Collections.sort(unsorted, new Comparator<PoolStats>() { 132 @Override 133 public int compare(PoolStats o1, PoolStats o2) { 134 return Long.signum(o2.totalSize - o1.totalSize); 135 } 136 }); 137 return unsorted; 138 } 139 recordMalloc(short poolId, long size)140 public void recordMalloc(short poolId, long size) { 141 PoolStats toRecord = getPoolStats(poolId); 142 toRecord.setAllocations(this.db, toRecord.numAllocations + 1); 143 toRecord.setTotalSize(this.db, toRecord.totalSize + size); 144 } 145 getPoolStats(short poolId)146 private PoolStats getPoolStats(short poolId) { 147 if (this.stats.isEmpty()) { 148 refresh(); 149 } 150 PoolStats result = this.stats.get((int)poolId); 151 if (result == null) { 152 if (this.stats.size() >= TOTAL_MALLOC_POOLS) { 153 throw new IndexException("Too many malloc pools. Please increase the size of TOTAL_MALLOC_POOLS."); //$NON-NLS-1$ 154 } 155 // Find the insertion position 156 int idx = 0; 157 for (;;idx++) { 158 PoolStats nextPool = readPool(idx); 159 if (idx > 0 && nextPool.poolId == 0) { 160 break; 161 } 162 if (nextPool.poolId == poolId) { 163 throw new IllegalStateException("The stats were out of sync with the database."); //$NON-NLS-1$ 164 } 165 if (nextPool.poolId > poolId) { 166 break; 167 } 168 } 169 170 // Find the last pool position 171 int lastIdx = idx; 172 for (;;lastIdx++) { 173 PoolStats nextPool = readPool(lastIdx); 174 if (lastIdx > 0 && nextPool.poolId == 0) { 175 break; 176 } 177 } 178 179 // Shift all the pools to make room 180 for (int shiftIdx = lastIdx; shiftIdx > idx; shiftIdx--) { 181 PoolStats writeTo = readPool(shiftIdx); 182 PoolStats readFrom = readPool(shiftIdx - 1); 183 184 writeTo.setAllocations(this.db, readFrom.numAllocations); 185 writeTo.setTotalSize(this.db, readFrom.totalSize); 186 writeTo.setPoolId(this.db, readFrom.poolId); 187 } 188 189 result = readPool(idx); 190 result.setAllocations(this.db, 0); 191 result.setTotalSize(this.db, 0); 192 result.setPoolId(this.db, poolId); 193 194 refresh(); 195 196 result = this.stats.get((int)poolId); 197 } 198 return result; 199 } 200 loadStats()201 private List<PoolStats> loadStats() { 202 List<PoolStats> result = new ArrayList<>(); 203 for (int idx = 0; idx < TOTAL_MALLOC_POOLS; idx++) { 204 PoolStats next = readPool(idx); 205 206 if (idx > 0 && next.poolId == 0) { 207 break; 208 } 209 210 result.add(next); 211 } 212 return result; 213 } 214 refresh()215 public void refresh() { 216 this.stats.clear(); 217 218 for (PoolStats next : loadStats()) { 219 this.stats.put((int)next.poolId, next); 220 } 221 } 222 readPool(int idx)223 public PoolStats readPool(int idx) { 224 return new PoolStats(this.db, this.address + idx * PoolStats.RECORD_SIZE); 225 } 226 recordFree(short poolId, long size)227 public void recordFree(short poolId, long size) { 228 PoolStats toRecord = getPoolStats(poolId); 229 if (toRecord.numAllocations <= 0 || toRecord.totalSize < size) { 230 throw new IndexException("Attempted to free more memory from pool " + poolId + " than was ever allocated"); //$NON-NLS-1$//$NON-NLS-2$ 231 } 232 toRecord.setAllocations(this.db, toRecord.numAllocations - 1); 233 toRecord.setTotalSize(this.db, toRecord.totalSize - size); 234 } 235 } 236