2005-12-23 Dick Porter <dick@ximian.com>
[mono.git] / mono / metadata / profiler.c
1
2 #include "config.h"
3 #include "mono/metadata/profiler-private.h"
4 #include "mono/metadata/debug-helpers.h"
5 #include "mono/metadata/mono-debug.h"
6 #include "mono/metadata/class-internals.h"
7 #include "mono/metadata/domain-internals.h"
8 #include "mono/metadata/gc-internal.h"
9 #include "mono/io-layer/io-layer.h"
10 #include <string.h>
11 #include <sys/time.h>
12 #include <gmodule.h>
13 #ifdef HAVE_BACKTRACE_SYMBOLS
14 #include <execinfo.h>
15 #endif
16
17 static MonoProfiler * current_profiler = NULL;
18
19 static MonoProfileAppDomainFunc   domain_start_load;
20 static MonoProfileAppDomainResult domain_end_load;
21 static MonoProfileAppDomainFunc   domain_start_unload;
22 static MonoProfileAppDomainFunc   domain_end_unload;
23
24 static MonoProfileAssemblyFunc   assembly_start_load;
25 static MonoProfileAssemblyResult assembly_end_load;
26 static MonoProfileAssemblyFunc   assembly_start_unload;
27 static MonoProfileAssemblyFunc   assembly_end_unload;
28
29 static MonoProfileModuleFunc   module_start_load;
30 static MonoProfileModuleResult module_end_load;
31 static MonoProfileModuleFunc   module_start_unload;
32 static MonoProfileModuleFunc   module_end_unload;
33
34 static MonoProfileClassFunc   class_start_load;
35 static MonoProfileClassResult class_end_load;
36 static MonoProfileClassFunc   class_start_unload;
37 static MonoProfileClassFunc   class_end_unload;
38
39 static MonoProfileMethodFunc   jit_start;
40 static MonoProfileMethodResult jit_end;
41 static MonoProfileMethodResult man_unman_transition;
42 static MonoProfileAllocFunc    allocation_cb;
43 static MonoProfileStatFunc     statistical_cb;
44 static MonoProfileMethodFunc   method_enter;
45 static MonoProfileMethodFunc   method_leave;
46
47 static MonoProfileThreadFunc   thread_start;
48 static MonoProfileThreadFunc   thread_end;
49
50 static MonoProfileCoverageFilterFunc coverage_filter_cb;
51
52 static MonoProfileFunc shutdown_callback;
53
54 static MonoProfileGCFunc        gc_event;
55 static MonoProfileGCResizeFunc  gc_heap_resize;
56
57 #define mono_profiler_coverage_lock() EnterCriticalSection (&profiler_coverage_mutex)
58 #define mono_profiler_coverage_unlock() LeaveCriticalSection (&profiler_coverage_mutex)
59 static CRITICAL_SECTION profiler_coverage_mutex;
60
61 /* this is directly accessible to other mono libs. */
62 MonoProfileFlags mono_profiler_events;
63
64 void
65 mono_profiler_install (MonoProfiler *prof, MonoProfileFunc callback)
66 {
67         if (current_profiler)
68                 g_error ("profiler already setup");
69         current_profiler = prof;
70         shutdown_callback = callback;
71         InitializeCriticalSection (&profiler_coverage_mutex);
72 }
73
74 void
75 mono_profiler_set_events (MonoProfileFlags events)
76 {
77         mono_profiler_events = events;
78 }
79
80 MonoProfileFlags
81 mono_profiler_get_events (void)
82 {
83         return mono_profiler_events;
84 }
85
86 void
87 mono_profiler_install_enter_leave (MonoProfileMethodFunc enter, MonoProfileMethodFunc fleave)
88 {
89         method_enter = enter;
90         method_leave = fleave;
91 }
92
93 void 
94 mono_profiler_install_jit_compile (MonoProfileMethodFunc start, MonoProfileMethodResult end)
95 {
96         jit_start = start;
97         jit_end = end;
98 }
99
100 void 
101 mono_profiler_install_thread (MonoProfileThreadFunc start, MonoProfileThreadFunc end)
102 {
103         thread_start = start;
104         thread_end = end;
105 }
106
107 void 
108 mono_profiler_install_transition (MonoProfileMethodResult callback)
109 {
110         man_unman_transition = callback;
111 }
112
113 void 
114 mono_profiler_install_allocation (MonoProfileAllocFunc callback)
115 {
116         allocation_cb = callback;
117 }
118
119 void 
120 mono_profiler_install_statistical (MonoProfileStatFunc callback)
121 {
122         statistical_cb = callback;
123 }
124
125 void 
126 mono_profiler_install_coverage_filter (MonoProfileCoverageFilterFunc callback)
127 {
128         coverage_filter_cb = callback;
129 }
130
131 void 
132 mono_profiler_install_appdomain   (MonoProfileAppDomainFunc start_load, MonoProfileAppDomainResult end_load,
133                                    MonoProfileAppDomainFunc start_unload, MonoProfileAppDomainFunc end_unload)
134
135 {
136         domain_start_load = start_load;
137         domain_end_load = end_load;
138         domain_start_unload = start_unload;
139         domain_end_unload = end_unload;
140 }
141
142 void 
143 mono_profiler_install_assembly    (MonoProfileAssemblyFunc start_load, MonoProfileAssemblyResult end_load,
144                                    MonoProfileAssemblyFunc start_unload, MonoProfileAssemblyFunc end_unload)
145 {
146         assembly_start_load = start_load;
147         assembly_end_load = end_load;
148         assembly_start_unload = start_unload;
149         assembly_end_unload = end_unload;
150 }
151
152 void 
153 mono_profiler_install_module      (MonoProfileModuleFunc start_load, MonoProfileModuleResult end_load,
154                                    MonoProfileModuleFunc start_unload, MonoProfileModuleFunc end_unload)
155 {
156         module_start_load = start_load;
157         module_end_load = end_load;
158         module_start_unload = start_unload;
159         module_end_unload = end_unload;
160 }
161
162 void
163 mono_profiler_install_class       (MonoProfileClassFunc start_load, MonoProfileClassResult end_load,
164                                    MonoProfileClassFunc start_unload, MonoProfileClassFunc end_unload)
165 {
166         class_start_load = start_load;
167         class_end_load = end_load;
168         class_start_unload = start_unload;
169         class_end_unload = end_unload;
170 }
171
172 void
173 mono_profiler_method_enter (MonoMethod *method)
174 {
175         if ((mono_profiler_events & MONO_PROFILE_ENTER_LEAVE) && method_enter)
176                 method_enter (current_profiler, method);
177 }
178
179 void
180 mono_profiler_method_leave (MonoMethod *method)
181 {
182         if ((mono_profiler_events & MONO_PROFILE_ENTER_LEAVE) && method_leave)
183                 method_leave (current_profiler, method);
184 }
185
186 void 
187 mono_profiler_method_jit (MonoMethod *method)
188 {
189         if ((mono_profiler_events & MONO_PROFILE_JIT_COMPILATION) && jit_start)
190                 jit_start (current_profiler, method);
191 }
192
193 void 
194 mono_profiler_method_end_jit (MonoMethod *method, int result)
195 {
196         if ((mono_profiler_events & MONO_PROFILE_JIT_COMPILATION) && jit_end)
197                 jit_end (current_profiler, method, result);
198 }
199
200 void 
201 mono_profiler_code_transition (MonoMethod *method, int result)
202 {
203         if ((mono_profiler_events & MONO_PROFILE_TRANSITIONS) && man_unman_transition)
204                 man_unman_transition (current_profiler, method, result);
205 }
206
207 void 
208 mono_profiler_allocation (MonoObject *obj, MonoClass *klass)
209 {
210         if ((mono_profiler_events & MONO_PROFILE_ALLOCATIONS) && allocation_cb)
211                 allocation_cb (current_profiler, obj, klass);
212 }
213
214 void
215 mono_profiler_stat_hit (guchar *ip, void *context)
216 {
217         if ((mono_profiler_events & MONO_PROFILE_STATISTICAL) && statistical_cb)
218                 statistical_cb (current_profiler, ip, context);
219 }
220
221 void
222 mono_profiler_thread_start (gsize tid)
223 {
224         if ((mono_profiler_events & MONO_PROFILE_THREADS) && thread_start)
225                 thread_start (current_profiler, tid);
226 }
227
228 void 
229 mono_profiler_thread_end (gsize tid)
230 {
231         if ((mono_profiler_events & MONO_PROFILE_THREADS) && thread_end)
232                 thread_end (current_profiler, tid);
233 }
234
235 void 
236 mono_profiler_assembly_event  (MonoAssembly *assembly, int code)
237 {
238         if (!(mono_profiler_events & MONO_PROFILE_ASSEMBLY_EVENTS))
239                 return;
240         
241         switch (code) {
242         case MONO_PROFILE_START_LOAD:
243                 if (assembly_start_load)
244                         assembly_start_load (current_profiler, assembly);
245                 break;
246         case MONO_PROFILE_START_UNLOAD:
247                 if (assembly_start_unload)
248                         assembly_start_unload (current_profiler, assembly);
249                 break;
250         case MONO_PROFILE_END_UNLOAD:
251                 if (assembly_end_unload)
252                         assembly_end_unload (current_profiler, assembly);
253                 break;
254         default:
255                 g_assert_not_reached ();
256         }
257 }
258
259 void 
260 mono_profiler_assembly_loaded (MonoAssembly *assembly, int result)
261 {
262         if ((mono_profiler_events & MONO_PROFILE_ASSEMBLY_EVENTS) && assembly_end_load)
263                 assembly_end_load (current_profiler, assembly, result);
264 }
265
266 void 
267 mono_profiler_module_event  (MonoImage *module, int code)
268 {
269         if (!(mono_profiler_events & MONO_PROFILE_MODULE_EVENTS))
270                 return;
271         
272         switch (code) {
273         case MONO_PROFILE_START_LOAD:
274                 if (module_start_load)
275                         module_start_load (current_profiler, module);
276                 break;
277         case MONO_PROFILE_START_UNLOAD:
278                 if (module_start_unload)
279                         module_start_unload (current_profiler, module);
280                 break;
281         case MONO_PROFILE_END_UNLOAD:
282                 if (module_end_unload)
283                         module_end_unload (current_profiler, module);
284                 break;
285         default:
286                 g_assert_not_reached ();
287         }
288 }
289
290 void 
291 mono_profiler_module_loaded (MonoImage *module, int result)
292 {
293         if ((mono_profiler_events & MONO_PROFILE_MODULE_EVENTS) && module_end_load)
294                 module_end_load (current_profiler, module, result);
295 }
296
297 void 
298 mono_profiler_class_event  (MonoClass *klass, int code)
299 {
300         if (!(mono_profiler_events & MONO_PROFILE_CLASS_EVENTS))
301                 return;
302         
303         switch (code) {
304         case MONO_PROFILE_START_LOAD:
305                 if (class_start_load)
306                         class_start_load (current_profiler, klass);
307                 break;
308         case MONO_PROFILE_START_UNLOAD:
309                 if (class_start_unload)
310                         class_start_unload (current_profiler, klass);
311                 break;
312         case MONO_PROFILE_END_UNLOAD:
313                 if (class_end_unload)
314                         class_end_unload (current_profiler, klass);
315                 break;
316         default:
317                 g_assert_not_reached ();
318         }
319 }
320
321 void 
322 mono_profiler_class_loaded (MonoClass *klass, int result)
323 {
324         if ((mono_profiler_events & MONO_PROFILE_CLASS_EVENTS) && class_end_load)
325                 class_end_load (current_profiler, klass, result);
326 }
327
328 void 
329 mono_profiler_appdomain_event  (MonoDomain *domain, int code)
330 {
331         if (!(mono_profiler_events & MONO_PROFILE_APPDOMAIN_EVENTS))
332                 return;
333         
334         switch (code) {
335         case MONO_PROFILE_START_LOAD:
336                 if (domain_start_load)
337                         domain_start_load (current_profiler, domain);
338                 break;
339         case MONO_PROFILE_START_UNLOAD:
340                 if (domain_start_unload)
341                         domain_start_unload (current_profiler, domain);
342                 break;
343         case MONO_PROFILE_END_UNLOAD:
344                 if (domain_end_unload)
345                         domain_end_unload (current_profiler, domain);
346                 break;
347         default:
348                 g_assert_not_reached ();
349         }
350 }
351
352 void 
353 mono_profiler_appdomain_loaded (MonoDomain *domain, int result)
354 {
355         if ((mono_profiler_events & MONO_PROFILE_APPDOMAIN_EVENTS) && domain_end_load)
356                 domain_end_load (current_profiler, domain, result);
357 }
358
359 void 
360 mono_profiler_shutdown (void)
361 {
362         if (current_profiler && shutdown_callback)
363                 shutdown_callback (current_profiler);
364 }
365
366 void
367 mono_profiler_gc_heap_resize (gint64 new_size)
368 {
369         if ((mono_profiler_events & MONO_PROFILE_GC) && gc_heap_resize)
370                 gc_heap_resize (current_profiler, new_size);
371 }
372
373 void
374 mono_profiler_gc_event (MonoGCEvent event, int generation)
375 {
376         if ((mono_profiler_events & MONO_PROFILE_GC) && gc_event)
377                 gc_event (current_profiler, event, generation);
378 }
379
380 void
381 mono_profiler_install_gc (MonoProfileGCFunc callback, MonoProfileGCResizeFunc heap_resize_callback)
382 {
383         mono_gc_enable_events ();
384         gc_event = callback;
385         gc_heap_resize = heap_resize_callback;
386 }
387
388 static GHashTable *coverage_hash = NULL;
389
390 MonoProfileCoverageInfo* 
391 mono_profiler_coverage_alloc (MonoMethod *method, int entries)
392 {
393         MonoProfileCoverageInfo *res;
394
395         if (coverage_filter_cb)
396                 if (! (*coverage_filter_cb) (current_profiler, method))
397                         return NULL;
398
399         mono_profiler_coverage_lock ();
400         if (!coverage_hash)
401                 coverage_hash = g_hash_table_new (NULL, NULL);
402
403         res = g_malloc0 (sizeof (MonoProfileCoverageInfo) + sizeof (void*) * 2 * entries);
404
405         res->entries = entries;
406
407         g_hash_table_insert (coverage_hash, method, res);
408         mono_profiler_coverage_unlock ();
409
410         return res;
411 }
412
413 /* safe only when the method antive code has been unloaded */
414 void
415 mono_profiler_coverage_free (MonoMethod *method)
416 {
417         MonoProfileCoverageInfo* info;
418
419         mono_profiler_coverage_lock ();
420         if (!coverage_hash) {
421                 mono_profiler_coverage_unlock ();
422                 return;
423         }
424
425         info = g_hash_table_lookup (coverage_hash, method);
426         if (info) {
427                 g_free (info);
428                 g_hash_table_remove (coverage_hash, method);
429         }
430         mono_profiler_coverage_unlock ();
431 }
432
433 void 
434 mono_profiler_coverage_get (MonoProfiler *prof, MonoMethod *method, MonoProfileCoverageFunc func)
435 {
436         MonoProfileCoverageInfo* info;
437         int i, offset;
438         guint32 line, col;
439         unsigned char *start, *end, *cil_code;
440         MonoMethodHeader *header;
441         MonoProfileCoverageEntry entry;
442
443         mono_profiler_coverage_lock ();
444         info = g_hash_table_lookup (coverage_hash, method);
445         mono_profiler_coverage_unlock ();
446
447         if (!info)
448                 return;
449
450         header = mono_method_get_header (method);
451         start = (unsigned char*)header->code;
452         end = start + header->code_size;
453         for (i = 0; i < info->entries; ++i) {
454                 cil_code = info->data [i].cil_code;
455                 if (cil_code && cil_code >= start && cil_code < end) {
456                         offset = cil_code - start;
457                         entry.iloffset = offset;
458                         entry.method = method;
459                         entry.counter = info->data [i].count;
460                         /* the debug interface doesn't support column info, sigh */
461                         col = line = 1;
462                         entry.filename = mono_debug_source_location_from_il_offset (method, offset, &line);
463                         entry.line = line;
464                         entry.col = col;
465                         func (prof, &entry);
466                 }
467         }
468 }
469
470 #ifndef DISABLE_PROFILER
471 /*
472  * Small profiler extracted from mint: we should move it in a loadable module
473  * and improve it to do graphs and more accurate timestamping with rdtsc.
474  */
475
476 #define USE_X86TSC 0
477 #define USE_WIN32COUNTER 0
478 #if USE_X86TSC
479
480 typedef struct {
481         unsigned int lows, highs, lowe, highe;
482 } MonoRdtscTimer;
483
484 #define rdtsc(low,high) \
485         __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))
486
487 static int freq;
488
489 static double
490 rdtsc_elapsed (MonoRdtscTimer *t)
491 {
492         unsigned long long diff;
493         unsigned int highe = t->highe;
494         if (t->lowe < t->lows)
495                 highe--;
496         diff = (((unsigned long long) highe - t->highs) << 32) + (t->lowe - t->lows);
497         return ((double)diff / freq) / 1000000; /* have to return the result in seconds */
498 }
499
500 static int 
501 have_rdtsc (void) {
502         char buf[256];
503         int have_freq = 0;
504         int have_flag = 0;
505         float val;
506         FILE *cpuinfo;
507
508         if (!(cpuinfo = fopen ("/proc/cpuinfo", "r")))
509                 return 0;
510         while (fgets (buf, sizeof(buf), cpuinfo)) {
511                 if (sscanf (buf, "cpu MHz : %f", &val) == 1) {
512                         /*printf ("got mh: %f\n", val);*/
513                         have_freq = val;
514                 }
515                 if (strncmp (buf, "flags", 5) == 0) {
516                         if (strstr (buf, "tsc")) {
517                                 have_flag = 1;
518                                 /*printf ("have tsc\n");*/
519                         }
520                 }
521         }
522         fclose (cpuinfo);
523         return have_flag? have_freq: 0;
524 }
525
526 #define MONO_TIMER_STARTUP      \
527         if (!(freq = have_rdtsc ())) g_error ("Compiled with TSC support, but none found");
528 #define MONO_TIMER_TYPE  MonoRdtscTimer
529 #define MONO_TIMER_INIT(t)
530 #define MONO_TIMER_DESTROY(t)
531 #define MONO_TIMER_START(t) rdtsc ((t).lows, (t).highs);
532 #define MONO_TIMER_STOP(t) rdtsc ((t).lowe, (t).highe);
533 #define MONO_TIMER_ELAPSED(t) rdtsc_elapsed (&(t))
534
535 #elif USE_WIN32COUNTER
536 #include <windows.h>
537
538 typedef struct {
539         LARGE_INTEGER start, stop;
540 } MonoWin32Timer;
541
542 static int freq;
543
544 static double
545 win32_elapsed (MonoWin32Timer *t)
546 {
547         LONGLONG diff = t->stop.QuadPart - t->start.QuadPart;
548         return ((double)diff / freq) / 1000000; /* have to return the result in seconds */
549 }
550
551 static int 
552 have_win32counter (void) {
553         LARGE_INTEGER f;
554
555         if (!QueryPerformanceFrequency (&f))
556                 return 0;
557         return f.LowPart;
558 }
559
560 #define MONO_TIMER_STARTUP      \
561         if (!(freq = have_win32counter ())) g_error ("Compiled with Win32 counter support, but none found");
562 #define MONO_TIMER_TYPE  MonoWin32Timer
563 #define MONO_TIMER_INIT(t)
564 #define MONO_TIMER_DESTROY(t)
565 #define MONO_TIMER_START(t) QueryPerformanceCounter (&(t).start)
566 #define MONO_TIMER_STOP(t) QueryPerformanceCounter (&(t).stop)
567 #define MONO_TIMER_ELAPSED(t) win32_elapsed (&(t))
568
569 #else
570
571 typedef struct {
572         GTimeVal start, stop;
573 } MonoGLibTimer;
574
575 static double
576 timeval_elapsed (MonoGLibTimer *t)
577 {
578         if (t->start.tv_usec > t->stop.tv_usec) {
579                 t->stop.tv_usec += G_USEC_PER_SEC;
580                 t->stop.tv_sec--;
581         }
582         return (t->stop.tv_sec - t->start.tv_sec) 
583                 + ((double)(t->stop.tv_usec - t->start.tv_usec))/ G_USEC_PER_SEC;
584 }
585
586 #define MONO_TIMER_STARTUP
587 #define MONO_TIMER_TYPE MonoGLibTimer
588 #define MONO_TIMER_INIT(t)
589 #define MONO_TIMER_DESTROY(t)
590 #define MONO_TIMER_START(t) g_get_current_time (&(t).start)
591 #define MONO_TIMER_STOP(t) g_get_current_time (&(t).stop)
592 #define MONO_TIMER_ELAPSED(t) timeval_elapsed (&(t))
593 #endif
594
595 typedef struct _AllocInfo AllocInfo;
596 typedef struct _CallerInfo CallerInfo;
597 typedef struct _LastCallerInfo LastCallerInfo;
598
599 struct _MonoProfiler {
600         GHashTable *methods;
601         MonoMemPool *mempool;
602         /* info about JIT time */
603         MONO_TIMER_TYPE jit_timer;
604         double      jit_time;
605         double      max_jit_time;
606         MonoMethod *max_jit_method;
607         int         methods_jitted;
608         
609         GSList     *per_thread;
610         
611         /* chain of callers for the current thread */
612         LastCallerInfo *callers;
613         /* LastCallerInfo nodes for faster allocation */
614         LastCallerInfo *cstorage;
615 };
616
617 typedef struct {
618         MonoMethod *method;
619         guint64 count;
620         double total;
621         AllocInfo *alloc_info;
622         CallerInfo *caller_info;
623 } MethodProfile;
624
625 typedef struct _MethodCallProfile MethodCallProfile;
626
627 struct _MethodCallProfile {
628         MethodCallProfile *next;
629         MONO_TIMER_TYPE timer;
630         MonoMethod *method;
631 };
632
633 struct _AllocInfo {
634         AllocInfo *next;
635         MonoClass *klass;
636         guint64 count;
637         guint64 mem;
638 };
639
640 struct _CallerInfo {
641         CallerInfo *next;
642         MonoMethod *caller;
643         guint count;
644 };
645
646 struct _LastCallerInfo {
647         LastCallerInfo *next;
648         MonoMethod *method;
649         MONO_TIMER_TYPE timer;
650 };
651
652 static MonoProfiler*
653 create_profiler (void)
654 {
655         MonoProfiler *prof = g_new0 (MonoProfiler, 1);
656
657         prof->methods = g_hash_table_new (NULL, NULL);
658         MONO_TIMER_INIT (prof->jit_timer);
659         prof->mempool = mono_mempool_new ();
660         return prof;
661 }
662 #if 1
663
664 #ifdef HAVE_KW_THREAD
665         static __thread MonoProfiler * tls_profiler;
666 #       define GET_PROFILER() tls_profiler
667 #       define SET_PROFILER(x) tls_profiler = (x)
668 #       define ALLOC_PROFILER() /* nop */
669 #else
670         static guint32 profiler_thread_id = -1;
671 #       define GET_PROFILER() ((MonoProfiler *)TlsGetValue (profiler_thread_id))
672 #       define SET_PROFILER(x) TlsSetValue (profiler_thread_id, x);
673 #       define ALLOC_PROFILER() profiler_thread_id = TlsAlloc ()
674 #endif
675
676 #define GET_THREAD_PROF(prof) do {                                                           \
677                 MonoProfiler *_tprofiler = GET_PROFILER ();                                  \
678                 if (!_tprofiler) {                                                           \
679                         _tprofiler = create_profiler ();                                     \
680                         prof->per_thread = g_slist_prepend (prof->per_thread, _tprofiler);   \
681                         SET_PROFILER (_tprofiler);                                           \
682                 }                                                                            \
683                 prof = _tprofiler;                                                           \
684         } while (0)
685 #else
686 /* thread unsafe but faster variant */
687 #define GET_THREAD_PROF(prof)
688 #endif
689
690 static gint
691 compare_profile (MethodProfile *profa, MethodProfile *profb)
692 {
693         return (gint)((profb->total - profa->total)*1000);
694 }
695
696 static void
697 build_profile (MonoMethod *m, MethodProfile *prof, GList **funcs)
698 {
699         prof->method = m;
700         *funcs = g_list_insert_sorted (*funcs, prof, (GCompareFunc)compare_profile);
701 }
702
703 static char*
704 method_get_name (MonoMethod* method)
705 {
706         char *sig, *res;
707         
708         sig = mono_signature_get_desc (mono_method_signature (method), FALSE);
709         res = g_strdup_printf ("%s%s%s::%s(%s)", method->klass->name_space,
710                         method->klass->name_space ? "." : "", method->klass->name,
711                 method->name, sig);
712         g_free (sig);
713         return res;
714 }
715
716 static void output_callers (MethodProfile *p);
717
718 static void
719 output_profile (GList *funcs)
720 {
721         GList *tmp;
722         MethodProfile *p;
723         char *m;
724         guint64 total_calls = 0;
725
726         if (funcs)
727                 g_print ("Time(ms) Count   P/call(ms) Method name\n");
728         for (tmp = funcs; tmp; tmp = tmp->next) {
729                 p = tmp->data;
730                 total_calls += p->count;
731                 if (!(gint)(p->total*1000))
732                         continue;
733                 m = method_get_name (p->method);
734                 printf ("########################\n");
735                 printf ("% 8.3f ", (double) (p->total * 1000));
736                 printf ("%7llu ", (unsigned long long)p->count);
737                 printf ("% 8.3f ", (double) (p->total * 1000)/(double)p->count);
738                 printf ("  %s\n", m);
739
740                 g_free (m);
741                 /* callers */
742                 output_callers (p);
743         }
744         printf ("Total number of calls: %lld\n", (long long)total_calls);
745 }
746
747 typedef struct {
748         MethodProfile *mp;
749         guint64 count;
750 } NewobjProfile;
751
752 static gint
753 compare_newobj_profile (NewobjProfile *profa, NewobjProfile *profb)
754 {
755         if (profb->count == profa->count)
756                 return 0;
757         else
758                 return profb->count > profa->count ? 1 : -1;
759 }
760
761 static void
762 build_newobj_profile (MonoClass *class, MethodProfile *mprof, GList **funcs)
763 {
764         NewobjProfile *prof = g_new (NewobjProfile, 1);
765         AllocInfo *tmp;
766         guint64 count = 0;
767         
768         prof->mp = mprof;
769         /* we use the total amount of memory to sort */
770         for (tmp = mprof->alloc_info; tmp; tmp = tmp->next)
771                 count += tmp->mem;
772         prof->count = count;
773         *funcs = g_list_insert_sorted (*funcs, prof, (GCompareFunc)compare_newobj_profile);
774 }
775
776 static int
777 compare_caller (CallerInfo *a, CallerInfo *b)
778 {
779         return b->count - a->count;
780 }
781
782 static int
783 compare_alloc (AllocInfo *a, AllocInfo *b)
784 {
785         return b->mem - a->mem;
786 }
787
788 static GSList*
789 sort_alloc_list (AllocInfo *ai)
790 {
791         GSList *l = NULL;
792         AllocInfo *tmp;
793         for (tmp = ai; tmp; tmp = tmp->next) {
794                 l = g_slist_insert_sorted (l, tmp, (GCompareFunc)compare_alloc);
795         }
796         return l;
797 }
798
799 static GSList*
800 sort_caller_list (CallerInfo *ai)
801 {
802         GSList *l = NULL;
803         CallerInfo *tmp;
804         for (tmp = ai; tmp; tmp = tmp->next) {
805                 l = g_slist_insert_sorted (l, tmp, (GCompareFunc)compare_caller);
806         }
807         return l;
808 }
809
810 static void
811 output_callers (MethodProfile *p) {
812         guint total_callers, percent;
813         GSList *sorted, *tmps;
814         CallerInfo *cinfo;
815         char *m;
816         
817         g_print ("  Callers (with count) that contribute at least for 1%%:\n");
818         total_callers = 0;
819         for (cinfo = p->caller_info; cinfo; cinfo = cinfo->next) {
820                 total_callers += cinfo->count;
821         }
822         sorted = sort_caller_list (p->caller_info);
823         for (tmps = sorted; tmps; tmps = tmps->next) {
824                 cinfo = tmps->data;
825                 percent = (cinfo->count * 100)/total_callers;
826                 if (percent < 1)
827                         continue;
828                 m = method_get_name (cinfo->caller);
829                 g_print ("    %8d % 3d %% %s\n", cinfo->count, percent, m);
830                 g_free (m);
831         }
832 }
833
834 /* This isn't defined on older glib versions and on some platforms */
835 #ifndef G_GUINT64_FORMAT
836 #define G_GUINT64_FORMAT "ul"
837 #endif
838
839 static void
840 output_newobj_profile (GList *proflist)
841 {
842         GList *tmp;
843         NewobjProfile *p;
844         MethodProfile *mp;
845         AllocInfo *ainfo;
846         MonoClass *klass;
847         const char* isarray;
848         char buf [256];
849         char *m;
850         guint64 total = 0;
851         GSList *sorted, *tmps;
852
853         g_print ("\nAllocation profiler\n");
854
855         if (proflist)
856                 g_print ("%-9s %s\n", "Total mem", "Method");
857         for (tmp = proflist; tmp; tmp = tmp->next) {
858                 p = tmp->data;
859                 total += p->count;
860                 if (p->count < 50000)
861                         continue;
862                 mp = p->mp;
863                 m = method_get_name (mp->method);
864                 g_print ("########################\n%8" G_GUINT64_FORMAT " KB %s\n", (p->count / 1024), m);
865                 g_free (m);
866                 sorted = sort_alloc_list (mp->alloc_info);
867                 for (tmps = sorted; tmps; tmps = tmps->next) {
868                         ainfo = tmps->data;
869                         if (ainfo->mem < 50000)
870                                 continue;
871                         klass = ainfo->klass;
872                         if (klass->rank) {
873                                 isarray = "[]";
874                                 klass = klass->element_class;
875                         } else {
876                                 isarray = "";
877                         }
878                         g_snprintf (buf, sizeof (buf), "%s%%s%s",
879                                 klass->name_space, klass->name_space ? "." : "", klass->name, isarray);
880                         g_print ("    %8" G_GUINT64_FORMAT " KB %8" G_GUINT64_FORMAT " %-48s\n", (ainfo->mem / 1024), ainfo->count, buf);
881                 }
882                 /* callers */
883                 output_callers (mp);
884         }
885         g_print ("Total memory allocated: %" G_GUINT64_FORMAT " KB\n", total / 1024);
886 }
887
888 static void
889 merge_methods (MonoMethod *method, MethodProfile *profile, MonoProfiler *prof)
890 {
891         MethodProfile *mprof;
892         AllocInfo *talloc_info, *alloc_info;
893         CallerInfo *tcaller_info, *caller_info;
894
895         mprof = g_hash_table_lookup (prof->methods, method);
896         if (!mprof) {
897                 /* the master thread didn't see this method, just transfer the info as is */
898                 g_hash_table_insert (prof->methods, method, profile);
899                 return;
900         }
901         /* merge the info from profile into mprof */
902         mprof->count += profile->count;
903         mprof->total += profile->total;
904         /* merge alloc info */
905         for (talloc_info = profile->alloc_info; talloc_info; talloc_info = talloc_info->next) {
906                 for (alloc_info = mprof->alloc_info; alloc_info; alloc_info = alloc_info->next) {
907                         if (alloc_info->klass == talloc_info->klass) {
908                                 /* mprof already has a record for the klass, merge */
909                                 alloc_info->count += talloc_info->count;
910                                 alloc_info->mem += talloc_info->mem;
911                                 break;
912                         }
913                 }
914                 if (!alloc_info) {
915                         /* mprof didn't have the info, just copy it over */
916                         alloc_info = mono_mempool_alloc0 (prof->mempool, sizeof (AllocInfo));
917                         *alloc_info = *talloc_info;
918                         alloc_info->next = mprof->alloc_info;
919                         mprof->alloc_info = alloc_info->next;
920                 }
921         }
922         /* merge callers info */
923         for (tcaller_info = profile->caller_info; tcaller_info; tcaller_info = tcaller_info->next) {
924                 for (caller_info = mprof->caller_info; caller_info; caller_info = caller_info->next) {
925                         if (caller_info->caller == tcaller_info->caller) {
926                                 /* mprof already has a record for the caller method, merge */
927                                 caller_info->count += tcaller_info->count;
928                                 break;
929                         }
930                 }
931                 if (!caller_info) {
932                         /* mprof didn't have the info, just copy it over */
933                         caller_info = mono_mempool_alloc0 (prof->mempool, sizeof (CallerInfo));
934                         *caller_info = *tcaller_info;
935                         caller_info->next = mprof->caller_info;
936                         mprof->caller_info = caller_info;
937                 }
938         }
939 }
940
941 static void
942 merge_thread_data (MonoProfiler *master, MonoProfiler *tprof)
943 {
944         master->jit_time += tprof->jit_time;
945         master->methods_jitted += tprof->methods_jitted;
946         if (master->max_jit_time < tprof->max_jit_time) {
947                 master->max_jit_time = tprof->max_jit_time;
948                 master->max_jit_method = tprof->max_jit_method;
949         }
950
951         g_hash_table_foreach (tprof->methods, (GHFunc)merge_methods, master);
952 }
953
954 static void
955 simple_method_enter (MonoProfiler *prof, MonoMethod *method)
956 {
957         MethodProfile *profile_info;
958         LastCallerInfo *callinfo;
959         GET_THREAD_PROF (prof);
960         /*g_print ("enter %p %s::%s in %d (%p)\n", method, method->klass->name, method->name, GetCurrentThreadId (), prof);*/
961         if (!(profile_info = g_hash_table_lookup (prof->methods, method))) {
962                 profile_info = mono_mempool_alloc0 (prof->mempool, sizeof (MethodProfile));
963                 MONO_TIMER_INIT (profile_info->u.timer);
964                 g_hash_table_insert (prof->methods, method, profile_info);
965         }
966         profile_info->count++;
967         if (prof->callers) {
968                 CallerInfo *cinfo;
969                 MonoMethod *caller = prof->callers->method;
970                 for (cinfo = profile_info->caller_info; cinfo; cinfo = cinfo->next) {
971                         if (cinfo->caller == caller)
972                                 break;
973                 }
974                 if (!cinfo) {
975                         cinfo = mono_mempool_alloc0 (prof->mempool, sizeof (CallerInfo));
976                         cinfo->caller = caller;
977                         cinfo->next = profile_info->caller_info;
978                         profile_info->caller_info = cinfo;
979                 }
980                 cinfo->count++;
981         }
982         if (!(callinfo = prof->cstorage)) {
983                 callinfo = mono_mempool_alloc (prof->mempool, sizeof (LastCallerInfo));
984                 MONO_TIMER_INIT (callinfo->timer);
985         } else {
986                 prof->cstorage = prof->cstorage->next;
987         }
988         callinfo->method = method;
989         callinfo->next = prof->callers;
990         prof->callers = callinfo;
991         MONO_TIMER_START (callinfo->timer);
992 }
993
994 static void
995 simple_method_leave (MonoProfiler *prof, MonoMethod *method)
996 {
997         MethodProfile *profile_info;
998         LastCallerInfo *callinfo, *newcallinfo = NULL;
999         
1000         GET_THREAD_PROF (prof);
1001         /*g_print ("leave %p %s::%s in %d (%p)\n", method, method->klass->name, method->name, GetCurrentThreadId (), prof);*/
1002         callinfo = prof->callers;
1003         /* should really not happen, but we don't catch exceptions events, yet ... */
1004         while (callinfo) {
1005                 MONO_TIMER_STOP (callinfo->timer);
1006                 profile_info = g_hash_table_lookup (prof->methods, callinfo->method);
1007                 if (profile_info)
1008                         profile_info->total += MONO_TIMER_ELAPSED (callinfo->timer);
1009                 newcallinfo = callinfo->next;
1010                 callinfo->next = prof->cstorage;
1011                 prof->cstorage = callinfo;
1012                 if (callinfo->method == method)
1013                         break;
1014                 callinfo = newcallinfo;
1015         }
1016         prof->callers = newcallinfo;
1017 }
1018
1019 static void
1020 simple_allocation (MonoProfiler *prof, MonoObject *obj, MonoClass *klass)
1021 {
1022         MethodProfile *profile_info;
1023         AllocInfo *tmp;
1024
1025         GET_THREAD_PROF (prof);
1026         if (prof->callers) {
1027                 MonoMethod *caller = prof->callers->method;
1028
1029                 /* Otherwise all allocations are attributed to icall_wrapper_mono_object_new */
1030                 if (caller->wrapper_type == MONO_WRAPPER_MANAGED_TO_NATIVE)
1031                         caller = prof->callers->next->method;
1032
1033                 if (!(profile_info = g_hash_table_lookup (prof->methods, caller)))
1034                         g_assert_not_reached ();
1035         } else {
1036                 return; /* fine for now */
1037         }
1038
1039         for (tmp = profile_info->alloc_info; tmp; tmp = tmp->next) {
1040                 if (tmp->klass == klass)
1041                         break;
1042         }
1043         if (!tmp) {
1044                 tmp = mono_mempool_alloc0 (prof->mempool, sizeof (AllocInfo));
1045                 tmp->klass = klass;
1046                 tmp->next = profile_info->alloc_info;
1047                 profile_info->alloc_info = tmp;
1048         }
1049         tmp->count++;
1050         tmp->mem += mono_object_get_size (obj);
1051 }
1052
1053 static void
1054 simple_method_jit (MonoProfiler *prof, MonoMethod *method)
1055 {
1056         GET_THREAD_PROF (prof);
1057         prof->methods_jitted++;
1058         MONO_TIMER_START (prof->jit_timer);
1059 }
1060
1061 static void
1062 simple_method_end_jit (MonoProfiler *prof, MonoMethod *method, int result)
1063 {
1064         double jtime;
1065         GET_THREAD_PROF (prof);
1066         MONO_TIMER_STOP (prof->jit_timer);
1067         jtime = MONO_TIMER_ELAPSED (prof->jit_timer);
1068         prof->jit_time += jtime;
1069         if (jtime > prof->max_jit_time) {
1070                 prof->max_jit_time = jtime;
1071                 prof->max_jit_method = method;
1072         }
1073 }
1074
1075 /* about 10 minutes of samples */
1076 #define MAX_PROF_SAMPLES (1000*60*10)
1077 static int prof_counts = 0;
1078 static int prof_ucounts = 0;
1079 static gpointer* prof_addresses = NULL;
1080 static GHashTable *prof_table = NULL;
1081
1082 static void
1083 simple_stat_hit (MonoProfiler *prof, guchar *ip, void *context)
1084 {
1085         int pos;
1086
1087         if (prof_counts >= MAX_PROF_SAMPLES)
1088                 return;
1089         pos = InterlockedIncrement (&prof_counts);
1090         prof_addresses [pos - 1] = ip;
1091 }
1092
1093 static int
1094 compare_methods_prof (gconstpointer a, gconstpointer b)
1095 {
1096         int ca = GPOINTER_TO_UINT (g_hash_table_lookup (prof_table, a));
1097         int cb = GPOINTER_TO_UINT (g_hash_table_lookup (prof_table, b));
1098         return cb-ca;
1099 }
1100
1101 static void
1102 prof_foreach (char *method, gpointer c, gpointer data)
1103 {
1104         GList **list = data;
1105         *list = g_list_insert_sorted (*list, method, compare_methods_prof);
1106 }
1107
1108 typedef struct Addr2LineData Addr2LineData;
1109
1110 struct Addr2LineData {
1111         Addr2LineData *next;
1112         FILE *pipein;
1113         FILE *pipeout;
1114         char *binary;
1115         int child_pid;
1116 };
1117
1118 static Addr2LineData *addr2line_pipes = NULL;
1119
1120 static char*
1121 try_addr2line (const char* binary, gpointer ip)
1122 {
1123         char buf [1024];
1124         char *res;
1125         Addr2LineData *addr2line;
1126
1127         for (addr2line = addr2line_pipes; addr2line; addr2line = addr2line->next) {
1128                 if (strcmp (binary, addr2line->binary) == 0)
1129                         break;
1130         }
1131         if (!addr2line) {
1132                 const char *addr_argv[] = {"addr2line", "-f", "-e", binary, NULL};
1133                 int child_pid;
1134                 int ch_in, ch_out;
1135                 if (!g_spawn_async_with_pipes (NULL, (char**)addr_argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
1136                                 &child_pid, &ch_in, &ch_out, NULL, NULL)) {
1137                         return g_strdup (binary);
1138                 }
1139                 addr2line = g_new0 (Addr2LineData, 1);
1140                 addr2line->child_pid = child_pid;
1141                 addr2line->binary = g_strdup (binary);
1142                 addr2line->pipein = fdopen (ch_in, "w");
1143                 addr2line->pipeout = fdopen (ch_out, "r");
1144                 addr2line->next = addr2line_pipes;
1145                 addr2line_pipes = addr2line;
1146         }
1147         fprintf (addr2line->pipein, "%p\n", ip);
1148         fflush (addr2line->pipein);
1149         /* we first get the func name and then file:lineno in a second line */
1150         if (fgets (buf, sizeof (buf), addr2line->pipeout) && buf [0] != '?') {
1151                 char *end = strchr (buf, '\n');
1152                 if (end)
1153                         *end = 0;
1154                 res = g_strdup_printf ("%s(%s", binary, buf);
1155                 /* discard the filename/line info */
1156                 fgets (buf, sizeof (buf), addr2line->pipeout);
1157         } else {
1158                 res = g_strdup (binary);
1159         }
1160         return res;
1161 }
1162
1163 static void
1164 stat_prof_report (void)
1165 {
1166         MonoJitInfo *ji;
1167         int count = prof_counts;
1168         int i, c;
1169         char *mn;
1170         gpointer ip;
1171         GList *tmp, *sorted = NULL;
1172         int pcount = ++ prof_counts;
1173
1174         prof_counts = MAX_PROF_SAMPLES;
1175         for (i = 0; i < count; ++i) {
1176                 ip = prof_addresses [i];
1177                 ji = mono_jit_info_table_find (mono_domain_get (), ip);
1178                 if (ji) {
1179                         mn = mono_method_full_name (ji->method, TRUE);
1180                 } else {
1181 #ifdef HAVE_BACKTRACE_SYMBOLS
1182                         char **names;
1183                         char *send;
1184                         int no_func;
1185                         prof_ucounts++;
1186                         names = backtrace_symbols (&ip, 1);
1187                         send = strchr (names [0], '+');
1188                         if (send) {
1189                                 *send = 0;
1190                                 no_func = 0;
1191                         } else {
1192                                 no_func = 1;
1193                         }
1194                         send = strchr (names [0], '[');
1195                         if (send)
1196                                 *send = 0;
1197                         if (no_func && names [0][0]) {
1198                                 char *endp = strchr (names [0], 0);
1199                                 while (--endp >= names [0] && g_ascii_isspace (*endp))
1200                                         *endp = 0;
1201                                 mn = try_addr2line (names [0], ip);
1202                         } else {
1203                                 mn = g_strdup (names [0]);
1204                         }
1205                         free (names);
1206 #else
1207                         prof_ucounts++;
1208                         mn = g_strdup_printf ("unmanaged [%p]", ip);
1209 #endif
1210                 }
1211                 c = GPOINTER_TO_UINT (g_hash_table_lookup (prof_table, mn));
1212                 c++;
1213                 g_hash_table_insert (prof_table, mn, GUINT_TO_POINTER (c));
1214                 if (c > 1)
1215                         g_free (mn);
1216         }
1217         g_print ("prof counts: total/unmanaged: %d/%d\n", pcount, prof_ucounts);
1218         g_hash_table_foreach (prof_table, (GHFunc)prof_foreach, &sorted);
1219         for (tmp = sorted; tmp; tmp = tmp->next) {
1220                 double perc;
1221                 c = GPOINTER_TO_UINT (g_hash_table_lookup (prof_table, tmp->data));
1222                 perc = c*100.0/count;
1223                 g_print ("%7d\t%5.2f %% %s\n", c, perc, (char*)tmp->data);
1224         }
1225         g_list_free (sorted);
1226 }
1227
1228 static void
1229 simple_appdomain_unload (MonoProfiler *prof, MonoDomain *domain)
1230 {
1231         /* FIXME: we should actually record partial data for each domain, 
1232          * since the ip->ji->method mappings are going away at domain unload time.
1233          */
1234         if (domain == mono_get_root_domain ())
1235                 stat_prof_report ();
1236 }
1237
1238 static void
1239 simple_shutdown (MonoProfiler *prof)
1240 {
1241         GList *profile = NULL;
1242         MonoProfiler *tprof;
1243         GSList *tmp;
1244         char *str;
1245
1246         for (tmp = prof->per_thread; tmp; tmp = tmp->next) {
1247                 tprof = tmp->data;
1248                 merge_thread_data (prof, tprof);
1249         }
1250
1251         printf("Total time spent compiling %d methods (sec): %.4g\n", prof->methods_jitted, prof->jit_time);
1252         if (prof->max_jit_method) {
1253                 str = method_get_name (prof->max_jit_method);
1254                 printf("Slowest method to compile (sec): %.4g: %s\n", prof->max_jit_time, str);
1255                 g_free (str);
1256         }
1257         g_hash_table_foreach (prof->methods, (GHFunc)build_profile, &profile);
1258         output_profile (profile);
1259         g_list_free (profile);
1260         profile = NULL;
1261                 
1262         g_hash_table_foreach (prof->methods, (GHFunc)build_newobj_profile, &profile);
1263         output_newobj_profile (profile);
1264         g_list_free (profile);
1265
1266         g_free (prof_addresses);
1267         prof_addresses = NULL;
1268         g_hash_table_destroy (prof_table);
1269 }
1270
1271 static void
1272 mono_profiler_install_simple (const char *desc)
1273 {
1274         MonoProfiler *prof;
1275         gchar **args, **ptr;
1276         MonoProfileFlags flags = 0;
1277
1278         MONO_TIMER_STARTUP;
1279
1280         if (!desc)
1281                 desc = "alloc,time,jit";
1282
1283         if (desc) {
1284                 /* Parse options */
1285                 if (strstr (desc, ":"))
1286                         desc = strstr (desc, ":") + 1;
1287                 else
1288                         desc = "alloc,time,jit";
1289                 args = g_strsplit (desc, ",", -1);
1290
1291                 for (ptr = args; ptr && *ptr; ptr++) {
1292                         const char *arg = *ptr;
1293
1294                         if (!strcmp (arg, "time"))
1295                                 flags |= MONO_PROFILE_ENTER_LEAVE;
1296                         else if (!strcmp (arg, "alloc"))
1297                                 flags |= MONO_PROFILE_ALLOCATIONS;
1298                         else if (!strcmp (arg, "stat"))
1299                                 flags |= MONO_PROFILE_STATISTICAL | MONO_PROFILE_APPDOMAIN_EVENTS;
1300                         else if (!strcmp (arg, "jit"))
1301                                 flags |= MONO_PROFILE_JIT_COMPILATION;
1302                         else {
1303                                 fprintf (stderr, "profiler : Unknown argument '%s'.\n", arg);
1304                                 return;
1305                         }
1306                 }
1307         }
1308
1309         prof = create_profiler ();
1310         ALLOC_PROFILER ();
1311         SET_PROFILER (prof);
1312
1313         /* statistical profiler data */
1314         prof_addresses = g_new0 (gpointer, MAX_PROF_SAMPLES);
1315         prof_table = g_hash_table_new (g_str_hash, g_str_equal);
1316
1317         mono_profiler_install (prof, simple_shutdown);
1318         mono_profiler_install_enter_leave (simple_method_enter, simple_method_leave);
1319         mono_profiler_install_jit_compile (simple_method_jit, simple_method_end_jit);
1320         mono_profiler_install_allocation (simple_allocation);
1321         mono_profiler_install_appdomain (NULL, NULL, simple_appdomain_unload, NULL);
1322         mono_profiler_install_statistical (simple_stat_hit);
1323         mono_profiler_set_events (flags);
1324 }
1325
1326 #endif /* DISABLE_PROFILER */
1327
1328 typedef void (*ProfilerInitializer) (const char*);
1329 #define INITIALIZER_NAME "mono_profiler_startup"
1330
1331 void 
1332 mono_profiler_load (const char *desc)
1333 {
1334 #ifndef DISABLE_PROFILER
1335         if (!desc || (strcmp ("default", desc) == 0) || (strncmp (desc, "default:", 8) == 0)) {
1336                 mono_profiler_install_simple (desc);
1337                 return;
1338         }
1339 #else
1340         if (!desc) {
1341                 desc = "default";
1342         }
1343 #endif
1344         {
1345                 GModule *pmodule;
1346                 const char* col = strchr (desc, ':');
1347                 char* libname;
1348                 char* path;
1349                 char *mname;
1350                 if (col != NULL) {
1351                         mname = g_memdup (desc, col - desc);
1352                         mname [col - desc] = 0;
1353                 } else {
1354                         mname = g_strdup (desc);
1355                 }
1356                 libname = g_strdup_printf ("mono-profiler-%s", mname);
1357                 path = g_module_build_path (NULL, libname);
1358                 pmodule = g_module_open (path, G_MODULE_BIND_LAZY);
1359                 if (pmodule) {
1360                         ProfilerInitializer func;
1361                         if (!g_module_symbol (pmodule, INITIALIZER_NAME, (gpointer *)&func)) {
1362                                 g_warning ("Cannot find initializer function %s in profiler module: %s", INITIALIZER_NAME, libname);
1363                         } else {
1364                                 func (desc);
1365                         }
1366                 } else {
1367                         g_warning ("Error loading profiler module '%s': %s", libname, g_module_error ());
1368                 }
1369
1370                 g_free (libname);
1371                 g_free (mname);
1372                 g_free (path);
1373         }
1374 }
1375