Usando procfs con Python

Procfs son unas librerías de Python cuya función es simplemente extraer y parsear información del directorio /proc/ de nuestro sistema GNU/Linux, esto puede ser útil a la hora de desarrollar herramientas que requieran monitorización del sistema o acceder a información acerca del hardware de nuestra máquina, estado de la memoria, procesos…

Podemos realizar su instalación de varias formas, desde repositorio tenemos el paquete python-procfs que nos instalará las dependencias necesarias, con PIP el paquete se llama procfs, o podemos descargarnos el zip de su repositorio Git, descomprimirlo e importar la librería a mano. Su uso es bastante simple aunque he visto poca documentación salvo los ejemplos que muestran en el readme de su Git, al final buceando un poco en el código de la aplicación y usando de guía la pequeña ayuda ofrecida se puede deducir fácilmente las funciones y el cometido de cada una.

Para explicar su uso he realizado una serie de scripts de ejemplo. Para ejecutarlos usaremos el intérprete Python del sistema o un script para automatizar las funciones, debemos asegurarnos de tener las librerías instaladas.

Uso Básico

El uso básico de procfs es muy simple ya que guarda semejanza con la información extraída del directorio /proc/, así que podemos usar esta como guía. Comenzaremos usando el intérprete para mostrar como se importa el objeto y como se asigna y se llama, luego las funciones las puedes copiar y pegar en un script. Para importar la librería usamos la siguiente línea:

from procfs import Proc

Ahora sólo tenemos que crear un objeto proc y comenzar a visualizar la información:

proc = Proc()
print proc.loadavg

Esto nos imprimirá por pantalla el diccionario de datos que devuelve la función loadavg los que incluye la carga del sistema, el contador de procesos y el último PID, para poder acceder a estos elementos por separado lo hacemos de la siguiente forma:

def infosystem():
    p = Proc()
    avg = p.loadavg
    print avg.average

Ahora imprimimos el contenido que hay dentro de average, dentro del objeto loadavg. Para imprimir los elementos por pantalla debemos tratar el diccionario, yo he hecho esta función de ejemplo:

def cargasistema():
    proc = Proc()
    load = proc.loadavg
    avg = load.average
    prs = load.entities
    try:
        print " ===> Carga del Sistema: "\
        + str(avg[1]) + " / " + str(avg[5]) + " / " + str(avg[15])
        print " ===> Procesos: "\
        + str(prs.current) + " Activos y " + str(prs.total) + " en total"
    except Exception, e:
        print "========= ¡ ¡ ¡ ¡ E R R O R ! ! ! ! ==========\n"\
        + "===> Se ha producido el siguiente error: \n"
        print "===> " + e

Como podemos ver, en la función almacenamos el contador de procesos y la carga del sistema, descartando la última parte del diccionario de “last pid”. Una vez extraída la información necesaria la formateamos y la imprimimos por pantalla. También he añadido una excepción por si probando me cargaba el script, para que me muestre el error.

Otras de las funciones principales de esta librería es sacar información de meminfo, el fichero de la memoria de /proc/, para su uso he hecho una función de ejemplo, es bastante simple, la función nos devuelve los valores en kilobytes, yo simplemente los he recogido y los he pasado a megabytes, luego imprimo por pantalla la información recogida, en este caso sólo recojo la memoria libre, total y cacheada.

def memoria():
    proc = Proc()
    fr = proc.meminfo.MemFree
    to = proc.meminfo.MemTotal
    ca = proc.meminfo.Cached
    frmb = fr / 1024
    tomb = to / 1024
    camb = ca / 1024
    print " ===> Memoria libre: " + str(frmb) + " Mb de " + str(tomb) + " Mb"
    print " ===> Memoria cache: " + str(camb) + " Mb"

Para recoger información relacionada con el procesador he tenido que realizar una función que sea capaz contar uno varios núcleos ya que el diccionario nos separa sus items por cpu (como en el /proc/cpuinfo, vamos), en la siguiente función los muestro con un bucle for, en el bucle accede a cada parte de cada procesador y lo muestra por pantalla, en este caso sólo he cogido el id del procesador, el vendor_id, la velocidad del procesador y el nombre del modelo.

def procesador():
    proc = Proc()
    cpus = proc.cpuinfo
    count = 0
    #print cpus
    print " ===> Informacion del Procesador:"
    try:
        for key, val in cpus.items():
            count += 1
            print "---------------------------------------------------------"
            print " ===> Informacion de la CPU  " + str(count)
            print " ======> " + val.model_name + " " + val.vendor_id
            print " ======> Velocidad: " + str(val.cpu_mhz) + " Mhz"
            print " ======> ID del Procesador: " + str(val.apicid)
            print " ======> Tamaño de Cache: " + str(val.cache_size) + " KB"
            print "=========================================================\n"
    except Exception, e:
        print "========= ¡ ¡ ¡ ¡ E R R O R ! ! ! ! ==========\n"
        + "===> Se ha producido el siguiente error: \n"
        print "===> " + e

Información de los procesos

A la hora de recoger información de varios procesos, la librería nos devuelve el listado de procesos y podemos acceder a su directorio en /proc/nomproceso y recoger la información que allí figura (de la que tengamos permisos, claro). La función tiene dos argumentos de entrada, con el primero indicamos el modo de lista, es decir, podemos consultar todos los procesos, o bien realizar búsquedas por usuario o por nombre de proceso. El segundo parámetro es el string que necesitamos buscar, ya sea usuario o proceso, también he configurado una excepción cuando en los campos de búsqueda se introduce un string vacío y otra excepción que recoge la excepción que genera la librería procfs cuando un usuario no está en el sistema.

def getProcessList(n, s=''):
    try:
        proc = Proc()
        if n == 't':
            procs = proc.processes
            return procs
        elif n == 'g':
            if s == '':
                raise ValueError('ERROR: Atributo string PROCESO vacio')
            else:
                pyt = proc.processes.cmdline(s)
                if pyt:
                    return pyt
                else:
                    re = {"Ningun proceso relacionado al nombre facilitado"}
                    return re
        elif n == 'u':
            if s == '':
                raise ValueError('ERROR: Falta el atributo ' +
                                    'string NOMBRE DE USUARIO')
            else:
                pyt = proc.processes.user(s)
                if pyt:
                    return pyt
                else:
                    re = {"Ningun proceso relacionado a ese nombre de usuario"}
                    return re
        else:
            raise ValueError('ERROR: Valor del PARAMETRO INCORRECTO o nulo')
    except KeyError, e:
        lp = {"Usuario no encontrado en el sistema", e}
        return lp
    except Exception, e:
        return e

En este caso, la función no realiza prints, si no que devuelve el resultado por return, así que en el script la he usado de la siguiente forma:

try:
    pr = getProcessList('u', 'root')
    for val in pr:
        if not hasattr(val, 'comm'):
            print val
        else:
            nomcom = str(val.comm)
            nomcom = nomcom.replace('\\n', '')
            print str(val.id) + " " + nomcom + " STATUS: " +\
                                                str(val.status.State)
except Exception, e:
    print e

La función getProcessList() la podemos usar pasándole uno o dos parámetros, si queremos que nos devuelva todos los procesos tenemos que pasarle como argumento la letra t, en caso de usar el argumento u o g, nos pedirá un string adicional (la palabra a buscar).
Si echamos un vistazo al bucle for de más arriba, vemos un if preguntando si tiene el atributo comm, la explicación de esto es simple, si hay una excepción en la función nos devuelve el error, por tanto pregunto por un atributo que se devolverá en el caso que la función no arroje una excepción, si devuelve la excepción, el condicional if no encontrará el atributo comm (el nombre del proceso) y arrojará el valor conteniendo el string de la excepción.

Por ahora ya sabemos obtener listados de procesos, estado del procesador, memoria, carga del sistema y otras muchas funciones que podemos realizar con las librerías procfs, como por ejemplo obtener el estado de la red, estadísticas de envío y recepción de paquetes y todo lo que podamos recoger del directorio /proc/.

Un Saludo!