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