Быстрый бесплатный сканер использования диска с открытым исходным кодом

Этот блог изначально был опубликован на https://beijaflor.io/blog/06-2020/filesaver-freeing-up-space/

Поскольку у меня есть последний MacBook, я могу работать только пару месяцев, прежде чем на нем закончится свободное место на диске. Это довольно плохая ситуация, когда, чтобы немного сэкономить на размере SSD, мне приходится тратить время на очистку больших файлов, таких как образцы музыки, виртуальные машины, node_modules и так далее.

Тогда я решил сэкономить много времени 😂 и вместо того, чтобы купить одно из доступных решений для «очистки диска», я потратил некоторое время, пробуя какое-то программное обеспечение с открытым исходным кодом, и в конце концов решил написать FileSaver, быстрый многопоточный сканер использования диска.

На моем MacBook сканирование 5,3 миллиона файлов занимает 100 секунд при пропускной способности около 51 000 файлов в секунду. Компромиссом является высокое использование памяти около 2 ГБ.

Что я делал с coreutils

Я использовал следующую комбинацию команд, чтобы найти размеры:

# Find file sizes
du -h ./directory | \
    # Sort by size
    # (just `sort` on linux, requires gnu coreutils on OSX)
    gsort -h      | \
    # Put on a pager
    less

Это отлично работает, за исключением того, что:

  • Это относительно медленно
  • Это не интерактивно

duc

Я также попробовал отличную альтернативу вышеперечисленному, которая называется duc.

duc — отличная утилита с открытым исходным кодом, написанная на C, которая выполняет эту задачу немного быстрее и с интерактивными опциями. Он также может генерировать индекс, который можно использовать повторно, поэтому нам не нужно сканировать все файлы при поиске мест для очистки.

Тем не менее, я обнаружил, что на моем заполненном диске очень медленно генерировался индекс для всего этого, включая системные каталоги, такие как /Library и /usr/local. Как разработчик, вокруг них было много мусора.

Параллельное сканирование размера

Я профилировал duc и обнаружил, что его однопоточная природа была небольшим узким местом. Запись в индекс заблокированных размеров файлов сканирования и вычисление агрегированных размеров также, вероятно, скомпрометировали пропускную способность.

Я пробежался по этому вопросу:

Некоторые мысли об индексации, фильтрации, распараллеливании и т. д. https://github.com/zevv/duc/issues/205

Мысль о том, что я могу написать многопоточную программу и получить значительный прирост производительности, не покидала меня.

Я хотел внести свой вклад в duc, но у него было два основных недостатка:

  • Лицензия GPL — YMMV
  • Это написано на С

2-й пункт как раз потому, что, немного зная C++, я не видел смысла переходить на C.

Затем я разработал и повторил архитектуру параллельного сканирования/алгоритм агрегации, который дает довольно хорошие результаты с точки зрения пропускной способности.

Обзор проблемной области и агрегации

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

Учитывая определенное path для сканирования, мы хотим lstat его. Это даст нам его размер и тип файла. Если этот path является каталогом, мы хотим просканировать все его дочерние элементы.

Этот путь будет «завершен», когда все его дочерние элементы будут завершены или если lstat скажет, что это не каталог.

Таким образом, мы должны постоянно сканировать дочерние элементы и по мере их поступления обновлять количество завершенных дочерних элементов родительского пути (и делать это рекурсивно вверх).

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

Например, представьте, что у нас есть следующая структура каталогов:

content
└── blog
    └── filesaver-freeing-up-space
        ├── index.md
        └── logo.png

1 Мы начнем сканирование с /content

  • Поскольку это каталог, мы читаем его дочерние элементы.
  • Сохраняется его размер и количество дочерних элементов.

2 Мы делаем то же самое в /content/blog, а затем /content/blog/filesaver-freeing-up-space

3 На данный момент наше состояние выглядит следующим образом:

  • /content - 1 ожидающий дочерний путь
  • /content/blog - 1 ожидающий дочерний путь
  • /content/blog/filesaver-freeing-up-space - 2 ожидающих дочерних пути

4 Мы сканируем index.md

  • /content/blog/filesaver-freeing-up-space/index.md - завершено + 100Кб
  • /content/blog/filesaver-freeing-up-space — 1 ожидающий дочерний путь (+ 100 КБ)
  • Счетчики ожидания других путей сохраняются, но их общие размеры обновляются.
  • /content/blog — 1 ожидающий дочерний путь (+ 100 КБ)
  • /content — 1 ожидающий дочерний путь (+ 100 КБ)

5 Мы сканируем logo.png

  • /content/blog/filesaver-freeing-up-space/logo.png - завершено + 300КБ
  • /content/blog/filesaver-freeing-up-space - завершено (+300КБ)
  • /content/blog - завершено (+300КБ)
  • /content - завершено (+300КБ)

Архитектура

FileSaver — это простая программа, но она структурирована. Архитектура следующая.

Компоненты

  1. Очередь на основе блокировки
  2. Рабочие потоки
  3. Тема чтения
  4. Поток пользовательского интерфейса

Очередь

Мы передаем данные между потоками, используя неинтересную очередь на основе блокировки, плюс одну дополнительную блокировку по размеру/состоянию ожидания, поскольку поток пользовательского интерфейса будет читать их во время их обработки.

Для этого у нас есть filesaver::data::WorkQueue

Рабочие потоки

Рабочие работают с двумя очередями:

  • workQueue предоставляет входящие пути для сканирования
  • В resultQueue добавлены записи с их размерами, типами и дочерними путями.

Рабочий поставит в очередь всех дочерних элементов, которые он найдет.

Тема чтения

Поток чтения выполняет агрегацию, как описано в обзоре агрегации. Состояние сохраняется в гигантской хеш-карте (std::unordered_map), сопоставляющей пути с их размерами и количеством ожидающих дочерних элементов.

UI

Пользовательский интерфейс использует AppKit и написан на Objective-C++. Он опрашивает состояние каталогов, видимых на экране.

Распределение

На данный момент FileSaver доступен на его странице GitHub. Структура проекта позволяет удобно включать его как библиотеку в другую кодовую базу. Хотя для этого варианта использования все еще могут быть улучшения, такие как организация общедоступных заголовков и система сборки, он проходит большую часть пути и должен иметь хорошие документы DoxyGen.

Код распространяется под лицензией MIT.

Загрузить FileSaver