Monday, December 06, 2010

Writing a profiler for mono

As some of you may know, my day job is working on Moonlight, the FOSS implementation of Silverlight. This is a fairly complex piece of code as we have C++ interoperating with C# and Javascript, so managing the lifecycle of objects can be difficult. C++ refcounts, C# has an automatic garbage collector and Javascript... well, let's just say that it complicates things a *lot* when combined with the other two languages.

One of the tricks we use to ensure that objects do not die early is to use a (normal) GCHandle on our C# objects to ensure the garbage collector doesn't mark them as junk before they're definitely finished with in C++. However, we were having issues where some of our objects appeared to never ever get garbage collected even though they should be eligible. We suspected that excess GCHandles were causing it but we had no easy way of tracking this. Without detailed information on when and where the GCHandles were allocated, it would be next to impossible to track down where we were going wrong. After several aborted attempts to track this with Console.WriteLine I figured there must be a better way.

How about a custom profiler for mono?

Documentation on this is fairly sparse currently, so hopefully this will help. Thanks go to Paolo Molaro for helping me when I got stuck! This code is mostly copied/pasted from moonlight so it's C++ ish and also may contain some moonlight specific code.

Step 1:
Open up the "mono/metadata/profiler.h" file and check out all the profiler hooks that are available. They are all documented in profiler.c, so open that if you want a description of how they all work. Take note of the ones you'll need. All I needed to do was track GCHandle allocations, so I just wanted this hook:
void mono_profiler_install_gc_roots    (MonoProfileGCHandleFunc handle_callback, MonoProfileGCRootFunc roots_callback);

Step 2:
I now know what function I need to provide and the data I want to gather, so it's time to create a struct to store my data. This is an opaque struct which will be passed to the mono runtime and passed back in every profiler hook, so you can stick whatever you want in it. I know I want to store stacktraces, allocated gchandles and a typename, so I'll start with just those. I'll also declare my prototype for my profiler hook here.

struct _MonoProfiler {
const char *type_name;
GPtrArray *gchandles;
GPtrArray *stacktraces;
Moonlight::Mutex locker; /* used to ensure only one thread accesses the arrays */

static void track_gchandle (_MonoProfiler *prof, int op, int type, uintptr_t handle, MonoObject *obj);

typedef _MonoProfiler MonoProfiler;

Step 3:
Implementation time! First the constructor for the MonoProfiler. I use an environment variable to limit which types we store stacktraces for as they're expensive to generate and consume a lot of memory to store when you have 10,000s of them. The important functions are the last three. The first registers our profiler with the runtime. The second installs the hook so we can get notifications for gchandle allocations. The final call enables the gc_handle events in the profiler which makes the runtime invoke the hook we just registered.

_MonoProfiler::_MonoProfiler ()
type_name = g_getenv ("GCHANDLES_FOR_TYPE");

gchandles = g_ptr_array_new ();
stacktraces = g_ptr_array_new_with_free_func (g_free);
/* Register the profiler with mono */
mono_profiler_install (this, NULL);
/* Supply a function for the gc_roots hook */
mono_profiler_install_gc_roots (track_gchandle, NULL);
/* Enable gc_roots tracking in the profiler so our hook is invoked */
mono_profiler_set_events (MONO_PROFILE_GC_ROOTS);

Now all I need is the implementation for track_gchandle and I'm done!
MonoProfiler::track_gchandle (MonoProfiler *prof, int op, int type, uintptr_t handle, MonoObject *obj)
// Ignore anything that isn't a strong GC handle (docs say type == 2 is a strong gchandle)
if (type != 2)

prof->locker.Lock ();

GPtrArray *gchandles = prof->gchandles;
GPtrArray *stacktraces = prof->stacktraces;

// Add the GCHandle to this array
g_ptr_array_add (gchandles, (gpointer) handle);
// If the target of the gchandle is of the correct type, store its stack trace
// Otherwise store NULL so that we can keep the index of the gchandle and corresponding
// stack trace in sync.
if (prof->type_name && !strcmp (prof->type_name, mono_class_get_name (mono_object_get_class(obj))))
g_ptr_array_add (stacktraces, get_stack_trace ());
g_ptr_array_add (stacktraces, NULL);
// Walk our list of gchandles and when we find the index of the destroyed handle
// remove the handle and corresponding stacktrace
for (int i = 0; i < (int)gchandles->len; i++) {
if (g_ptr_array_index (gchandles, i) == (gpointer) handle) {
g_ptr_array_remove_index_fast (gchandles, i);
g_ptr_array_remove_index_fast (stacktraces, i);

prof->locker.Unlock ();

Step 4:
Profit! When you're ready, you can iterate over the gchandle array and pull out whatever statistics you want. For example here is the code I use to iterate the gchandles array and work out how many instances of each object type there are.
accumulate_g_ptr_array_by_type (gpointer data, gpointer user_data)
// The hashtable I passed in as the user_data in g_ptr_array_foreach
// which i am using to link a type name to a count
GHashTable *by_type = (GHashTable*) user_data;

// Get the MonoObject from the gchandle.
MonoObject *ob = mono_gchandle_get_target (GPOINTER_TO_INT (data));

// Get the type name from the mono_object
const char *name = mono_class_get_name (mono_object_get_class(ob));

// Find out how many instances we have already
int count = GPOINTER_TO_INT (g_hash_table_lookup (by_type, name)) + 1;

// Update the hashtable with an incremented count.
g_hash_table_insert (by_type, name, GINT_TO_POINTER (count));

I run that hashtable through another function to order to sort the list by the number of each type of object allocated so I can have a nicely formatted list as follows:

1 instances GCHandled of type Surface
1 instances GCHandled of type Deployment
4 instances GCHandled of type NameScope
20 instances GCHandled of type ControlTemplate
218 instances GCHandled of type MonoType
3985 instances GCHandled of type Uri

Needless to say, it's now immediately obvious to me that Uris are being GChandled and never freed, so i just have to enable tracing of their stacktraces and I can see exactly where we allocated the handle and from there figure out why we haven't freed it.


sports handicapping services said...

I wonder why I did not knew this before.... it was a good read. You are working on a very good blog.. I look forward to visit your blog again....

ADmin said...

What would you be able to do to serve some useful purpose? The response is: a considerable amount.

dissertation writing said...

The codes are too difficult for me to understand please elaborate more. Exactly where perhaps you have emerged your own bottom line? I should be aware of factors that produced you're able to this theory? You may could reveal a few helpful pointers?

aliya seen said...

The eras personal statement length provides best writing opportunity for the readers and writers so that they can write perfect statement.

Rokki Winchester said...

Pretty good post! I just stumbled upon your blog and wanted reviews to say that I have really enjoyed reading your blog posts. Any way I'll be subscribing to your feed and I hope you post again soon.

huong duong said...

When it comes to thank the other person that you know why that is, they have to wait like and respect their time spent for you. Verbal apology not fully reflect that, further to say sorry too much will cause discomfort to the other
baixar musicas gratis , baixar snapchat , geometry dash 2.0 , play run 2 , b612

Faisal said...

Two full thumbs up for this magnificent article of yours. I've really enjoyed reading this article today.casio

KarlDeville said...

Thank you for taking the time and sharing this information with us. It was indeed very helpful and insightful while being straight forward and to the point. | |

Hit Counter