Calculate CPU% by process from /proc/stat and /proc//stat We don't have the possibility to install modules like psutil on some boxes, thus I decided to write a simple script to calculate the CPU percentage by the given PID.

It uses /proc/stat to get CPU times and /proc/&lt;pid&gt;/stat to get the process CPU usage.

I added top output here, just to compare results and to be sure it's (about) correct. But - I don't like to use external utilities in Python scripts, thus top will be removed.


OS: CentOS 6.5
Python: 2.6.6


Next - it calculates % of CPU, taken by this process:

#!/usr/bin/env python

import sys
import os
import time
import subprocess

if len(sys.argv) == 2:
    pid = sys.argv[1]
else:
    print('No PID specified. Usage: %s &lt;PID&gt;' % os.path.basename(__file__))
    sys.exit(1)


def proct(pid):
    try:
        with open(os.path.join('/proc/', pid, 'stat'), 'r') as pidfile:
            proctimes = pidfile.readline()
            # get utime from /proc/&lt;pid&gt;/stat, 14 item
            utime = proctimes.split(' ')[13]
            # get stime from proc/&lt;pid&gt;/stat, 15 item
            stime = proctimes.split(' ')[14]
            # count total process used time
            proctotal = int(utime) + int(stime)
            return(float(proctotal))
    except IOError as e:
        print('ERROR: %s' % e)
        sys.exit(2)


def cput():
    try:
        with open('/proc/stat', 'r') as procfile:
            cputimes = procfile.readline()
            cputotal = 0
            # count from /proc/stat: user, nice, system, idle, iowait, irc, softirq, steal, guest
            for i in cputimes.split(' ')[2:]:
                i = int(i)
                cputotal = (cputotal + i)
            return(float(cputotal))
    except IOError as e:
        print('ERROR: %s' % e)
        sys.exit(3)

# assign start values before loop them
proctotal = proct(pid)
cputotal = cput()

try:
    while True:

        # for test, to compare results
        proc = subprocess.Popen("top -p %s -b -n 1 | grep -w mysql | awk '{print $9}'" % pid, shell=True, stdout=subprocess.PIPE)
        cpu_percentage = proc.communicate()
        print('With TOP: %s' % (cpu_percentage[0].rstrip('\n')))

        pr_proctotal = proctotal
        pr_cputotal = cputotal

        proctotal = proct(pid)
        cputotal = cput()

        try:
            res = ((proctotal - pr_proctotal) / (cputotal - pr_cputotal) * 100)
            print('With script: %s\n' % round(res, 1))
        except ZeroDivisionError:
            pass

        time.sleep(1)
except KeyboardInterrupt:
    sys.exit(0)


And its results:




$ ./cpu_usage_by_proccess.py 24846
With TOP: 0.0
With script: 0.0

With TOP: 0.0
With script: 0.0

With TOP: 0.0
With script: 0.7

With TOP: 14.0
With script: 4.8

With TOP: 32.0
With script: 34.4

With TOP: 29.9
With script: 32.6

With TOP: 30.0
With script: 35.1

With TOP: 18.0
With script: 26.5

With TOP: 20.0
With script: 20.6



What's done incorrectly here, or what can be done better (maybe a more "Pythonic way")?
You have code outside of functions or an if __name__ == "__main__" guard (which isn't ideal in itself), both above and below the various function definitions, which makes the code relatively difficult to follow. I would structure the script as follows:

import os
import subprocess
import sys
import time

def proct(pid):
    ...

def cput():
    ...

def main(pid):
    ...

def parse_args():
    ...
    return pid

if __name__ == "__main__":
    main(parse_args())


Now if you want to import this functionality elsewhere, it's easy. Also, note that I have made the imports alphabetical, per the style guide.



I would factor out any temporary variables, e.g.

proctotal = int(utime) + int(stime)
return(float(proctotal))


can be written:

return float(int(utime) + int(stime))




Try not to do the same thing twice, e.g. 

proctimes = pidfile.readline()
utime = proctimes.split(' ')[13]
stime = proctimes.split(' ')[14]


could be rewritten

proctimes = pidfile.readline().split(' ')
utime = proctime[13] # you could also call int here
stime = proctime[14]


which only calls str.split once.



Consider adding docstrings to your functions, rather than the inline comments (some of which are redundant - utime = proctimes.split(' ')[13] does not need the explanation # get utime from /proc/&lt;pid&gt;/stat, 14 item, which just adds an extra line to read). Better function names would also help with clarity - e.g. cpu_total rather than cput.



The main part of cput:

cputimes = procfile.readline()
cputotal = 0
for i in cputimes.split(' ')[2:]:
    i = int(i)
    cputotal = (cputotal + i)
return(float(cputotal))


can be rewritten in a functional style:

return sum(map(float, procfile.readline().split(' ')[2:]))


or using a generator expression:

return sum(float(s) for s in procfile.readline().split(' ')[2:])