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