2010-03-12 Jb Evain <jbevain@novell.com>
[mono.git] / mono / profiler / mono-profiler-iomap.c
1 /*
2  * mono-profiler-iomap.c: IOMAP string profiler for Mono.
3  *
4  * Authors:
5  *   Marek Habersack <mhabersack@novell.com>
6  *
7  * Copyright (c) 2009 Novell, Inc (http://novell.com)
8  */
9 #include "config.h"
10
11 #include <string.h>
12 #include <mono/utils/mono-io-portability.h>
13 #include <mono/metadata/metadata.h>
14 #include <mono/metadata/metadata-internals.h>
15 #include <mono/metadata/class.h>
16 #include <mono/metadata/class-internals.h>
17 #include <mono/metadata/image.h>
18 #include <mono/metadata/mono-debug.h>
19 #include <mono/metadata/debug-helpers.h>
20 #include <mono/metadata/threads.h>
21 #include <mono/metadata/profiler.h>
22 #include <mono/metadata/loader.h>
23 #include <mono/io-layer/mono-mutex.h>
24
25 #define LOCATION_INDENT "        "
26 #define BACKTRACE_SIZE 64
27
28 typedef struct _MonoStackBacktraceInfo 
29 {
30         MonoMethod *method;
31         gint native_offset;
32 } MonoStackBacktraceInfo;
33
34 typedef struct 
35 {
36         guint32 count;
37         gchar *requestedName;
38         gchar *actualName;
39 } MismatchedFilesStats;
40
41 typedef struct _SavedString
42 {
43         MonoString *string;
44         MonoDomain *domain;
45         void *stack [BACKTRACE_SIZE];
46         gint stack_entries;
47         struct _SavedString *next;
48 } SavedString;
49
50 typedef struct _SavedStringFindInfo
51 {
52         guint32 hash;
53         size_t len;
54 } SavedStringFindInfo;
55
56 typedef struct _StringLocation
57 {
58         gchar *hint;
59         struct _StringLocation *next;
60 } StringLocation;
61
62 struct _MonoProfiler
63 {
64         GHashTable *mismatched_files_hash;
65         GHashTable *saved_strings_hash;
66         GHashTable *string_locations_hash;
67         gboolean may_have_locations;
68 };
69
70 typedef struct _StackWalkData
71 {
72         MonoProfiler *prof;
73         void **stack;
74         int stack_size;
75         int frame_count;
76 } StackWalkData;
77
78 static mono_mutex_t mismatched_files_section;
79 static gboolean runtime_initialized = FALSE;
80
81 static inline void append_report (GString **report, const gchar *format, ...);
82 static inline void print_report (const gchar *format, ...);
83 static inline guint32 do_calc_string_hash (guint32 hash, const gchar *str);
84 static inline guint32 calc_strings_hash (const gchar *str1, const gchar *str2, guint32 *str1hash);
85 static void print_mismatched_stats (MonoProfiler *prof);
86 static inline gchar *build_hint (SavedString *head);
87 static inline gchar *build_hint_from_stack (MonoDomain *domain, void **stack, gint stack_entries);
88 static inline void store_string_location (MonoProfiler *prof, const gchar *string, guint32 hash, size_t len);
89 static void mono_portability_remember_string (MonoProfiler *prof, MonoDomain *domain, MonoString *str);
90 void mono_profiler_startup (const char *desc);
91
92 static void mismatched_stats_foreach_func (gpointer key, gpointer value, gpointer user_data)
93 {
94         MismatchedFilesStats *stats = (MismatchedFilesStats*)value;
95         StringLocation *location;
96         MonoProfiler *prof = (MonoProfiler*)user_data;
97         guint32 hash;
98         gboolean bannerShown = FALSE;
99
100         hash = do_calc_string_hash (0, stats->requestedName);
101         fprintf (stdout,
102                  "    Count: %u\n"
103                  "Requested: %s\n"
104                  "   Actual: %s\n",
105                  stats->count, stats->requestedName, stats->actualName);
106
107         if (!prof->may_have_locations) {
108                 fprintf (stdout, "\n");
109                 return;
110         }
111
112         location = g_hash_table_lookup (prof->string_locations_hash, &hash);
113         while (location) {
114                 if (location->hint && strlen (location->hint) > 0) {
115                         if (!bannerShown) {
116                                 fprintf (stdout, "Locations:\n");
117                                 bannerShown = TRUE;
118                         }
119                         fprintf (stdout, "%s", location->hint);
120                 }
121                 location = location->next;
122                 if (location)
123                         fprintf (stdout, LOCATION_INDENT "--\n");
124         }
125
126         fprintf (stdout, "\n");
127 }
128
129 static void print_mismatched_stats (MonoProfiler *prof)
130 {
131         if (!prof->mismatched_files_hash || g_hash_table_size (prof->mismatched_files_hash) == 0)
132                 return;
133
134         prof->may_have_locations = g_hash_table_size (prof->string_locations_hash) > 0;
135
136         fprintf (stdout, "\n-=-=-=-=-=-=-= MONO_IOMAP Stats -=-=-=-=-=-=-=\n");
137         g_hash_table_foreach (prof->mismatched_files_hash, mismatched_stats_foreach_func, (gpointer)prof);
138         fflush (stdout);
139 }
140
141 static guint mismatched_files_guint32_hash (gconstpointer key)
142 {
143         if (!key)
144                 return 0;
145
146         return *((guint32*)key);
147 }
148
149 static gboolean mismatched_files_guint32_equal (gconstpointer key1, gconstpointer key2)
150 {
151         if (!key1 || !key2)
152                 return FALSE;
153
154         return (gboolean)(*((guint32*)key1) == *((guint32*)key2));
155 }
156
157 static inline guint32 do_calc_string_hash (guint32 hash, const gchar *str)
158 {
159         guint32 ret = hash;
160         gchar *cc = (gchar*)str;
161         gchar *end = (gchar*)(str + strlen (str) - 1);
162
163         for (; cc < end; cc += 2) {
164                 ret = (ret << 5) - ret + *cc;
165                 ret = (ret << 5) - ret + cc [1];
166         }
167         end++;
168         if (cc < end)
169                 ret = (ret << 5) - ret + *cc;
170
171         return ret;
172 }
173
174 static inline guint32 calc_strings_hash (const gchar *str1, const gchar *str2, guint32 *str1hash)
175 {
176         guint32 hash = do_calc_string_hash (0, str1);
177         if (str1hash)
178                 *str1hash = hash;
179         return do_calc_string_hash (hash, str2);
180 }
181
182 static inline void print_report (const gchar *format, ...)
183 {
184         MonoClass *klass;
185         MonoProperty *prop;
186         MonoString *str;
187         char *stack_trace;
188         va_list ap;
189
190         fprintf (stdout, "-=-=-=-=-=-=- MONO_IOMAP REPORT -=-=-=-=-=-=-\n");
191         va_start (ap, format);
192         vfprintf (stdout, format, ap);
193         fprintf (stdout, "\n");
194         va_end (ap);
195         klass = mono_class_from_name (mono_get_corlib (), "System", "Environment");
196         mono_class_init (klass);
197         prop = mono_class_get_property_from_name (klass, "StackTrace");
198         str = (MonoString*)mono_property_get_value (prop, NULL, NULL, NULL);
199         stack_trace = mono_string_to_utf8 (str);
200
201         fprintf (stdout, "-= Stack Trace =-\n%s\n\n", stack_trace);
202         g_free (stack_trace);
203         fflush (stdout);
204 }
205
206 static inline void append_report (GString **report, const gchar *format, ...)
207 {
208 #if GLIB_CHECK_VERSION(2,14,0)
209         va_list ap;
210         if (!*report)
211                 *report = g_string_new ("");
212
213         va_start (ap, format);
214         g_string_append_vprintf (*report, format, ap);
215         va_end (ap);
216 #else
217         g_assert_not_reached ();
218 #endif
219 }
220
221 static gboolean saved_strings_find_func (gpointer key, gpointer value, gpointer user_data)
222 {
223         SavedStringFindInfo *info = (SavedStringFindInfo*)user_data;
224         SavedString *saved = (SavedString*)value;
225         gchar *utf_str;
226         guint32 hash;
227
228         if (!info || !saved || saved->string->length != info->len)
229                 return FALSE;
230
231         utf_str = mono_string_to_utf8 (saved->string);
232         hash = do_calc_string_hash (0, utf_str);
233         g_free (utf_str);
234
235         if (hash != info->hash)
236                 return FALSE;
237
238         return TRUE;
239 }
240
241 static inline void store_string_location (MonoProfiler *prof, const gchar *string, guint32 hash, size_t len)
242 {
243         StringLocation *location = g_hash_table_lookup (prof->string_locations_hash, &hash);
244         SavedString *saved;
245         SavedStringFindInfo info;
246         guint32 *hashptr;
247
248         if (location)
249                 return;
250
251         info.hash = hash;
252         info.len = len;
253
254         /* Expensive but unavoidable... */
255         saved = (SavedString*)g_hash_table_find (prof->saved_strings_hash, saved_strings_find_func, &info);
256         hashptr = (guint32*)g_malloc (sizeof (guint32));
257         *hashptr = hash;
258         location = (StringLocation*)g_malloc0 (sizeof (location));
259
260         g_hash_table_insert (prof->string_locations_hash, hashptr, location);
261         if (!saved)
262                 return;
263
264         g_hash_table_remove (prof->saved_strings_hash, saved->string);
265         location->hint = build_hint (saved);
266 }
267
268 static gboolean ignore_frame (MonoMethod *method)
269 {
270         MonoClass *klass = method->klass;
271
272         if (method->wrapper_type != MONO_WRAPPER_NONE)
273                 return TRUE;
274
275         /* Now ignore the assemblies we know shouldn't contain mixed-case names (only the most frequent cases) */
276         if (klass->image ) {
277                 if (strcmp (klass->image->assembly_name, "mscorlib") == 0)
278                         return TRUE;
279                 else if (strcmp (klass->image->assembly_name, "System") == 0)
280                         return TRUE;
281                 else if (strncmp (klass->image->assembly_name, "Mono.", 5) == 0)
282                         return TRUE;
283                 else if (strncmp (klass->image->assembly_name, "System.", 7) == 0)
284                         return TRUE;
285                 else if (strcmp (klass->image->assembly_name, "PEAPI") == 0)
286                         return TRUE;
287         }
288
289         return FALSE;
290 }
291
292 static inline gchar *build_hint_from_stack (MonoDomain *domain, void **stack, gint stack_entries)
293 {
294         gchar *hint;
295         MonoMethod *method, *selectedMethod;
296         MonoAssembly *assembly;
297         MonoImage *image;
298         MonoDebugSourceLocation *location;
299         MonoStackBacktraceInfo *info;
300         gboolean use_full_trace;
301         char *methodName;
302         gint i, native_offset, firstAvailable;
303
304         selectedMethod = NULL;
305         firstAvailable = -1;
306         use_full_trace = FALSE;
307         native_offset = -1;
308         for (i = 0; i < stack_entries; i++) {
309                 info = (MonoStackBacktraceInfo*) stack [i];
310                 method = info ? info->method : NULL;
311
312                 if (!method || method->wrapper_type != MONO_WRAPPER_NONE)
313                         continue;
314
315                 if (firstAvailable == -1)
316                         firstAvailable = i;
317
318                 image = method->klass->image;
319                 assembly = image->assembly;
320
321                 if ((assembly && assembly->in_gac) || ignore_frame (method))
322                         continue;
323                 selectedMethod = method;
324                 native_offset = info->native_offset;
325                 break;
326         }
327
328         if (!selectedMethod) {
329                 /* All the frames were from assemblies installed in GAC. Find first frame that is
330                  * not in the ignore list */
331                 for (i = 0; i < stack_entries; i++) {
332                         info = (MonoStackBacktraceInfo*) stack [i];
333                         method = info ? info->method : NULL;
334
335                         if (!method || ignore_frame (method))
336                                 continue;
337                         selectedMethod = method;
338                         native_offset = info->native_offset;
339                         break;
340                 }
341
342                 if (!selectedMethod)
343                         use_full_trace = TRUE;
344         }
345
346         hint = NULL;
347         if (use_full_trace) {
348                 GString *trace = g_string_new ("Full trace:\n");
349                 for (i = firstAvailable; i < stack_entries; i++) {
350                         info = (MonoStackBacktraceInfo*) stack [i];
351                         method = info ? info->method : NULL;
352                         if (!method || method->wrapper_type != MONO_WRAPPER_NONE)
353                                 continue;
354
355                         location = mono_debug_lookup_source_location (method, info->native_offset, domain);
356                         methodName = mono_method_full_name (method, TRUE);
357
358                         if (location) {
359                                 append_report (&trace, LOCATION_INDENT "%s in %s:%u\n", methodName, location->source_file, location->row);
360                                 mono_debug_free_source_location (location);
361                         } else
362                                 append_report (&trace, LOCATION_INDENT "%s\n", methodName);
363                         g_free (methodName);
364                 }
365
366                 if (trace) {
367                         if (trace->len)
368                                 hint = g_string_free (trace, FALSE);
369                         else
370                                 g_string_free (trace, TRUE);
371                 }
372         } else {
373                 location = mono_debug_lookup_source_location (selectedMethod, native_offset, domain);
374                 methodName = mono_method_full_name (selectedMethod, TRUE);
375
376                 if (location) {
377                         hint = g_strdup_printf (LOCATION_INDENT "%s in %s:%u\n", methodName, location->source_file, location->row);
378                         mono_debug_free_source_location (location);
379                 } else
380                         hint = g_strdup_printf (LOCATION_INDENT "%s\n", methodName);
381                 g_free (methodName);
382         }
383
384         return hint;
385 }
386
387 static inline gchar *build_hint (SavedString *head)
388 {
389         SavedString *current;
390         gchar *tmp;
391         GString *hint = NULL;
392
393         current = head;
394         while (current) {
395                 tmp = build_hint_from_stack (current->domain, current->stack, current->stack_entries);
396                 current = current->next;
397                 if (!tmp)
398                         continue;
399
400                 append_report (&hint, tmp);
401         }
402
403         if (hint) {
404                 if (hint->len)
405                         return g_string_free (hint, FALSE);
406                 else {
407                         g_string_free (hint, FALSE);
408                         return NULL;
409                 }
410         }
411
412         return NULL;
413 }
414
415 static gboolean stack_walk_func (MonoMethod *method, gint32 native_offset, gint32 il_offset, gboolean managed, gpointer data)
416 {
417         StackWalkData *swdata = (StackWalkData*)data;
418         MonoStackBacktraceInfo *info;
419
420         if (swdata->frame_count >= swdata->stack_size)
421                 return TRUE;
422
423         info = (MonoStackBacktraceInfo*)g_malloc (sizeof (*info));
424         info->method = method;
425         info->native_offset = native_offset;
426
427         swdata->stack [swdata->frame_count++] = info;
428         return FALSE;
429 }
430
431 static inline int mono_stack_backtrace (MonoProfiler *prof, MonoDomain *domain, void **stack, int size)
432 {
433         StackWalkData data;
434
435         data.prof = prof;
436         data.stack = stack;
437         data.stack_size = size;
438         data.frame_count = 0;
439
440         mono_stack_walk_no_il (stack_walk_func, (gpointer)&data);
441
442         return data.frame_count;
443 }
444
445 static void mono_portability_remember_string (MonoProfiler *prof, MonoDomain *domain, MonoString *str)
446 {
447         SavedString *head, *entry;
448
449         if (!str || !domain || !runtime_initialized)
450                 return;
451
452         entry = (SavedString*)g_malloc0 (sizeof (SavedString));
453         entry->string = str;
454         entry->domain = domain;
455         entry->stack_entries = mono_stack_backtrace (prof, domain, entry->stack, BACKTRACE_SIZE);
456         if (entry->stack_entries == 0) {
457                 g_free (entry);
458                 return;
459         }
460
461         mono_mutex_lock (&mismatched_files_section);
462         head = (SavedString*)g_hash_table_lookup (prof->saved_strings_hash, (gpointer)str);
463         if (head) {
464                 while (head->next)
465                         head = head->next;
466                 head->next = entry;
467         } else
468                 g_hash_table_insert (prof->saved_strings_hash, (gpointer)str, (gpointer)entry);
469         mono_mutex_unlock (&mismatched_files_section);
470 }
471
472 static MonoClass *string_class = NULL;
473
474 static void mono_portability_remember_alloc (MonoProfiler *prof, MonoObject *obj, MonoClass *klass)
475 {
476         if (klass != string_class)
477                 return;
478         mono_portability_remember_string (prof, mono_object_domain (obj), (MonoString*)obj);
479 }
480
481 static void mono_portability_iomap_event (MonoProfiler *prof, const char *report, const char *pathname, const char *new_pathname)
482 {
483         guint32 hash, pathnameHash;
484         MismatchedFilesStats *stats;
485
486         if (!runtime_initialized)
487                 return;
488
489         mono_mutex_lock (&mismatched_files_section);
490         hash = calc_strings_hash (pathname, new_pathname, &pathnameHash);
491         stats = (MismatchedFilesStats*)g_hash_table_lookup (prof->mismatched_files_hash, &hash);
492         if (stats == NULL) {
493                 guint32 *hashptr;
494
495                 stats = (MismatchedFilesStats*) g_malloc (sizeof (MismatchedFilesStats));
496                 stats->count = 1;
497                 stats->requestedName = g_strdup (pathname);
498                 stats->actualName = g_strdup (new_pathname);
499                 hashptr = (guint32*)g_malloc (sizeof (guint32));
500                 if (hashptr) {
501                         *hashptr = hash;
502                         g_hash_table_insert (prof->mismatched_files_hash, (gpointer)hashptr, stats);
503                 } else
504                         g_error ("Out of memory allocating integer pointer for mismatched files hash table.");
505
506                 store_string_location (prof, (const gchar*)stats->requestedName, pathnameHash, strlen (stats->requestedName));
507                 mono_mutex_unlock (&mismatched_files_section);
508
509                 print_report ("%s -     Found file path: '%s'\n", report, new_pathname);
510         } else {
511                 mono_mutex_unlock (&mismatched_files_section);
512                 stats->count++;
513         }
514 }
515
516 static void runtime_initialized_cb (MonoProfiler *prof)
517 {
518         runtime_initialized = TRUE;
519         string_class = mono_get_string_class ();
520 }
521
522 static void profiler_shutdown (MonoProfiler *prof)
523 {
524         print_mismatched_stats (prof);
525         mono_mutex_destroy (&mismatched_files_section);
526 }
527
528 void mono_profiler_startup (const char *desc)
529 {
530         MonoProfiler *prof = g_new0 (MonoProfiler, 1);
531
532         mono_mutex_init (&mismatched_files_section, NULL);
533         prof->mismatched_files_hash = g_hash_table_new (mismatched_files_guint32_hash, mismatched_files_guint32_equal);
534         prof->saved_strings_hash = g_hash_table_new (NULL, NULL);
535         prof->string_locations_hash = g_hash_table_new (mismatched_files_guint32_hash, mismatched_files_guint32_equal);
536
537         mono_profiler_install (prof, profiler_shutdown);
538         mono_profiler_install_runtime_initialized (runtime_initialized_cb);
539         mono_profiler_install_iomap (mono_portability_iomap_event);
540         mono_profiler_install_allocation (mono_portability_remember_alloc);
541
542         mono_profiler_set_events (MONO_PROFILE_ALLOCATIONS | MONO_PROFILE_IOMAP_EVENTS);
543 }