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