[gimp] app, tools: add "running" thread attribute to GimpBacktrace/performance-log



commit 78adb7c9008011161009153a56354198c0842661
Author: Ell <ell_se yahoo com>
Date:   Mon Sep 3 18:13:16 2018 -0400

    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).

 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  | 39 +++++++++++-----------
 6 files changed, 177 insertions(+), 40 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 <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,
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 f9b358a3f0..11638e73b6 100644
--- a/app/widgets/gimpdashboard.c
+++ b/app/widgets/gimpdashboard.c
@@ -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)
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)
 
-            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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]