[Profiler] Add a new profiler module to track GCHandle allocations
[mono.git] / mono / profiler / gchandle-profiler.c
1 #include <glib.h>
2 #include <pthread.h>
3
4 #include <mono/io-layer/mono-mutex.h>
5 #include <mono/metadata/class.h>
6 #include <mono/metadata/profiler.h>
7
8 #include "gchandle-profiler.h"
9
10 void
11 mono_profiler_startup (const char *desc)
12 {
13         MonoProfiler *prof;
14
15         g_print ("*** Running with the GCHandle profiler ***\n");
16
17         // The profiler uses gchandle alloc info and jit info
18         prof = gchandle_profiler_new ();
19         mono_profiler_install (prof, gchandle_profiler_shutdown);
20         mono_profiler_install_gc_roots (gchandle_profiler_track_gchandle, NULL);
21         mono_profiler_install_jit_end (gchandle_profiler_method_jitted);
22         mono_profiler_set_events (MONO_PROFILE_GC_ROOTS | MONO_PROFILE_JIT_COMPILATION);
23         mono_profiler_set_events (MONO_PROFILE_GC_ROOTS);
24 }
25
26 void gchandle_profiler_shutdown (MonoProfiler *prof)
27 {
28         g_print ("Shutting down the profiler\n");
29         gchandle_profiler_dump_jitted_methods (prof);
30         gchandle_profiler_dump_gchandles (prof);
31 }
32
33 MonoProfiler *gchandle_profiler_new ()
34 {
35         MonoProfiler *prof;
36
37         prof = g_new0 (MonoProfiler, 1);
38         prof->type_name = g_getenv ("GCHANDLES_FOR_TYPE");
39         prof->gchandles = g_ptr_array_new ();
40         prof->jitted_methods = g_hash_table_new (g_str_hash, g_str_equal);
41         prof->stacktraces = g_ptr_array_new ();
42         mono_mutex_init (&prof->mutex, NULL);
43
44         if (prof->type_name)
45                 g_print ("*** Recording GCHandle allocation stacktraces for type '%s'\n",  prof->type_name);
46
47         return prof;
48 }
49
50 void
51 gchandle_profiler_dump_jitted_methods (MonoProfiler *prof)
52 {
53         g_hash_table_foreach (prof->jitted_methods, dump_jitted_methods, NULL);
54 }
55
56 void
57 gchandle_profiler_method_jitted (MonoProfiler *prof, MonoMethod *method, MonoJitInfo* jinfo, int result)
58 {
59         // Whenever a method is jitted, store the method name and increment the count by 1.
60         // Methods can be jitted multiple times if instance delegates are passed to native code
61         int count;
62         char *name;
63
64         mono_mutex_lock (&prof->mutex);
65
66         name = mono_method_full_name (method, 1);
67         count = GPOINTER_TO_INT (g_hash_table_lookup (prof->jitted_methods, name));
68         g_hash_table_insert (prof->jitted_methods, name, GINT_TO_POINTER (count + 1));
69
70         mono_mutex_unlock (&prof->mutex);
71 }
72
73 void gchandle_profiler_track_gchandle (MonoProfiler *prof, int op, int type, uintptr_t handle, MonoObject *obj)
74 {
75         int i;
76         GPtrArray *gchandles;
77         GPtrArray *stacktraces;
78         
79         // Ignore anything that isn't a strong GC handle
80         if (type != 2)
81                 return;
82
83         gchandles = prof->gchandles;
84         stacktraces = prof->stacktraces;
85
86         mono_mutex_lock (&prof->mutex);
87
88         // Keep the two arrays in sync so that the gchandle at index i stores its stacktrace at index i in the
89         // other gptrarray. 
90         if (op == MONO_PROFILER_GC_HANDLE_CREATED) {
91                 g_ptr_array_add (gchandles, (gpointer) handle);
92                 if (prof->type_name && !strcmp (prof->type_name, mono_class_get_name (mono_object_get_class(obj))))
93                         g_ptr_array_add (stacktraces, get_stack_trace ());
94                 else
95                         g_ptr_array_add (stacktraces, NULL);
96         } else if (op == MONO_PROFILER_GC_HANDLE_DESTROYED) {
97                 for (i = 0; i < (int)gchandles->len; i++) {
98                         if (g_ptr_array_index (gchandles, i) == (gpointer) handle) {
99                                 g_free (g_ptr_array_index (stacktraces, i));
100                                 g_ptr_array_remove_index_fast (gchandles, i);
101                                 g_ptr_array_remove_index_fast (stacktraces, i);
102                                 break;
103                         }
104                 }
105         }
106
107         mono_mutex_unlock (&prof->mutex);
108 }
109
110 void dump_jitted_methods (gpointer key, gpointer value, gpointer user_data)
111 {
112         // We only care about methods which are jitted multiple times.
113         int jit_count = GPOINTER_TO_INT (value);
114         if (jit_count > 10)
115                 g_print ("%d:\t%s\n", jit_count, (char*)key);
116 }
117
118 char *get_stack_trace ()
119 {
120         GString *str;
121         char *trace;
122
123         str = g_string_new ("");
124         mono_stack_walk_no_il (stack_walk_fn, str);
125         trace = str->str;
126
127         g_string_free (str, FALSE);
128         return trace;
129 }
130
131
132 gboolean stack_walk_fn (MonoMethod *method, gint32 native_offset, gint32 il_offset, gboolean managed, gpointer data)
133 {
134         GString *str;
135         MonoClass *klass;
136
137         if (managed) {
138                 str = (GString *) data;
139                 klass = mono_method_get_class (method);
140
141                 g_string_append_c (str, '\t');
142                 g_string_append (str, mono_class_get_namespace (klass));
143                 g_string_append_c (str, '.');
144                 g_string_append (str, mono_class_get_name (klass));
145                 g_string_append_c (str, '.');
146                 g_string_append (str, mono_method_get_name (method));
147                 g_string_append_c (str, '\n');
148         }
149     return FALSE;
150 }
151
152
153 void accumulate_gchandles_by_type (gpointer data, gpointer user_data)
154 {
155         // For every GCHandle we get the class name and store it in a hashtable
156         // along with the number of times we've seen that class name. This tells
157         // us how many GCHandles we have allocated for each class type.
158         int count;
159         GHashTable *by_type;
160         const char *name;
161         
162         by_type = (GHashTable*) user_data;
163         name = class_name_from_gchandle (GPOINTER_TO_INT (data));
164
165         if (name) {
166                 count = GPOINTER_TO_INT (g_hash_table_lookup (by_type, name)) + 1;
167                 g_hash_table_insert (by_type, (void*) name, GINT_TO_POINTER (count));
168         }
169 }
170
171 const char *class_name_from_gchandle (gint32 gchandle)
172 {
173         MonoObject *ob;
174         const char *name;
175         
176         ob = mono_gchandle_get_target (gchandle);
177         if (!ob)
178                 return NULL;
179
180         // Add in specific support for Gtk.ToggleRef in gtk-sharp so that
181         // the profiler can detect what objects the ToggleRef is keeping alive
182         name = mono_class_get_name (mono_object_get_class(ob));
183         if (name && !strcmp (name, "ToggleRef")) {
184                 MonoClassField *field = mono_class_get_field_from_name (mono_object_get_class(ob), "reference");
185                 if (field) {
186                         mono_field_get_value (ob, field, &ob);
187                         if (ob)
188                                 name = mono_class_get_name (mono_object_get_class(ob));
189                 }
190         }
191
192         return name;
193 }
194
195 void gchandle_profiler_dump_gchandles (MonoProfiler *prof)
196 {
197         int i;
198         GHashTable *by_type;
199         GPtrArray *top_n_by_type;
200
201         by_type = g_hash_table_new (g_str_hash, g_str_equal);
202         top_n_by_type = g_ptr_array_new ();
203         
204         // Generate a sorted/filtered list of results so that we can print the
205         // number of gchandles allocated for each type in ascending order so types
206         // with a lot of GChandles are printed last. 
207         g_ptr_array_foreach (prof->gchandles, accumulate_gchandles_by_type, by_type);
208         g_hash_table_foreach (by_type, add_hashtable_keys_to_ptr_array, top_n_by_type);
209         g_ptr_array_sort_with_data (top_n_by_type, gchandle_instances_comparer, by_type);
210
211         for (i = 0; i < (int) top_n_by_type->len; i++)
212                 g_print ("\t%d GCHandles referencing type '%s'\n", GPOINTER_TO_INT (g_hash_table_lookup (by_type, top_n_by_type->pdata [i])), (char *) top_n_by_type->pdata [i]);
213         g_print ("\n");
214
215         gchandle_profiler_dump_gchandle_traces (prof);
216 }
217
218 void gchandle_profiler_dump_gchandle_traces (MonoProfiler *prof)
219 {
220         int i, j;
221         gint32 gchandle;
222         GPtrArray *gchandles;
223         MonoObject *ob;
224         const char *name;
225         GPtrArray *stacktraces;
226
227         if (!prof->type_name)
228                 return;
229
230         gchandles = prof->gchandles;
231         stacktraces = prof->stacktraces;
232
233         // For all allocated handles, see if any of them are referencing objects of the type
234         // we care about. If they are, print out the allocation trace of all handles targetting
235         // that object. Note that multiple handles targetting the same object are grouped together
236         for (i = 0; i < (int) gchandles->len; i ++) {
237                 gchandle = GPOINTER_TO_INT (g_ptr_array_index (gchandles, i));
238                 ob = mono_gchandle_get_target (gchandle);
239                 name = class_name_from_gchandle (gchandle);
240                 if (name && !strcmp (name, prof->type_name)) {
241                         g_print ("Strong GCHandles allocated for object %p:\n", ob);
242                         for (j = i; j < (int) gchandles->len; j++) {
243                                 if (mono_gchandle_get_target (GPOINTER_TO_INT (g_ptr_array_index (gchandles, j))) == ob) {
244                                         g_print ("%s\n", (char *) g_ptr_array_index (stacktraces, j));
245                                         g_ptr_array_remove_index_fast (gchandles, j);
246                                         g_ptr_array_remove_index_fast (stacktraces, j);
247                                         j --;
248                                 }
249                         }
250                         g_print ("\n");
251                         i --;
252                 }
253         }
254 }
255
256 void add_hashtable_keys_to_ptr_array (gpointer key, gpointer value, gpointer user_data)
257 {
258         GPtrArray *by_type = (GPtrArray*) user_data;
259         g_ptr_array_add (by_type, key);
260 }
261
262 gint gchandle_instances_comparer (gconstpointer base1, gconstpointer base2, gpointer user_data)
263 {
264         GHashTable *by_type = (GHashTable *) user_data;
265         char *left = *((char **) base1);
266         char *right = *((char **) base2);
267
268         int iddiff =  GPOINTER_TO_INT (g_hash_table_lookup (by_type, left)) - GPOINTER_TO_INT (g_hash_table_lookup (by_type, right));
269
270         if (iddiff == 0)
271                 return 0;
272         else if (iddiff < 0)
273                 return -1;
274         else
275                 return 1;
276 }
277