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 78adb7c900
)
This commit is contained in:
@ -42,6 +42,7 @@
|
|||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#ifdef HAVE_LIBUNWIND
|
#ifdef HAVE_LIBUNWIND
|
||||||
#define UNW_LOCAL_ONLY
|
#define UNW_LOCAL_ONLY
|
||||||
@ -68,6 +69,7 @@ struct _GimpBacktraceThread
|
|||||||
{
|
{
|
||||||
pid_t tid;
|
pid_t tid;
|
||||||
gchar name[MAX_THREAD_NAME_SIZE];
|
gchar name[MAX_THREAD_NAME_SIZE];
|
||||||
|
gchar state;
|
||||||
|
|
||||||
guintptr frames[MAX_N_FRAMES];
|
guintptr frames[MAX_N_FRAMES];
|
||||||
gint 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,
|
static void gimp_backtrace_read_thread_name (pid_t tid,
|
||||||
gchar *name,
|
gchar *name,
|
||||||
gint size);
|
gint size);
|
||||||
|
static gchar gimp_backtrace_read_thread_state (pid_t tid);
|
||||||
|
|
||||||
static void gimp_backtrace_signal_handler (gint signum);
|
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
|
static void
|
||||||
gimp_backtrace_signal_handler (gint signum)
|
gimp_backtrace_signal_handler (gint signum)
|
||||||
{
|
{
|
||||||
@ -367,6 +398,8 @@ gimp_backtrace_new (gboolean include_current_thread)
|
|||||||
gimp_backtrace_read_thread_name (thread->tid,
|
gimp_backtrace_read_thread_name (thread->tid,
|
||||||
thread->name, MAX_THREAD_NAME_SIZE);
|
thread->name, MAX_THREAD_NAME_SIZE);
|
||||||
|
|
||||||
|
thread->state = gimp_backtrace_read_thread_state (thread->tid);
|
||||||
|
|
||||||
syscall (SYS_tgkill, pid, threads[i], BACKTRACE_SIGNAL);
|
syscall (SYS_tgkill, pid, threads[i], BACKTRACE_SIGNAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,7 +493,7 @@ const gchar *
|
|||||||
gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
|
gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
|
||||||
gint thread)
|
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);
|
g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, NULL);
|
||||||
|
|
||||||
if (backtrace->threads[thread].name[0])
|
if (backtrace->threads[thread].name[0])
|
||||||
@ -469,6 +502,16 @@ gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
|
|||||||
return NULL;
|
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
|
gint
|
||||||
gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
|
gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
|
||||||
guintptr thread_id,
|
guintptr thread_id,
|
||||||
|
@ -85,6 +85,13 @@ gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
|
|||||||
g_return_val_if_reached (NULL);
|
g_return_val_if_reached (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
gimp_backtrace_is_thread_running (GimpBacktrace *backtrace,
|
||||||
|
gint thread)
|
||||||
|
{
|
||||||
|
g_return_val_if_reached (FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
gint
|
gint
|
||||||
gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
|
gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
|
||||||
guintptr thread_id,
|
guintptr thread_id,
|
||||||
|
@ -55,13 +55,20 @@ typedef struct _GimpBacktraceThread GimpBacktraceThread;
|
|||||||
struct _Thread
|
struct _Thread
|
||||||
{
|
{
|
||||||
DWORD tid;
|
DWORD tid;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
gchar *name;
|
gchar *name;
|
||||||
|
guint64 time;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct _GimpBacktraceThread
|
struct _GimpBacktraceThread
|
||||||
{
|
{
|
||||||
DWORD tid;
|
DWORD tid;
|
||||||
const gchar *name;
|
const gchar *name;
|
||||||
|
guint64 time;
|
||||||
|
guint64 last_time;
|
||||||
|
|
||||||
guintptr frames[MAX_N_FRAMES];
|
guintptr frames[MAX_N_FRAMES];
|
||||||
gint n_frames;
|
gint n_frames;
|
||||||
@ -96,6 +103,8 @@ gint64 last_thread_enumeration_time;
|
|||||||
Thread thread_names[MAX_N_THREADS];
|
Thread thread_names[MAX_N_THREADS];
|
||||||
gint n_thread_names;
|
gint n_thread_names;
|
||||||
gint thread_names_spinlock;
|
gint thread_names_spinlock;
|
||||||
|
Thread thread_times[MAX_N_THREADS];
|
||||||
|
gint n_thread_times;
|
||||||
|
|
||||||
DWORD WINAPI (* gimp_backtrace_SymSetOptions) (DWORD SymOptions);
|
DWORD WINAPI (* gimp_backtrace_SymSetOptions) (DWORD SymOptions);
|
||||||
BOOL WINAPI (* gimp_backtrace_SymInitialize) (HANDLE hProcess,
|
BOOL WINAPI (* gimp_backtrace_SymInitialize) (HANDLE hProcess,
|
||||||
@ -308,6 +317,7 @@ gimp_backtrace_start (void)
|
|||||||
{
|
{
|
||||||
n_threads = 0;
|
n_threads = 0;
|
||||||
last_thread_enumeration_time = 0;
|
last_thread_enumeration_time = 0;
|
||||||
|
n_thread_times = 0;
|
||||||
|
|
||||||
initialized = TRUE;
|
initialized = TRUE;
|
||||||
}
|
}
|
||||||
@ -374,6 +384,10 @@ gimp_backtrace_new (gboolean include_current_thread)
|
|||||||
CONTEXT context = {};
|
CONTEXT context = {};
|
||||||
STACKFRAME64 frame = {};
|
STACKFRAME64 frame = {};
|
||||||
DWORD machine_type;
|
DWORD machine_type;
|
||||||
|
FILETIME creation_time;
|
||||||
|
FILETIME exit_time;
|
||||||
|
FILETIME kernel_time;
|
||||||
|
FILETIME user_time;
|
||||||
|
|
||||||
if (! include_current_thread && threads[i].tid == tid)
|
if (! include_current_thread && threads[i].tid == tid)
|
||||||
continue;
|
continue;
|
||||||
@ -428,6 +442,9 @@ gimp_backtrace_new (gboolean include_current_thread)
|
|||||||
|
|
||||||
thread->tid = threads[i].tid;
|
thread->tid = threads[i].tid;
|
||||||
thread->name = threads[i].name;
|
thread->name = threads[i].name;
|
||||||
|
thread->last_time = 0;
|
||||||
|
thread->time = 0;
|
||||||
|
|
||||||
thread->n_frames = 0;
|
thread->n_frames = 0;
|
||||||
|
|
||||||
while (thread->n_frames < MAX_N_FRAMES &&
|
while (thread->n_frames < MAX_N_FRAMES &&
|
||||||
@ -443,6 +460,35 @@ gimp_backtrace_new (gboolean include_current_thread)
|
|||||||
break;
|
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)
|
if (threads[i].tid != tid)
|
||||||
ResumeThread (hThread);
|
ResumeThread (hThread);
|
||||||
|
|
||||||
@ -452,6 +498,14 @@ gimp_backtrace_new (gboolean include_current_thread)
|
|||||||
backtrace->n_threads++;
|
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);
|
g_mutex_unlock (&mutex);
|
||||||
|
|
||||||
if (backtrace->n_threads == 0)
|
if (backtrace->n_threads == 0)
|
||||||
@ -503,6 +557,17 @@ gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
|
|||||||
return backtrace->threads[thread].name;
|
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
|
gint
|
||||||
gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
|
gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
|
||||||
guintptr thread_id,
|
guintptr thread_id,
|
||||||
|
@ -50,6 +50,8 @@ guintptr gimp_backtrace_get_thread_id (GimpBacktrace *backt
|
|||||||
gint thread);
|
gint thread);
|
||||||
const gchar * gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
|
const gchar * gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
|
||||||
gint thread);
|
gint thread);
|
||||||
|
gboolean gimp_backtrace_is_thread_running (GimpBacktrace *backtrace,
|
||||||
|
gint thread);
|
||||||
|
|
||||||
gint gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
|
gint gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
|
||||||
guintptr thread_id,
|
guintptr thread_id,
|
||||||
|
@ -3620,6 +3620,8 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard,
|
|||||||
{
|
{
|
||||||
guintptr thread_id;
|
guintptr thread_id;
|
||||||
const gchar *thread_name;
|
const gchar *thread_name;
|
||||||
|
gint last_running = -1;
|
||||||
|
gint running;
|
||||||
gint n_frames;
|
gint n_frames;
|
||||||
gint n_head = 0;
|
gint n_head = 0;
|
||||||
gint n_tail = 0;
|
gint n_tail = 0;
|
||||||
@ -3640,6 +3642,9 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard,
|
|||||||
gint n;
|
gint n;
|
||||||
gint i;
|
gint i;
|
||||||
|
|
||||||
|
last_running = gimp_backtrace_is_thread_running (
|
||||||
|
priv->log_backtrace, other_thread);
|
||||||
|
|
||||||
n = gimp_backtrace_get_n_frames (priv->log_backtrace,
|
n = gimp_backtrace_get_n_frames (priv->log_backtrace,
|
||||||
other_thread);
|
other_thread);
|
||||||
n = MIN (n, n_frames);
|
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;
|
continue;
|
||||||
|
|
||||||
BACKTRACE_NONEMPTY ();
|
BACKTRACE_NONEMPTY ();
|
||||||
@ -3691,6 +3698,10 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard,
|
|||||||
"\"");
|
"\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gimp_dashboard_log_printf (dashboard,
|
||||||
|
" running=\"%d\"",
|
||||||
|
running);
|
||||||
|
|
||||||
if (n_head > 0)
|
if (n_head > 0)
|
||||||
{
|
{
|
||||||
gimp_dashboard_log_printf (dashboard,
|
gimp_dashboard_log_printf (dashboard,
|
||||||
@ -3705,6 +3716,8 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard,
|
|||||||
n_tail);
|
n_tail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (n_head + n_tail < n_frames)
|
||||||
|
{
|
||||||
gimp_dashboard_log_printf (dashboard,
|
gimp_dashboard_log_printf (dashboard,
|
||||||
">\n");
|
">\n");
|
||||||
|
|
||||||
@ -3725,6 +3738,12 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard,
|
|||||||
gimp_dashboard_log_printf (dashboard,
|
gimp_dashboard_log_printf (dashboard,
|
||||||
"</thread>\n");
|
"</thread>\n");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gimp_dashboard_log_printf (dashboard,
|
||||||
|
" />\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (! backtrace_empty)
|
if (! backtrace_empty)
|
||||||
{
|
{
|
||||||
|
@ -63,37 +63,38 @@ for sample in (log.find ("samples") or empty_element).iterfind ("sample"):
|
|||||||
|
|
||||||
for thread in backtrace:
|
for thread in backtrace:
|
||||||
id = thread.get ("id")
|
id = thread.get ("id")
|
||||||
frames = list (thread)
|
|
||||||
|
|
||||||
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")
|
head = thread.get ("head")
|
||||||
tail = thread.get ("tail")
|
tail = thread.get ("tail")
|
||||||
|
attrib = dict (thread.attrib)
|
||||||
|
frames = list (thread)
|
||||||
|
|
||||||
|
last_thread = last_backtrace.setdefault (id, [{}, []])
|
||||||
|
last_frames = last_thread[1]
|
||||||
|
|
||||||
if head:
|
if head:
|
||||||
frames = last_frames[:int (head)] + frames
|
frames = last_frames[:int (head)] + frames
|
||||||
|
|
||||||
|
del attrib["head"]
|
||||||
|
|
||||||
if tail:
|
if tail:
|
||||||
frames = frames + last_frames[-int (tail):]
|
frames = frames + last_frames[-int (tail):]
|
||||||
|
|
||||||
last_thread[0] = name
|
del attrib["tail"]
|
||||||
|
|
||||||
|
last_thread[0] = attrib
|
||||||
last_thread[1] = frames
|
last_thread[1] = frames
|
||||||
|
|
||||||
|
if not frames:
|
||||||
|
del last_backtrace[id]
|
||||||
|
|
||||||
for thread in list (backtrace):
|
for thread in list (backtrace):
|
||||||
backtrace.remove (thread)
|
backtrace.remove (thread)
|
||||||
|
|
||||||
for id, (name, frames) in last_backtrace.items ():
|
for id, (attrib, frames) in last_backtrace.items ():
|
||||||
thread = ElementTree.SubElement (backtrace, "thread", id=id)
|
thread = ElementTree.SubElement (backtrace, "thread", attrib)
|
||||||
thread.text = "\n"
|
thread.text = "\n"
|
||||||
thread.tail = "\n"
|
thread.tail = "\n"
|
||||||
|
|
||||||
if name:
|
|
||||||
thread.set ("name", name)
|
|
||||||
|
|
||||||
thread.extend (frames)
|
thread.extend (frames)
|
||||||
|
|
||||||
# Expand address map
|
# Expand address map
|
||||||
|
Reference in New Issue
Block a user