Je reçois une erreur dans un programme qui est censé s'exécuter pendant trop longtemps car trop de fichiers sont ouverts. Existe-t-il un moyen de savoir quels fichiers sont ouverts afin que je puisse imprimer cette liste de temps en temps et voir où est le problème?
J'ai fini par encapsuler l'objet fichier intégré au point d'entrée de mon programme. J'ai découvert que je ne fermais pas mes enregistreurs.
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)
Pour répertorier tous les fichiers ouverts de manière multiplateforme, je recommanderais psutil .
#!/usr/bin/env python
import psutil
for proc in psutil.process_iter():
print proc.open_files()
La question d'origine limite implicitement l'opération au processus en cours d'exécution, accessible via la classe Process de psutil.
proc = psutil.Process()
print proc.open_files()
Enfin, vous voudrez exécuter le code en utilisant un compte avec les autorisations appropriées pour accéder à ces informations ou vous pouvez voir des erreurs AccessDenied.
Sous Linux, vous pouvez consulter le contenu 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
Bien que les solutions ci-dessus que le wrapper ouvre soient utiles pour son propre code, je déboguais mon client vers une bibliothèque tierce comprenant un code d'extension c, j'avais donc besoin d'une méthode plus directe. La routine suivante fonctionne sous darwin et (j'espère) d'autres environnements de type 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 quelqu'un peut s'étendre pour être portable sous Windows, je lui en serais reconnaissant.
Sous Linux, vous pouvez utiliser lsof
pour afficher tous les fichiers ouverts par un processus.
Sous Windows, vous pouvez utiliser Process Explorer pour afficher tous les descripteurs de fichiers appartenant à un processus.
Comme dit précédemment, vous pouvez lister les fds sur Linux dans / proc/self/fd, voici une méthode simple pour les lister par programme:
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
La réponse acceptée présente certaines limites, car elle ne semble pas compter les tuyaux. J'avais un script python qui ouvrait de nombreux sous-processus et ne fermait pas correctement les canaux d'entrée, de sortie et d'erreur standard, qui étaient utilisés pour la communication. Si j'utilise la réponse acceptée, elle échouera à compter ces canaux ouverts comme des fichiers ouverts, mais (au moins sous Linux) ce sont des fichiers ouverts et comptent dans la limite des fichiers ouverts. La solution lsof -p
suggérée par sumid et shunc fonctionne dans cette situation, car elle vous montre également les tuyaux ouverts.
Obtenez une liste de tous les fichiers ouverts. handle.exe
fait partie de Microsoft Sysinternals Suite . Une alternative est le psutil Python, mais je trouve que 'handle' imprimera plus de fichiers en cours d'utilisation.
Voici ce que j'ai fait. Avertissement de code Kludgy.
#!/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)
Vous pouvez utiliser le script suivant. Il s'appuie sur Claudiu réponse . Il résout certains des problèmes et ajoute des fonctionnalités supplémentaires:
Voici le code et un lien vers le Gist , qui est peut-être plus à jour.
"""
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 <module>
> 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()
Je suppose que vous perdez des descripteurs de fichiers. Vous voudrez probablement parcourir votre code pour vous assurer que vous fermez tous les fichiers que vous ouvrez.