/*
* Copyright (c) 2012 Rob Eden.
* All Rights Reserved.
*/
package com.starlight.io.hprof;
import com.starlight.IOKit;
import com.starlight.ValidationKit;
import com.starlight.io.PositionTrackingInputStream;
import java.io.*;
/**
* A parser for processing HPROF files. The parser will read the file and call
* {@link HPROFParseHandler} methods as the file is read, in the same style as a SAX
* parser.
* <pre>
* File hprof_file = new File( "my_dump.hprof" );
* HPROFParseHandler my_handler = // implementation of handler
* HPROFParser parser = new HPROFParser( file, my_handler );
* parser.parse();
* </pre>
*/
public class HPROFParser {
private final File file;
private final HPROFParseHandler handler;
private boolean inside_heap_dump = false;
/**
* Instantiate a new parser that will read the given file.
*/
public HPROFParser( File file, HPROFParseHandler handler )
throws FileNotFoundException {
ValidationKit.checkNonnull( file, "file" );
ValidationKit.checkNonnull( handler, "handler" );
this.file = file;
this.handler = handler;
if ( !file.exists() ) {
throw new FileNotFoundException( "File \"" + file + "\" does not exist." );
}
}
/**
* Parse the file, calling the handler during processing. This method will return
* at the completion of processing. Parsing may be done multiple times and will
* parse the entire file each time.
*
* @throws IOException If a read error occurs during parsing.
*/
public synchronized void parse() throws IOException {
if ( !file.exists() ) {
throw new FileNotFoundException( "File \"" + file + "\" does not exist." );
}
FileInputStream fin = null;
BufferedInputStream bin = null;
PositionTrackingInputStream pin = null;
DataInputStream din = null;
try {
fin = new FileInputStream( file );
bin = new BufferedInputStream( fin );
pin = new PositionTrackingInputStream( bin );
din = new DataInputStream( pin );
parse_internal( din, pin );
}
finally {
IOKit.close( din );
IOKit.close( pin );
IOKit.close( bin );
IOKit.close( fin );
}
}
private void parse_internal( DataInputStream in, PositionTrackingInputStream pin )
throws IOException {
///////////////////////////////////////////////////////////////
// Header
StringBuilder format_name_buf = new StringBuilder();
int i;
while( ( i = in.readByte() & 0xff ) != 0 ) {
format_name_buf.append( ( char ) i );
}
// System.out.println( "Format name: " + format_name_buf );
int identifier_size = in.readInt();
// System.out.println( "Identifier size: " + identifier_size );
long time = in.readInt() & 0xFFFFFFFFL;
time = time << 32;
time |= in.readInt() & 0xFFFFFFFFL;
// System.out.println( "Time: " + new Date( time ) );
handler.header( format_name_buf.toString(), identifier_size, time );
////////////////////////////////////////////////////////////////
// Tags
while( true ) {
byte tag_type;
try {
tag_type = in.readByte();
}
catch( EOFException ex ) {
// If we're inside a heap dump and there was no HEAP DUMP END marker,
// act as though there was.
if ( inside_heap_dump ) {
handler.recordHeapDumpEnd();
}
// This is a place we expect to hit the end of the file.
// Just stop processing.
handler.end();
return;
}
long micros_since_header = in.readInt() & 0xFFFFFFFFL;
long length = in.readInt() & 0xFFFFFFFFL;
handler.beginRecord( tag_type, micros_since_header, length );
parseTag( in, pin, tag_type, length, identifier_size );
handler.endRecord( tag_type );
}
}
private void parseTag( DataInputStream in, PositionTrackingInputStream pin,
byte tag_type, long length, long identifier_size ) throws IOException {
switch( tag_type ) {
// STRING IN UTF8
case 0x01:
{
ID id = readID( in, identifier_size );
String string = readString( in, length - identifier_size );
handler.recordStringInUTF8( id, string );
break;
}
// LOAD CLASS
case 0x02:
{
int serial_number = in.readInt();
ID object_id = readID( in, identifier_size );
int stack_trace_serial = in.readInt();
ID class_name_string_id = readID( in, identifier_size );
handler.recordLoadClass( serial_number, object_id, stack_trace_serial,
class_name_string_id );
break;
}
// UNLOAD CLASS
case 0x03:
{
int serial_number = in.readInt();
handler.recordUnloadClass( serial_number );
break;
}
// STACK FRAME
case 0x04:
{
ID frame_id = readID( in, identifier_size );
ID method_name_id = readID( in, identifier_size );
ID method_signature_id = readID( in, identifier_size );
ID source_file_name_id = readID( in, identifier_size );
int class_serial_number = in.readInt();
// >0 - line number
// 0 - no line info available
// -1 - unknown location
// -2 - compiled method
// -3 - native method
int line = in.readInt();
handler.recordStackFrame( frame_id, method_name_id,
method_signature_id, source_file_name_id, class_serial_number,
line );
break;
}
// STACK TRACE
case 0x05:
{
int trace_serial_number = in.readInt();
int thread_serial_number = in.readInt();
long frame_count = in.readInt() & 0xFFFFFFFFL;
ID[] frame_ids = new ID[ ( int ) frame_count ];
for( long frame = 0; frame < frame_count; frame++ ) {
ID frame_id = readID( in, identifier_size );
frame_ids[ ( int ) frame ] = frame_id;
}
handler.recordStackTrace( trace_serial_number, thread_serial_number,
frame_ids );
break;
}
// ALLOC SITES
case 0x06:
{
short bit_mask_flags = in.readShort();
float cutoff_ratio = in.readFloat();
int total_live_bytes = in.readInt();
int total_live_instances = in.readInt();
long total_bytes_allocated = in.readLong();
long total_instances_allocated = in.readLong();
int site_count = in.readInt();
for( int i = 0; i < site_count; i++ ) {
byte array_indicator = in.readByte();
long class_serial_number = in.readInt() & 0xFFFFFFFFL;
long stack_trace_serial_number = in.readInt() & 0xFFFFFFFFL;
int site_total_live_bytes = in.readInt();
int site_total_live_instances = in.readInt();
int site_total_bytes_allocated = in.readInt();
int site_total_instances_allocated = in.readInt();
}
// TODO: call handler
break;
}
// HEAP SUMMARY
case 0x07:
{
int total_live_bytes = in.readInt();
int total_live_instances = in.readInt();
long total_bytes_allocated = in.readLong();
long total_instances_allocated = in.readLong();
// TODO: call handler
break;
}
// START THREAD
case 0x0A:
{
long thread_serial_number = in.readInt() & 0xFFFFFFFFL;
ID thread_object_id = readID( in, identifier_size );
long stack_trace_serial_number = in.readInt() & 0xFFFFFFFFL;
ID thread_name_string_id = readID( in, identifier_size );
ID thread_group_name_id = readID( in, identifier_size );
ID thread_parent_group_name_id = readID( in, identifier_size );
// TODO: call handler
break;
}
// END THREAD
case 0x0B:
{
long thread_serial_number = in.readInt() & 0xFFFFFFFFL;
// TODO: call handler
break;
}
// HEAP DUMP
case 0x0C:
// HEAP DUMP SEGMENT
case 0x1C:
{
inside_heap_dump = true;
if ( tag_type == 0x0C ) handler.recordHeapDump();
else handler.recordHeapDumpSegment();
long starting_position = pin.position();
while( pin.position() - starting_position < length ) {
byte sub_tag = in.readByte();
processHeapDumpSubTag( in, sub_tag, identifier_size );
}
break;
}
// HEAP DUMP END
case 0x2C:
inside_heap_dump = false;
handler.recordHeapDumpEnd();
break;
// CPU SAMPLES
case 0x0D:
{
long total_number_of_samples = in.readInt() & 0xFFFFFFFFL;
long number_of_traces = in.readInt() & 0xFFFFFFFFL;
int[] num_samples = new int[ ( int ) number_of_traces ];
int[] trace_serial = new int[ ( int ) number_of_traces ];
for( long n = 0; n < number_of_traces; n++ ) {
num_samples[ ( int ) n ] = in.readInt(); // number of samples
trace_serial[ ( int ) n ] = in.readInt(); // stack trace serial number
}
handler.recordCPUSamples( total_number_of_samples, num_samples,
trace_serial );
break;
}
// CONTROL SETTINGS
case 0x0E:
{
int bit_mask_flags = in.readInt();
int stack_trace_depth = in.readUnsignedShort();
handler.recordControlSettings( bit_mask_flags, stack_trace_depth );
break;
}
default:
System.out.println( "Hit unhandled type: 0x" +
Integer.toHexString( tag_type ) );
System.exit( -1 );
}
}
private void processHeapDumpSubTag( DataInputStream in, byte sub_tag,
long identifier_size ) throws IOException {
ID object_id = readID( in, identifier_size );
switch( sub_tag ) {
// ROOT UNKNOWN
case ( byte ) 0xFF:
handler.heapDumpRootUnknown( object_id );
break;
// ROOT JNI GLOBAL
case 0x01:
{
ID jni_global_ref_id = readID( in, identifier_size );
handler.heapDumpRootJNIGlobal( object_id, jni_global_ref_id );
break;
}
// ROOT JNI LOCAL
case 0x02:
{
int thread_serial_number = in.readInt();
int frame_number_in_trace = in.readInt(); // -1 for empty
handler.heapDumpRootJNILocal( object_id, thread_serial_number,
frame_number_in_trace );
break;
}
// ROOT JAVA FRAME
case 0x03:
{
int thread_serial_number = in.readInt();
int frame_number_in_trace = in.readInt(); // -1 for empty
handler.heapDumpRootJavaFrame( object_id, thread_serial_number,
frame_number_in_trace );
break;
}
// ROOT NATIVE STACK
case 0x04:
{
int thread_serial_number = in.readInt();
handler.heapDumpRootNativeStack( object_id, thread_serial_number );
break;
}
// ROOT STICKY CLASS
case 0x05:
handler.heapDumpRootStickyClass( object_id );
break;
// ROOT THREAD BLOCK
case 0x06:
{
int thread_serial_number = in.readInt();
handler.heapDumpRootThreadBlock( object_id, thread_serial_number );
break;
}
// ROOT MONITOR USED
case 0x07:
handler.heapDumpRootMonitorUsed( object_id );
break;
// ROOT THREAD OBJECT
case 0x08:
{
int thread_serial_number = in.readInt();
int stack_trace_serial_number = in.readInt();
handler.heapDumpRootThreadObject( object_id, thread_serial_number,
stack_trace_serial_number );
break;
}
// CLASS DUMP
case 0x20:
{
int stack_trace_serial_number = in.readInt();
ID super_class_id = readID( in, identifier_size );
ID class_loader_id = readID( in, identifier_size );
ID signers_object_id = readID( in, identifier_size );
ID protection_domain_object_id = readID( in, identifier_size );
readID( in, identifier_size ); // reserved
readID( in, identifier_size ); // reserved
long instance_size = in.readInt() & 0xFFFFFFFFL;
int constant_pool_size = in.readShort() & 0xFFFF;
int[] constant_pool_indexes = new int[ constant_pool_size ];
BasicType[] constant_pool_types = new BasicType[ constant_pool_size ];
Object[] constant_pool_values = new Object[ constant_pool_size ];
for( int i = 0; i < constant_pool_size; i++ ) {
constant_pool_indexes[ i ] = in.readShort() & 0xFFFF;
constant_pool_types[ i ] = BasicType.fromID( in.readByte() );
constant_pool_values[ i ] = valueForType( in,
constant_pool_types[ i ].getID(), identifier_size );
}
int static_field_count = in.readShort() & 0xFFFF;
ID[] static_field_names = new ID[ static_field_count ];
BasicType[] static_field_types = new BasicType[ static_field_count ];
Object[] static_field_values = new Object[ static_field_count ];
for( int i = 0; i < static_field_count; i++ ) {
static_field_names[ i ] = readID( in, identifier_size );
static_field_types[ i ] = BasicType.fromID( in.readByte() );
static_field_values[ i ] = valueForType( in,
static_field_types[ i ].getID(), identifier_size );
}
int instance_field_count = in.readShort() & 0xFFFF;
ID[] instance_field_names = new ID[ instance_field_count ];
BasicType[] instance_field_types = new BasicType[ instance_field_count ];
for( int i = 0; i < instance_field_count; i++ ) {
instance_field_names[ i ] = readID( in, identifier_size );
instance_field_types[ i ] = BasicType.fromID( in.readByte() );
}
handler.heapDumpClassDump( object_id, stack_trace_serial_number,
super_class_id, class_loader_id, signers_object_id,
protection_domain_object_id, instance_size, constant_pool_indexes,
constant_pool_types, constant_pool_values, static_field_names,
static_field_types, static_field_values, instance_field_names,
instance_field_types );
break;
}
// INSTANCE DUMP
case 0x21:
{
int stack_trace_serial_number = in.readInt();
ID class_object_id = readID( in, identifier_size );
long value_byte_length = in.readInt() & 0xFFFFFFFFL;
for( long i = 0; i < value_byte_length; i++ ) {
in.readByte();
}
handler.heapDumpInstanceDump( object_id, stack_trace_serial_number,
class_object_id, value_byte_length );
break;
}
// OBJECT ARRAY DUMP
case 0x22:
{
int stack_trace_serial_number = in.readInt();
int number_of_elements = in.readInt();
ID array_class_object_id = readID( in, identifier_size );
for( long i = 0; i < number_of_elements; i++ ) {
readID( in, identifier_size );
}
handler.heapDumpArrayDump( object_id, stack_trace_serial_number,
number_of_elements, array_class_object_id );
break;
}
// PRIMITIVE ARRAY DUMP
case 0x23:
{
int stack_trace_serial_number = in.readInt();
int number_of_elements = in.readInt();
byte entry_type = in.readByte(); // see Basic Type
for( long i = 0; i < number_of_elements; i++ ) {
valueForType( in, entry_type, identifier_size );
}
handler.headDumpPrimitiveArrayDump( object_id, stack_trace_serial_number,
number_of_elements, entry_type );
break;
}
default:
System.out.println( "Hit unhandled heap dump sub-type: " +
Integer.toHexString( sub_tag ) );
System.exit( -1 );
}
}
private ID readID( DataInputStream in, long identifier_size ) throws IOException {
byte[] to_return = new byte[ ( int ) identifier_size ];
in.readFully( to_return );
return new ID( to_return );
}
private String readString( DataInputStream in, long length ) throws IOException {
StringBuilder buf = new StringBuilder();
for( long i = 0; i < length; i++ ) {
buf.append( ( char ) ( in.readByte() & 0xFF ) );
}
return buf.toString();
}
private Object valueForType( DataInputStream in, byte type, long identifier_size )
throws IOException {
switch ( type ) {
case 2:
return readID( in, identifier_size );
case 4:
return Boolean.valueOf( in.readBoolean() );
case 5:
return Character.valueOf( in.readChar() );
case 6:
return Float.valueOf( in.readFloat() );
case 7:
return Double.valueOf( in.readDouble() );
case 8:
return Byte.valueOf( in.readByte() );
case 9:
return Short.valueOf( in.readShort() );
case 10:
return Integer.valueOf( in.readInt() );
case 11:
return Long.valueOf( in.readLong() );
default:
throw new IllegalArgumentException( "Unknown basic type: " + type );
}
}
}