Repository: giampaolo/psutil
Pull request #785 (2016-03-01 15:10:59)

779 proc cpu children times


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 8393e278..46438783 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -7,6 +7,8 @@ Bug tracker at https://github.com/giampaolo/psutil/issues
 
 - #777: [Linux] Process.open_files() on Linux return 3 new fields: position,
   mode and flags.
+- #779: Process.cpu_times() returns two new fields, 'children_user' and
+  'children_system' (always set to 0 on OSX and Windows).
 
 **Bug fixes**
 
diff --git a/IDEAS b/IDEAS
index 2b27c714..b82998be 100644
--- a/IDEAS
+++ b/IDEAS
@@ -17,9 +17,9 @@ PLATFORMS
 FEATURES
 ========
 
-- #782: (UNIX) process num of signals received.
+- (UNIX) process root (different from cwd)
 
-- (UNIX) process root (different from xwd)
+- #782: (UNIX) process num of signals received.
 
 - (Linux) locked files via /proc/locks:
   https://www.centos.org/docs/5/html/5.2/Deployment_Guide/s2-proc-locks.html
diff --git a/README.rst b/README.rst
index 2a88f934..1559636b 100644
--- a/README.rst
+++ b/README.rst
@@ -226,7 +226,7 @@ Process management
     pgids(real=1000, effective=1000, saved=1000)
     >>>
     >>> p.cpu_times()
-    pcputimes(user=1.02, system=0.31)
+    pcputimes(user=1.02, system=0.31, children_user=0.32, children_system=0.1)
     >>> p.cpu_percent(interval=1.0)
     12.1
     >>> p.cpu_affinity()
diff --git a/docs/index.rst b/docs/index.rst
index b6cf4d91..c9066493 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -266,16 +266,22 @@ Disks
 .. function:: disk_io_counters(perdisk=False)
 
   Return system-wide disk I/O statistics as a namedtuple including the
-  following fields.
+  following fields:
+
   - **read_count**: number of reads
   - **write_count**: number of writes
   - **read_bytes**: number of bytes read
   - **write_bytes**: number of bytes written
-  - **read_time**: (all except NetBSD and OpenBSD) time spent reading from disk (in milliseconds)
-  - **write_time**: (all except NetBSD and OpenBSD) time spent writing to disk (in milliseconds)
-  - **busy_time**: (Linux, FreeBSD) time spent doing actual I/Os (in milliseconds)
-  - **read_merged_count** (Linux): number of merged reads (see `iostat doc <https://www.kernel.org/doc/Documentation/iostats.txt>`__)
-  - **write_merged_count** (Linux): number of merged writes (see `iostats doc <https://www.kernel.org/doc/Documentation/iostats.txt>`__)
+  - **read_time**: (all except NetBSD and OpenBSD) time spent reading from
+    disk (in milliseconds)
+  - **write_time**: (all except NetBSD and OpenBSD) time spent writing to disk
+    (in milliseconds)
+  - **busy_time**: (Linux, FreeBSD) time spent doing actual I/Os (in
+    milliseconds)
+  - **read_merged_count** (Linux): number of merged reads
+    (see `iostat doc <https://www.kernel.org/doc/Documentation/iostats.txt>`__)
+  - **write_merged_count** (Linux): number of merged writes
+    (see `iostats doc <https://www.kernel.org/doc/Documentation/iostats.txt>`__)
 
   If *perdisk* is ``True`` return the same information for every physical disk
   installed on the system as a dictionary with partition names as the keys and
@@ -915,14 +921,18 @@ Process class
 
   .. method:: cpu_times()
 
-     Return a tuple whose values are process CPU **user** and **system**
-     times which means the amount of time expressed in seconds that a process
-     has spent in
-     `user / system mode <http://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1>`__.
+     Return a `(user, system, children_user, children_system)` namedtuple
+     representing the accumulated process time, in seconds (see
+     `explanation <http://stackoverflow.com/questions/556405/>`__).
+     On Windows and OSX only *user* and *system* are filled, the others are
+     set to ``0``.
      This is similar to
      `os.times() <http://docs.python.org//library/os.html#os.times>`__
      but can be used for any process PID.
 
+     .. versionchanged:: 4.1.0 return two extra fields: *children_user* and
+        *children_system*.
+
   .. method:: cpu_percent(interval=None)
 
      Return a float representing the process CPU utilization as a percentage.
diff --git a/psutil/__init__.py b/psutil/__init__.py
index 8594e4b9..f1b437ae 100644
--- a/psutil/__init__.py
+++ b/psutil/__init__.py
@@ -960,9 +960,12 @@ class Process(object):
             return round(overall_percent, 1)
 
     def cpu_times(self):
-        """Return a (user, system) namedtuple representing  the
-        accumulated process time, in seconds.
-        This is the same as os.times() but per-process.
+        """Return a (user, system, children_user, children_system)
+        namedtuple representing the accumulated process time, in
+        seconds.
+        This is similar to os.times() but per-process.
+        On OSX and Windows children_user and children_system are
+        always set to 0.
         """
         return self._proc.cpu_times()
 
diff --git a/psutil/_common.py b/psutil/_common.py
index 51cc1a2c..b146c736 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -311,7 +311,8 @@ snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu'])
 # --- namedtuples for psutil.Process methods
 
 # psutil.Process.cpu_times()
-pcputimes = namedtuple('pcputimes', ['user', 'system'])
+pcputimes = namedtuple('pcputimes',
+                       ['user', 'system', 'children_user', 'children_system'])
 # psutil.Process.open_files()
 popenfile = namedtuple('popenfile', ['path', 'fd'])
 # psutil.Process.threads()
diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py
index b43e052a..d628f3f7 100644
--- a/psutil/_psbsd.py
+++ b/psutil/_psbsd.py
@@ -100,6 +100,8 @@ scputimes = namedtuple(
     'scputimes', ['user', 'nice', 'system', 'idle', 'irq'])
 pmem = namedtuple('pmem', ['rss', 'vms', 'text', 'data', 'stack'])
 pfullmem = pmem
+pcputimes = namedtuple('pcputimes',
+                       ['user', 'system', 'children_user', 'children_system'])
 pmmap_grouped = namedtuple(
     'pmmap_grouped', 'path rss, private, ref_count, shadow_count')
 pmmap_ext = namedtuple(
@@ -436,8 +438,7 @@ class Process(object):
 
     @wrap_exceptions
     def cpu_times(self):
-        user, system = cext.proc_cpu_times(self.pid)
-        return _common.pcputimes(user, system)
+        return _common.pcputimes(*cext.proc_cpu_times(self.pid))
 
     @wrap_exceptions
     def memory_info(self):
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 47084ede..0b996e70 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -251,7 +251,6 @@ popenfile = namedtuple('popenfile',
                        ['path', 'fd', 'position', 'mode', 'flags'])
 pmem = namedtuple('pmem', 'rss vms shared text lib data dirty')
 pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', 'pss', 'swap'))
-
 pmmap_grouped = namedtuple(
     'pmmap_grouped', ['path', 'rss', 'size', 'pss', 'shared_clean',
                       'shared_dirty', 'private_clean', 'private_dirty',
@@ -989,7 +988,9 @@ class Process(object):
         values = st.split(b' ')
         utime = float(values[11]) / CLOCK_TICKS
         stime = float(values[12]) / CLOCK_TICKS
-        return _common.pcputimes(utime, stime)
+        children_utime = float(values[13]) / CLOCK_TICKS
+        children_stime = float(values[14]) / CLOCK_TICKS
+        return _common.pcputimes(utime, stime, children_utime, children_stime)
 
     @wrap_exceptions
     def wait(self, timeout=None):
diff --git a/psutil/_psosx.py b/psutil/_psosx.py
index 5f39589c..b8066775 100644
--- a/psutil/_psosx.py
+++ b/psutil/_psosx.py
@@ -282,7 +282,8 @@ class Process(object):
     @wrap_exceptions
     def cpu_times(self):
         user, system = cext.proc_cpu_times(self.pid)
-        return _common.pcputimes(user, system)
+        # Children user/system times are not retrievable (set to 0).
+        return _common.pcputimes(user, system, 0, 0)
 
     @wrap_exceptions
     def create_time(self):
diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py
index e76b40d7..8779c425 100644
--- a/psutil/_pssunos.py
+++ b/psutil/_pssunos.py
@@ -59,6 +59,8 @@ TCP_STATUSES = {
 }
 
 scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait'])
+pcputimes = namedtuple('pcputimes',
+                       ['user', 'system', 'children_user', 'children_system'])
 svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
 pmem = namedtuple('pmem', ['rss', 'vms'])
 pmmap_grouped = namedtuple('pmmap_grouped',
@@ -363,8 +365,8 @@ class Process(object):
 
     @wrap_exceptions
     def cpu_times(self):
-        user, system = cext.proc_cpu_times(self.pid, self._procfs_path)
-        return _common.pcputimes(user, system)
+        times = cext.proc_cpu_times(self.pid, self._procfs_path)
+        return _common.pcputimes(*times)
 
     @wrap_exceptions
     def terminal(self):
diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c
index db05dc8e..31511abb 100644
--- a/psutil/_psutil_bsd.c
+++ b/psutil/_psutil_bsd.c
@@ -368,7 +368,7 @@ psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) {
 static PyObject *
 psutil_proc_cpu_times(PyObject *self, PyObject *args) {
     long pid;
-    double user_t, sys_t;
+    double user_t, sys_t, children_user_t, children_sys_t;
     kinfo_proc kp;
     if (! PyArg_ParseTuple(args, "l", &pid))
         return NULL;
@@ -378,11 +378,18 @@ psutil_proc_cpu_times(PyObject *self, PyObject *args) {
 #ifdef __FreeBSD__
     user_t = PSUTIL_TV2DOUBLE(kp.ki_rusage.ru_utime);
     sys_t = PSUTIL_TV2DOUBLE(kp.ki_rusage.ru_stime);
+    children_user_t = PSUTIL_TV2DOUBLE(kp.ki_rusage_ch.ru_utime);
+    children_sys_t = PSUTIL_TV2DOUBLE(kp.ki_rusage_ch.ru_stime);
 #elif defined(__OpenBSD__) || defined(__NetBSD__)
     user_t = PSUTIL_KPT2DOUBLE(kp.p_uutime);
     sys_t = PSUTIL_KPT2DOUBLE(kp.p_ustime);
+    // OpenBSD and NetBSD provide children user + system times summed
+    // together (no distinction).
+    children_user_t = kp.p_uctime_sec + kp.p_uctime_usec / 1000000.0;
+    children_sys_t = children_user_t;
 #endif
-    return Py_BuildValue("(dd)", user_t, sys_t);
+    return Py_BuildValue("(dddd)",
+                         user_t, sys_t, children_user_t, children_sys_t);
 }
 
 
diff --git a/psutil/_psutil_sunos.c b/psutil/_psutil_sunos.c
index 7ffee306..9a7067d3 100644
--- a/psutil/_psutil_sunos.c
+++ b/psutil/_psutil_sunos.c
@@ -159,9 +159,13 @@ psutil_proc_cpu_times(PyObject *self, PyObject *args) {
     if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
         return NULL;
     // results are more precise than os.times()
-    return Py_BuildValue("dd",
-                         PSUTIL_TV2DOUBLE(info.pr_utime),
-                         PSUTIL_TV2DOUBLE(info.pr_stime));
+    return Py_BuildValue(
+        "(dddd)",
+         PSUTIL_TV2DOUBLE(info.pr_utime),
+         PSUTIL_TV2DOUBLE(info.pr_stime),
+         PSUTIL_TV2DOUBLE(info.pr_cutime),
+         PSUTIL_TV2DOUBLE(info.pr_cstime)
+    );
 }
 
 
diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py
index 92472576..25ed2a59 100644
--- a/psutil/_pswindows.py
+++ b/psutil/_pswindows.py
@@ -448,14 +448,15 @@ class Process(object):
     @wrap_exceptions
     def cpu_times(self):
         try:
-            ret = cext.proc_cpu_times(self.pid)
+            user, system = cext.proc_cpu_times(self.pid)
         except OSError as err:
             if err.errno in ACCESS_DENIED_SET:
                 nt = ntpinfo(*cext.proc_info(self.pid))
-                ret = (nt.user_time, nt.kernel_time)
+                user, system = (nt.user_time, nt.kernel_time)
             else:
                 raise
-        return _common.pcputimes(*ret)
+        # Children user/system times are not retrievable (set to 0).
+        return _common.pcputimes(user, system, 0, 0)
 
     @wrap_exceptions
     def suspend(self):


Test patch:
diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py
index 5ad606da..79d28b19 100644
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -268,9 +268,11 @@ class TestProcess(unittest.TestCase):
     def test_cpu_times(self):
         times = psutil.Process().cpu_times()
         assert (times.user > 0.0) or (times.system > 0.0), times
+        assert (times.children_user >= 0.0), times
+        assert (times.children_system >= 0.0), times
         # make sure returned values can be pretty printed with strftime
-        time.strftime("%H:%M:%S", time.localtime(times.user))
-        time.strftime("%H:%M:%S", time.localtime(times.system))
+        for name in times._fields:
+            time.strftime("%H:%M:%S", time.localtime(getattr(times, name)))
 
     # Test Process.cpu_times() against os.times()
     # os.times() is broken on Python 2.6
@@ -280,8 +282,8 @@ class TestProcess(unittest.TestCase):
 
     @unittest.skipUnless(sys.version_info > (2, 6, 1) and not OSX,
                          'os.times() is not reliable on this Python version')
-    def test_cpu_times2(self):
-        user_time, kernel_time = psutil.Process().cpu_times()
+    def test_cpu_times_2(self):
+        user_time, kernel_time = psutil.Process().cpu_times()[:2]
         utime, ktime = os.times()[:2]
 
         # Use os.times()[:2] as base values to compare our results