2 * mono-profiler-iomap.c: IOMAP string profiler for Mono.
5 * Marek Habersack <mhabersack@novell.com>
7 * Copyright (c) 2009 Novell, Inc (http://novell.com)
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.
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>
29 #define LOCATION_INDENT " "
30 #define BACKTRACE_SIZE 64
32 typedef struct _MonoStackBacktraceInfo
36 } MonoStackBacktraceInfo;
43 } MismatchedFilesStats;
45 typedef struct _SavedString
49 void *stack [BACKTRACE_SIZE];
51 struct _SavedString *next;
54 typedef struct _SavedStringFindInfo
58 } SavedStringFindInfo;
60 typedef struct _StringLocation
63 struct _StringLocation *next;
68 GHashTable *mismatched_files_hash;
69 GHashTable *saved_strings_hash;
70 GHashTable *string_locations_hash;
71 gboolean may_have_locations;
74 typedef struct _StackWalkData
82 static mono_mutex_t mismatched_files_section;
83 static gboolean runtime_initialized = FALSE;
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);
96 static void mismatched_stats_foreach_func (gpointer key, gpointer value, gpointer user_data)
98 MismatchedFilesStats *stats = (MismatchedFilesStats*)value;
99 StringLocation *location;
100 MonoProfiler *prof = (MonoProfiler*)user_data;
102 gboolean bannerShown = FALSE;
104 hash = do_calc_string_hash (0, stats->requestedName);
109 stats->count, stats->requestedName, stats->actualName);
111 if (!prof->may_have_locations) {
112 fprintf (stdout, "\n");
116 location = (StringLocation *)g_hash_table_lookup (prof->string_locations_hash, &hash);
118 if (location->hint && strlen (location->hint) > 0) {
120 fprintf (stdout, "Locations:\n");
123 fprintf (stdout, "%s", location->hint);
125 location = location->next;
127 fprintf (stdout, LOCATION_INDENT "--\n");
130 fprintf (stdout, "\n");
133 static void print_mismatched_stats (MonoProfiler *prof)
135 if (!prof->mismatched_files_hash || g_hash_table_size (prof->mismatched_files_hash) == 0)
138 prof->may_have_locations = g_hash_table_size (prof->string_locations_hash) > 0;
140 fprintf (stdout, "\n-=-=-=-=-=-=-= MONO_IOMAP Stats -=-=-=-=-=-=-=\n");
141 g_hash_table_foreach (prof->mismatched_files_hash, mismatched_stats_foreach_func, (gpointer)prof);
145 static guint mismatched_files_guint32_hash (gconstpointer key)
150 return *((guint32*)key);
153 static gboolean mismatched_files_guint32_equal (gconstpointer key1, gconstpointer key2)
158 return (gboolean)(*((guint32*)key1) == *((guint32*)key2));
161 static inline guint32 do_calc_string_hash (guint32 hash, const gchar *str)
164 gchar *cc = (gchar*)str;
165 gchar *end = (gchar*)(str + strlen (str) - 1);
167 for (; cc < end; cc += 2) {
168 ret = (ret << 5) - ret + *cc;
169 ret = (ret << 5) - ret + cc [1];
173 ret = (ret << 5) - ret + *cc;
178 static inline guint32 calc_strings_hash (const gchar *str1, const gchar *str2, guint32 *str1hash)
180 guint32 hash = do_calc_string_hash (0, str1);
183 return do_calc_string_hash (hash, str2);
186 static inline void print_report (const gchar *format, ...)
194 fprintf (stdout, "-=-=-=-=-=-=- MONO_IOMAP REPORT -=-=-=-=-=-=-\n");
195 va_start (ap, format);
196 vfprintf (stdout, format, ap);
197 fprintf (stdout, "\n");
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);
205 fprintf (stdout, "-= Stack Trace =-\n%s\n\n", stack_trace);
206 g_free (stack_trace);
210 static inline void append_report (GString **report, const gchar *format, ...)
212 #if defined (_EGLIB_MAJOR) || GLIB_CHECK_VERSION(2,14,0)
215 *report = g_string_new ("");
217 va_start (ap, format);
218 g_string_append_vprintf (*report, format, ap);
221 g_assert_not_reached ();
225 static gboolean saved_strings_find_func (gpointer key, gpointer value, gpointer user_data)
227 SavedStringFindInfo *info = (SavedStringFindInfo*)user_data;
228 SavedString *saved = (SavedString*)value;
232 if (!info || !saved || mono_string_length (saved->string) != info->len)
235 utf_str = mono_string_to_utf8 (saved->string);
236 hash = do_calc_string_hash (0, utf_str);
239 if (hash != info->hash)
245 static inline void store_string_location (MonoProfiler *prof, const gchar *string, guint32 hash, size_t len)
247 StringLocation *location = (StringLocation *)g_hash_table_lookup (prof->string_locations_hash, &hash);
249 SavedStringFindInfo info;
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));
262 location = (StringLocation*)g_malloc0 (sizeof (location));
264 g_hash_table_insert (prof->string_locations_hash, hashptr, location);
268 g_hash_table_remove (prof->saved_strings_hash, saved->string);
269 location->hint = build_hint (saved);
272 static gboolean ignore_frame (MonoMethod *method)
274 MonoClass *klass = method->klass;
276 if (method->wrapper_type != MONO_WRAPPER_NONE)
279 /* Now ignore the assemblies we know shouldn't contain mixed-case names (only the most frequent cases) */
281 if (strcmp (klass->image->assembly_name, "mscorlib") == 0)
283 else if (strcmp (klass->image->assembly_name, "System") == 0)
285 else if (strncmp (klass->image->assembly_name, "Mono.", 5) == 0)
287 else if (strncmp (klass->image->assembly_name, "System.", 7) == 0)
289 else if (strcmp (klass->image->assembly_name, "PEAPI") == 0)
296 static inline gchar *build_hint_from_stack (MonoDomain *domain, void **stack, gint stack_entries)
299 MonoMethod *method, *selectedMethod;
300 MonoAssembly *assembly;
302 MonoDebugSourceLocation *location;
303 MonoStackBacktraceInfo *info;
304 gboolean use_full_trace;
306 gint i, native_offset, firstAvailable;
308 selectedMethod = NULL;
310 use_full_trace = FALSE;
312 for (i = 0; i < stack_entries; i++) {
313 info = (MonoStackBacktraceInfo*) stack [i];
314 method = info ? info->method : NULL;
316 if (!method || method->wrapper_type != MONO_WRAPPER_NONE)
319 if (firstAvailable == -1)
322 image = method->klass->image;
323 assembly = image->assembly;
325 if ((assembly && assembly->in_gac) || ignore_frame (method))
327 selectedMethod = method;
328 native_offset = info->native_offset;
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;
339 if (!method || ignore_frame (method))
341 selectedMethod = method;
342 native_offset = info->native_offset;
347 use_full_trace = TRUE;
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)
359 location = mono_debug_lookup_source_location (method, info->native_offset, domain);
360 methodName = mono_method_full_name (method, TRUE);
363 append_report (&trace, LOCATION_INDENT "%s in %s:%u\n", methodName, location->source_file, location->row);
364 mono_debug_free_source_location (location);
366 append_report (&trace, LOCATION_INDENT "%s\n", methodName);
372 hint = g_string_free (trace, FALSE);
374 g_string_free (trace, TRUE);
377 location = mono_debug_lookup_source_location (selectedMethod, native_offset, domain);
378 methodName = mono_method_full_name (selectedMethod, TRUE);
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);
384 hint = g_strdup_printf (LOCATION_INDENT "%s\n", methodName);
391 static inline gchar *build_hint (SavedString *head)
393 SavedString *current;
395 GString *hint = NULL;
399 tmp = build_hint_from_stack (current->domain, current->stack, current->stack_entries);
400 current = current->next;
404 append_report (&hint, tmp);
409 return g_string_free (hint, FALSE);
411 g_string_free (hint, FALSE);
419 static gboolean stack_walk_func (MonoMethod *method, gint32 native_offset, gint32 il_offset, gboolean managed, gpointer data)
421 StackWalkData *swdata = (StackWalkData*)data;
422 MonoStackBacktraceInfo *info;
424 if (swdata->frame_count >= swdata->stack_size)
427 info = (MonoStackBacktraceInfo*)g_malloc (sizeof (*info));
428 info->method = method;
429 info->native_offset = native_offset;
431 swdata->stack [swdata->frame_count++] = info;
435 static inline int mono_stack_backtrace (MonoProfiler *prof, MonoDomain *domain, void **stack, int size)
441 data.stack_size = size;
442 data.frame_count = 0;
444 mono_stack_walk_no_il (stack_walk_func, (gpointer)&data);
446 return data.frame_count;
449 static void mono_portability_remember_string (MonoProfiler *prof, MonoDomain *domain, MonoString *str)
451 SavedString *head, *entry;
453 if (!str || !domain || !runtime_initialized)
456 entry = (SavedString*)g_malloc0 (sizeof (SavedString));
458 entry->domain = domain;
459 entry->stack_entries = mono_stack_backtrace (prof, domain, entry->stack, BACKTRACE_SIZE);
460 if (entry->stack_entries == 0) {
465 mono_os_mutex_lock (&mismatched_files_section);
466 head = (SavedString*)g_hash_table_lookup (prof->saved_strings_hash, (gpointer)str);
472 g_hash_table_insert (prof->saved_strings_hash, (gpointer)str, (gpointer)entry);
473 mono_os_mutex_unlock (&mismatched_files_section);
476 static MonoClass *string_class = NULL;
478 static void mono_portability_remember_alloc (MonoProfiler *prof, MonoObject *obj, MonoClass *klass)
480 if (klass != string_class)
482 mono_portability_remember_string (prof, mono_object_get_domain (obj), (MonoString*)obj);
485 static void mono_portability_iomap_event (MonoProfiler *prof, const char *report, const char *pathname, const char *new_pathname)
487 guint32 hash, pathnameHash;
488 MismatchedFilesStats *stats;
490 if (!runtime_initialized)
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);
499 stats = (MismatchedFilesStats*) g_malloc (sizeof (MismatchedFilesStats));
501 stats->requestedName = g_strdup (pathname);
502 stats->actualName = g_strdup (new_pathname);
503 hashptr = (guint32*)g_malloc (sizeof (guint32));
506 g_hash_table_insert (prof->mismatched_files_hash, (gpointer)hashptr, stats);
508 g_error ("Out of memory allocating integer pointer for mismatched files hash table.");
510 store_string_location (prof, (const gchar*)stats->requestedName, pathnameHash, strlen (stats->requestedName));
511 mono_os_mutex_unlock (&mismatched_files_section);
513 print_report ("%s - Found file path: '%s'\n", report, new_pathname);
515 mono_os_mutex_unlock (&mismatched_files_section);
520 static void runtime_initialized_cb (MonoProfiler *prof)
522 runtime_initialized = TRUE;
523 string_class = mono_get_string_class ();
526 static void profiler_shutdown (MonoProfiler *prof)
528 print_mismatched_stats (prof);
529 mono_os_mutex_destroy (&mismatched_files_section);
532 void mono_profiler_startup (const char *desc)
534 MonoProfiler *prof = g_new0 (MonoProfiler, 1);
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);
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);
546 mono_profiler_set_events ((MonoProfileFlags)(MONO_PROFILE_ALLOCATIONS | MONO_PROFILE_IOMAP_EVENTS));