comprobar qué archivos están abiertos en Python

Me aparece un error en un progtwig que se supone que debe ejecutarse durante mucho tiempo y que hay demasiados archivos abiertos. ¿Hay alguna forma de que pueda hacer un seguimiento de qué archivos están abiertos para poder imprimir esa lista de vez en cuando y ver dónde está el problema?

Terminé envolviendo el objeto de archivo incorporado en el punto de entrada de mi progtwig. Descubrí que no estaba cerrando mis registradores.

import io import sys import builtins import traceback from functools import wraps def opener(old_open): @wraps(old_open) def tracking_open(*args, **kw): file = old_open(*args, **kw) old_close = file.close @wraps(old_close) def close(): old_close() open_files.remove(file) file.close = close file.stack = traceback.extract_stack() open_files.add(file) return file return tracking_open def print_open_files(): print(f'### {len(open_files)} OPEN FILES: [{", ".join(f.name for f in open_files)}]', file=sys.stderr) for file in open_files: print(f'Open file {file.name}:\n{"".join(traceback.format_list(file.stack))}', file=sys.stderr) open_files = set() io.open = opener(io.open) builtins.open = opener(builtins.open) 

Para enumerar todos los archivos abiertos de manera multiplataforma, recomendaría psutil .

 #!/usr/bin/env python import psutil for proc in psutil.process_iter(): print proc.open_files() 

La pregunta original restringe implícitamente la operación al proceso actualmente en ejecución, al que se puede acceder a través de la clase de proceso de psutil.

 proc = psutil.Process() print proc.open_files() 

Por último, querrá ejecutar el código usando una cuenta con los permisos apropiados para acceder a esta información o puede ver errores AccessDenied.

En Linux, puede ver los contenidos de /proc/self/fd :

 $ ls -l /proc/self/fd/ total 0 lrwx------ 1 foo users 64 Jan 7 15:15 0 -> /dev/pts/3 lrwx------ 1 foo users 64 Jan 7 15:15 1 -> /dev/pts/3 lrwx------ 1 foo users 64 Jan 7 15:15 2 -> /dev/pts/3 lr-x------ 1 foo users 64 Jan 7 15:15 3 -> /proc/9527/fd 

Aunque las soluciones que se abren sobre ese wrap-up son útiles para el código propio, estaba depurando a mi cliente en una biblioteca de terceros, incluido un código de extensión c, por lo que necesitaba una forma más directa. La siguiente rutina funciona bajo darwin y (espero) otros entornos unix:

 def get_open_fds(): ''' return the number of open file descriptors for current process .. warning: will only work on UNIX-like os-es. ''' import subprocess import os pid = os.getpid() procs = subprocess.check_output( [ "lsof", '-w', '-Ff', "-p", str( pid ) ] ) nprocs = len( filter( lambda s: s and s[ 0 ] == 'f' and s[1: ].isdigit(), procs.split( '\n' ) ) ) return nprocs 

Si alguien puede extenderse para ser portátil a Windows, estaría agradecido.

En Linux, puede usar lsof para mostrar todos los archivos abiertos por un proceso.

En Windows, puede usar Process Explorer para mostrar todos los identificadores de archivos que pertenecen a un proceso.

Existen algunas limitaciones a la respuesta aceptada, ya que no parece contar las tuberías. Tenía una secuencia de comandos python que abrió muchos subprocesos y no cerró correctamente las canalizaciones de entrada, salida y error estándar, que se usaron para la comunicación. Si utilizo la respuesta aceptada, no podrá contar estas canalizaciones abiertas como archivos abiertos, pero (al menos en Linux) son archivos abiertos y cuentan para el límite de archivos abiertos. La solución lsof -p sugerida por sumid y shunc funciona en esta situación, porque también muestra las tuberías abiertas.

Como se dijo anteriormente, puede listar fds en Linux en / proc / self / fd , aquí hay un método simple para listarlos programáticamente:

 import os import sys import errno def list_fds(): """List process currently open FDs and their target """ if sys.platform != 'linux2': raise NotImplementedError('Unsupported platform: %s' % sys.platform) ret = {} base = '/proc/self/fd' for num in os.listdir(base): path = None try: path = os.readlink(os.path.join(base, num)) except OSError as err: # Last FD is always the "listdir" one (which may be closed) if err.errno != errno.ENOENT: raise ret[int(num)] = path return ret 

Supongo que está filtrando descriptores de archivos. Es probable que desee revisar su código para asegurarse de que está cerrando todos los archivos que abre.

Obtenga una lista de todos los archivos abiertos. handle.exe es parte de la Suite Sysinternals de Microsoft. Una alternativa es el módulo psutil Python, pero encuentro que ‘handle’ imprimirá más archivos en uso.

Esto es lo que hice. Kludgy código de advertencia.

 #!/bin/python3 # coding: utf-8 """Build set of files that are in-use by processes. Requires 'handle.exe' from Microsoft SysInternals Suite. This seems to give a more complete list than using the psutil module. """ from collections import OrderedDict import os import re import subprocess # Path to handle executable handle = "E:/Installers and ZIPs/Utility/Sysinternalssuite/handle.exe" # Get output string from 'handle' handle_str = subprocess.check_output([handle]).decode(encoding='ASCII') """ Build list of lists. 1. Split string output, using '-' * 78 as section breaks. 2. Ignore first section, because it is executable version info. 3. Turn list of strings into a list of lists, ignoring first item (it's empty). """ work_list = [x.splitlines()[1:] for x in handle_str.split(sep='-' * 78)[1:]] """ Build OrderedDict of pid information. pid_dict['pid_num'] = ['pid_name','open_file_1','open_file_2', ...] """ pid_dict = OrderedDict() re1 = re.compile("(.*?\.exe) pid: ([0-9]+)") # pid name, pid number re2 = re.compile(".*File.*\s\s\s(.*)") # File name for x_list in work_list: key = '' file_values = [] m1 = re1.match(x_list[0]) if m1: key = m1.group(2) # file_values.append(m1.group(1)) # pid name first item in list for y_strings in x_list: m2 = re2.match(y_strings) if m2: file_values.append(m2.group(1)) pid_dict[key] = file_values # Make a set of all the open files values = [] for v in pid_dict.values(): values.extend(v) files_open = sorted(set(values)) txt_file = os.path.join(os.getenv('TEMP'), 'lsof_handle_files') with open(txt_file, 'w') as fd: for a in sorted(files_open): fd.write(a + '\n') subprocess.call(['notepad', txt_file]) os.remove(txt_file) 

Puede usar la siguiente secuencia de comandos. Se basa en la respuesta de Claudiu. Aborda algunos de los problemas y agrega características adicionales:

  • Imprime un seguimiento de la stack donde se abrió el archivo
  • Imprime en la salida del progtwig
  • Soporte de argumento de palabra clave

Aquí está el código y un enlace a la esencia , que posiblemente esté más actualizado.

 """ Collect stacktraces of where files are opened, and prints them out before the program exits. Example ======== monitor.py ---------- from filemonitor import FileMonitor FileMonitor().patch() f = open('/bin/ls') # end of monitor.py $ python monitor.py ---------------------------------------------------------------------------- path = /bin/ls > File "monitor.py", line 3, in  > f = open('/bin/ls') ---------------------------------------------------------------------------- Solution modified from: https://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python """ from __future__ import print_function import __builtin__ import traceback import atexit import textwrap class FileMonitor(object): def __init__(self, print_only_open=True): self.openfiles = [] self.oldfile = __builtin__.file self.oldopen = __builtin__.open self.do_print_only_open = print_only_open self.in_use = False class File(self.oldfile): def __init__(this, *args, **kwargs): path = args[0] self.oldfile.__init__(this, *args, **kwargs) if self.in_use: return self.in_use = True self.openfiles.append((this, path, this._stack_trace())) self.in_use = False def close(this): self.oldfile.close(this) def _stack_trace(this): try: raise RuntimeError() except RuntimeError as e: stack = traceback.extract_stack()[:-2] return traceback.format_list(stack) self.File = File def patch(self): __builtin__.file = self.File __builtin__.open = self.File atexit.register(self.exit_handler) def unpatch(self): __builtin__.file = self.oldfile __builtin__.open = self.oldopen def exit_handler(self): indent = ' > ' terminal_width = 80 for file, path, trace in self.openfiles: if file.closed and self.do_print_only_open: continue print("-" * terminal_width) print(" {} = {}".format('path', path)) lines = ''.join(trace).splitlines() _updated_lines = [] for l in lines: ul = textwrap.fill(l, initial_indent=indent, subsequent_indent=indent, width=terminal_width) _updated_lines.append(ul) lines = _updated_lines print('\n'.join(lines)) print("-" * terminal_width) print()