Python: скрипт подсчёт % CPU процесса из данных в /proc

Автор: | 10/26/2014
 

PythonНе всегда есть возможность использовать psutil, поэтому — напишем свой велосипед.

Для сбора информации нам потребуются два файла:

/proc/<PID>/stat — для получения информации о процессе;
/proc/stat — для информации о процессоре.

Описание всех полей файла stat можно найти в документации ядра, в файле /usr/share/doc/kernel-doc-2.6.32/Documentation/filesystems/proc.txt, таблица 1-4.

Нас интересуют тут такие поля:

# cat /proc/stat
cpu  5887673 38913 840321 222907812 2302570 4734 16449 0 0

Данные тут отображают количество времени, которое CPU тратит на выполнение различных задач. Эти данные (time units) предоставлены как USER_HZ — сотые доли секунды.

Значение колонок по очереди:

user: обычные процессы, которые выполняются в user mode;
nice: процессы с nice в user mode;
system: процессы в kernel mode;
idle: время в простое;
iowait: ожидание операций I/O;
irq: обработка прерываний;
softirq: обработка  softirqs;
steal: «украденное» время, потраченное другими операционными системами при использовании виртуализации; (см. тут>>> и тут>>>)
guest: обработка «гостевых» (виртуальных) процессоров;

Сложив все эти данные — мы получим «общее время работы» процессора, включая время бездействия.

Для получения информации о времени, которое было затрачено на выполнение конкретным процессом — воспользуемся файлом /proc/<PID>/stat. Он достаточно большой, и весь рассматривать не будем (см. тут>>> описание всех полей). нас интересует 2 поля: 14-ое поле — utime :

(14) utime  %lu
                        Время CPU, которое этот процесс затратил в user mode;

и 15-ое — stime:

(15) stime  %lu
                        Время CPU, которое этот процесс затратил в kernel mode;

Теперь мы можем создать скрипт:

#!/usr/bin/env python

import sys
import os
import time

'''Pause before calculate data.'''

SLEEP = 1

def getpid():

    '''Checks if there is argument with PID given;
       if is - return PID to other functions.'''

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


def proct(pid):

    '''Reads /proc/<pid>/stat file, if extis.
       Get amount of 'utime' (14 item)  and 'stime' (15 item).
       Returns sum of used times by process.'''

    try:
        with open(os.path.join('/proc/', pid, 'stat'), 'r') as pidfile:
            proctimes = pidfile.readline().split(' ')
            utime = proctimes[13]
            stime = proctimes[14]
            return(float(int(utime) + int(stime)))
    except IOError as e:
        print('ERROR: %s' % e)
        sys.exit(2)


def cput():

    '''Reads /proc/stat file, if extis.
       Get amount of:
                   'user'
                   'nice'
                   'system'
                   'idle'
                   'iowait'
                   'irq'
                   'softirq'
                   'steal'
                   'steal'.
        Returns sum of total used times by CPU.'''

    try:
        with open('/proc/stat', 'r') as procfile:
            cputimes = procfile.readline()
            cputotal = 0
            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)


def main():

    '''First - create start-sum for CPU times and process times.
       pr_proctotal and pr_cputotal - save data from previous iteration.
       While loop - calculates remains between previous and current (proctotal and cputotal) values.
       Rest of times, taken by process, divided to rest of CPU times, to get part of CPU used by process,
       and multiplies to 100, to create %.
       As first loop always return 0 for cputotal - pr_cputotal - it's skipped by `except ZeroDivisionError`.'''

    proctotal = proct(getpid())
    cputotal = cput()

    try:
        while True:

            pr_proctotal = proctotal
            pr_cputotal = cputotal

            proctotal = proct(getpid())
            cputotal = cput()

            try:
                res = ((proctotal - pr_proctotal) / (cputotal - pr_cputotal) * 100)
                print('CPU%% %s by PID: %s' % ((round(res, 1)), getpid()))
            except ZeroDivisionError:
                pass

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

if __name__ == "__main__":
    main()

Документация:

$ pydoc cpu_usage_by_proccess.main
Help on function main in cpu_usage_by_proccess:

cpu_usage_by_proccess.main = main()
    First - create start-sum for CPU times and process times.
    pr_proctotal and pr_cputotal - save data from previous iteration.
    While loop - calculates remains between previous and current (proctotal and cputotal) values.
    Rest of times, taken by process, divided to rest of CPU times, to get part of CPU used by process,
    and multiplies to 100, to create %.
    As first loop always return 0 for cputotal - pr_cputotal - it's skipped by `except ZeroDivisionError`.

И результат его работы:

$ ./cpu_usage_by_proccess.py 24846
CPU% 0.0 by PID: 24846
CPU% 0.0 by PID: 24846
CPU% 0.0 by PID: 24846
CPU% 14.3 by PID: 24846
CPU% 27.6 by PID: 24846
CPU% 25.6 by PID: 24846
CPU% 21.1 by PID: 24846
CPU% 28.7 by PID: 24846

ToDo: переписать его с использованием метода sum() и убрать лишние переменные.