From 0fcf02a7c6bdda3cae1d0e57d614d9a42a6d4e33 Mon Sep 17 00:00:00 2001 From: Ell Date: Mon, 3 Sep 2018 18:13:16 -0400 Subject: [PATCH] app, tools: add "running" thread attribute to GimpBacktrace/performance-log The "running" attribute (readable through gimp_backtrace_is_thread_running(), and recorded in the performance log) specifies if the thread was in a running or suspended state at the time the backtrace was taken. It is accurate on Linux, but only approximated on Windows. Adapt the performance-log-expand.py tool to maintain this attribute (and any future thread attributes we might add). (cherry picked from commit 78adb7c9008011161009153a56354198c0842661) --- app/core/gimpbacktrace-linux.c | 45 +++++++++++++++++++- app/core/gimpbacktrace-none.c | 7 ++++ app/core/gimpbacktrace-windows.c | 71 ++++++++++++++++++++++++++++++-- app/core/gimpbacktrace.h | 2 + app/widgets/gimpdashboard.c | 53 ++++++++++++++++-------- tools/performance-log-expand.py | 43 +++++++++---------- 6 files changed, 179 insertions(+), 42 deletions(-) diff --git a/app/core/gimpbacktrace-linux.c b/app/core/gimpbacktrace-linux.c index a76b5be2ce..43859df1ab 100644 --- a/app/core/gimpbacktrace-linux.c +++ b/app/core/gimpbacktrace-linux.c @@ -42,6 +42,7 @@ #include #include #include +#include #ifdef HAVE_LIBUNWIND #define UNW_LOCAL_ONLY @@ -68,6 +69,7 @@ struct _GimpBacktraceThread { pid_t tid; gchar name[MAX_THREAD_NAME_SIZE]; + gchar state; guintptr frames[MAX_N_FRAMES]; gint n_frames; @@ -93,6 +95,7 @@ static gint gimp_backtrace_enumerate_threads (gboolean include_cu static void gimp_backtrace_read_thread_name (pid_t tid, gchar *name, gint size); +static gchar gimp_backtrace_read_thread_state (pid_t tid); static void gimp_backtrace_signal_handler (gint signum); @@ -210,6 +213,34 @@ gimp_backtrace_read_thread_name (pid_t tid, } } +static gchar +gimp_backtrace_read_thread_state (pid_t tid) +{ + gchar buffer[64]; + gint fd; + gchar state = '\0'; + + g_snprintf (buffer, sizeof (buffer), + "/proc/self/task/%llu/stat", + (unsigned long long) tid); + + fd = open (buffer, O_RDONLY); + + if (fd >= 0) + { + gint n = read (fd, buffer, sizeof (buffer)); + + if (n > 0) + buffer[n - 1] = '\0'; + + sscanf (buffer, "%*d %*s %c", &state); + + close (fd); + } + + return state; +} + static void gimp_backtrace_signal_handler (gint signum) { @@ -367,6 +398,8 @@ gimp_backtrace_new (gboolean include_current_thread) gimp_backtrace_read_thread_name (thread->tid, thread->name, MAX_THREAD_NAME_SIZE); + thread->state = gimp_backtrace_read_thread_state (thread->tid); + syscall (SYS_tgkill, pid, threads[i], BACKTRACE_SIGNAL); } @@ -460,7 +493,7 @@ const gchar * gimp_backtrace_get_thread_name (GimpBacktrace *backtrace, gint thread) { - g_return_val_if_fail (backtrace != NULL, 0); + g_return_val_if_fail (backtrace != NULL, NULL); g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, NULL); if (backtrace->threads[thread].name[0]) @@ -469,6 +502,16 @@ gimp_backtrace_get_thread_name (GimpBacktrace *backtrace, return NULL; } +gboolean +gimp_backtrace_is_thread_running (GimpBacktrace *backtrace, + gint thread) +{ + g_return_val_if_fail (backtrace != NULL, FALSE); + g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, FALSE); + + return backtrace->threads[thread].state == 'R'; +} + gint gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace, guintptr thread_id, diff --git a/app/core/gimpbacktrace-none.c b/app/core/gimpbacktrace-none.c index 489a63c7fe..087e8cb518 100644 --- a/app/core/gimpbacktrace-none.c +++ b/app/core/gimpbacktrace-none.c @@ -85,6 +85,13 @@ gimp_backtrace_get_thread_name (GimpBacktrace *backtrace, g_return_val_if_reached (NULL); } +gboolean +gimp_backtrace_is_thread_running (GimpBacktrace *backtrace, + gint thread) +{ + g_return_val_if_reached (FALSE); +} + gint gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace, guintptr thread_id, diff --git a/app/core/gimpbacktrace-windows.c b/app/core/gimpbacktrace-windows.c index 2f60f51524..8fe705e818 100644 --- a/app/core/gimpbacktrace-windows.c +++ b/app/core/gimpbacktrace-windows.c @@ -55,13 +55,20 @@ typedef struct _GimpBacktraceThread GimpBacktraceThread; struct _Thread { DWORD tid; - gchar *name; + + union + { + gchar *name; + guint64 time; + }; }; struct _GimpBacktraceThread { DWORD tid; const gchar *name; + guint64 time; + guint64 last_time; guintptr frames[MAX_N_FRAMES]; gint n_frames; @@ -96,6 +103,8 @@ gint64 last_thread_enumeration_time; Thread thread_names[MAX_N_THREADS]; gint n_thread_names; gint thread_names_spinlock; +Thread thread_times[MAX_N_THREADS]; +gint n_thread_times; DWORD WINAPI (* gimp_backtrace_SymSetOptions) (DWORD SymOptions); BOOL WINAPI (* gimp_backtrace_SymInitialize) (HANDLE hProcess, @@ -308,6 +317,7 @@ gimp_backtrace_start (void) { n_threads = 0; last_thread_enumeration_time = 0; + n_thread_times = 0; initialized = TRUE; } @@ -374,6 +384,10 @@ gimp_backtrace_new (gboolean include_current_thread) CONTEXT context = {}; STACKFRAME64 frame = {}; DWORD machine_type; + FILETIME creation_time; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; if (! include_current_thread && threads[i].tid == tid) continue; @@ -426,8 +440,11 @@ gimp_backtrace_new (gboolean include_current_thread) #error unsupported architecture #endif - thread->tid = threads[i].tid; - thread->name = threads[i].name; + thread->tid = threads[i].tid; + thread->name = threads[i].name; + thread->last_time = 0; + thread->time = 0; + thread->n_frames = 0; while (thread->n_frames < MAX_N_FRAMES && @@ -443,6 +460,35 @@ gimp_backtrace_new (gboolean include_current_thread) break; } + if (GetThreadTimes (hThread, + &creation_time, &exit_time, + &kernel_time, &user_time)) + { + thread->time = (((guint64) kernel_time.dwHighDateTime << 32) | + ((guint64) kernel_time.dwLowDateTime)) + + (((guint64) user_time.dwHighDateTime << 32) | + ((guint64) user_time.dwLowDateTime)); + + if (i < n_thread_times && thread->tid == thread_times[i].tid) + { + thread->last_time = thread_times[i].time; + } + else + { + gint j; + + for (j = 0; j < n_thread_times; j++) + { + if (thread->tid == thread_times[j].tid) + { + thread->last_time = thread_times[j].time; + + break; + } + } + } + } + if (threads[i].tid != tid) ResumeThread (hThread); @@ -452,6 +498,14 @@ gimp_backtrace_new (gboolean include_current_thread) backtrace->n_threads++; } + n_thread_times = backtrace->n_threads; + + for (i = 0; i < backtrace->n_threads; i++) + { + thread_times[i].tid = backtrace->threads[i].tid; + thread_times[i].time = backtrace->threads[i].time; + } + g_mutex_unlock (&mutex); if (backtrace->n_threads == 0) @@ -503,6 +557,17 @@ gimp_backtrace_get_thread_name (GimpBacktrace *backtrace, return backtrace->threads[thread].name; } +gboolean +gimp_backtrace_is_thread_running (GimpBacktrace *backtrace, + gint thread) +{ + g_return_val_if_fail (backtrace != NULL, FALSE); + g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, FALSE); + + return backtrace->threads[thread].time > + backtrace->threads[thread].last_time; +} + gint gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace, guintptr thread_id, diff --git a/app/core/gimpbacktrace.h b/app/core/gimpbacktrace.h index a4ea2c2a30..8c172b2d13 100644 --- a/app/core/gimpbacktrace.h +++ b/app/core/gimpbacktrace.h @@ -50,6 +50,8 @@ guintptr gimp_backtrace_get_thread_id (GimpBacktrace *backt gint thread); const gchar * gimp_backtrace_get_thread_name (GimpBacktrace *backtrace, gint thread); +gboolean gimp_backtrace_is_thread_running (GimpBacktrace *backtrace, + gint thread); gint gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace, guintptr thread_id, diff --git a/app/widgets/gimpdashboard.c b/app/widgets/gimpdashboard.c index a53d91ebfe..9f25e83934 100644 --- a/app/widgets/gimpdashboard.c +++ b/app/widgets/gimpdashboard.c @@ -3620,9 +3620,11 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard, { guintptr thread_id; const gchar *thread_name; + gint last_running = -1; + gint running; gint n_frames; - gint n_head = 0; - gint n_tail = 0; + gint n_head = 0; + gint n_tail = 0; gint frame; thread_id = gimp_backtrace_get_thread_id (backtrace, thread); @@ -3640,6 +3642,9 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard, gint n; gint i; + last_running = gimp_backtrace_is_thread_running ( + priv->log_backtrace, other_thread); + n = gimp_backtrace_get_n_frames (priv->log_backtrace, other_thread); n = MIN (n, n_frames); @@ -3673,7 +3678,9 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard, } } - if (n_head + n_tail == n_frames) + running = gimp_backtrace_is_thread_running (backtrace, thread); + + if (running == last_running && n_head + n_tail == n_frames) continue; BACKTRACE_NONEMPTY (); @@ -3691,6 +3698,10 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard, "\""); } + gimp_dashboard_log_printf (dashboard, + " running=\"%d\"", + running); + if (n_head > 0) { gimp_dashboard_log_printf (dashboard, @@ -3705,25 +3716,33 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard, n_tail); } - gimp_dashboard_log_printf (dashboard, - ">\n"); - - for (frame = n_head; frame < n_frames - n_tail; frame++) + if (n_head + n_tail < n_frames) { - unsigned long long address; + gimp_dashboard_log_printf (dashboard, + ">\n"); - address = gimp_backtrace_get_frame_address (backtrace, - thread, frame); + for (frame = n_head; frame < n_frames - n_tail; frame++) + { + unsigned long long address; + + address = gimp_backtrace_get_frame_address (backtrace, + thread, frame); + + gimp_dashboard_log_printf (dashboard, + "\n", + address); + + g_hash_table_add (priv->log_addresses, (gpointer) address); + } gimp_dashboard_log_printf (dashboard, - "\n", - address); - - g_hash_table_add (priv->log_addresses, (gpointer) address); + "\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + " />\n"); } - - gimp_dashboard_log_printf (dashboard, - "\n"); } if (! backtrace_empty) diff --git a/tools/performance-log-expand.py b/tools/performance-log-expand.py index c0f7cc9471..36fc1b6c8e 100755 --- a/tools/performance-log-expand.py +++ b/tools/performance-log-expand.py @@ -63,37 +63,38 @@ for sample in (log.find ("samples") or empty_element).iterfind ("sample"): for thread in backtrace: id = thread.get ("id") + head = thread.get ("head") + tail = thread.get ("tail") + attrib = dict (thread.attrib) frames = list (thread) + last_thread = last_backtrace.setdefault (id, [{}, []]) + last_frames = last_thread[1] + + if head: + frames = last_frames[:int (head)] + frames + + del attrib["head"] + + if tail: + frames = frames + last_frames[-int (tail):] + + del attrib["tail"] + + last_thread[0] = attrib + last_thread[1] = frames + if not frames: - last_backtrace.pop (id, None) - else: - last_thread = last_backtrace.setdefault (id, [None, []]) - last_frames = last_thread[1] - - name = thread.get ("name") - head = thread.get ("head") - tail = thread.get ("tail") - - if head: - frames = last_frames[:int (head)] + frames - if tail: - frames = frames + last_frames[-int (tail):] - - last_thread[0] = name - last_thread[1] = frames + del last_backtrace[id] for thread in list (backtrace): backtrace.remove (thread) - for id, (name, frames) in last_backtrace.items (): - thread = ElementTree.SubElement (backtrace, "thread", id=id) + for id, (attrib, frames) in last_backtrace.items (): + thread = ElementTree.SubElement (backtrace, "thread", attrib) thread.text = "\n" thread.tail = "\n" - if name: - thread.set ("name", name) - thread.extend (frames) # Expand address map