*/
#include <config.h>
+#include "../mini/jit.h"
#include <mono/metadata/profiler.h>
#include <mono/metadata/threads.h>
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/mono-perfcounters.h>
+#include <mono/metadata/appdomain.h>
#include <mono/utils/atomic.h>
#include <mono/utils/mono-membar.h>
#include <mono/utils/mono-counters.h>
#include <mono/utils/mono-mutex.h>
+#include <mono/utils/mono-conc-hashtable.h>
+#include <mono/utils/lock-free-queue.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#endif
#if defined(__linux__)
+
#include <unistd.h>
#include <sys/syscall.h>
#include "perf_event.h"
+
+#ifndef DISABLE_PERF_EVENTS
#define USE_PERF_EVENTS 1
+
static int read_perf_mmap (MonoProfiler* prof, int cpu);
#endif
+#endif
+
#define BUFFER_SIZE (4096 * 16)
static int nocalls = 0;
static int notraces = 0;
};
struct _MonoProfiler {
- LogBuffer *buffers;
StatBuffer *stat_buffers;
FILE* file;
#if defined (HAVE_SYS_ZLIB)
int pipes [2];
#ifndef HOST_WIN32
pthread_t helper_thread;
+ pthread_t writer_thread;
#endif
+ volatile gint32 run_writer_thread;
+ MonoLockFreeQueue writer_queue;
+ MonoConcurrentHashTable *method_table;
+ mono_mutex_t method_table_mutex;
BinaryObject *binary_objects;
};
+typedef struct _WriterQueueEntry WriterQueueEntry;
+struct _WriterQueueEntry {
+ MonoLockFreeQueueNode node;
+ GPtrArray *methods;
+ LogBuffer *buffer;
+};
+
+typedef struct _MethodInfo MethodInfo;
+struct _MethodInfo {
+ MonoMethod *method;
+ MonoJitInfo *ji;
+};
+
#ifdef HOST_WIN32
-#define TLS_SET(x,y) TlsSetValue(x, y)
-#define TLS_GET(x) ((LogBuffer *) TlsGetValue(x))
-#define TLS_INIT(x) x = TlsAlloc ()
+#define TLS_SET(x,y) (TlsSetValue (x, y))
+#define TLS_GET(t,x) ((t *) TlsGetValue (x))
+#define TLS_INIT(x) (x = TlsAlloc ())
static int tlsbuffer;
+static int tlsmethodlist;
#elif HAVE_KW_THREAD
-#define TLS_SET(x,y) x = y
-#define TLS_GET(x) x
+#define TLS_SET(x,y) (x = y)
+#define TLS_GET(t,x) (x)
#define TLS_INIT(x)
static __thread LogBuffer* tlsbuffer = NULL;
+static __thread GPtrArray* tlsmethodlist = NULL;
#else
-#define TLS_SET(x,y) pthread_setspecific(x, y)
-#define TLS_GET(x) ((LogBuffer *) pthread_getspecific(x))
-#define TLS_INIT(x) pthread_key_create(&x, NULL)
+#define TLS_SET(x,y) (pthread_setspecific (x, y))
+#define TLS_GET(t,x) ((t *) pthread_getspecific (x))
+#define TLS_INIT(x) (pthread_key_create (&x, NULL))
static pthread_key_t tlsbuffer;
+static pthread_key_t tlsmethodlist;
#endif
-static void safe_dump (MonoProfiler *profiler, LogBuffer *logbuffer);
+static void safe_send (MonoProfiler *profiler, LogBuffer *logbuffer);
static char*
pstrdup (const char *s)
static void
init_thread (void)
{
- LogBuffer *logbuffer;
- if (TLS_GET (tlsbuffer))
- return;
- logbuffer = create_buffer ();
- TLS_SET (tlsbuffer, logbuffer);
- logbuffer->thread_id = thread_id ();
+ if (!TLS_GET (LogBuffer, tlsbuffer)) {
+ LogBuffer *logbuffer = create_buffer ();
+ TLS_SET (tlsbuffer, logbuffer);
+ logbuffer->thread_id = thread_id ();
+ }
+ if (!TLS_GET (GPtrArray, tlsmethodlist)) {
+ GPtrArray *methodlist = g_ptr_array_new ();
+ TLS_SET (tlsmethodlist, methodlist);
+ }
+
//printf ("thread %p at time %llu\n", (void*)logbuffer->thread_id, logbuffer->time_base);
}
-static LogBuffer*
-ensure_logbuf (int bytes)
+static LogBuffer *
+ensure_logbuf_inner (LogBuffer *old, int bytes)
{
- LogBuffer *old = TLS_GET (tlsbuffer);
if (old && old->data + bytes + 100 < old->data_end)
return old;
- TLS_SET (tlsbuffer, NULL);
- init_thread ();
- TLS_GET (tlsbuffer)->next = old;
+
+ LogBuffer *new = create_buffer ();
+ new->thread_id = thread_id ();
+ new->next = old;
+
if (old)
- TLS_GET (tlsbuffer)->call_depth = old->call_depth;
- //printf ("new logbuffer\n");
- return TLS_GET (tlsbuffer);
+ new->call_depth = old->call_depth;
+
+ return new;
+}
+
+static LogBuffer*
+ensure_logbuf (int bytes)
+{
+ LogBuffer *old = TLS_GET (LogBuffer, tlsbuffer);
+ LogBuffer *new = ensure_logbuf_inner (old, bytes);
+
+ if (new == old)
+ return old; // Still enough space.
+
+ TLS_SET (tlsbuffer, new);
+ init_thread ();
+
+ return new;
}
static void
}
static void
-emit_method (LogBuffer *logbuffer, void *method)
+emit_method_inner (LogBuffer *logbuffer, void *method)
{
if (!logbuffer->method_base) {
logbuffer->method_base = (intptr_t)method;
assert (logbuffer->data <= logbuffer->data_end);
}
+static void
+register_method_local (MonoProfiler *prof, MonoDomain *domain, MonoMethod *method, MonoJitInfo *ji)
+{
+ if (!domain)
+ g_assert (ji);
+
+ if (!mono_conc_hashtable_lookup (prof->method_table, method)) {
+ if (!ji)
+ ji = mono_get_jit_info_from_method (domain, method);
+
+ // It could be AOT'd, so we need to force it to be loaded.
+ if (!ji) {
+ // Loads the method as a side effect.
+ mono_aot_get_method (domain, method);
+
+ ji = mono_get_jit_info_from_method (domain, method);
+ }
+
+ g_assert (ji);
+
+ MethodInfo *info = malloc (sizeof (MethodInfo));
+
+ info->method = method;
+ info->ji = ji;
+
+ g_ptr_array_add (TLS_GET (GPtrArray, tlsmethodlist), info);
+ }
+}
+
+static void
+emit_method (MonoProfiler *prof, LogBuffer *logbuffer, MonoDomain *domain, MonoMethod *method)
+{
+ register_method_local (prof, domain, method, NULL);
+ emit_method_inner (logbuffer, method);
+}
+
static void
emit_obj (LogBuffer *logbuffer, void *ptr)
{
#endif
}
+static void
+send_buffer (MonoProfiler *prof, GPtrArray *methods, LogBuffer *buffer)
+{
+ WriterQueueEntry *entry = calloc (1, sizeof (WriterQueueEntry));
+ mono_lock_free_queue_node_init (&entry->node, FALSE);
+ entry->methods = methods;
+ entry->buffer = buffer;
+ mono_lock_free_queue_enqueue (&prof->writer_queue, &entry->node);
+}
+
static void
dump_buffer (MonoProfiler *profiler, LogBuffer *buf)
{
counters_sample (profiler, 0);
#endif
/* ensure the main thread data and startup are available soon */
- safe_dump (profiler, ensure_logbuf (0));
+ safe_send (profiler, ensure_logbuf (0));
}
/*
* Can be called only at safe callback locations.
*/
static void
-safe_dump (MonoProfiler *profiler, LogBuffer *logbuffer)
+safe_send (MonoProfiler *profiler, LogBuffer *logbuffer)
{
int cd = logbuffer->call_depth;
- take_lock ();
- dump_buffer (profiler, TLS_GET (tlsbuffer));
- release_lock ();
+
+ send_buffer (profiler, TLS_GET (GPtrArray, tlsmethodlist), TLS_GET (LogBuffer, tlsbuffer));
+
TLS_SET (tlsbuffer, NULL);
+ TLS_SET (tlsmethodlist, NULL);
+
init_thread ();
- TLS_GET (tlsbuffer)->call_depth = cd;
+
+ TLS_GET (LogBuffer, tlsbuffer)->call_depth = cd;
}
static int
heap_walk (profiler);
EXIT_LOG (logbuffer);
if (ev == MONO_GC_EVENT_POST_START_WORLD)
- safe_dump (profiler, logbuffer);
+ safe_send (profiler, logbuffer);
//printf ("gc event %d for generation %d\n", ev, generation);
}
emit_bt (logbuffer, &data);
EXIT_LOG (logbuffer);
if (logbuffer->next)
- safe_dump (prof, logbuffer);
+ safe_send (prof, logbuffer);
process_requests (prof);
//printf ("gc alloc %s at %p\n", mono_class_get_name (klass), obj);
}
//printf ("loaded image %p (%s)\n", image, name);
EXIT_LOG (logbuffer);
if (logbuffer->next)
- safe_dump (prof, logbuffer);
+ safe_send (prof, logbuffer);
process_requests (prof);
}
free (name);
EXIT_LOG (logbuffer);
if (logbuffer->next)
- safe_dump (prof, logbuffer);
+ safe_send (prof, logbuffer);
process_requests (prof);
}
ENTER_LOG (logbuffer, "enter");
emit_byte (logbuffer, TYPE_ENTER | TYPE_METHOD);
emit_time (logbuffer, now);
- emit_method (logbuffer, method);
+ emit_method (prof, logbuffer, mono_domain_get (), method);
EXIT_LOG (logbuffer);
process_requests (prof);
}
ENTER_LOG (logbuffer, "leave");
emit_byte (logbuffer, TYPE_LEAVE | TYPE_METHOD);
emit_time (logbuffer, now);
- emit_method (logbuffer, method);
+ emit_method (prof, logbuffer, mono_domain_get (), method);
EXIT_LOG (logbuffer);
if (logbuffer->next)
- safe_dump (prof, logbuffer);
+ safe_send (prof, logbuffer);
process_requests (prof);
}
ENTER_LOG (logbuffer, "eleave");
emit_byte (logbuffer, TYPE_EXC_LEAVE | TYPE_METHOD);
emit_time (logbuffer, now);
- emit_method (logbuffer, method);
+ emit_method (prof, logbuffer, mono_domain_get (), method);
EXIT_LOG (logbuffer);
process_requests (prof);
}
static void
-method_jitted (MonoProfiler *prof, MonoMethod *method, MonoJitInfo* jinfo, int result)
+method_jitted (MonoProfiler *prof, MonoMethod *method, MonoJitInfo *ji, int result)
{
- uint64_t now;
- char *name;
- int nlen;
- LogBuffer *logbuffer;
if (result != MONO_PROFILE_OK)
return;
- name = mono_method_full_name (method, 1);
- nlen = strlen (name) + 1;
- logbuffer = ensure_logbuf (32 + nlen);
- now = current_time ();
- ENTER_LOG (logbuffer, "jit");
- emit_byte (logbuffer, TYPE_JIT | TYPE_METHOD);
- emit_time (logbuffer, now);
- emit_method (logbuffer, method);
- emit_ptr (logbuffer, mono_jit_info_get_code_start (jinfo));
- emit_value (logbuffer, mono_jit_info_get_code_size (jinfo));
- memcpy (logbuffer->data, name, nlen);
- logbuffer->data += nlen;
- mono_free (name);
- EXIT_LOG (logbuffer);
- if (logbuffer->next)
- safe_dump (prof, logbuffer);
- process_requests (prof);
+
+ register_method_local (prof, NULL, method, ji);
}
static void
emit_time (logbuffer, now);
emit_value (logbuffer, clause_type);
emit_value (logbuffer, clause_num);
- emit_method (logbuffer, method);
+ emit_method (prof, logbuffer, mono_domain_get (), method);
EXIT_LOG (logbuffer);
}
static void
thread_end (MonoProfiler *prof, uintptr_t tid)
{
- take_lock ();
- if (TLS_GET (tlsbuffer))
- dump_buffer (prof, TLS_GET (tlsbuffer));
- release_lock ();
+ if (TLS_GET (LogBuffer, tlsbuffer))
+ send_buffer (prof, TLS_GET (GPtrArray, tlsmethodlist), TLS_GET (LogBuffer, tlsbuffer));
+
TLS_SET (tlsbuffer, NULL);
+ TLS_SET (tlsmethodlist, NULL);
}
static void
}
static void
-dump_sample_hits_inner (MonoProfiler *prof, StatBuffer *sbuf, int recurse, GPtrArray **array);
-
-static void
-dump_sample_hits (MonoProfiler *prof, StatBuffer *sbuf, int recurse)
-{
- GPtrArray *array = NULL;
- dump_sample_hits_inner (prof, sbuf, recurse, &array);
-
- if (array) {
- int i;
- g_ptr_array_sort (array, g_direct_equal);
- for (i = 0; i < array->len; ++i) {
- MonoJitInfo *cur = array->pdata [i];
- //Ignore duplicates
- if (i > 0 && array->pdata [i - 1] == cur)
- continue;
- method_jitted (prof, mono_jit_info_get_method (cur), cur, MONO_PROFILE_OK);
- }
- }
-}
-
-static void
-dump_sample_hits_inner (MonoProfiler *prof, StatBuffer *sbuf, int recurse, GPtrArray **array)
+dump_sample_hits (MonoProfiler *prof, StatBuffer *sbuf)
{
uintptr_t *sample;
LogBuffer *logbuffer;
if (!sbuf)
return;
- if (recurse && sbuf->next) {
- dump_sample_hits_inner (prof, sbuf->next, 1, array);
+ if (sbuf->next) {
+ dump_sample_hits (prof, sbuf->next);
free_buffer (sbuf->next, sbuf->next->size);
sbuf->next = NULL;
}
if (!method) {
MonoJitInfo *ji = mono_jit_info_table_find (domain, address);
- if (ji) {
+
+ if (ji)
managed_sample_base [i * 4 + 0] = (uintptr_t)mono_jit_info_get_method (ji);
- if (!*array)
- *array = g_ptr_array_new ();
- g_ptr_array_add (*array, ji);
- }
}
}
logbuffer = ensure_logbuf (20 + count * 8);
/* new in data version 6 */
emit_uvalue (logbuffer, mbt_count);
for (i = 0; i < mbt_count; ++i) {
- emit_method (logbuffer, (void*)sample [i * 4]); /* method */
+ MonoMethod *method = (MonoMethod *) sample [i * 4 + 0];
+ MonoDomain *domain = (MonoDomain *) sample [i * 4 + 1];
+ uintptr_t native_offset = sample [i * 4 + 3];
+
+ emit_method (prof, logbuffer, domain, method);
emit_svalue (logbuffer, 0); /* il offset will always be 0 from now on */
- emit_svalue (logbuffer, sample [i * 4 + 3]); /* native offset */
+ emit_svalue (logbuffer, native_offset);
}
sample += 4 * mbt_count;
}
}
#if USE_PERF_EVENTS
-#ifndef __NR_perf_event_open
-#ifdef __arm__
-#define __NR_perf_event_open 364
-#else
-#define __NR_perf_event_open 241
-#endif
-#endif
static int
mono_cpu_count (void)
return syscall(/*__NR_perf_event_open*/ 298, attr, pid, cpu, group_fd, flags);
#elif defined(__i386__)
return syscall(/*__NR_perf_event_open*/ 336, attr, pid, cpu, group_fd, flags);
-#elif defined(__arm__)
+#elif defined(__arm__) || defined (__aarch64__)
return syscall(/*__NR_perf_event_open*/ 364, attr, pid, cpu, group_fd, flags);
#else
return -1;
}
EXIT_LOG (logbuffer);
- safe_dump (profiler, ensure_logbuf (0));
+ safe_send (profiler, ensure_logbuf (0));
mono_mutex_unlock (&counters_mutex);
}
emit_value (logbuffer, 0);
EXIT_LOG (logbuffer);
- safe_dump (profiler, ensure_logbuf (0));
+ safe_send (profiler, ensure_logbuf (0));
mono_mutex_unlock (&counters_mutex);
}
}
EXIT_LOG (logbuffer);
- safe_dump (profiler, ensure_logbuf (0));
+ safe_send (profiler, ensure_logbuf (0));
}
static gboolean
emit_value (logbuffer, 0);
EXIT_LOG (logbuffer);
- safe_dump (profiler, ensure_logbuf (0));
+ safe_send (profiler, ensure_logbuf (0));
mono_mutex_unlock (&counters_mutex);
}
static void
log_shutdown (MonoProfiler *prof)
{
+ void *res;
+
in_shutdown = 1;
#ifndef DISABLE_HELPER_THREAD
counters_and_perfcounters_sample (prof);
if (prof->command_port) {
char c = 1;
- void *res;
ign_res (write (prof->pipes [1], &c, 1));
pthread_join (prof->helper_thread, &res);
}
read_perf_mmap (prof, i);
}
#endif
- dump_sample_hits (prof, prof->stat_buffers, 1);
- take_lock ();
- if (TLS_GET (tlsbuffer))
- dump_buffer (prof, TLS_GET (tlsbuffer));
+ dump_sample_hits (prof, prof->stat_buffers);
+
+ if (TLS_GET (LogBuffer, tlsbuffer))
+ send_buffer (prof, TLS_GET (GPtrArray, tlsmethodlist), TLS_GET (LogBuffer, tlsbuffer));
+
TLS_SET (tlsbuffer, NULL);
- release_lock ();
+ TLS_SET (tlsmethodlist, NULL);
+
+ InterlockedWrite (&prof->run_writer_thread, 0);
+ pthread_join (prof->writer_thread, &res);
+
#if defined (HAVE_SYS_ZLIB)
if (prof->gzfile)
gzclose (prof->gzfile);
pclose (prof->file);
else
fclose (prof->file);
+
+ mono_conc_hashtable_destroy (prof->method_table);
+ mono_mutex_destroy (&prof->method_table_mutex);
+
free (prof);
}
return res;
}
-#ifndef DISABLE_HELPER_THREAD
-
//this is exposed by the JIT, but it's not meant to be a supported API for now.
extern void mono_threads_attach_tools_thread (void);
+#ifndef DISABLE_HELPER_THREAD
+
static void*
helper_thread (void* arg)
{
if (do_debug)
fprintf (stderr, "stat buffer dump\n");
if (sbuf) {
- dump_sample_hits (prof, sbuf, 1);
+ dump_sample_hits (prof, sbuf);
free_buffer (sbuf, sbuf->size);
- safe_dump (prof, ensure_logbuf (0));
+ safe_send (prof, ensure_logbuf (0));
}
continue;
}
}
}
#endif
- safe_dump (prof, ensure_logbuf (0));
+ safe_send (prof, ensure_logbuf (0));
return NULL;
}
#if USE_PERF_EVENTS
continue;
if (FD_ISSET (perf_data [i].perf_fd, &rfds)) {
read_perf_mmap (prof, i);
- safe_dump (prof, ensure_logbuf (0));
+ safe_send (prof, ensure_logbuf (0));
}
}
}
}
#endif
+static void *
+writer_thread (void *arg)
+{
+ MonoProfiler *prof = arg;
+
+ mono_threads_attach_tools_thread ();
+
+ dump_header (prof);
+
+ while (InterlockedRead (&prof->run_writer_thread)) {
+ WriterQueueEntry *entry;
+
+ while ((entry = (WriterQueueEntry *) mono_lock_free_queue_dequeue (&prof->writer_queue))) {
+ LogBuffer *method_buffer = NULL;
+ gboolean new_methods = FALSE;
+
+ if (entry->methods->len)
+ method_buffer = create_buffer ();
+
+ /*
+ * Encode the method events in a temporary log buffer that we
+ * flush to disk before the main buffer, ensuring that all
+ * methods have metadata emitted before they're referenced.
+ */
+ for (guint i = 0; i < entry->methods->len; i++) {
+ MethodInfo *info = g_ptr_array_index (entry->methods, i);
+
+ if (mono_conc_hashtable_lookup (prof->method_table, info->method))
+ continue;
+
+ new_methods = TRUE;
+
+ /*
+ * Other threads use this hash table to get a general
+ * idea of whether a method has already been emitted to
+ * the stream. Due to the way we add to this table, it
+ * can easily happen that multiple threads queue up the
+ * same methods, but that's OK since eventually all
+ * methods will be in this table and the thread-local
+ * method lists will just be empty for the rest of the
+ * app's lifetime.
+ */
+ mono_conc_hashtable_insert (prof->method_table, info->method, info->method);
+
+ char *name = mono_method_full_name (info->method, 1);
+ int nlen = strlen (name) + 1;
+ uint64_t now = current_time ();
+
+ method_buffer = ensure_logbuf_inner (method_buffer, 32 + nlen);
+
+ emit_byte (method_buffer, TYPE_JIT | TYPE_METHOD);
+ emit_time (method_buffer, now);
+ emit_method_inner (method_buffer, info->method);
+ emit_ptr (method_buffer, mono_jit_info_get_code_start (info->ji));
+ emit_value (method_buffer, mono_jit_info_get_code_size (info->ji));
+
+ memcpy (method_buffer->data, name, nlen);
+ method_buffer->data += nlen;
+
+ mono_free (name);
+ free (info);
+ }
+
+ g_ptr_array_free (entry->methods, TRUE);
+
+ if (new_methods)
+ dump_buffer (prof, method_buffer);
+ else if (method_buffer)
+ free_buffer (method_buffer, method_buffer->size);
+
+ dump_buffer (prof, entry->buffer);
+
+ free (entry);
+ }
+ }
+
+ return NULL;
+}
+
+static int
+start_writer_thread (MonoProfiler* prof)
+{
+ InterlockedWrite (&prof->run_writer_thread, 1);
+
+ return !pthread_create (&prof->writer_thread, NULL, writer_thread, prof);
+}
+
static MonoProfiler*
create_profiler (const char *filename)
{
if (hs_mode_ondemand)
fprintf (stderr, "Ondemand heapshot unavailable on this arch.\n");
#endif
+
+ mono_lock_free_queue_init (&prof->writer_queue);
+ mono_mutex_init (&prof->method_table_mutex);
+ prof->method_table = mono_conc_hashtable_new (&prof->method_table_mutex, NULL, NULL);
+
+ start_writer_thread (prof);
+
prof->startup_time = current_time ();
- dump_header (prof);
return prof;
}
mono_profiler_set_events (events);
TLS_INIT (tlsbuffer);
+ TLS_INIT (tlsmethodlist);
}