/*******************************************************************************
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.apache.kato.hprof;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Vector;
import org.apache.kato.common.BasicType;
import org.apache.kato.common.ClassSummary;
import org.apache.kato.common.IDLocationMap;
import org.apache.kato.common.IDToLocationHashMapper;
import org.apache.kato.common.IViewMonitor;
import org.apache.kato.hprof.datalayer.ConstantPoolEntry;
import org.apache.kato.hprof.datalayer.HProfFile;
import org.apache.kato.hprof.datalayer.IGCClassHeapDumpRecord;
import org.apache.kato.hprof.datalayer.IHProfRecord;
import org.apache.kato.hprof.datalayer.IHeapDumpHProfRecord;
import org.apache.kato.hprof.datalayer.IHeapObject;
import org.apache.kato.hprof.datalayer.IJavaStackFrameHProfRecord;
import org.apache.kato.hprof.datalayer.IJavaStackTraceHProfRecord;
import org.apache.kato.hprof.datalayer.ILoadClassHProfRecord;
import org.apache.kato.hprof.datalayer.IThreadActiveHProfRecord;
import org.apache.kato.hprof.datalayer.IThreadEndingHProfRecord;
import org.apache.kato.hprof.datalayer.IUTF8HProfRecord;
import org.apache.kato.hprof.datalayer.IUnloadClassHProfRecord;
import org.apache.kato.hprof.datalayer.InstanceFieldEntry;
import org.apache.kato.hprof.datalayer.StaticFieldEntry;
public class HProfView {
private HProfFile file = null;
private HashMap<Long, Integer> utf8s = new HashMap<Long, Integer>();
private Long[] utf8ids = null;
private IJavaClass[] loadedClassRecordNumbers = null;
private IJavaThread[] threads = null;
private HashMap<Integer, Integer> stackTraces = new HashMap<Integer, Integer>();
private Integer[] stackTraceRecords=null;
private HashMap<Long, Integer> stackTraceFrames = new HashMap<Long, Integer>();
private ClassSummary recordSummary=null;
private IHeapDumpHProfRecord heapRecord=null;
private long heapRecordLocation=0;
// private HashMap<Long,JavaClassLoader> = new HashMap<Long,JavaClassLoader>();
private IDLocationMap idToLocationMap;
// Class maps
private HashMap<Long,IJavaClassLoader> javaClassLoaders = new HashMap<Long,IJavaClassLoader>();
private HashMap<Integer,IJavaClass> loadedClassesBySerial=new HashMap<Integer,IJavaClass>();
private HashMap<Long,IJavaClass> loadedClassesByID=new HashMap<Long,IJavaClass>();
// Thread ID Maps
private HashMap<Integer,IJavaThread> activeThreadsBySerial = new HashMap<Integer, IJavaThread>();
/**
* Takes an open file.
*
* @param file
*/
public HProfView(HProfFile file) {
this.file = file;
}
public void open(IViewMonitor monitor) {
// try {
// file.open();
// } catch(IOException ioe) {
// throw new IllegalArgumentException(ioe);
// }
int r = 0;
LinkedList<IJavaClass> loadedClassesRecordNO = new LinkedList<IJavaClass>();
LinkedList<IJavaThread> threadRecordNO = new LinkedList<IJavaThread>();
LinkedList<Integer> javastacks = new LinkedList<Integer>();
idToLocationMap = new IDToLocationHashMapper();
recordSummary=new ClassSummary();
int heapRecordNo=0;
while (true) {
IHProfRecord record = file.getRecord(r);
if (record == null)
break;
String tagName=HProfRecordFormatter.getTagName(record);
recordSummary.record(record);
if (record instanceof IUTF8HProfRecord) {
IUTF8HProfRecord utf8 = (IUTF8HProfRecord) record;
Long id = utf8.getNameID();
utf8s.put(id, r);
} else if (record instanceof ILoadClassHProfRecord) {
JavaClass c=new JavaClass(r);
loadedClassesBySerial.put(c.getClassSerialNumber(),c);
} else if (record instanceof IUnloadClassHProfRecord) {
// Remove a loaded class if it is subsequently unloaded.
loadedClassesBySerial.remove(((IUnloadClassHProfRecord)record).getClassSerialNumber());
} else if (record instanceof IThreadActiveHProfRecord) {
ActiveJavaThread ajt = new ActiveJavaThread(r);
threadRecordNO.add( ajt );
// add by serial number
activeThreadsBySerial.put(ajt.getThreadSerialNumber(), ajt);
} else if (record instanceof IThreadEndingHProfRecord) {
InactiveJavaThread ijt = new InactiveJavaThread(r);
threadRecordNO.add(ijt);
// remove thread by serial number.
//SRDM activeThreadsBySerial.remove(ijt.getThreadSerialNumber());
} else if (record instanceof IJavaStackTraceHProfRecord) {
// Stack traces should be attached to threads
// keep the most recent. Attach to thread so that when thread removed,
// stacktrace is removed too.
IJavaStackTraceHProfRecord stackTrace = (IJavaStackTraceHProfRecord) record;
int sn = stackTrace.getSerialNumber();
stackTraces.put(sn, r);
javastacks.add(r);
// Attach this stack trace to its thread if the thread
// exists and this trace is newer.
int threadSerial = stackTrace.getThreadSerialNumber();
IJavaThread ajt = activeThreadsBySerial.get(threadSerial);
if(ajt != null) {
((ActiveJavaThread)ajt).setStackTrace(stackTrace);
}
} else if (record instanceof IJavaStackFrameHProfRecord) {
IJavaStackFrameHProfRecord frame = (IJavaStackFrameHProfRecord) record;
long id = frame.getStackFrameID();
stackTraceFrames.put(id, r);
}
else if(record instanceof IHeapDumpHProfRecord) {
heapRecord=(IHeapDumpHProfRecord) record;
heapRecordNo=r;
summariseHeap(heapRecord);
}
r++;
}
heapRecordLocation=file.getRecordLocation(heapRecordNo);
utf8ids = utf8s.keySet().toArray(new Long[0]);
loadedClassRecordNumbers = loadedClassesRecordNO
.toArray(new IJavaClass[0]);
threads = threadRecordNO.toArray(new IJavaThread[0]);
stackTraceRecords=javastacks.toArray(new Integer[0]);
correlateClasses();
}
private IJavaClass[] primitiveArrayClasses = new IJavaClass[8];
/**
* Called once during processing.
* Iterates over all of the classes loaded and never unloaded.
* Creates the JavaClassLoaders, and creates map from object IDs to JavaClasses.
*
*/
private void correlateClasses() {
Iterator<Integer> classes = loadedClassesBySerial.keySet().iterator();
while (classes.hasNext()) {
int classSerialNo = classes.next();
IJavaClass clazz = loadedClassesBySerial.get(classSerialNo);
Long location = idToLocationMap.getLocation(clazz.getClassObjectID());
if (location == null) {
continue;
}
IHProfRecord record = heapRecord.getSubRecordByLocation(location);
long classLoaderObjectID = 0L;
long classObjectID = 0L;
if (record instanceof IGCClassHeapDumpRecord) {
IGCClassHeapDumpRecord classRecord = (IGCClassHeapDumpRecord) record;
classLoaderObjectID = classRecord.getClassLoaderObjectID();
classObjectID = classRecord.getID();
String name = clazz.getClassName();
if ("boolean[]".equals(name)) {
primitiveArrayClasses[BasicType.BOOLEAN-4] = clazz;
} else if ("byte[]".equals(name)) {
primitiveArrayClasses[BasicType.BYTE-4] = clazz;
} else if ("char[]".equals(name)) {
primitiveArrayClasses[BasicType.CHAR-4] = clazz;
} else if ("short[]".equals(name)) {
primitiveArrayClasses[BasicType.SHORT-4] = clazz;
} else if ("int[]".equals(name)) {
primitiveArrayClasses[BasicType.INT-4] = clazz;
} else if ("long[]".equals(name)) {
primitiveArrayClasses[BasicType.LONG-4] = clazz;
} else if ("float[]".equals(name)) {
primitiveArrayClasses[BasicType.FLOAT-4] = clazz;
} else if ("double[]".equals(name)) {
primitiveArrayClasses[BasicType.DOUBLE-4] = clazz;
}
// Create map of classes loaded by ID.
loadedClassesByID.put(classObjectID, clazz);
} else {
// If the record wasn't pointing to a class, then don't do anything with this class.
continue;
}
// retrieve classloader by ID. If it doesn't match, create one.
// then add this class object ID.
JavaClassLoader loader = (JavaClassLoader) javaClassLoaders.get(classLoaderObjectID);
if (loader == null) {
loader = new JavaClassLoader(classLoaderObjectID);
javaClassLoaders.put(classLoaderObjectID, loader);
}
loader.addClass(classObjectID);
}
}
/**
* Primitive
* @param type
* @return
*/
public IJavaClass getPrimitiveArrayClass(int type) {
if(type <4 || type >11) {
throw new IllegalArgumentException("Invalid basic type for primitive array " + type);
}
return primitiveArrayClasses[type-4];
}
/**
* Returns a collection of all of the JavaClassLoaders.
*
* @return Collection<JavaClassLoader>
*/
public Collection<IJavaClassLoader> getJavaClassLoaders() {
return javaClassLoaders.values();
}
/**
* Given the Object ID of a JavaClassLoader, return a JavaClassLoader
* of it's ID.
*
* @param id
* @return
*/
public IJavaClassLoader getJavaClassLoader(long id) {
return javaClassLoaders.get(id);
}
/**
* Return a JavaClass by its serial number.
*
* @param serial
* @return IJavaClass instance or null.
*/
public IJavaClass getJavaClassBySerial(int serial) {
return loadedClassesBySerial.get(serial);
}
/**
* Return a IJavaClass instance referenced by its ID.
* @param id object ID of class
* @return IJavaClass or null
*/
public IJavaClass getJavaClassByID(long id) {
IJavaClass clazz = loadedClassesByID.get(id);
if (clazz == null) {
System.err.println("Failed class lookup : 0x" +
Long.toHexString(id));
}
//assert(clazz != null);
return clazz;
}
/**
* Return an IHeapObject instance with the passed object ID.
*
* @param id
* @return IJavaObject with passed ID
*/
public IHeapObject getJavaObjectByID(long id) {
Long loc = idToLocationMap.getLocation(id);
if (loc == null) {
return null;
}
IHProfRecord record = heapRecord.getSubRecordByLocation(loc);
if (record == null) {
return null;
}
if (record instanceof IHeapObject) {
return (IHeapObject) record;
}
return null;
}
private void summariseHeap(IHeapDumpHProfRecord heapRecord) {
int index=0;
while(true) {
IHProfRecord rec=heapRecord.getSubRecord(index);
/* Record the ID of this record and location in ID to record map
* if this record is of an appropriate type.
*/
if (rec instanceof IHeapObject) {
long location = heapRecord.getSubRecordLocation(index);
idToLocationMap.put(((IHeapObject)rec).getID(), location);
}
if(rec==null) break;
recordSummary.record(rec);
index++;
}
}
public IJavaStackFrameHProfRecord getStackFrameRecordFromID(long id) {
int record=stackTraceFrames.get(id);
return (IJavaStackFrameHProfRecord) file.getRecord(record);
}
public String getName(long id) {
return getUTF8String(id);
}
public String getUTF8String(long id) {
int record = getUTF8RecordID(id);
if (record < 0)
return "";
IHProfRecord rec = file.getRecord(record);
if (rec == null)
return null;
if (rec instanceof IUTF8HProfRecord) {
IUTF8HProfRecord utf8record = (IUTF8HProfRecord) rec;
return utf8record.getAsString();
}
return null;
}
public int getUTF8RecordID(long id) {
if (utf8s.containsKey(id)) {
int record = utf8s.get(id);
return record;
}
return -1;
}
public int getThreadCount() {
return threads.length;
}
public IJavaThread getThread(int ref) {
return threads[ref];
}
public int getStackTraceCount() {
return stackTraceRecords.length;
}
public IJavaStackTraceHProfRecord getStackTraceRecord(int ref) {
return (IJavaStackTraceHProfRecord) file.getRecord(stackTraceRecords[ref]);
}
/**
* Represents a JavaClass - pulls together the LoadClass hprofrecord
* and it corresponding GCClassHeapDumpRecord.
*
*/
public class JavaClass implements IJavaClass {
private int ref = 0;
private JavaClass(int r) {
this.ref = r;
}
@Override
public String getClassName() {
ILoadClassHProfRecord record = getLoadedClassHProfRecord(ref);
return getName(record.getClassNameID());
}
@Override
public long getClassObjectID() {
return getLoadedClassHProfRecord(ref).getClassObjectID();
}
@Override
public int getClassSerialNumber() {
return getLoadedClassHProfRecord(ref).getClassSerialNumber();
}
@Override
public int getStackTraceSerialNumber() {
return getLoadedClassHProfRecord(ref).getStackTraceSerialNumber();
}
@Override
public long getClassLoaderObjectID() {
return getHProfRecord().getClassLoaderObjectID();
}
private IGCClassHeapDumpRecord getHProfRecord() {
return (IGCClassHeapDumpRecord) heapRecord.getSubRecordByLocation(idToLocationMap.getLocation(getClassObjectID()));
}
@Override
public long getSuperClassObjectID() {
return getHProfRecord().getSuperClassObjectID();
}
@Override
public int getInstanceSize() {
return getHProfRecord().getInstanceSize();
}
/**
* Check for equality
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
} else if (!(obj instanceof JavaClass)) {
return false;
}
return ((JavaClass)obj).getClassSerialNumber() == getClassSerialNumber();
}
@Override
public long getProtectionDomainObjectID() {
return getHProfRecord().getProtectionDomainObjectID();
}
@Override
public long getSignersObjectID() {
return getHProfRecord().getSignersObjectID();
}
@Override
public StaticFieldEntry[] getStaticFields() {
return getHProfRecord().getStaticFields();
}
@Override
public ConstantPoolEntry[] getConstantPoolEntries() {
return getHProfRecord().getConstantPool();
}
@Override
public InstanceFieldEntry[] getInstanceFields() {
return getHProfRecord().getInstanceFields();
}
@Override
public int getOffsetSize() {
return getHProfRecord().getOffsetSize();
}
}
/**
* Holds the IDs of classes loaded by this classloader.
*
*/
private class JavaClassLoader implements IJavaClassLoader {
private Long id;
private Vector<Long> classes = new Vector<Long>();
/**
* Creates a JavaClassLoader.
* This identifying object ID is passed.
*
* @param id Object ID of the class loader.
*/
private JavaClassLoader(Long id) {
this.id = id;
}
/**
* Returns the Object ID of this classloader.
* @return long
*/
public long getID() {
return this.id;
}
/**
* Add a class to the classloader.
* Private as only the
* @param ID
*/
private void addClass(Long ID) {
this.classes.add(ID);
}
public Vector<Long> getClasses() {
return classes;
}
}
public class InactiveJavaThread implements IJavaThread {
private int ref = 0;
private InactiveJavaThread(int ref) {
this.ref = ref;
}
@Override
public int getStackTraceSerialNumber() {
return 0;
}
@Override
public String getThreadGroupName() {
return "";
}
@Override
public String getThreadGroupParentName() {
return "";
}
@Override
public String getThreadName() {
return "";
}
@Override
public long getThreadObjectID() {
return 0;
}
@Override
public int getThreadSerialNumber() {
return getEndingJavaThreadRecord(ref).getThreadSerialNumber();
}
@Override
public boolean isActive() {
return false;
}
@Override
public IJavaStack getStack() {
return null;
}
}
public class ActiveJavaThread implements IJavaThread {
private final class JavaStackFrame implements IJavaStackFrame {
private int frameRecord=0;
public JavaStackFrame(int stackRecord) {
this.frameRecord=stackRecord;
}
@Override
public Long getID() {
IJavaStackFrameHProfRecord record=(IJavaStackFrameHProfRecord) file.getRecord(frameRecord);
return record.getStackFrameID();
}
@Override
public int getLineNumber() {
IJavaStackFrameHProfRecord record=(IJavaStackFrameHProfRecord) file.getRecord(frameRecord);
return record.getLineNumber();
}
@Override
public IJavaClass getMethodClass() {
IJavaStackFrameHProfRecord record=(IJavaStackFrameHProfRecord) file.getRecord(frameRecord);
int sn=record.getClassSerialNumber();
return loadedClassesBySerial.get(sn);
}
@Override
public String getMethodName() {
IJavaStackFrameHProfRecord record=(IJavaStackFrameHProfRecord) file.getRecord(frameRecord);
long id=record.getMethodNameID();
return getName(id);
}
@Override
public String getMethodSignature() {
IJavaStackFrameHProfRecord record=(IJavaStackFrameHProfRecord) file.getRecord(frameRecord);
long id=record.getMethodSignatureID();
return getName(id);
}
@Override
public String getSourceFileName() {
IJavaStackFrameHProfRecord record=(IJavaStackFrameHProfRecord) file.getRecord(frameRecord);
long id=record.getSourceFileNameID();
return getName(id);
}
}
private int ref = 0;
private ActiveJavaThread(int ref) {
this.ref = ref;
}
@Override
public boolean isActive() {
return true;
}
@Override
public int getStackTraceSerialNumber() {
return getActiveJavaThreadRecord(ref).getStackTraceSerialNumber();
}
@Override
public String getThreadGroupName() {
long id = getActiveJavaThreadRecord(ref).getThreadGroupNameID();
return getName(id);
}
@Override
public String getThreadGroupParentName() {
long id = getActiveJavaThreadRecord(ref)
.getThreadGroupParentNameID();
return getName(id);
}
@Override
public String getThreadName() {
long id = getActiveJavaThreadRecord(ref).getThreadNameID();
return getName(id);
}
@Override
public long getThreadObjectID() {
return getActiveJavaThreadRecord(ref).getThreadObjectID();
}
@Override
public int getThreadSerialNumber() {
return getActiveJavaThreadRecord(ref).getThreadSerialNumber();
}
private IJavaStackTraceHProfRecord stackTrace;
public void setStackTrace(IJavaStackTraceHProfRecord stackTrace) {
if (this.stackTrace == null) {
this.stackTrace = stackTrace;
return;
}
if (this.stackTrace.getSerialNumber() < stackTrace.getSerialNumber()) {
this.stackTrace = stackTrace;
}
}
@Override
public IJavaStack getStack() {
// stack trace record no
IJavaStackTraceHProfRecord record = stackTrace;
final IJavaStackFrame[] data;
if (record != null) { // There might not be any frames with this thread.
int stackElements = record.getFrameCount();
data = new IJavaStackFrame[stackElements];
for (int i = 0; i < stackElements; i++) {
long stackFrameElementID = record.getStackFrameID(i);
int stackRecord=stackTraceFrames.get(stackFrameElementID);
data[i]=new JavaStackFrame(stackRecord);
}
} else {
data = new IJavaStackFrame[0];
}
IJavaStack stack = new IJavaStack() {
@Override
public IJavaStackFrame[] getStack() {
return data;
}
@Override
public IJavaThread getThread() {
return ActiveJavaThread.this;
}
};
return stack;
}
}
private IThreadEndingHProfRecord getEndingJavaThreadRecord(int ref) {
return (IThreadEndingHProfRecord) file.getRecord(ref);
}
private IThreadActiveHProfRecord getActiveJavaThreadRecord(int ref) {
return (IThreadActiveHProfRecord) file.getRecord(ref);
}
private ILoadClassHProfRecord getLoadedClassHProfRecord(int ref) {
return (ILoadClassHProfRecord) file.getRecord(ref);
}
public int getUTF8Count() {
return utf8ids.length;
}
public long getUTF8IDFromIndex(int index) {
return utf8ids[index];
}
public int getLoadedClassCount() {
return loadedClassRecordNumbers.length;
}
public IJavaClass getLoadedClass(int index) {
return loadedClassRecordNumbers[index];
}
public HProfFile getFile() {
return file;
}
public int getUTF8Length(long id) {
int record = getUTF8RecordID(id);
if (record < 0)
return record;
IHProfRecord rec = file.getRecord(record);
if (rec == null)
return -2;
if (rec instanceof IUTF8HProfRecord) {
IUTF8HProfRecord utf8record = (IUTF8HProfRecord) rec;
return utf8record.getCharacters().length;
}
return -3;
}
public ClassSummary getRecordSummary() {
return recordSummary;
}
public IHeapDumpHProfRecord getHeapRecord() {
return heapRecord;
}
/**
* Return the record number for the heap record
* @return
*/
public long getHeapRecordLocation() {
return heapRecordLocation;
}
/**
* Returns the IJavaThreads that are active, i.e.
* haven't got corresponding END THREAD records.
*
* @return Collection of active JavaThreads
*/
public Collection<IJavaThread> getActiveThreads() {
return activeThreadsBySerial.values();
}
public File getSource() {
return file.getSource();
}
}