Commit 78adb7c9 authored by Ell's avatar Ell

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).
parent 667efc22
......@@ -42,6 +42,7 @@
#include <execinfo.h>
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
#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,
......
......@@ -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,
......
......@@ -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,
......
......@@ -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,
......
......@@ -3590,9 +3590,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);
......@@ -3610,6 +3612,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);
......@@ -3643,7 +3648,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 ();
......@@ -3661,6 +3668,10 @@ gimp_dashboard_log_sample (GimpDashboard *dashboard,
"\"");
}
gimp_dashboard_log_printf (dashboard,
" running=\"%d\"",
running);
if (n_head > 0)
{
gimp_dashboard_log_printf (dashboard,
......@@ -3675,25 +3686,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;
gimp_dashboard_log_printf (dashboard,
"<frame address=\"0x%llx\" />\n",
address);
address = gimp_backtrace_get_frame_address (backtrace,
thread, frame);
g_hash_table_add (priv->log_addresses, (gpointer) address);
}
gimp_dashboard_log_printf (dashboard,
"<frame address=\"0x%llx\" />\n",
address);
gimp_dashboard_log_printf (dashboard,
"</thread>\n");
g_hash_table_add (priv->log_addresses, (gpointer) address);
}
gimp_dashboard_log_printf (dashboard,
"</thread>\n");
}
else
{
gimp_dashboard_log_printf (dashboard,
" />\n");
}
}
if (! backtrace_empty)
......
......@@ -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)
if not frames:
last_backtrace.pop (id, None)
else:
last_thread = last_backtrace.setdefault (id, [None, []])
last_frames = last_thread[1]
last_thread = last_backtrace.setdefault (id, [{}, []])
last_frames = last_thread[1]
if head:
frames = last_frames[:int (head)] + frames
del attrib["head"]
name = thread.get ("name")
head = thread.get ("head")
tail = thread.get ("tail")
if tail:
frames = frames + last_frames[-int (tail):]
if head:
frames = last_frames[:int (head)] + frames
if tail:
frames = frames + last_frames[-int (tail):]
del attrib["tail"]
last_thread[0] = name
last_thread[1] = frames
last_thread[0] = attrib
last_thread[1] = frames
if not 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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment