Backend разработчик
0,0
рейтинг
25 марта 2012 в 14:51

Разработка → Мониторинг за изменениями файловой системы

В поисках готового велосипеда для решения задачи мониторинга за изменениями в ФС с поддержкой linux+freebsd наткнулся на приятную python либу watchdog (github, packages.python.org). Которая помимо интересных мне ОС поддерживает также MacOS (есть своя специфика) и Windows.
Тем, кому данный вопрос интересен и кого не отпугнет индийское происхождение автора, прошу .

Установка


Можно взять готовую версию из PIP:
$ pip install watchdog
Сам PIP ставится как пакет python-pip, порт devel/py-pip, etc.
Либо собрать из исходников через setup.py.

Достаточно подробно все расписано в оригинальном руководстве. Правда там описание версии 0.5.4, а сейчас актуальна 0.6.0. Однако, вся разница в правке копирайтов и замене отступа в 4 пробела на отступ в 2. «Google code style» :)

Вообще, там довольно много особенностей сборки по версиям самого python так и по целевой платформе. Они все описаны по ссылке выше, но если будет нужно, допишу в статью вкратце на русском.

Кроме того, собрать модуль можно на несовместимой ОС, но тогда в дело вступится fallback-реализация, делающая «слепки» структуры ФС с последующими сравнениями. Возможно, так кто-то и делал у себя при решении подобной задачи :)

Сам же я пробовал собрать под ubuntu 11.4 и freebsd-8.2 RELEASE, каких-либо проблем при сборке и работе не возникло.

Базовый пример


Предположим, что нас интересуют изменения по некоему пути /path/to/smth, связанные с созданием, удалением и переименованием файлов и директорий.

Подключаем:
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

Класс Observer выбирается в /observers/__init__.py исходя из возможностей вашей ОС, так что нет необходимости самостоятельно решать, что же выбрать.
Класс FileSystemEventHandler является базовым классом обработчика событий изменения. Он мало что умеет, но мы научим его потомка:
class Handler(FileSystemEventHandler):
    def on_created(self, event):
        print event

    def on_deleted(self, event):
        print event

    def on_moved(self, event):
        print event

Полный список методов можно увидеть в самом FileSystemEventHandler.dispatch: on_modified, on_moved, on_created, on_deleted.

Запускаем это все:
observer = Observer()
observer.schedule(Handler(), path='/path/to/smth', recursive=True)
observer.start()


Observer является относительно далеким потомком threading.Thread, соотвественно после вызова start() мы получаем фоновый поток, следящий за изменениями. Так что если скрипт сразу завершится, то ничего толкового мы не получим. Реалиация ожидания зависит в первую очередь от применения модуля в реальном проекте, сейчас же можно просто сделать костыль:
try:
    while True:
        time.sleep(0.1)
except KeyboardInterrupt:
    observer.stop()
observer.join()

Ждем событий изменений ФС до прихода Ctrl+C (SIGINT), после чего говорим нашему потоку завершиться и ждем, пока он это выполнит.

Запускаем скрипт, идем по нашему пути и:
# mkdir foo
# touch bar
# mv bar baz
# cd foo/
# mkdir foz
# mv ../baz ./quz
# cp ./quz ../hw
# cd ..
# rm -r ./foo
# rm -f ./*


На выходе скрипта имеем:
<DirCreatedEvent: src_path=/path/to/smth/foo>
<FileCreatedEvent: src_path=/path/to/smth/bar>
<FileMovedEvent: src_path=/path/to/smth/bar, dest_path=/path/to/smth/baz>
<DirCreatedEvent: src_path=/path/to/smth/foo/foz>
<FileMovedEvent: src_path=/path/to/smth/baz, dest_path=/path/to/smth/foo/quz>
<FileCreatedEvent: src_path=/path/to/smth/hw>
<FileDeletedEvent: src_path=/path/to/smth/foo/quz>
<DirDeletedEvent: src_path=/path/to/smth/foo/foz>
<DirDeletedEvent: src_path=/path/to/smth/foo>
<FileDeletedEvent: src_path=/path/to/smth/hw>

В методы нашего класса Handler в поле event приходят потомки FileSystemEvent, перечисленные в watchdog/events.py.
У всех есть свойства src_path, is_directory, event_type («created», «deleted», и т.п.). Для события moved добавляется свойство dest_path.

Ну если вы больше ничего не хотите… А разве ещё что-нибудь есть?


На закуску у нас остаются потомки FileSystemEventHandler:


PatternMatchingEventHandler можно использовать для получения событий только о тех узлах ФС, имена которых подходят по маске с правилами:
  • * любые символы
  • ? любой единичный символ
  • [seq] любой единичный символ из указанных
  • [!seq] любой единичный символ НЕ из указанных

Задание правил выполняется при создании:
class Handler(PatternMatchingEventHandler): pass
event_handler = Handler(
    patterns = ['*.py*'],
    ignore_patterns = ['cache/*'],
    ignore_directories = True,
    case_sensitive = False
)

observer = Observer()
observer.schedule(event_handler, path='/home/LOGS/', recursive=True)


RegexMatchingEventHandler делает тоже самое, но с явным указанием regexp-выражений в конструкторе:
class Handler(RegexMatchingEventHandler): pass
event_handler = Handler(
    regexes = ['\.py.?'],
    ignore_regexes = ['cache/.*'],
    ignore_directories = True,
    case_sensitive = False
)


PatternMatchingEventHandler внутри себя в итоге транслирует шаблоны в регулярки, так что должен работать медленнее из-за наличия такого оверхеда.

Наконец, LoggingEventHandler выводит все в лог через logging.info().

— Вот и все. Может кому пригодится.

P.S.
При слежении за директорией, в которой (и в ее дочерних) содержатся папки/файлы не с ascii именованием, возникнет исключение exceptions.UnicodeEncodeError в глубинах watchdog'а. В Linux (inotify) он возникает в watchdog.observers.inotify.Inotify._add_watch.
Причина — чтение содержимого в ascii кодировке.
Для исправления ситуации можно пропатчить метод:
from watchdog.observers.inotify import Inotify
_save = Inotify._add_watch
Inotify._add_watch = lambda self, path, mask: _save(self, path.encode('utf-8'), mask)


Вот пример исходной строки, и ее repr() до и после обработки encode():
/home/atercattus/.wine/drive_c/users/Public/Рабочий стол
u'/home/atercattus/.wine/drive_c/users/Public/\u0420\u0430\u0431\u043e\u0447\u0438\u0439
        \u0441\u0442\u043e\u043b'
'/home/atercattus/.wine/drive_c/users/Public/\xd0\xa0\xd0\xb0\xd0\xb1\xd0\xbe\xd1\x87\xd0\xb8\xd0\xb9
        \xd1\x81\xd1\x82\xd0\xbe\xd0\xbb'
Алексей @AterCattus
карма
103,0
рейтинг 0,0
Backend разработчик
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (34)

  • 0
    Ну, если Вы уже в тэгах упомянули inotify, то надо было посвятить нас — чем он Вас не устроил? Особенно в свете существования incron'а.
    • 0
      Мне также нужна поддержка FreeBSD, я написал об этом.
      • 0
        Ну так механизм inotify там в полной красе. А вот incron'а увы нету…
        • 0
          Насколько мне известно, на фре вместо inotify используется kqueue.
          • 0
            kqueue это конечно да, Но мне казалось, что inotify из Linux там тоже присутствует.
            • 0
              нет конечно. зачем? inotify это нашлёпка в ядро Linux. kqueue это базовая функциональность FreeBSD. зачем ему нашлёпка inotify?
              • 0
                Да вот, сейчас искал так и не нашёл. Странно, откуда у меня это ощущение, что видел я во фре inotify :-( Постараемся больше смуту не вносить.
                • 0
                  нене, я смотрел специально. просто не нужно. тем более, inotify догнал kqueue совсем недавно. а так там были некоторые проблемы.
              • 0
                У Inotify:
                Гораздо выше лимит на кол-во отслеживаемых файлов
                Нет проблем с отмонтированием ФС на которых отслеживаются файлы (если поставить следилку на файл а потом попытаться отмонтировать этот раздел, то ничего не выйдет)
                • 0
                  нет там лимита. там немного удобнее тем, что в каталоге inotify будет давать нотификацию по всем файлам, не беря отдельно. зато там долгие годы были иные проблемы.
                  с монтированием да — придётся ловить отдельно
                  • 0
                    Нет лимита? 1024 же по умолчанию лимит на кол-во файловых дескрипторов. Да, можно увеличить, но не всегда.
                    • 0
                      Где такой лимит?
                      • 0
                        packages.python.org/watchdog/installation.html#dependencies

                        BSD variants come with kqueue which programs can use to monitor changes to open file descriptors. Because of the way kqueue(2) works, watchdog needs to open these files and directories in read-only non-blocking mode and keep books about them.

                        watchdog will automatically open file descriptors for all new files/directories created and close those for which are deleted.

                        The maximum number of open file descriptor per process limit on your operating system can hinder watchdog‘s ability to monitor files.
                        • 0
                          Где 1024? :)
                          • 0
                            freebsd:
                            open files (-n) 294912

                            ubuntu:
                            open files (-n) 1024

                            • 0
                              Ну так мы про FreeBSD? :)
                            • 0
                              Хм, ну если на FreeBSD так по умолчанию и без ущерба производительности то ок. MacOS под рукой нет, как там с этим не могу сейчас сказать.
                              Но да, я именно про ulimit -n говорил
                              • 0
                                В БСД упрется в
                                kern.maxfiles: 12328
                                kern.maxfilesperproc: 11095

                                К счастью без зазрения совести можно сделать так.
                                Главное помнить об оверхеде.

                                kern.maxfiles: 25000000
                                kern.maxfilesperproc: 22500000

              • 0
                Еще момент вспомнил про KQueue чисто субъективный — довольно сложно было найти хорошую документацию/примеры/хауту по слежению за файлами через KQueue.
                Просто где ни читаешь — на Windows RDCW, примеры, описания, на Linux Inotify + примеры, описания. А про FreeBSD в лучшем случае напишут «аналогом Inotify на FreeBSD является KQueue» и ни примера ни подробностей. Т.е. о такой функциональности в принципе слышали, но никто ее не видел. Когда разберешься, то уже все просто, но в начале даже непонятно: «работает ли это на практике или просто для галочки, а если и работает то почему так трудно найти примеры, наверное криво работает рас никто не пользуется». Да и если гуглить KQueue то в основном про слежение за сокетами пишут.
            • 0
              Есть эмуляция INotify на KQueue github.com/dmatveev/libinotify-kqueue
  • 0
    Еще есть вполне удобный auditd, auditctl.
    • 0
      Мне нужно было кроссплатформенное решение мониторинга в самом python, т.к. результаты в нем же потом и обрабатываются.
  • +7
    Имел опыт написания асинхронной следилки под MacOS, Windows и Linux на Python (Twisted)…
    Задача была отслеживать рекурсивно каталог и все изменения в нем. При этом, по возможности, обойтись без отдельного треда (т.е. следить в EventLoop Twisted-а).
    По легкости написания:
    Windows самый простой O_o (ReadDirectoryChangesW + WaitForMultipleEvents) Опционально асинхронное, поддерживает все события, рекурсию, переименования. Для слежения навешивается обработчик на отслеживаемый корневой каталог.
    Linux посерединке (Inotify) Опционально асинхронное, поддерживает все события, но не поддерживает рекурсию и переименование (только через механизм «Cookies»). Для слежения навешивается обработчик на отслеживаемый корневой каталог и все его дочерние каталоги.
    MacOS самый геморройный (Kqueue) — Асинхронное, поддерживает все события (вроде), не поддерживает рекурсию, переименования (засчитывается как удаление и создание). Для слежения навешивается обработчик на отслеживаемый корневой каталог и все его дочерние каталоги и НА ВСЕ ФАЙЛЫ, если файлов больше 1024 нужно лезть в ulimit. Ну и желательно иметь в памяти слепок структуры каталогов, чтобы отслеживать переименования. На FreeBSD все точно так же, но есть проблема с отмонтированием ФС на которых следят. На MacOS есть еще механизм FSEvents, но у него не нашел асинхронного варианта.
    • 0
      или WaitForMultipleObjects, не помню.
    • 0
      FSEventStream на Mac OS X отлично поддерживает асинхронные нотификации (FSEventStreamScheduleWithRunLoop). Допускаю, что с пайтоном это дружит плохо.
      • 0
        Гуглится масса python-скриптов где используется FSEventStreamScheduleWithRunLoop.
        Так что видимо вполне себе дружит :)
  • 0
    можно еще использовать rsync для мониторинга изменений удаленно, прикрутить к крону или подобному скрипту, вызывать из java и тому прочее… вот, в целом отличная статья, небольшая и полезная!
    • 0
      Целевым обработчиком событий в ФС является все тот же python, который предпринимает некоторые действия. Чем меньше внешних вызовов, тем лучше.

      А rsync показывает не самую высокую производительность на большом числе файлов и нагруженных дисках… Как раз те условия, где будет использоваться система с python.
  • 0
    А чем aide не устроила?
    • 0
      Результаты мониторинга обрабатываются там же, внутри python. Цель не проверять целостность и неизменность ФС, а оперативно учитывать изменения ФС в основной логике программы.
      Модуль из статьи и AIDE частично пересекаются в реализации, но предназначены для разных вещей.
  • 0
    Во FreeBSD есть mtree.
    • 0
      и как оно помогает в реальном времени отслеживать изменения файлов?
      • 0
        Можно в крон запихнуть.
        Но, как и в варианте ТС, лишние IOPS могут тормозить сервер.
        • 0
          откуда там лишние IOPS'ы? там нотификации из ядра о событиях. сам inotify ничего с диска не читает.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.