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.
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/io-layer/mono-mutex.h>
28 #define LOCATION_INDENT " "
29 #define BACKTRACE_SIZE 64
31 typedef struct _MonoStackBacktraceInfo
35 } MonoStackBacktraceInfo
;
42 } MismatchedFilesStats
;
44 typedef struct _SavedString
48 void *stack
[BACKTRACE_SIZE
];
50 struct _SavedString
*next
;
53 typedef struct _SavedStringFindInfo
57 } SavedStringFindInfo
;
59 typedef struct _StringLocation
62 struct _StringLocation
*next
;
67 GHashTable
*mismatched_files_hash
;
68 GHashTable
*saved_strings_hash
;
69 GHashTable
*string_locations_hash
;
70 gboolean may_have_locations
;
73 typedef struct _StackWalkData
81 static mono_mutex_t mismatched_files_section
;
82 static gboolean runtime_initialized
= FALSE
;
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
);
95 static void mismatched_stats_foreach_func (gpointer key
, gpointer value
, gpointer user_data
)
97 MismatchedFilesStats
*stats
= (MismatchedFilesStats
*)value
;
98 StringLocation
*location
;
99 MonoProfiler
*prof
= (MonoProfiler
*)user_data
;
101 gboolean bannerShown
= FALSE
;
103 hash
= do_calc_string_hash (0, stats
->requestedName
);
108 stats
->count
, stats
->requestedName
, stats
->actualName
);
110 if (!prof
->may_have_locations
) {
111 fprintf (stdout
, "\n");
115 location
= g_hash_table_lookup (prof
->string_locations_hash
, &hash
);
117 if (location
->hint
&& strlen (location
->hint
) > 0) {
119 fprintf (stdout
, "Locations:\n");
122 fprintf (stdout
, "%s", location
->hint
);
124 location
= location
->next
;
126 fprintf (stdout
, LOCATION_INDENT
"--\n");
129 fprintf (stdout
, "\n");
132 static void print_mismatched_stats (MonoProfiler
*prof
)
134 if (!prof
->mismatched_files_hash
|| g_hash_table_size (prof
->mismatched_files_hash
) == 0)
137 prof
->may_have_locations
= g_hash_table_size (prof
->string_locations_hash
) > 0;
139 fprintf (stdout
, "\n-=-=-=-=-=-=-= MONO_IOMAP Stats -=-=-=-=-=-=-=\n");
140 g_hash_table_foreach (prof
->mismatched_files_hash
, mismatched_stats_foreach_func
, (gpointer
)prof
);
144 static guint
mismatched_files_guint32_hash (gconstpointer key
)
149 return *((guint32
*)key
);
152 static gboolean
mismatched_files_guint32_equal (gconstpointer key1
, gconstpointer key2
)
157 return (gboolean
)(*((guint32
*)key1
) == *((guint32
*)key2
));
160 static inline guint32
do_calc_string_hash (guint32 hash
, const gchar
*str
)
163 gchar
*cc
= (gchar
*)str
;
164 gchar
*end
= (gchar
*)(str
+ strlen (str
) - 1);
166 for (; cc
< end
; cc
+= 2) {
167 ret
= (ret
<< 5) - ret
+ *cc
;
168 ret
= (ret
<< 5) - ret
+ cc
[1];
172 ret
= (ret
<< 5) - ret
+ *cc
;
177 static inline guint32
calc_strings_hash (const gchar
*str1
, const gchar
*str2
, guint32
*str1hash
)
179 guint32 hash
= do_calc_string_hash (0, str1
);
182 return do_calc_string_hash (hash
, str2
);
185 static inline void print_report (const gchar
*format
, ...)
193 fprintf (stdout
, "-=-=-=-=-=-=- MONO_IOMAP REPORT -=-=-=-=-=-=-\n");
194 va_start (ap
, format
);
195 vfprintf (stdout
, format
, ap
);
196 fprintf (stdout
, "\n");
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
);
204 fprintf (stdout
, "-= Stack Trace =-\n%s\n\n", stack_trace
);
205 g_free (stack_trace
);
209 static inline void append_report (GString
**report
, const gchar
*format
, ...)
211 #if defined (_EGLIB_MAJOR) || GLIB_CHECK_VERSION(2,14,0)
214 *report
= g_string_new ("");
216 va_start (ap
, format
);
217 g_string_append_vprintf (*report
, format
, ap
);
220 g_assert_not_reached ();
224 static gboolean
saved_strings_find_func (gpointer key
, gpointer value
, gpointer user_data
)
226 SavedStringFindInfo
*info
= (SavedStringFindInfo
*)user_data
;
227 SavedString
*saved
= (SavedString
*)value
;
231 if (!info
|| !saved
|| mono_string_length (saved
->string
) != info
->len
)
234 utf_str
= mono_string_to_utf8 (saved
->string
);
235 hash
= do_calc_string_hash (0, utf_str
);
238 if (hash
!= info
->hash
)
244 static inline void store_string_location (MonoProfiler
*prof
, const gchar
*string
, guint32 hash
, size_t len
)
246 StringLocation
*location
= g_hash_table_lookup (prof
->string_locations_hash
, &hash
);
248 SavedStringFindInfo info
;
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
));
261 location
= (StringLocation
*)g_malloc0 (sizeof (location
));
263 g_hash_table_insert (prof
->string_locations_hash
, hashptr
, location
);
267 g_hash_table_remove (prof
->saved_strings_hash
, saved
->string
);
268 location
->hint
= build_hint (saved
);
271 static gboolean
ignore_frame (MonoMethod
*method
)
273 MonoClass
*klass
= method
->klass
;
275 if (method
->wrapper_type
!= MONO_WRAPPER_NONE
)
278 /* Now ignore the assemblies we know shouldn't contain mixed-case names (only the most frequent cases) */
280 if (strcmp (klass
->image
->assembly_name
, "mscorlib") == 0)
282 else if (strcmp (klass
->image
->assembly_name
, "System") == 0)
284 else if (strncmp (klass
->image
->assembly_name
, "Mono.", 5) == 0)
286 else if (strncmp (klass
->image
->assembly_name
, "System.", 7) == 0)
288 else if (strcmp (klass
->image
->assembly_name
, "PEAPI") == 0)
295 static inline gchar
*build_hint_from_stack (MonoDomain
*domain
, void **stack
, gint stack_entries
)
298 MonoMethod
*method
, *selectedMethod
;
299 MonoAssembly
*assembly
;
301 MonoDebugSourceLocation
*location
;
302 MonoStackBacktraceInfo
*info
;
303 gboolean use_full_trace
;
305 gint i
, native_offset
, firstAvailable
;
307 selectedMethod
= NULL
;
309 use_full_trace
= FALSE
;
311 for (i
= 0; i
< stack_entries
; i
++) {
312 info
= (MonoStackBacktraceInfo
*) stack
[i
];
313 method
= info
? info
->method
: NULL
;
315 if (!method
|| method
->wrapper_type
!= MONO_WRAPPER_NONE
)
318 if (firstAvailable
== -1)
321 image
= method
->klass
->image
;
322 assembly
= image
->assembly
;
324 if ((assembly
&& assembly
->in_gac
) || ignore_frame (method
))
326 selectedMethod
= method
;
327 native_offset
= info
->native_offset
;
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
;
338 if (!method
|| ignore_frame (method
))
340 selectedMethod
= method
;
341 native_offset
= info
->native_offset
;
346 use_full_trace
= TRUE
;
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
)
358 location
= mono_debug_lookup_source_location (method
, info
->native_offset
, domain
);
359 methodName
= mono_method_full_name (method
, TRUE
);
362 append_report (&trace
, LOCATION_INDENT
"%s in %s:%u\n", methodName
, location
->source_file
, location
->row
);
363 mono_debug_free_source_location (location
);
365 append_report (&trace
, LOCATION_INDENT
"%s\n", methodName
);
371 hint
= g_string_free (trace
, FALSE
);
373 g_string_free (trace
, TRUE
);
376 location
= mono_debug_lookup_source_location (selectedMethod
, native_offset
, domain
);
377 methodName
= mono_method_full_name (selectedMethod
, TRUE
);
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
);
383 hint
= g_strdup_printf (LOCATION_INDENT
"%s\n", methodName
);
390 static inline gchar
*build_hint (SavedString
*head
)
392 SavedString
*current
;
394 GString
*hint
= NULL
;
398 tmp
= build_hint_from_stack (current
->domain
, current
->stack
, current
->stack_entries
);
399 current
= current
->next
;
403 append_report (&hint
, tmp
);
408 return g_string_free (hint
, FALSE
);
410 g_string_free (hint
, FALSE
);
418 static gboolean
stack_walk_func (MonoMethod
*method
, gint32 native_offset
, gint32 il_offset
, gboolean managed
, gpointer data
)
420 StackWalkData
*swdata
= (StackWalkData
*)data
;
421 MonoStackBacktraceInfo
*info
;
423 if (swdata
->frame_count
>= swdata
->stack_size
)
426 info
= (MonoStackBacktraceInfo
*)g_malloc (sizeof (*info
));
427 info
->method
= method
;
428 info
->native_offset
= native_offset
;
430 swdata
->stack
[swdata
->frame_count
++] = info
;
434 static inline int mono_stack_backtrace (MonoProfiler
*prof
, MonoDomain
*domain
, void **stack
, int size
)
440 data
.stack_size
= size
;
441 data
.frame_count
= 0;
443 mono_stack_walk_no_il (stack_walk_func
, (gpointer
)&data
);
445 return data
.frame_count
;
448 static void mono_portability_remember_string (MonoProfiler
*prof
, MonoDomain
*domain
, MonoString
*str
)
450 SavedString
*head
, *entry
;
452 if (!str
|| !domain
|| !runtime_initialized
)
455 entry
= (SavedString
*)g_malloc0 (sizeof (SavedString
));
457 entry
->domain
= domain
;
458 entry
->stack_entries
= mono_stack_backtrace (prof
, domain
, entry
->stack
, BACKTRACE_SIZE
);
459 if (entry
->stack_entries
== 0) {
464 mono_mutex_lock (&mismatched_files_section
);
465 head
= (SavedString
*)g_hash_table_lookup (prof
->saved_strings_hash
, (gpointer
)str
);
471 g_hash_table_insert (prof
->saved_strings_hash
, (gpointer
)str
, (gpointer
)entry
);
472 mono_mutex_unlock (&mismatched_files_section
);
475 static MonoClass
*string_class
= NULL
;
477 static void mono_portability_remember_alloc (MonoProfiler
*prof
, MonoObject
*obj
, MonoClass
*klass
)
479 if (klass
!= string_class
)
481 mono_portability_remember_string (prof
, mono_object_get_domain (obj
), (MonoString
*)obj
);
484 static void mono_portability_iomap_event (MonoProfiler
*prof
, const char *report
, const char *pathname
, const char *new_pathname
)
486 guint32 hash
, pathnameHash
;
487 MismatchedFilesStats
*stats
;
489 if (!runtime_initialized
)
492 mono_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
);
498 stats
= (MismatchedFilesStats
*) g_malloc (sizeof (MismatchedFilesStats
));
500 stats
->requestedName
= g_strdup (pathname
);
501 stats
->actualName
= g_strdup (new_pathname
);
502 hashptr
= (guint32
*)g_malloc (sizeof (guint32
));
505 g_hash_table_insert (prof
->mismatched_files_hash
, (gpointer
)hashptr
, stats
);
507 g_error ("Out of memory allocating integer pointer for mismatched files hash table.");
509 store_string_location (prof
, (const gchar
*)stats
->requestedName
, pathnameHash
, strlen (stats
->requestedName
));
510 mono_mutex_unlock (&mismatched_files_section
);
512 print_report ("%s - Found file path: '%s'\n", report
, new_pathname
);
514 mono_mutex_unlock (&mismatched_files_section
);
519 static void runtime_initialized_cb (MonoProfiler
*prof
)
521 runtime_initialized
= TRUE
;
522 string_class
= mono_get_string_class ();
525 static void profiler_shutdown (MonoProfiler
*prof
)
527 print_mismatched_stats (prof
);
528 mono_mutex_destroy (&mismatched_files_section
);
531 void mono_profiler_startup (const char *desc
)
533 MonoProfiler
*prof
= g_new0 (MonoProfiler
, 1);
535 mono_mutex_init (&mismatched_files_section
, NULL
);
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
);
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
);
545 mono_profiler_set_events (MONO_PROFILE_ALLOCATIONS
| MONO_PROFILE_IOMAP_EVENTS
);