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