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