Access to Mono’s Low Level Profiling Functions in Unity

Unity has already quite useful profiling functions, but certain information are still difficult to gather. I once had to determine the cause of a memory leak problem in a Unity project not written by me. That was kind of hard to find. Should I encounter such a problem again it would be nice to have a tool-based approach.

Inspired by a recent twitter discussion it occurred to me that one could possibly hook into the low level profile interface of Mono.

One can take a look at Mono’s profiler header here:
profiler.h

The code (and documentation^^) for the profiler can be found here:
profiler.c

Probably the only way to determine, if that’s feasible at all, is to quickly create a minimal plugin.

So can we call these methods in the Mono runtime of Unity?

Here’s a quick and dirty attempt:

#define WIN32_LEAN_AND_MEAN
#define EXPORT_API __declspec(dllexport)

#include <stdio.h>
#include <Windows.h>

//
//  MonoProfilePlugin.c
//
//  Created by stephan / Software 7 GmbH on 21.05.13 - 17:14
//  Only a quick & dirty hack
//  use at your own risk
//

static HMODULE monoLibraryHandle;

//did I already mention that it's a quick and dirty hack? 
struct _MonoMethod {
    short flags;  
    short iflags; 
    int token;
    char *klassDummy;
    char *signatureDummy;
    const char *name;
	//some fields were omitted
};

typedef struct _MonoMethod MonoMethod;
        
typedef enum {
    MONO_PROFILE_NONE = 0,
    MONO_PROFILE_APPDOMAIN_EVENTS = 1 << 0,
    MONO_PROFILE_ASSEMBLY_EVENTS  = 1 << 1,
    MONO_PROFILE_MODULE_EVENTS    = 1 << 2,
    MONO_PROFILE_CLASS_EVENTS     = 1 << 3,
    MONO_PROFILE_JIT_COMPILATION  = 1 << 4,
    MONO_PROFILE_INLINING         = 1 << 5,
    MONO_PROFILE_EXCEPTIONS       = 1 << 6,
    MONO_PROFILE_ALLOCATIONS      = 1 << 7,
    MONO_PROFILE_GC               = 1 << 8,
    MONO_PROFILE_THREADS          = 1 << 9,
    MONO_PROFILE_REMOTING         = 1 << 10,
    MONO_PROFILE_TRANSITIONS      = 1 << 11,
    MONO_PROFILE_ENTER_LEAVE      = 1 << 12,
    MONO_PROFILE_COVERAGE         = 1 << 13,
    MONO_PROFILE_INS_COVERAGE     = 1 << 14,
    MONO_PROFILE_STATISTICAL      = 1 << 15,
    MONO_PROFILE_METHOD_EVENTS    = 1 << 16,
    MONO_PROFILE_MONITOR_EVENTS   = 1 << 17,
    MONO_PROFILE_IOMAP_EVENTS     = 1 << 18, 
    MONO_PROFILE_GC_MOVES         = 1 << 19,
    MONO_PROFILE_GC_ROOTS         = 1 << 20
} MonoProfileFlags;


typedef enum
{
    MONO_GC_EVENT_START,
    MONO_GC_EVENT_MARK_START,
    MONO_GC_EVENT_MARK_END,
    MONO_GC_EVENT_RECLAIM_START,
    MONO_GC_EVENT_RECLAIM_END,
    MONO_GC_EVENT_END,
    MONO_GC_EVENT_PRE_STOP_WORLD,
    MONO_GC_EVENT_POST_STOP_WORLD,
    MONO_GC_EVENT_PRE_START_WORLD,
    MONO_GC_EVENT_POST_START_WORLD
} MonoGCEvent;

struct _MonoProfiler {
    char dummy[10];
};
        
typedef struct _MonoProfiler MonoProfiler;

static FILE *fp;
static MonoProfiler _monoProfiler;
static BOOLEAN gc_eventCalledAtLeastOnce = FALSE;
static BOOLEAN gc_resizeCalledAtLeastOnce = FALSE;
static BOOLEAN sample_method_enterCalledAtLeastOnce = FALSE;
static BOOLEAN sample_method_leaveCalledAtLeastOnce = FALSE;

typedef void (*MonoProfileFunc) (MonoProfiler *prof);
typedef void (*MonoProfileMethodFunc) (MonoProfiler *prof, MonoMethod* method);
typedef void (*MonoProfileGCFunc) (MonoProfiler *prof, MonoGCEvent event, int generation);
typedef void (*MonoProfileGCResizeFunc)   (MonoProfiler *prof, __int64 new_size);

typedef void (*mono_profiler_installFunc) (MonoProfiler *prof, MonoProfileFunc shutdown_callback);
typedef void (*mono_profiler_install_enter_leaveFunc) (MonoProfileMethodFunc enterCallback, MonoProfileMethodFunc leaveCallback);
typedef void (*mono_profiler_set_eventsFunc) (MonoProfileFlags events);
typedef void (*mono_profiler_install_gcFunc) (MonoProfileGCFunc callback, MonoProfileGCResizeFunc heap_resize_callback);

static mono_profiler_installFunc mono_profiler_install;
static mono_profiler_install_enter_leaveFunc mono_profiler_install_enter_leave;
static mono_profiler_set_eventsFunc mono_profiler_set_events;
static mono_profiler_install_gcFunc mono_profiler_install_gc;
    
static void debug_out(const char *txt)
{
    if(fp != NULL) {
        fprintf(fp, "%s\n", txt);
        fflush(fp);
    }
}

static void profiler_shutdown (MonoProfiler *prof)
{
    debug_out("profiler_shutdown");        
}

static void gc_event(MonoProfiler *profiler, MonoGCEvent event, int generation)
{
	if(!gc_eventCalledAtLeastOnce) {
		debug_out("gc_event");
		gc_eventCalledAtLeastOnce = TRUE;
	}

}

static void gc_resize (MonoProfiler *profiler, __int64 new_size)
{
	if(!gc_resizeCalledAtLeastOnce) {
		debug_out("gc_resize");
		gc_resizeCalledAtLeastOnce = TRUE;
	}
}

static void sample_method_enter (MonoProfiler *prof, MonoMethod *method)
{
	if(!sample_method_enterCalledAtLeastOnce) {
		debug_out("sample_method_enter");
		debug_out(method->name);
		sample_method_enterCalledAtLeastOnce = TRUE;
	}
}
        
static void sample_method_leave (MonoProfiler *prof, MonoMethod *method)
{
	if(!sample_method_leaveCalledAtLeastOnce) {
		debug_out("sample_method_leave");
		debug_out(method->name);
		sample_method_leaveCalledAtLeastOnce = TRUE;
	}        
}

void EXPORT_API InitializeProfiler()
{
    fopen_s(&fp, "C:\\Users\\stephan\\tmp\\mytestlog.txt", "w");
        
    debug_out("InitializeProfiler");
        
	monoLibraryHandle = LoadLibrary(L"C:\\Program Files (x86)\\Unity\\Editor\\Data\\Mono\\EmbedRuntime\\mono.dll");
	if(monoLibraryHandle) { 
		mono_profiler_install = (mono_profiler_installFunc)GetProcAddress(monoLibraryHandle, "mono_profiler_install");
		mono_profiler_install_gc = (mono_profiler_install_gcFunc)GetProcAddress(monoLibraryHandle, "mono_profiler_install_gc");
		mono_profiler_install_enter_leave = (mono_profiler_install_enter_leaveFunc)GetProcAddress(monoLibraryHandle, "mono_profiler_install_enter_leave");
		mono_profiler_set_events = (mono_profiler_set_eventsFunc)GetProcAddress(monoLibraryHandle, "mono_profiler_set_events");
		debug_out("after GetProcAddress");

	    mono_profiler_install (&_monoProfiler, profiler_shutdown);
		debug_out("after mono_profiler_install");

		mono_profiler_install_gc (gc_event, gc_resize);
		debug_out("after mono_profiler_install_gc");
        
		mono_profiler_install_enter_leave (sample_method_enter, sample_method_leave);
		debug_out("after mono_profiler_install_enter_leave");

		mono_profiler_set_events((MonoProfileFlags)(MONO_PROFILE_ENTER_LEAVE | MONO_PROFILE_GC));
		debug_out("after mono_profiler_set_events");
	}
	else {
		debug_out("Loading mono.dll failed!");			
	}
}

Calling the plugin from Unity:

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;

public class MyTest : MonoBehaviour
{
	[DllImport ("MonoProfilePlugin")]
	private static extern void InitializeProfiler();

	void Start ()
	{
		InitializeProfiler();
		//we need to call a method to see whether sample_method_enter/-leave works
		Debug.Log(AddIntegers(1, 2));
	}

	void Update ()
	{
	}

	private int AddIntegers(int a, int b)
	{
		return a + b;
	}
}

Now it gets interesting.

What will we find in mytestlog.txt?

This is the result:

InitializeProfiler
after GetProcAddress
after mono_profiler_install
after mono_profiler_install_gc
after mono_profiler_install_enter_leave
after mono_profiler_set_events
sample_method_enter
AddIntegers
sample_method_leave
AddIntegers
gc_event
profiler_shutdown

Wooh. This means it works. We successfully hooked into Mono’s profiling.

Next step would be to collect all the allocation data together with the corresponding stack traces and add some reporting.

Unfortunately currently I don’t have time for this. Perhaps I can later work on it. Or perhaps someone wants to develop this further? (HINT, HINT, HINT) 🙂

4 thoughts on “Access to Mono’s Low Level Profiling Functions in Unity

  1. Chris Hoyean Song

    Hi. I’m Chris Song.

    I’m a Unity developer.

    Recently I’m developing a run-time profiler module for detailed inspection.

    I almost finished the memory / resource / frame rate monitoring part.

    but for script(function call ..) analysis, I need this approach 😛

    Thank you for your work!

  2. stephan Post author

    Hi Chris,

    I’m glad to hear the blog post was useful to you. If your profiler module is completed, and you need a tester: I volunteer 🙂

    Stephan

  3. Chris Hoyean Song

    By the way, I’m a MAC user..

    I’m trying to load mono.dll, but it doesn’t work.

    InitializeProfiler
    dlopen(/Applications/Unity/Unity.app/Contents/Frameworks/Mono/bin/mono.dll, 5): no suitable image found. Did find:
    /Applications/Unity/Unity.app/Contents/Frameworks/Mono/bin/mono.dll: unknown file type, first eight bytes: 0x4D 0x5A 0x90 0x00 0x03 0x00 0x00 0x00
    Loading mono.dll failed!

    It seems like 64bit / 32bit problem.. I don’t know exactly why..

    #ifdef WIN32
    #define EXPORT_API __declspec(dllexport)
    #else
    #define EXPORT_API
    #endif
    #include
    #include
    #include
    #include “Plugin.pch”
    //
    // MonoProfilePlugin.c
    //
    // Created by stephan / Software 7 GmbH on 21.05.13 – 17:14
    // Only a quick & dirty hack
    // use at your own risk
    //

    //static HMODULE monoLibraryHandle;

    //did I already mention that it’s a quick and dirty hack?
    struct _MonoMethod {
    short flags;
    short iflags;
    int token;
    char *klassDummy;
    char *signatureDummy;
    const char *name;
    //some fields were omitted
    };

    typedef struct _MonoMethod MonoMethod;

    typedef enum {
    MONO_PROFILE_NONE = 0,
    MONO_PROFILE_APPDOMAIN_EVENTS = 1 << 0,
    MONO_PROFILE_ASSEMBLY_EVENTS = 1 << 1,
    MONO_PROFILE_MODULE_EVENTS = 1 << 2,
    MONO_PROFILE_CLASS_EVENTS = 1 << 3,
    MONO_PROFILE_JIT_COMPILATION = 1 << 4,
    MONO_PROFILE_INLINING = 1 << 5,
    MONO_PROFILE_EXCEPTIONS = 1 << 6,
    MONO_PROFILE_ALLOCATIONS = 1 << 7,
    MONO_PROFILE_GC = 1 << 8,
    MONO_PROFILE_THREADS = 1 << 9,
    MONO_PROFILE_REMOTING = 1 << 10,
    MONO_PROFILE_TRANSITIONS = 1 << 11,
    MONO_PROFILE_ENTER_LEAVE = 1 << 12,
    MONO_PROFILE_COVERAGE = 1 << 13,
    MONO_PROFILE_INS_COVERAGE = 1 << 14,
    MONO_PROFILE_STATISTICAL = 1 << 15,
    MONO_PROFILE_METHOD_EVENTS = 1 << 16,
    MONO_PROFILE_MONITOR_EVENTS = 1 << 17,
    MONO_PROFILE_IOMAP_EVENTS = 1 << 18,
    MONO_PROFILE_GC_MOVES = 1 << 19,
    MONO_PROFILE_GC_ROOTS = 1 <name);
    sample_method_enterCalledAtLeastOnce = true;
    }
    }

    static void sample_method_leave (MonoProfiler *prof, MonoMethod *method)
    {
    if(!sample_method_leaveCalledAtLeastOnce) {
    debug_out(“sample_method_leave”);
    debug_out(method->name);
    sample_method_leaveCalledAtLeastOnce = true;
    }
    }

    void EXPORT_API InitializeProfiler()
    {
    fp = fopen(“/mytext.txt”, “w”);

    debug_out(“InitializeProfiler”);

    void *handle = NULL;
    handle = dlopen(“/Applications/Unity/Unity.app/Contents/Frameworks/Mono/bin/mono.dll”, RTLD_LOCAL | RTLD_LAZY);
    //monoLibraryHandle = LoadLibrary(L”C:\\Program Files (x86)\\Unity\\Editor\\Data\\Mono\\EmbedRuntime\\mono.dll”);
    if(handle) {
    mono_profiler_install = (mono_profiler_installFunc)dlsym(handle, “mono_profiler_install”);
    mono_profiler_install_gc = (mono_profiler_install_gcFunc)dlsym(handle, “mono_profiler_install_gc”);
    mono_profiler_install_enter_leave = (mono_profiler_install_enter_leaveFunc)dlsym(handle, “mono_profiler_install_enter_leave”);
    mono_profiler_set_events = (mono_profiler_set_eventsFunc)dlsym(handle, “mono_profiler_set_events”);
    debug_out(“after GetProcAddress”);

    mono_profiler_install (&_monoProfiler, profiler_shutdown);
    debug_out(“after mono_profiler_install”);

    mono_profiler_install_gc (gc_event, gc_resize);
    debug_out(“after mono_profiler_install_gc”);

    mono_profiler_install_enter_leave (sample_method_enter, sample_method_leave);
    debug_out(“after mono_profiler_install_enter_leave”);

    mono_profiler_set_events((MonoProfileFlags)(MONO_PROFILE_ENTER_LEAVE | MONO_PROFILE_GC));
    debug_out(“after mono_profiler_set_events”);
    }
    else {

    debug_out( dlerror() );

    debug_out(“Loading mono.dll failed!”);
    }
    }

    const char* PrintHello(){
    return string;
    }

    int PrintANumber(){
    string = (char *)malloc(1000);
    //str = “”;

    //fp = fopen(“/Users/Chris/Documents/Unity/Nike/mytext.txt”, “w”);
    /*
    debug_out(“InitializeProfiler”);

    void *handle = NULL;
    handle = dlopen(“/Applications/Unity/Unity.app/Contents/Frameworks/Mono/bin/mono.dll”, RTLD_NOW);
    //monoLibraryHandle = LoadLibrary(L”C:\\Program Files (x86)\\Unity\\Editor\\Data\\Mono\\EmbedRuntime\\mono.dll”);
    if(handle) {
    mono_profiler_install = (mono_profiler_installFunc)dlsym(handle, “mono_profiler_install”);
    mono_profiler_install_gc = (mono_profiler_install_gcFunc)dlsym(handle, “mono_profiler_install_gc”);
    mono_profiler_install_enter_leave = (mono_profiler_install_enter_leaveFunc)dlsym(handle, “mono_profiler_install_enter_leave”);
    mono_profiler_set_events = (mono_profiler_set_eventsFunc)dlsym(handle, “mono_profiler_set_events”);
    debug_out(“after GetProcAddress”);

    mono_profiler_install (&_monoProfiler, profiler_shutdown);
    debug_out(“after mono_profiler_install”);

    mono_profiler_install_gc (gc_event, gc_resize);
    debug_out(“after mono_profiler_install_gc”);

    mono_profiler_install_enter_leave (sample_method_enter, sample_method_leave);
    debug_out(“after mono_profiler_install_enter_leave”);

    mono_profiler_set_events((MonoProfileFlags)(MONO_PROFILE_ENTER_LEAVE | MONO_PROFILE_GC));
    debug_out(“after mono_profiler_set_events”);
    }
    else {
    debug_out(“Loading mono.dll failed!”);
    }*/

    return 5;
    }

    int AddTwoIntegers(int a, int b) {
    return a + b;
    }

    float AddTwoFloats(float a, float b) {
    return a + b;
    }

  4. stephan Post author

    Well, in principle, it should run also on a Mac.
    Why do you think it could be a 32/64 bit problem?

Comments are closed.