/*
* Copyright (c) 2012 Rob Eden.
* All Rights Reserved.
*/
package com.starlight.io.hprof;
import com.starlight.StringKit;
import com.starlight.types.Pair;
import gnu.trove.map.TIntLongMap;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.TObjectLongMap;
import gnu.trove.map.hash.TIntLongHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import gnu.trove.map.hash.TObjectLongHashMap;
import gnu.trove.procedure.TIntLongProcedure;
import gnu.trove.procedure.TObjectIntProcedure;
import gnu.trove.procedure.TObjectLongProcedure;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.*;
/**
* Basic implementation of a {@link HPROFParseHandler} which analyzes an HPROF memory dump
* file.
*/
public class MemoryAnalyzer {
public static void main( String[] args ) throws IOException {
if ( args.length != 1 ) {
System.out.println( "Usage: MemoryAnalyzer <hprof_file>" );
return;
}
File file = new File( args[ 0 ] );
analyze( file, System.out );
}
/**
* Analyze an HPROF memory file and output the results to the given stream.
*/
public static void analyze( File hprof_file, PrintStream output ) throws IOException {
analyze( hprof_file, new PrintWriter( output ) );
}
/**
* Analyze an HPROF memory file and output the results to the given writer.
*/
public static void analyze( File hprof_file, PrintWriter output ) throws IOException {
MemoryAnalyzer analyzer = new MemoryAnalyzer( output );
HPROFParser parser = new HPROFParser( hprof_file, analyzer.handler );
parser.parse();
}
private final PrintWriter output;
private final Map<ID,String> string_map = new HashMap<ID, String>();
private final Map<ID,ID> class_name_map = new HashMap<ID, ID>();
private final TIntObjectMap<ID> classes = new TIntObjectHashMap<ID>();
private final Map<ID,StackTraceElement> frames = new HashMap<ID, StackTraceElement>();
private final TIntObjectMap<List<ID>> stack_traces = new TIntObjectHashMap<List<ID>>();
private int instance_dumps = 0;
private int class_dumps = 0;
private TObjectIntMap<ID> class_instances = new TObjectIntHashMap<ID>();
private TObjectLongMap<ID> class_aggregate_size = new TObjectLongHashMap<ID>();
private TIntLongMap trace_counts = new TIntLongHashMap();
private Handler handler = new Handler();
private MemoryAnalyzer( PrintWriter output ) {
this.output = output;
}
private class Handler extends AbstractHPROFParseHandler {
@Override
public void recordStringInUTF8( ID id, String string ) {
string_map.put( id, string );
}
@Override
public void recordLoadClass( int class_serial_number, ID object_id,
int stack_trace_serial, ID class_name_string_id ) {
class_name_map.put( object_id, class_name_string_id );
classes.put( class_serial_number, object_id );
}
@Override
public void recordStackFrame( ID frame_id, ID method_name_id, ID method_signature_id,
ID source_file_name_id, int class_serial_number, int line ) {
ID class_id = classes.get( class_serial_number );
String class_name = "***unknown class***";
if ( class_id != null && string_map.containsKey( class_id ) ) {
class_name = string_map.get( class_id );
}
// TODO: need signature, examples:
// Method name: init
// Signature: ()V
// Method name: <clinit>
// Signature: ()V
// Method name: socketBind
// Signature: (Ljava/net/ InetAddress;I)V
// Method name: bind
// Signature: (Ljava/net/InetAddress;I)V
// Method name: bind
// Signature: (Ljava/net/ SocketAddress;I)V
// Method name: <init>
// Signature: (IILjava/net/InetAddress;)V
// Method name: <init>
// Signature: (I)V
// Method name: run
// Signature: ()V
frames.put( frame_id, new StackTraceElement( class_name,
string_map.get( method_name_id ),
string_map.get( source_file_name_id ), line ) );
}
@Override
public void recordStackTrace( int trace_serial_number, int thread_serial_number,
ID[] frame_ids ) {
if ( frame_ids == null || frame_ids.length == 0 ) return;
List<ID> frames = stack_traces.get( trace_serial_number );
if ( frames == null ) {
frames = new ArrayList<ID>( 2 );
stack_traces.put( trace_serial_number, frames );
}
for( ID frame_id : frame_ids ) {
frames.add( frame_id );
}
}
@Override
public void heapDumpClassDump( ID object_id, int stack_trace_serial_number,
ID super_class_id, ID class_loader_id, ID signers_object_id,
ID protection_domain_object_id, long instance_size, int[] constant_pool_indexes,
BasicType[] constant_pool_types, Object[] constant_pool_values,
ID[] static_field_names, BasicType[] static_field_types,
Object[] static_field_values, ID[] instance_field_names,
BasicType[] instance_field_types ) {
class_dumps++;
}
@Override
public void heapDumpInstanceDump( ID object_id, int stack_trace_serial_number,
ID class_object_id, long data_length ) {
instance_dumps++;
class_instances.adjustOrPutValue( class_object_id, 1, 1 );
class_aggregate_size.adjustOrPutValue( class_object_id, data_length, data_length );
trace_counts.adjustOrPutValue( stack_trace_serial_number, 1, 1 );
}
@Override
public void end() {
output.println( "Strings: " + string_map.size() );
output.println( "Classes: " + class_dumps );
output.println( "Instances: " + instance_dumps );
output.println();
final List<Pair<ID,Integer>> instance_counts =
new ArrayList<Pair<ID, Integer>>( class_instances.size() );
class_instances.forEachEntry( new TObjectIntProcedure<ID>() {
@Override
public boolean execute( ID id, int i ) {
instance_counts.add( Pair.create( id, Integer.valueOf( i ) ) );
return true;
}
} );
Collections.sort( instance_counts, new Comparator<Pair<ID, Integer>>() {
@Override
public int compare( Pair<ID, Integer> o1, Pair<ID, Integer> o2 ) {
return o2.getTwo().intValue() - o1.getTwo().intValue();
}
} );
output.println( "Top Classes By Count" );
int string_size = 10;
for( int i = 0; i < 10 && i < instance_counts.size(); i++ ) {
Pair<ID, Integer> p = instance_counts.get( i );
String count_string = p.getTwo().toString();
if ( i == 0 ) {
string_size = count_string.length();
}
output.print( " " );
output.print( StringKit.ensureSize( count_string, string_size ) );
output.print( " - " );
output.println( string_map.get( class_name_map.get(
p.getOne() ) ) );
}
final List<Pair<ID,Long>> instance_sizes =
new ArrayList<Pair<ID, Long>>( class_aggregate_size.size() );
class_aggregate_size.forEachEntry( new TObjectLongProcedure<ID>() {
@Override
public boolean execute( ID id, long l ) {
instance_sizes.add( Pair.create( id, Long.valueOf( l ) ) );
return true;
}
} );
Collections.sort( instance_sizes, new Comparator<Pair<ID, Long>>() {
@Override
public int compare( Pair<ID, Long> o1, Pair<ID, Long> o2 ) {
if ( o1.getTwo().longValue() > o2.getTwo().longValue() ) {
return -1;
}
else if ( o1.getTwo().longValue() == o2.getTwo().longValue() ) {
return 0;
}
else return 1;
}
} );
output.println();
output.println( "Top Classes By Size" );
string_size = 10;
for( int i = 0; i < 10 && i < instance_sizes.size(); i++ ) {
Pair<ID, Long> p = instance_sizes.get( i );
String count_string = p.getTwo().toString();
if ( i == 0 ) {
string_size = count_string.length();
}
output.print( " " );
output.print( StringKit.ensureSize( count_string, string_size ) );
output.print( " - " );
output.println( string_map.get( class_name_map.get(
p.getOne() ) ) );
}
final List<Pair<Integer,Long>> trace_counts =
new ArrayList<Pair<Integer, Long>>( MemoryAnalyzer.this.trace_counts.size() );
MemoryAnalyzer.this.trace_counts.forEachEntry( new TIntLongProcedure() {
@Override
public boolean execute( int i, long l ) {
trace_counts.add(
Pair.create( Integer.valueOf( i ), Long.valueOf( l ) ) );
return true;
}
} );
Collections.sort( trace_counts, new Comparator<Pair<Integer, Long>>() {
@Override
public int compare( Pair<Integer, Long> o1, Pair<Integer, Long> o2 ) {
if ( o1.getTwo().longValue() > o2.getTwo().longValue() ) {
return -1;
}
else if ( o1.getTwo().longValue() == o2.getTwo().longValue() ) {
return 0;
}
else return 1;
}
} );
output.println();
output.println( "Top Trace Counts" );
string_size = 10;
List<Integer> traces_to_print = new ArrayList<Integer>();
for( int i = 0; i < 10 && i < trace_counts.size(); i++ ) {
Pair<Integer, Long> p = trace_counts.get( i );
String count_string = p.getTwo().toString();
if ( i == 0 ) {
string_size = count_string.length();
}
output.print( " " );
output.print( StringKit.ensureSize( count_string, string_size ) );
output.print( " - " );
output.println( Integer.toHexString( p.getOne().intValue() ) );
traces_to_print.add( p.getOne() );
}
for( Integer trace_id : traces_to_print ) {
output.println();
output.println( " Trace " + Integer.toHexString( trace_id.intValue() ) );
List<ID> frames = stack_traces.get( trace_id.intValue() );
if ( frames == null ) {
output.print( " No frames" );
}
else {
for( ID frame : frames ) {
StackTraceElement trace_element =
MemoryAnalyzer.this.frames.get( frame );
output.print( " " );
output.print( trace_element.getClassName() );
output.print( '.' );
output.print( trace_element.getMethodName() );
output.print( " (" );
output.print( trace_element.getFileName() );
output.print( ':' );
output.print( trace_element.getLineNumber() );
output.println( ')' );
}
}
}
output.println();
output.flush();
}
}
}