Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

357 proc cpu num #954

Merged
merged 14 commits into from
Jan 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -850,39 +850,41 @@ Process class
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| Linux | Windows | OSX | BSD | SunOS |
+==============================+===============================+==============================+==============================+==========================+
| :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`name` |
| :meth:`cpu_num` | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`cpu_num` | :meth:`name` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`cmdline` |
| :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_percent` | :meth:`cmdline` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`create_time` | :meth:`io_counters()` | :meth:`memory_info` | :meth:`create_time` | :meth:`create_time` |
| :meth:`~Process.cpu_times` | :meth:`io_counters()` | :meth:`memory_info` | :meth:`~Process.cpu_times` | :meth:`create_time` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`name` | :meth:`ionice` | :meth:`memory_percent` | :meth:`gids` | |
| :meth:`create_time` | :meth:`ionice` | :meth:`memory_percent` | :meth:`create_time` | |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`ppid` | :meth:`memory_info` | :meth:`num_ctx_switches` | :meth:`io_counters` | :meth:`memory_info` |
| :meth:`name` | :meth:`memory_info` | :meth:`num_ctx_switches` | :meth:`gids` | :meth:`memory_info` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`status` | :meth:`nice` | :meth:`num_threads` | :meth:`name` | :meth:`memory_percent` |
| :meth:`ppid` | :meth:`nice` | :meth:`num_threads` | :meth:`io_counters` | :meth:`memory_percent` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`terminal` | :meth:`memory_maps` | | :meth:`memory_info` | :meth:`nice` |
| :meth:`status` | :meth:`memory_maps` | | :meth:`name` | :meth:`nice` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| | :meth:`num_ctx_switches` | :meth:`create_time` | :meth:`memory_percent` | :meth:`num_threads` |
| :meth:`terminal` | :meth:`num_ctx_switches` | :meth:`create_time` | :meth:`memory_info` | :meth:`num_threads` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`gids` | :meth:`num_handles` | :meth:`gids` | :meth:`num_ctx_switches` | :meth:`ppid` |
| | :meth:`num_handles` | :meth:`gids` | :meth:`memory_percent` | :meth:`ppid` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`num_ctx_switches` | :meth:`num_threads` | :meth:`name` | :meth:`ppid` | :meth:`status` |
| :meth:`gids` | :meth:`num_threads` | :meth:`name` | :meth:`num_ctx_switches` | :meth:`status` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`num_threads` | :meth:`username` | :meth:`ppid` | :meth:`status` | :meth:`terminal` |
| :meth:`num_ctx_switches` | :meth:`username` | :meth:`ppid` | :meth:`ppid` | :meth:`terminal` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`uids` | | :meth:`status` | :meth:`terminal` | |
| :meth:`num_threads` | | :meth:`status` | :meth:`status` | |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`username` | | :meth:`terminal` | :meth:`uids` | :meth:`gids` |
| :meth:`uids` | | :meth:`terminal` | :meth:`terminal` | :meth:`gids` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| | | :meth:`uids` | :meth:`username` | :meth:`uids` |
| :meth:`username` | | :meth:`uids` | :meth:`uids` | :meth:`uids` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`memory_full_info` | | :meth:`username` | | :meth:`username` |
| | | :meth:`username` | :meth:`username` | :meth:`username` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`memory_full_info` | | | | |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`memory_maps` | | | | |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| *speedup: +2.5x* | *speedup: +1.8x / +6.5x* | *speedup: +1.9x* | *speedup: +2.0x* | *speedup: +1.3x* |
| *speedup: +2.6x* | *speedup: +1.8x / +6.5x* | *speedup: +1.9x* | *speedup: +2.0x* | *speedup: +1.3x* |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+

.. versionadded:: 5.0.0
Expand Down Expand Up @@ -1239,6 +1241,18 @@ Process class

.. versionchanged:: 2.2.0 added support for FreeBSD

.. method:: cpu_num()

Return what CPU this process is currently running on.
The returned number should be ``<=`` :func:`psutil.cpu_count()`.
It may be used in conjunction with ``psutil.cpu_percent(percpu=True)`` to
observe the system workload distributed across multiple CPUs as shown by
`cpu_workload.py <https://github.com/giampaolo/psutil/blob/master/scripts/cpu_workload.py>`__ example script.

Availability: Linux, FreeBSD, SunOS

.. versionadded:: 5.1.0

.. method:: memory_info()

Return a namedtuple with variable fields depending on the platform
Expand Down
13 changes: 13 additions & 0 deletions psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,19 @@ def cpu_affinity(self, cpus=None):
else:
self._proc.cpu_affinity_set(list(set(cpus)))

# Linux, FreeBSD, SunOS
if hasattr(_psplatform.Process, "cpu_num"):

def cpu_num(self):
"""Return what CPU this process is currently running on.
The returned number should be <= psutil.cpu_count()
and <= len(psutil.cpu_percent(percpu=True)).
It may be used in conjunction with
psutil.cpu_percent(percpu=True) to observe the system
workload distributed across CPUs.
"""
return self._proc.cpu_num()

# Linux, OSX and Windows only
if hasattr(_psplatform.Process, "environ"):

Expand Down
8 changes: 7 additions & 1 deletion psutil/_psbsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@
memtext=20,
memdata=21,
memstack=22,
name=23,
cpunum=23,
name=24,
)


Expand Down Expand Up @@ -609,6 +610,11 @@ def cpu_times(self):
rawtuple[kinfo_proc_map['ch_user_time']],
rawtuple[kinfo_proc_map['ch_sys_time']])

if FREEBSD:
@wrap_exceptions
def cpu_num(self):
return self.oneshot()[kinfo_proc_map['cpunum']]

@wrap_exceptions
def memory_info(self):
rawtuple = self.oneshot()
Expand Down
5 changes: 5 additions & 0 deletions psutil/_pslinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,11 @@ def cpu_times(self):
children_stime = float(values[15]) / CLOCK_TICKS
return _common.pcputimes(utime, stime, children_utime, children_stime)

@wrap_exceptions
def cpu_num(self):
"""What CPU the process is on."""
return int(self._parse_stat_file()[37])

@wrap_exceptions
def wait(self, timeout=None):
try:
Expand Down
4 changes: 4 additions & 0 deletions psutil/_pssunos.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,10 @@ def cpu_times(self):
raise
return _common.pcputimes(*times)

@wrap_exceptions
def cpu_num(self):
return cext.proc_cpu_num(self.pid, self._procfs_path)

@wrap_exceptions
def terminal(self):
procfs_path = self._procfs_path
Expand Down
23 changes: 22 additions & 1 deletion psutil/_psutil_bsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) {
long memtext;
long memdata;
long memstack;
unsigned char oncpu;
kinfo_proc kp;
long pagesize = sysconf(_SC_PAGESIZE);
char str[1000];
Expand Down Expand Up @@ -252,9 +253,25 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) {
memstack = (long)kp.p_vm_ssize * pagesize;
#endif

#ifdef PSUTIL_FREEBSD
// what CPU we're on; top was used as an example:
// https://svnweb.freebsd.org/base/head/usr.bin/top/machine.c?
// view=markup&pathrev=273835
if (kp.ki_stat == SRUN && kp.ki_oncpu != NOCPU)
oncpu = kp.ki_oncpu;
else
oncpu = kp.ki_lastcpu;
#else
// On Net/OpenBSD we have kp.p_cpuid but it appears it's always
// set to KI_NOCPU. Even if it's not, ki_lastcpu does not exist
// so there's no way to determine where "sleeping" processes
// were. Not supported.
oncpu = -1;
#endif

// Return a single big tuple with all process info.
py_retlist = Py_BuildValue(
"(lillllllidllllddddlllllO)",
"(lillllllidllllddddlllllbO)",
#ifdef PSUTIL_FREEBSD
//
(long)kp.ki_ppid, // (long) ppid
Expand Down Expand Up @@ -287,6 +304,8 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) {
memtext, // (long) mem text
memdata, // (long) mem data
memstack, // (long) mem stack
// others
oncpu, // (unsigned char) the CPU we are on
#elif defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD)
//
(long)kp.p_ppid, // (long) ppid
Expand Down Expand Up @@ -321,6 +340,8 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) {
memtext, // (long) mem text
memdata, // (long) mem data
memstack, // (long) mem stack
// others
oncpu, // (unsigned char) the CPU we are on
#endif
py_name // (pystr) name
);
Expand Down
82 changes: 82 additions & 0 deletions psutil/_psutil_sunos.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,86 @@ psutil_proc_cpu_times(PyObject *self, PyObject *args) {
}


/*
* Return what CPU the process is running on.
*/
static PyObject *
psutil_proc_cpu_num(PyObject *self, PyObject *args) {
int fd = NULL;
int pid;
char path[1000];
struct prheader header;
struct lwpsinfo *lwp;
char *lpsinfo = NULL;
char *ptr = NULL;
int nent;
int size;
int proc_num;
size_t nbytes;
const char *procfs_path;

if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path))
return NULL;

sprintf(path, "%s/%i/lpsinfo", procfs_path, pid);
fd = open(path, O_RDONLY);
if (fd == -1) {
PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
return NULL;
}

// read header
nbytes = pread(fd, &header, sizeof(header), 0);
if (nbytes == -1) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
if (nbytes != sizeof(header)) {
PyErr_SetString(
PyExc_RuntimeError, "read() file structure size mismatch");
goto error;
}

// malloc
nent = header.pr_nent;
size = header.pr_entsize * nent;
ptr = lpsinfo = malloc(size);
if (lpsinfo == NULL) {
PyErr_NoMemory();
goto error;
}

// read the rest
nbytes = pread(fd, lpsinfo, size, sizeof(header));
if (nbytes == -1) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
if (nbytes != size) {
PyErr_SetString(
PyExc_RuntimeError, "read() file structure size mismatch");
goto error;
}

// done
lwp = (lwpsinfo_t *)ptr;
proc_num = lwp->pr_onpro;
close(fd);
free(ptr);
free(lpsinfo);
return Py_BuildValue("i", proc_num);

error:
if (fd != NULL)
close(fd);
if (ptr != NULL)
free(ptr);
if (lpsinfo != NULL)
free(lpsinfo);
return NULL;
}


/*
* Return process uids/gids as a Python tuple.
*/
Expand Down Expand Up @@ -1340,6 +1420,8 @@ PsutilMethods[] = {
"Return process memory mappings"},
{"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS,
"Return the number of context switches performed by process"},
{"proc_cpu_num", psutil_proc_cpu_num, METH_VARARGS,
"Return what CPU the process is on"},

// --- system-related functions
{"swap_mem", psutil_swap_mem, METH_VARARGS,
Expand Down
6 changes: 6 additions & 0 deletions psutil/tests/test_memory_leaks.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,12 @@ def test_threads(self):
def test_cpu_times(self):
self.execute(self.proc.cpu_times)

@skip_if_linux()
@unittest.skipUnless(hasattr(psutil.Process, "cpu_num"),
"platform not supported")
def test_cpu_num(self):
self.execute(self.proc.cpu_num)

@skip_if_linux()
def test_memory_info(self):
self.execute(self.proc.memory_info)
Expand Down
23 changes: 23 additions & 0 deletions psutil/tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,16 @@ def test_cpu_times_2(self):
if (max([kernel_time, ktime]) - min([kernel_time, ktime])) > 0.1:
self.fail("expected: %s, found: %s" % (ktime, kernel_time))

@unittest.skipUnless(hasattr(psutil.Process, "cpu_num"),
"platform not supported")
def test_cpu_num(self):
p = psutil.Process()
num = p.cpu_num()
self.assertGreaterEqual(num, 0)
if psutil.cpu_count() == 1:
self.assertEqual(num, 0)
self.assertIn(p.cpu_num(), range(psutil.cpu_count()))

def test_create_time(self):
sproc = get_test_subprocess()
now = time.time()
Expand Down Expand Up @@ -853,6 +863,10 @@ def test_cpu_affinity(self):
if hasattr(os, "sched_getaffinity"):
self.assertEqual(p.cpu_affinity(),
list(os.sched_getaffinity(p.pid)))
# also test num_cpu()
if hasattr(p, "num_cpu"):
self.assertEqual(p.cpu_affinity()[0], p.num_cpu())

#
p.cpu_affinity(all_cpus)
self.assertEqual(p.cpu_affinity(), all_cpus)
Expand Down Expand Up @@ -1723,6 +1737,12 @@ def cpu_times(self, ret, proc):
self.assertTrue(ret.user >= 0)
self.assertTrue(ret.system >= 0)

def cpu_num(self, ret, proc):
self.assertGreaterEqual(ret, 0)
if psutil.cpu_count() == 1:
self.assertEqual(ret, 0)
self.assertIn(ret, range(psutil.cpu_count()))

def memory_info(self, ret, proc):
for name in ret._fields:
self.assertGreaterEqual(getattr(ret, name), 0)
Expand Down Expand Up @@ -1793,6 +1813,9 @@ def is_running(self, ret, proc):

def cpu_affinity(self, ret, proc):
assert ret != [], ret
cpus = range(psutil.cpu_count())
for n in ret:
self.assertIn(n, cpus)

def terminal(self, ret, proc):
if ret is not None:
Expand Down
Loading