Merge pull request #1624 from esdrubal/getprocesstimes
authorMarcos Henrich <marcoshenrich@gmail.com>
Mon, 6 Apr 2015 18:46:31 +0000 (19:46 +0100)
committerMarcos Henrich <marcoshenrich@gmail.com>
Mon, 6 Apr 2015 18:46:31 +0000 (19:46 +0100)
[runtime] GetProcessTimes now works with all processes.

1  2 
mono/io-layer/processes.c
mono/utils/mono-proclib.c
mono/utils/mono-proclib.h

index 913a08570a4706be089a033700e4e79c98011e03,d6f7dcd38fa6f1dd08d71b7770fe568183b2e708..ce7aee14c884732d669200fc8bcbc3a2599d718b
@@@ -90,6 -90,7 +90,7 @@@
  #include <mono/utils/mono-membar.h>
  #include <mono/utils/mono-mutex.h>
  #include <mono/utils/mono-signal-handler.h>
+ #include <mono/utils/mono-proclib.h>
  
  /* The process' environment strings */
  #if defined(__APPLE__) && !defined (__arm__) && !defined (__aarch64__)
@@@ -148,6 -149,7 +149,6 @@@ static void process_add_sigchld_handle
   * signal handler)
   */
  static struct MonoProcess *mono_processes = NULL;
 -static volatile gint32 mono_processes_read_lock = 0;
  static volatile gint32 mono_processes_cleaning_up = 0;
  static mono_mutex_t mono_processes_mutex;
  static void mono_processes_cleanup (void);
@@@ -1309,11 -1311,19 +1310,19 @@@ GetProcessTimes (gpointer process, Wapi
                /* Not sure if w32 allows NULLs here or not */
                return FALSE;
        
-       if (WAPI_IS_PSEUDO_PROCESS_HANDLE (process))
-               /* This is a pseudo handle, so just fail for now
-                */
-               return FALSE;
-       
+       if (WAPI_IS_PSEUDO_PROCESS_HANDLE (process)) {
+               gpointer pid = GINT_TO_POINTER (WAPI_HANDLE_TO_PID(process));
+               gint64 start_ticks, user_ticks, kernel_ticks;
+               mono_process_get_times (pid, &start_ticks, &user_ticks, &kernel_ticks);
+               _wapi_guint64_to_filetime (start_ticks, create_time);
+               _wapi_guint64_to_filetime (user_ticks, kernel_time);
+               _wapi_guint64_to_filetime (kernel_ticks, user_time);
+               return TRUE;
+       }
        process_handle = lookup_process_handle (process);
        if (!process_handle) {
                DEBUG ("%s: Can't find process %p", __func__, process);
@@@ -2434,9 -2444,9 +2443,9 @@@ mono_processes_cleanup (void
  {
        struct MonoProcess *mp;
        struct MonoProcess *prev = NULL;
 -      struct MonoProcess *candidate = NULL;
 +      GSList *finished = NULL;
 +      GSList *l;
        gpointer unref_handle;
 -      int spin;
  
        DEBUG ("%s", __func__);
  
        if (InterlockedCompareExchange (&mono_processes_cleaning_up, 1, 0) != 0)
                return;
  
 -      mp = mono_processes;
 -      while (mp != NULL) {
 -              if (mp->pid == 0 && mp->handle != NULL) {
 +      for (mp = mono_processes; mp; mp = mp->next) {
 +              if (mp->pid == 0 && mp->handle) {
                        /* This process has exited and we need to remove the artifical ref
                         * on the handle */
                        mono_mutex_lock (&mono_processes_mutex);
                        mono_mutex_unlock (&mono_processes_mutex);
                        if (unref_handle)
                                _wapi_handle_unref (unref_handle);
 -                      continue;
                }
 -              mp = mp->next;
        }
  
        /*
         * asynchronously. The handler requires that the mono_processes list
         * remain valid.
         */
 -      mp = mono_processes;
 -      spin = 0;
 -      while (mp != NULL) {
 -              if ((mp->handle_count == 0 && mp->pid == 0) || candidate != NULL) {
 -                      if (spin > 0) {
 -                              _wapi_handle_spin (spin);
 -                              spin <<= 1;
 -                      }
 +      mono_mutex_lock (&mono_processes_mutex);
  
 -                      /* We've found a candidate */
 -                      mono_mutex_lock (&mono_processes_mutex);
 +      mp = mono_processes;
 +      while (mp) {
 +              if (mp->handle_count == 0 && mp->freeable) {
                        /*
 +                       * Unlink the entry.
                         * This code can run parallel with the sigchld handler, but the
                         * modifications it makes are safe.
                         */
 -                      if (candidate == NULL) {
 -                              /* unlink it */
 -                              if (mp == mono_processes) {
 -                                      mono_processes = mp->next;
 -                              } else {
 -                                      prev->next = mp->next;
 -                              }
 -                              candidate = mp;
 -                      }
 -
 -                      /* It's still safe to traverse the structure.*/
 -                      mono_memory_barrier ();
 +                      if (mp == mono_processes)
 +                              mono_processes = mp->next;
 +                      else
 +                              prev->next = mp->next;
 +                      finished = g_slist_prepend (finished, mp);
  
 -                      if (mono_processes_read_lock != 0) {
 -                              /* The sigchld handler is watching us. Spin a bit and try again */
 -                              if (spin == 0) {
 -                                      spin = 1;
 -                              } else if (spin >= 8) {
 -                                      /* Just give up for now */
 -                                      mono_mutex_unlock (&mono_processes_mutex);
 -                                      break;
 -                              }
 -                      } else {
 -                              /* We've modified the list of processes, and we know the sigchld handler
 -                               * isn't executing, so even if it executes at any moment, it'll see the
 -                               * new version of the list. So now we can free the candidate. */
 -                              DEBUG ("%s: freeing candidate %p", __func__, candidate);
 -                              mp = candidate->next;
 -                              MONO_SEM_DESTROY (&candidate->exit_sem);
 -                              g_free (candidate);
 -                              candidate = NULL;
 -                      }
 +                      mp = mp->next;
 +              } else {
 +                      prev = mp;
 +                      mp = mp->next;
 +              }
 +      }
  
 -                      mono_mutex_unlock (&mono_processes_mutex);
 +      mono_memory_barrier ();
  
 -                      continue;
 -              }
 -              spin = 0;
 -              prev = mp;
 -              mp = mp->next;
 +      for (l = finished; l; l = l->next) {
 +              /*
 +               * All the entries in the finished list are unlinked from mono_processes, and
 +               * they have the 'finished' flag set, which means the sigchld handler is done
 +               * accessing them.
 +               */
 +              mp = l->data;
 +              MONO_SEM_DESTROY (&mp->exit_sem);
 +              g_free (mp);
        }
 +      g_slist_free (finished);
 +
 +      mono_mutex_unlock (&mono_processes_mutex);
  
        DEBUG ("%s done", __func__);
  
@@@ -2531,6 -2562,8 +2540,6 @@@ MONO_SIGNAL_HANDLER_FUNC (static, mono_
  
        DEBUG ("SIG CHILD handler for pid: %i\n", info->si_pid);
  
 -      InterlockedIncrement (&mono_processes_read_lock);
 -
        do {
                do {
                        pid = waitpid (-1, &status, WNOHANG);
                        break;
  
                DEBUG ("child ended: %i", pid);
 -              p = mono_processes;
 -              while (p != NULL) {
 +
 +              /*
 +               * This can run concurrently with the code in the rest of this module.
 +               */
 +              for (p = mono_processes; p; p = p->next) {
                        if (p->pid == pid) {
 -                              p->pid = 0; /* this pid doesn't exist anymore, clear it */
 -                              p->status = status;
 -                              MONO_SEM_POST (&p->exit_sem);
                                break;
                        }
 -                      p = p->next;
 +              }
 +              if (p) {
 +                      p->pid = 0; /* this pid doesn't exist anymore, clear it */
 +                      p->status = status;
 +                      MONO_SEM_POST (&p->exit_sem);
 +                      mono_memory_barrier ();
 +                      /* Mark this as freeable, the pointer becomes invalid afterwards */
 +                      p->freeable = TRUE;
                }
        } while (1);
  
 -      InterlockedDecrement (&mono_processes_read_lock);
 -
        DEBUG ("SIG CHILD handler: done looping.");
  }
  
index 38dea0d5111190982f9e94f21d38772a33436e3c,551fca592bc22e09eef833923eb63d626ef2355a..26d78cccfa59e4b5182e580c1c37056f9bc7f214
  #include <process.h>
  #endif
  
 -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
 +#if defined(_POSIX_VERSION)
  #include <sys/errno.h>
  #include <sys/param.h>
 +#ifdef HAVE_SYS_TYPES_H
  #include <sys/types.h>
 +#endif
 +#ifdef HAVE_SYS_SYSCTL_H
  #include <sys/sysctl.h>
 +#endif
 +#include <sys/resource.h>
 +#endif
 +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
  #include <sys/proc.h>
  #if defined(__APPLE__)
  #include <mach/mach.h>
@@@ -207,57 -200,67 +207,67 @@@ get_pid_status_item_buf (int pid, cons
        return NULL;
  }
  
- /**
-  * mono_process_get_name:
-  * @pid: pid of the process
-  * @buf: byte buffer where to store the name of the prcoess
-  * @len: size of the buffer @buf
-  *
-  * Return the name of the process identified by @pid, storing it
-  * inside @buf for a maximum of len bytes (including the terminating 0).
-  */
- char*
- mono_process_get_name (gpointer pid, char *buf, int len)
- {
  #if USE_SYSCTL
-       int res;
  #ifdef KERN_PROC2
-       int mib [6];
-       size_t data_len = sizeof (struct kinfo_proc2);
-       struct kinfo_proc2 processi;
+ #define KINFO_PROC struct kinfo_proc2
  #else
-       int mib [4];
-       size_t data_len = sizeof (struct kinfo_proc);
-       struct kinfo_proc processi;
- #endif /* KERN_PROC2 */
+ #define KINFO_PROC struct kinfo_proc
+ #endif
  
-       memset (buf, 0, len);
+ static gboolean
+ sysctl_kinfo_proc (gpointer pid, KINFO_PROC* processi)
+ {
+       int res;
+       size_t data_len = sizeof (KINFO_PROC);
  
  #ifdef KERN_PROC2
+       int mib [6];
        mib [0] = CTL_KERN;
        mib [1] = KERN_PROC2;
        mib [2] = KERN_PROC_PID;
        mib [3] = GPOINTER_TO_UINT (pid);
-       mib [4] = sizeof(struct kinfo_proc2);
+       mib [4] = sizeof(KINFO_PROC);
        mib [5] = 400; /* XXX */
  
-       res = sysctl (mib, 6, &processi, &data_len, NULL, 0);
-       if (res < 0 || data_len != sizeof (struct kinfo_proc2)) {
-               return buf;
-       }
+       res = sysctl (mib, 6, processi, &data_len, NULL, 0);
  #else
+       int mib [4];
        mib [0] = CTL_KERN;
        mib [1] = KERN_PROC;
        mib [2] = KERN_PROC_PID;
        mib [3] = GPOINTER_TO_UINT (pid);
-       
-       res = sysctl (mib, 4, &processi, &data_len, NULL, 0);
-       if (res < 0 || data_len != sizeof (struct kinfo_proc)) {
-               return buf;
-       }
+       res = sysctl (mib, 4, processi, &data_len, NULL, 0);
  #endif /* KERN_PROC2 */
-       strncpy (buf, processi.kinfo_name_member, len - 1);
+       if (res < 0 || data_len != sizeof (KINFO_PROC))
+               return FALSE;
+       return TRUE;
+ }
+ #endif /* USE_SYSCTL */
+ /**
+  * mono_process_get_name:
+  * @pid: pid of the process
+  * @buf: byte buffer where to store the name of the prcoess
+  * @len: size of the buffer @buf
+  *
+  * Return the name of the process identified by @pid, storing it
+  * inside @buf for a maximum of len bytes (including the terminating 0).
+  */
+ char*
+ mono_process_get_name (gpointer pid, char *buf, int len)
+ {
+ #if USE_SYSCTL
+       KINFO_PROC processi;
+       memset (buf, 0, len);
+       if (sysctl_kinfo_proc (pid, &processi))
+               strncpy (buf, processi.kinfo_name_member, len - 1);
        return buf;
  #else
        char fname [128];
  #endif
  }
  
+ void
+ mono_process_get_times (gpointer pid, gint64 *start_time, gint64 *user_time, gint64 *kernel_time)
+ {
+       if (user_time)
+               *user_time = mono_process_get_data (pid, MONO_PROCESS_USER_TIME);
+       if (kernel_time)
+               *kernel_time = mono_process_get_data (pid, MONO_PROCESS_SYSTEM_TIME);
+       if (start_time) {
+               *start_time = 0;
+ #if USE_SYSCTL
+               {
+                       KINFO_PROC processi;
+                       if (sysctl_kinfo_proc (pid, &processi))
+                               *start_time = mono_100ns_datetime_from_timeval (processi.kp_proc.p_starttime);
+               }
+ #endif
+               if (*start_time == 0) {
+                       static guint64 boot_time = 0;
+                       if (!boot_time)
+                               boot_time = mono_100ns_datetime () - ((guint64)mono_msec_ticks ()) * 10000;
+                       *start_time = boot_time + mono_process_get_data (pid, MONO_PROCESS_ELAPSED);
+               }
+       }
+ }
  /*
   * /proc/pid/stat format:
   * pid (cmdname) S 
   *    [0] ppid pgid sid tty_nr tty_pgrp flags min_flt cmin_flt maj_flt cmaj_flt
-  *    [10] utime stime cutime cstime prio nice threads 0 start_time vsize rss
+  *    [10] utime stime cutime cstime prio nice threads 0 start_time vsize
   *    [20] rss rsslim start_code end_code start_stack esp eip pending blocked sigign
   *    [30] sigcatch wchan 0 0 exit_signal cpu rt_prio policy
   */
@@@ -521,7 -555,7 +562,7 @@@ mono_process_get_data_with_error (gpoin
        case MONO_PROCESS_FAULTS:
                return get_process_stat_item (rpid, 6, TRUE, error);
        case MONO_PROCESS_ELAPSED:
-               return get_process_stat_item (rpid, 18, FALSE, error) / get_user_hz ();
+               return get_process_stat_time (rpid, 18, FALSE, error);
        case MONO_PROCESS_PPID:
                return get_process_stat_time (rpid, 0, FALSE, error);
        case MONO_PROCESS_PAGED_BYTES:
@@@ -702,62 -736,3 +743,62 @@@ mono_atexit (void (*func)(void)
        return atexit (func);
  #endif
  }
 +
 +gint32
 +mono_cpu_usage (MonoCpuUsageState *prev)
 +{
 +      gint32 cpu_usage = 0;
 +      gint64 cpu_total_time;
 +      gint64 cpu_busy_time;
 +
 +#ifndef HOST_WIN32
 +      struct rusage resource_usage;
 +      gint64 current_time;
 +      gint64 kernel_time;
 +      gint64 user_time;
 +
 +      if (getrusage (RUSAGE_SELF, &resource_usage) == -1) {
 +              g_error ("getrusage() failed, errno is %d (%s)\n", errno, strerror (errno));
 +              return -1;
 +      }
 +
 +      current_time = mono_100ns_ticks ();
 +      kernel_time = resource_usage.ru_stime.tv_sec * 1000 * 1000 * 10 + resource_usage.ru_stime.tv_usec * 10;
 +      user_time = resource_usage.ru_utime.tv_sec * 1000 * 1000 * 10 + resource_usage.ru_utime.tv_usec * 10;
 +
 +      cpu_busy_time = (user_time - (prev ? prev->user_time : 0)) + (kernel_time - (prev ? prev->kernel_time : 0));
 +      cpu_total_time = (current_time - (prev ? prev->current_time : 0)) * mono_cpu_count ();
 +
 +      if (prev) {
 +              prev->kernel_time = kernel_time;
 +              prev->user_time = user_time;
 +              prev->current_time = current_time;
 +      }
 +#else
 +      guint64 idle_time;
 +      guint64 kernel_time;
 +      guint64 user_time;
 +
 +      if (!GetSystemTimes ((FILETIME*) &idle_time, (FILETIME*) &kernel_time, (FILETIME*) &user_time)) {
 +              g_error ("GetSystemTimes() failed, error code is %d\n", GetLastError ());
 +              return -1;
 +      }
 +
 +      cpu_total_time = (gint64)((user_time - (prev ? prev->user_time : 0)) + (kernel_time - (prev ? prev->kernel_time : 0)));
 +      cpu_busy_time = (gint64)(cpu_total_time - (idle_time - (prev ? prev->idle_time : 0)));
 +
 +      if (prev) {
 +              prev->idle_time = idle_time;
 +              prev->kernel_time = kernel_time;
 +              prev->user_time = user_time;
 +      }
 +#endif
 +
 +      if (cpu_total_time > 0 && cpu_busy_time > 0)
 +              cpu_usage = (gint32)(cpu_busy_time * 100 / cpu_total_time);
 +
 +      g_assert (cpu_usage >= 0);
 +      g_assert (cpu_usage <= 100);
 +
 +      return cpu_usage;
 +}
index 48144a00613e3348b59be04b61b36b35cf31a4b5,dc870cde93a1d5cd4a5bd14df81b5798346abc00..67db3f3f31715e57ab0ca97e4dc5364a2d202e3a
@@@ -41,23 -41,10 +41,25 @@@ typedef enum 
        MONO_PROCESS_ERROR_OTHER
  } MonoProcessError;
  
 +typedef struct _MonoCpuUsageState MonoCpuUsageState;
 +#ifndef HOST_WIN32
 +struct _MonoCpuUsageState {
 +      gint64 kernel_time;
 +      gint64 user_time;
 +      gint64 current_time;
 +};
 +#else
 +struct _MonoCpuUsageState {
 +      guint64 kernel_time;
 +      guint64 user_time;
 +      guint64 idle_time;
 +};
 +#endif
 +
  gpointer* mono_process_list     (int *size);
  
+ void      mono_process_get_times (gpointer pid, gint64 *start_time, gint64 *user_time, gint64 *kernel_time);
  char*     mono_process_get_name (gpointer pid, char *buf, int len);
  
  gint64    mono_process_get_data (gpointer pid, MonoProcessData data);
@@@ -67,7 -54,6 +69,7 @@@ int       mono_process_current_pid (voi
  
  int       mono_cpu_count    (void);
  gint64    mono_cpu_get_data (int cpu_id, MonoCpuData data, MonoProcessError *error);
 +gint32    mono_cpu_usage (MonoCpuUsageState *prev);
  
  int       mono_atexit (void (*func)(void));