org.eclipse.mat.parser.index.IndexWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of haha Show documentation
Show all versions of haha Show documentation
Java library to automate the analysis of Android heap dumps.
/**
* ****************************************************************************
* Copyright (c) 2008 SAP AG.
* 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:
* SAP AG - initial API and implementation
* *****************************************************************************
*/
package org.eclipse.mat.parser.index;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.NoSuchElementException;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.collect.ArrayIntCompressed;
import org.eclipse.mat.collect.ArrayLong;
import org.eclipse.mat.collect.ArrayLongCompressed;
import org.eclipse.mat.collect.ArrayUtils;
import org.eclipse.mat.collect.BitField;
import org.eclipse.mat.collect.HashMapIntLong;
import org.eclipse.mat.collect.HashMapIntObject;
import org.eclipse.mat.collect.IteratorInt;
import org.eclipse.mat.collect.IteratorLong;
import org.eclipse.mat.collect.SetInt;
import org.eclipse.mat.parser.index.IIndexReader.IOne2LongIndex;
import org.eclipse.mat.parser.index.IIndexReader.IOne2OneIndex;
import org.eclipse.mat.parser.io.BitInputStream;
import org.eclipse.mat.parser.io.BitOutputStream;
import org.eclipse.mat.util.IProgressListener;
public abstract class IndexWriter {
public static final int PAGE_SIZE_INT = 1000000;
public static final int PAGE_SIZE_LONG = 500000;
public interface KeyWriter {
public void storeKey(int index, Serializable key);
}
// //////////////////////////////////////////////////////////////
// integer based indices
// //////////////////////////////////////////////////////////////
public static class Identifier implements IIndexReader.IOne2LongIndex {
long[] identifiers;
int size;
public void add(long id) {
if (identifiers == null) {
identifiers = new long[10000];
size = 0;
}
if (size + 1 > identifiers.length) {
int newCapacity = (identifiers.length * 3) / 2 + 1;
if (newCapacity < size + 1) newCapacity = size + 1;
identifiers = copyOf(identifiers, newCapacity);
}
identifiers[size++] = id;
}
public void sort() {
Arrays.sort(identifiers, 0, size);
}
public int size() {
return size;
}
public long get(int index) {
if (index < 0 || index >= size) throw new IndexOutOfBoundsException();
return identifiers[index];
}
public int reverse(long val) {
int a, c;
for (a = 0, c = size; a < c; ) {
// Avoid overflow problems by using unsigned divide by 2
int b = (a + c) >>> 1;
long probeVal = get(b);
if (val < probeVal) {
c = b;
} else if (probeVal < val) {
a = b + 1;
} else {
return b;
}
}
// Negative index indicates not found (and where to insert)
return -1 - a;
}
public IteratorLong iterator() {
return new IteratorLong() {
int index = 0;
public boolean hasNext() {
return index < size;
}
public long next() {
return identifiers[index++];
}
};
}
public long[] getNext(int index, int length) {
long answer[] = new long[length];
for (int ii = 0; ii < length; ii++)
answer[ii] = identifiers[index + ii];
return answer;
}
public void close() throws IOException {
}
public void delete() {
identifiers = null;
}
public void unload() throws IOException {
throw new UnsupportedOperationException();
}
}
public static class IntIndexCollectorUncompressed {
int[] dataElements;
public IntIndexCollectorUncompressed(int size) {
dataElements = new int[size];
}
public void set(int index, int value) {
dataElements[index] = value;
}
public int get(int index) {
return dataElements[index];
}
public IIndexReader.IOne2OneIndex writeTo(File indexFile) throws IOException {
return new IntIndexStreamer().writeTo(indexFile, dataElements);
}
}
static class Pages {
int size;
Object[] elements;
public Pages(int initialSize) {
elements = new Object[initialSize];
size = 0;
}
private void ensureCapacity(int minCapacity) {
int oldCapacity = elements.length;
if (minCapacity > oldCapacity) {
int newCapacity = (oldCapacity * 3) / 2 + 1;
if (newCapacity < minCapacity) newCapacity = minCapacity;
Object[] copy = new Object[newCapacity];
System.arraycopy(elements, 0, copy, 0, Math.min(elements.length, newCapacity));
elements = copy;
}
}
@SuppressWarnings("unchecked") public V get(int key) {
return (key >= elements.length) ? null : (V) elements[key];
}
public void put(int key, V value) {
ensureCapacity(key + 1);
elements[key] = value;
size = Math.max(size, key + 1);
}
public int size() {
return size;
}
}
abstract static class IntIndex {
int pageSize;
int size;
Pages pages;
protected IntIndex() {
}
protected IntIndex(int size) {
init(size, PAGE_SIZE_INT);
}
protected void init(int size, int pageSize) {
this.size = size;
this.pageSize = pageSize;
this.pages = new Pages(size / pageSize + 1);
}
public int get(int index) {
ArrayIntCompressed array = getPage(index / pageSize);
return array.get(index % pageSize);
}
public int[] getNext(int index, int length) {
int answer[] = new int[length];
int page = index / pageSize;
int pageIndex = index % pageSize;
ArrayIntCompressed array = getPage(page);
for (int ii = 0; ii < length; ii++) {
answer[ii] = array.get(pageIndex++);
if (pageIndex >= pageSize) {
array = getPage(++page);
pageIndex = 0;
}
}
return answer;
}
@SuppressWarnings("null") public int[] getAll(int index[]) {
int[] answer = new int[index.length];
int page = -1;
ArrayIntCompressed array = null;
for (int ii = 0; ii < answer.length; ii++) {
int p = index[ii] / pageSize;
if (p != page) array = getPage(page = p);
answer[ii] = array.get(index[ii] % pageSize);
}
return answer;
}
public void set(int index, int value) {
ArrayIntCompressed array = getPage(index / pageSize);
array.set(index % pageSize, value);
}
protected abstract ArrayIntCompressed getPage(int page);
public synchronized void unload() {
this.pages = new Pages(size / pageSize + 1);
}
public int size() {
return size;
}
public IteratorInt iterator() {
return new IntIndexIterator(this);
}
}
static class IntIndexIterator implements IteratorInt {
IntIndex> intArray;
int nextIndex = 0;
public IntIndexIterator(IntIndex> intArray) {
this.intArray = intArray;
}
public int next() {
return intArray.get(nextIndex++);
}
public boolean hasNext() {
return nextIndex < intArray.size();
}
}
public static class IntIndexCollector extends IntIndex
implements IOne2OneIndex {
int mostSignificantBit;
public IntIndexCollector(int size, int mostSignificantBit) {
super(size);
this.mostSignificantBit = mostSignificantBit;
}
@Override protected ArrayIntCompressed getPage(int page) {
ArrayIntCompressed array = pages.get(page);
if (array == null) {
int ps = page < (size / pageSize) ? pageSize : size % pageSize;
array = new ArrayIntCompressed(ps, 31 - mostSignificantBit, 0);
pages.put(page, array);
}
return array;
}
public IIndexReader.IOne2OneIndex writeTo(File indexFile) throws IOException {
// needed to re-compress
return new IntIndexStreamer().writeTo(indexFile, this.iterator());
}
public IIndexReader.IOne2OneIndex writeTo(DataOutputStream out, long position)
throws IOException {
return new IntIndexStreamer().writeTo(out, position, this.iterator());
}
public void close() throws IOException {
}
public void delete() {
pages = null;
}
}
public static class IntIndexStreamer extends IntIndex> {
DataOutputStream out;
ArrayLong pageStart;
int[] page;
int left;
public IIndexReader.IOne2OneIndex writeTo(File indexFile, IteratorInt iterator)
throws IOException {
DataOutputStream out =
new DataOutputStream(new BufferedOutputStream(new FileOutputStream(indexFile)));
openStream(out, 0);
addAll(iterator);
closeStream();
out.close();
return getReader(indexFile);
}
public IIndexReader.IOne2OneIndex writeTo(File indexFile, int[] array) throws IOException {
DataOutputStream out =
new DataOutputStream(new BufferedOutputStream(new FileOutputStream(indexFile)));
openStream(out, 0);
addAll(array);
closeStream();
out.close();
return getReader(indexFile);
}
public IIndexReader.IOne2OneIndex writeTo(DataOutputStream out, long position,
IteratorInt iterator) throws IOException {
openStream(out, position);
addAll(iterator);
closeStream();
return getReader(null);
}
public IIndexReader.IOne2OneIndex writeTo(DataOutputStream out, long position, int[] array)
throws IOException {
openStream(out, position);
addAll(array);
closeStream();
return getReader(null);
}
void openStream(DataOutputStream out, long position) {
this.out = out;
init(0, PAGE_SIZE_INT);
this.page = new int[pageSize];
this.pageStart = new ArrayLong();
this.pageStart.add(position);
this.left = page.length;
}
/**
* @return total bytes written to index file
*/
long closeStream() throws IOException {
if (left < page.length) addPage();
// write header information
for (int jj = 0; jj < pageStart.size(); jj++)
out.writeLong(pageStart.get(jj));
out.writeInt(pageSize);
out.writeInt(size);
this.page = null;
this.out = null;
return this.pageStart.lastElement() + (8 * pageStart.size()) + 8
- this.pageStart.firstElement();
}
IndexReader.IntIndexReader getReader(File indexFile) {
return new IndexReader.IntIndexReader(indexFile, pages, size, pageSize, pageStart.toArray());
}
void addAll(IteratorInt iterator) throws IOException {
while (iterator.hasNext()) add(iterator.next());
}
void add(int value) throws IOException {
if (left == 0) addPage();
page[page.length - left--] = value;
size++;
}
void addAll(int[] values) throws IOException {
addAll(values, 0, values.length);
}
void addAll(int[] values, int offset, int length) throws IOException {
while (length > 0) {
if (left == 0) addPage();
int chunk = Math.min(left, length);
System.arraycopy(values, offset, page, page.length - left, chunk);
left -= chunk;
size += chunk;
length -= chunk;
offset += chunk;
}
}
private void addPage() throws IOException {
ArrayIntCompressed array = new ArrayIntCompressed(page, 0, page.length - left);
byte[] buffer = array.toByteArray();
out.write(buffer);
int written = buffer.length;
pages.put(pages.size(), new SoftReference(array));
pageStart.add(pageStart.lastElement() + written);
left = page.length;
}
@Override protected ArrayIntCompressed getPage(int page) {
throw new UnsupportedOperationException();
}
}
public static class IntArray1NWriter {
int[] header;
File indexFile;
DataOutputStream out;
IntIndexStreamer body;
public IntArray1NWriter(int size, File indexFile) throws IOException {
this.header = new int[size];
this.indexFile = indexFile;
this.out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(indexFile)));
this.body = new IntIndexStreamer();
this.body.openStream(this.out, 0);
}
public void log(Identifier identifer, int index, ArrayLong references) throws IOException {
// remove duplicates and convert to identifiers
// keep pseudo reference as first one
long pseudo = references.firstElement();
references.sort();
int[] objectIds = new int[references.size()];
int length = 1;
long current = 0, last = references.firstElement() - 1;
for (int ii = 0; ii < objectIds.length; ii++) {
current = references.get(ii);
if (last != current) {
int objectId = identifer.reverse(current);
if (objectId >= 0) {
int jj = (current == pseudo) ? 0 : length++;
objectIds[jj] = objectId;
}
}
last = current;
}
this.set(index, objectIds, 0, length);
}
/**
* must not contain duplicates!
*/
public void log(int index, ArrayInt references) throws IOException {
this.set(index, references.toArray(), 0, references.size());
}
public void log(int index, int[] values) throws IOException {
this.set(index, values, 0, values.length);
}
protected void set(int index, int[] values, int offset, int length) throws IOException {
header[index] = body.size();
body.add(length);
body.addAll(values, offset, length);
}
public IIndexReader.IOne2ManyIndex flush() throws IOException {
long divider = body.closeStream();
IIndexReader.IOne2OneIndex headerIndex = new IntIndexStreamer().writeTo(out, divider, header);
out.writeLong(divider);
out.close();
out = null;
return createReader(headerIndex, body.getReader(null));
}
/**
* @throws IOException
*/
protected IIndexReader.IOne2ManyIndex createReader(IIndexReader.IOne2OneIndex headerIndex,
IIndexReader.IOne2OneIndex bodyIndex) throws IOException {
return new IndexReader.IntIndex1NReader(this.indexFile, headerIndex, bodyIndex);
}
public void cancel() {
try {
if (out != null) {
out.close();
body = null;
out = null;
}
} catch (IOException ignore) {
} finally {
if (indexFile.exists()) indexFile.delete();
}
}
public File getIndexFile() {
return indexFile;
}
}
public static class IntArray1NSortedWriter extends IntArray1NWriter {
public IntArray1NSortedWriter(int size, File indexFile) throws IOException {
super(size, indexFile);
}
protected void set(int index, int[] values, int offset, int length) throws IOException {
header[index] = body.size() + 1;
body.addAll(values, offset, length);
}
protected IIndexReader.IOne2ManyIndex createReader(IIndexReader.IOne2OneIndex headerIndex,
IIndexReader.IOne2OneIndex bodyIndex) throws IOException {
return new IndexReader.IntIndex1NSortedReader(this.indexFile, headerIndex, bodyIndex);
}
}
public static class InboundWriter {
int size;
File indexFile;
int bitLength;
int pageSize;
BitOutputStream[] segments;
int[] segmentSizes;
/**
* @throws IOException
*/
public InboundWriter(int size, File indexFile) throws IOException {
this.size = size;
this.indexFile = indexFile;
int requiredSegments = (size / 500000) + 1;
int segments = 1;
while (segments < requiredSegments) segments <<= 1;
this.bitLength = mostSignificantBit(size) + 1;
this.pageSize = (size / segments) + 1;
this.segments = new BitOutputStream[segments];
this.segmentSizes = new int[segments];
}
public void log(int objectIndex, int refIndex, boolean isPseudo) throws IOException {
int segment = objectIndex / pageSize;
if (segments[segment] == null) {
File segmentFile =
new File(this.indexFile.getAbsolutePath() + segment + ".log");//$NON-NLS-1$
segments[segment] = new BitOutputStream(new FileOutputStream(segmentFile));
}
segments[segment].writeBit(isPseudo ? 1 : 0);
segments[segment].writeInt(objectIndex, bitLength);
segments[segment].writeInt(refIndex, bitLength);
segmentSizes[segment]++;
}
public IIndexReader.IOne2ManyObjectsIndex flush(IProgressListener monitor, KeyWriter keyWriter)
throws IOException {
close();
int[] header = new int[size];
DataOutputStream index = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(this.indexFile), 1024 * 256));
BitInputStream segmentIn = null;
try {
IntIndexStreamer body = new IntIndexStreamer();
body.openStream(index, 0);
for (int segment = 0; segment < segments.length; segment++) {
if (monitor.isCanceled()) throw new IProgressListener.OperationCanceledException();
File segmentFile =
new File(this.indexFile.getAbsolutePath() + segment + ".log");//$NON-NLS-1$
if (!segmentFile.exists()) continue;
// read & sort payload
segmentIn = new BitInputStream(new FileInputStream(segmentFile));
int objIndex[] = new int[segmentSizes[segment]];
int refIndex[] = new int[segmentSizes[segment]];
for (int ii = 0; ii < segmentSizes[segment]; ii++) {
boolean isPseudo = segmentIn.readBit() == 1;
objIndex[ii] = segmentIn.readInt(bitLength);
refIndex[ii] = segmentIn.readInt(bitLength);
if (isPseudo) refIndex[ii] = -1 - refIndex[ii]; // 0 is a valid!
}
segmentIn.close();
segmentIn = null;
if (monitor.isCanceled()) throw new IProgressListener.OperationCanceledException();
// delete segment log
segmentFile.delete();
segmentFile = null;
processSegment(monitor, keyWriter, header, body, objIndex, refIndex);
}
// write header
long divider = body.closeStream();
IIndexReader.IOne2OneIndex headerIndex =
new IntIndexStreamer().writeTo(index, divider, header);
index.writeLong(divider);
index.flush();
index.close();
index = null;
// return index reader
return new IndexReader.InboundReader(this.indexFile, headerIndex, body.getReader(null));
} finally {
try {
if (index != null) index.close();
} catch (IOException ignore) {
}
try {
if (segmentIn != null) segmentIn.close();
} catch (IOException ignore) {
}
if (monitor.isCanceled()) cancel();
}
}
private void processSegment(IProgressListener monitor, KeyWriter keyWriter, int[] header,
IntIndexStreamer body, int[] objIndex, int[] refIndex) throws IOException {
// sort (only by objIndex though)
ArrayUtils.sort(objIndex, refIndex);
// write index body
int start = 0;
int previous = -1;
for (int ii = 0; ii <= objIndex.length; ii++) {
if (ii == 0) {
start = ii;
previous = objIndex[ii];
} else if (ii == objIndex.length || previous != objIndex[ii]) {
if (monitor.isCanceled()) throw new IProgressListener.OperationCanceledException();
header[previous] = body.size() + 1;
processObject(keyWriter, header, body, previous, refIndex, start, ii);
if (ii < objIndex.length) {
previous = objIndex[ii];
start = ii;
}
}
}
}
private void processObject(KeyWriter keyWriter, int[] header, IntIndexStreamer body,
int objectId, int[] refIndex, int fromIndex, int toIndex) throws IOException {
Arrays.sort(refIndex, fromIndex, toIndex);
int endPseudo = fromIndex;
if ((toIndex - fromIndex) > 100000) {
BitField duplicates = new BitField(size);
int jj = fromIndex;
for (; jj < toIndex; jj++) // pseudo references
{
if (refIndex[jj] >= 0) break;
endPseudo++;
refIndex[jj] = -refIndex[jj] - 1;
if (!duplicates.get(refIndex[jj])) {
body.add(refIndex[jj]);
duplicates.set(refIndex[jj]);
}
}
for (; jj < toIndex; jj++) // other references
{
if ((jj == fromIndex || refIndex[jj - 1] != refIndex[jj]) && !duplicates.get(
refIndex[jj])) {
body.add(refIndex[jj]);
}
}
} else {
SetInt duplicates = new SetInt(toIndex - fromIndex);
int jj = fromIndex;
for (; jj < toIndex; jj++) // pseudo references
{
if (refIndex[jj] >= 0) break;
endPseudo++;
refIndex[jj] = -refIndex[jj] - 1;
if (duplicates.add(refIndex[jj])) body.add(refIndex[jj]);
}
for (; jj < toIndex; jj++) // other references
{
if ((jj == fromIndex || refIndex[jj - 1] != refIndex[jj]) && !duplicates.contains(
refIndex[jj])) {
body.add(refIndex[jj]);
}
}
}
if (endPseudo > fromIndex) {
keyWriter.storeKey(objectId, new int[] { header[objectId] - 1, endPseudo - fromIndex });
}
}
public synchronized void cancel() {
try {
close();
if (segments != null) {
for (int ii = 0; ii < segments.length; ii++) {
new File(this.indexFile.getAbsolutePath() + ii + ".log").delete();//$NON-NLS-1$
}
}
} catch (IOException ignore) {
} finally {
indexFile.delete();
}
}
public synchronized void close() throws IOException {
if (segments != null) {
for (int ii = 0; ii < segments.length; ii++) {
if (segments[ii] != null) {
segments[ii].flush();
segments[ii].close();
segments[ii] = null;
}
}
}
}
public File getIndexFile() {
return indexFile;
}
}
public static class IntArray1NUncompressedCollector {
int[][] elements;
File indexFile;
/**
* @throws IOException
*/
public IntArray1NUncompressedCollector(int size, File indexFile) throws IOException {
this.elements = new int[size][];
this.indexFile = indexFile;
}
public void log(int classId, int methodId) {
if (elements[classId] == null) {
elements[classId] = new int[] { methodId };
} else {
int[] newChildren = new int[elements[classId].length + 1];
System.arraycopy(elements[classId], 0, newChildren, 0, elements[classId].length);
newChildren[elements[classId].length] = methodId;
elements[classId] = newChildren;
}
}
public File getIndexFile() {
return indexFile;
}
public IIndexReader.IOne2ManyIndex flush() throws IOException {
IntArray1NSortedWriter writer = new IntArray1NSortedWriter(elements.length, indexFile);
for (int ii = 0; ii < elements.length; ii++) {
if (elements[ii] != null) writer.log(ii, elements[ii]);
}
return writer.flush();
}
}
// //////////////////////////////////////////////////////////////
// long based indices
// //////////////////////////////////////////////////////////////
public static class LongIndexCollectorUncompressed {
long[] dataElements;
public LongIndexCollectorUncompressed(int size) {
dataElements = new long[size];
}
public void set(int index, long value) {
dataElements[index] = value;
}
public long get(int index) {
return dataElements[index];
}
public IIndexReader.IOne2LongIndex writeTo(File indexFile) throws IOException {
return new LongIndexStreamer().writeTo(indexFile, dataElements);
}
}
abstract static class LongIndex {
private static final int DEPTH = 10;
int pageSize;
int size;
// pages are either IntArrayCompressed or
// SoftReference
HashMapIntObject