1. stqn
  2. profiled

Source

profiled / main.d

/*
 * Profiling GTK GUI for the D programming language.
 * Reads the trace.log file and presents the data in a sortable list.
 *
 * Original author: Olivier Fabre
 * Licence: MIT
 * Uses GtkD (http://www.dsource.org/projects/gtkd) for the includes only.
 * Compiles with dmd -v1 and libphobos.
 */

import gtkc.gtk;
import gtkc.gtktypes;
import gtkc.gobject;
import gtkc.glib;

// Missing from GtkD's gtkc:
//#define g_signal_connect(instance, detailed_signal, c_handler, data) \
//    g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0)
uint g_signal_connect( void* instance, char* detailed_signal, GCallback c_handler, void* data ) {
	return g_signal_connect_data( instance, detailed_signal, c_handler, data, null, cast(GConnectFlags) 0 );
}
//uint connectData(void* instanc, string detailedSignal, GCallback cHandler, Object data, GClosureNotify destroyData, GConnectFlags connectFlags)


import std.conv;
import std.demangle;
import std.file;
import std.regexp;
import std.stdio;
import std.string;


enum Column {
	NUM_CALLS,
	TREE_TIME,
	TREE_TIME_PERCENT,
	FUNC_TIME,
	FUNC_TIME_PERCENT,
	AVG_FUNC_TIME,
	FUNC_NAME,
	SIZE
}

struct Data {
	long numCalls;
	long treeTime;
	long funcTime;
	long avgFuncTime;
	string funcName;
}
Data[] g_data;


void main( char[][] args ) {
	string filename;

	switch(args.length) {
	case 1:
		filename = "trace.log";
		break;
	default:
		filename = args[1];
		break;
	}

	char[] file;
	try {
		file = cast(char[]) read(filename);
	}
	catch {
		writefln( "Can't read " ~ filename ~ "."
		 "\nUsage: " ~ args[0] ~ " [trace file] (default: trace.log)." );
		return;
	}

	char[][] inputLines = file.splitlines();

	uint lineCounter;
	foreach( uint counter, char[] contents; inputLines ) {
		if( contents.length > 0 && contents[0] == '=' ) {
			lineCounter = counter;
			break;
		}
	}

	// Extract tick value
	auto ticksPerSecondRegExp = std.regexp.search( inputLines[lineCounter], "[0-9]+" );
	long ticksPerSecond = std.conv.toLong( ticksPerSecondRegExp.match(0) );
	//writefln( "ticksPerSecond: %d", ticksPerSecond );

	// Skip five lines to the beginning of the data
	lineCounter += 5;
	inputLines = inputLines[lineCounter..$];


	foreach( uint counter, string contents; inputLines ) {
		auto re = std.string.split( inputLines[counter] );

		Data tmp;
		tmp.numCalls = std.conv.toLong( re[0] );
		tmp.treeTime = std.conv.toLong( re[1] );
		tmp.funcTime = std.conv.toLong( re[2] );
		tmp.avgFuncTime = std.conv.toLong( re[3] );
		tmp.funcName = std.demangle.demangle( re[4] );
		g_data ~= tmp;

		//writefln( "%s %s %s %s", tmp.numCalls, tmp.treeTime, tmp.funcTime, tmp.avgFuncTime );
	}

	// Build the GUI
	gtk_init( null, null );

	// Create window
	GtkWidget* window = gtk_window_new( GtkWindowType.TOPLEVEL /*GTK_WINDOW_TOPLEVEL*/ );
	gtk_window_set_title( cast(GtkWindow*) window, std.string.toStringz("profileD") );
	gtk_widget_set_size_request( window, 800, 250 );
	//g_signal_connect( cast(void*) window, std.string.toStringz("delete_event"), cast(GCallback) gtk_main_quit, null );
	g_signal_connect( cast(void*) window, std.string.toStringz("destroy"), cast(GCallback) gtk_main_quit, null );

	GtkWidget* treeView = createViewAndModel();

	// Create a scrolled window so that we can move in the tree view
	GtkWidget* scrolledWindow = gtk_scrolled_window_new( null, null );

	// Doing this makes the treeview headers move when the vertical scrollbar
	// is used:
	//gtk_scrolled_window_add_with_viewport( cast(GtkScrolledWindow*) scrolledWindow, treeView );

	// This, on the contrary, doesn't move the headers (for some reason):
	// (thanks http://git.gnome.org/browse/gtk+/tree/gtk/gtkfilechooserdefault.c)
	gtk_container_add( cast(GtkContainer*) scrolledWindow, treeView );

	// Set horizontal scrollbar to show only when needed, vertical scrollbar
	// always
	gtk_scrolled_window_set_policy( cast(GtkScrolledWindow*) scrolledWindow,
		GtkPolicyType.AUTOMATIC, GtkPolicyType.ALWAYS );

	gtk_container_add( cast(GtkContainer*) window, scrolledWindow );
	gtk_widget_show_all( window );
	gtk_main();
}


static GtkWidget* createViewAndModel() {
	GtkTreeModel* model;
	GtkWidget* view;

	view = gtk_tree_view_new();

	string[Column.SIZE] headers = [
		"Num calls",
		"Tree time",
		"%",
		"Func time",
		"%",
		"Avg Func time",
		"Function name",
	];

	foreach( int col, string text; headers ) {
		GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
		if( col != Column.FUNC_NAME ) {
			// Align numbers to the right
			gtk_cell_renderer_set_alignment( renderer, 1.0f, 1.0f );
		}
		GtkTreeViewColumn* treeViewColumn = gtk_tree_view_column_new_with_attributes(
			std.string.toStringz(text),
			renderer,
			std.string.toStringz("text"), col,
			null
		);
		int sortColumn = col;
		if( col == Column.FUNC_TIME_PERCENT || col == Column.TREE_TIME_PERCENT ) {
			// Sort according to FUNC_TIME / TREE_TIME values
			sortColumn--;
		}
		gtk_tree_view_column_set_sort_column_id( treeViewColumn, sortColumn );
		//gtk_tree_view_column_set_clickable( treeViewColumn, true );
		//g_signal_connect/*_swapped*/( cast(void*) treeViewColumn, std.string.toStringz("clicked"), cast(GCallback) treeViewColumnClicked, null );
		gtk_tree_view_append_column( cast(GtkTreeView*) view, treeViewColumn );
	}

	// Make rows alternate color
	gtk_tree_view_set_rules_hint( cast(GtkTreeView*) view, true );

	model = createAndFillModel();

	gtk_tree_view_set_model( cast(GtkTreeView*) view, model );
	gtk_tree_view_set_search_column( cast(GtkTreeView*) view, Column.FUNC_NAME );
	gtk_tree_view_set_search_equal_func( cast(GtkTreeView*) view, cast(GtkTreeViewSearchEqualFunc) &treeViewSearchEqualFunc, null, null );

	return view;
}


extern(C) {
	int treeViewColumnClicked( /*GtkTreeViewColumn* treeViewColumn,*/ void* data ) {
		//gtk_tree_view_column_set_sort_order( treeViewColumn,
		//gtk_tree_view_column_set_sort_column_id( treeViewColumn, *col );
		return true;
	}

	/*gboolean*/int treeViewSearchEqualFunc( GtkTreeModel* model,
		/*gint*/int column,
		/*const*/ /*gchar*/char* key,
		GtkTreeIter* iter,
		/*gpointer*/void* search_data ) {
		char* text;
		gtk_tree_model_get( model, iter, column, &text, -1 );
		bool result = ( std.string.ifind( std.string.toString(text), std.string.toString(key) ) == -1 );
		g_free( text );
		return result;
	}
}


GtkTreeModel* createAndFillModel() {
	// Create ListStore
	// Using INT and not UINT because sometimes trace.log contains negative
	// values.
	static GType[Column.SIZE] columnTypes = [
		GType.INT64,
		GType.INT64,
		GType.STRING,
		GType.INT64,
		GType.STRING,
		GType.INT64,
		GType.STRING
	];

	GtkListStore *listStore = gtk_list_store_newv( Column.SIZE, &columnTypes[0] );

	// Find main() tree time (total program execution time)
	long totalProgramTime = 1;	// to avoid crash if not found (shouldn't happen?)
	foreach( datas; g_data ) {
		if( datas.funcName == "_Dmain" ) {
			totalProgramTime = datas.treeTime;
			break;
		}
	}

	// Fill ListStore with data
	GtkTreeIter iter;
	foreach( num, datas; g_data ) {
		//writefln( "%s %s %s %s %s %s", num, datas.numCalls, datas.treeTime, datas.funcTime, datas.avgFuncTime, datas.funcName );
		gtk_list_store_append( listStore, &iter );
		gtk_list_store_set( listStore, &iter,
			Column.NUM_CALLS, datas.numCalls,
			Column.TREE_TIME, datas.treeTime,
			Column.TREE_TIME_PERCENT, std.string.toStringz( std.string.format( "%.1f %%", 100.f * datas.treeTime / totalProgramTime ) ),
			Column.FUNC_TIME, datas.funcTime,
			Column.FUNC_TIME_PERCENT, std.string.toStringz( std.string.format( "%.1f %%", 100.f * datas.funcTime / totalProgramTime ) ),
			Column.AVG_FUNC_TIME, datas.avgFuncTime,
			Column.FUNC_NAME, std.string.toStringz(datas.funcName),
			-1
		);
	}

	return cast(GtkTreeModel*) listStore;
}