ESPNWho is on upset alert in Week 9? Connelly breaks down 31 key matchupsInquirerBARMM lawmakers arrive for inaugural session amid tight securityPunchSoldiers kill 48 terrorists, repel Borno, Yobe attacksThe Jerusalem PostParashat Vayera: What a vote cannot decideBollywood HungamaSCOOP: Anirudh exits Shah Rukh Khan’s King due to packed schedule; gifts SRK the theme track as Sachin-Jigar take overDaily MaverickTROLL TRAUMA: SA Olympic triathlete Henri Schoeman opens up on cyberbullying dimming his lightEl ComercioElecciones en Israel: los 5 escenarios para formar gobierno tras comicios sin mayoría absolutaZDF heuteEntdecken Sie das ZDF-NachrichtenstudioSBS 뉴스[정치사냥꾼] 대통령의 'SNS 정치' 논란…여당서도 자제 촉구?20 MinutenHeftige Attacken: F1-Fahrer kriegt 13’000 MorddrohungenESPN CricinfoPhillips' century, Duffy's four-for topple IndiaGolem.deQuartalszahlen: Audi-Gewinn bricht ein
The Daily Newsstand · Free, Always
Friday, October 30, 2026

Оперативка есть? А если найду?

Translate

У меня все еще работает CHUWI CoreBox, который уже появлялся в предыдущих статьях: Core i7-13620H, 16 ГБ LPDDR5 (а жаль!), SSD на 512 ГБ. Он успел побыть прокси, NAS, VPN, сервером Lineage 2 и складом 3D-моделей для печати. Склад потом переехал на отдельный накопитель, а сбор моделей я остановил. Даже качественные модели скачивались гораздо быстрее, чем я успевал их печатать. На освободившемся мини-ПК шла разработка своего продукта, ибо я привык к linux, и там же крутились тесты. С обычными проверками еще можно было жить, но мутационное тестирование занимало часы. Хотелось продолжать работу, пока оно выполняется, а свободных ресурсов на той же машине было немного.

Зато рядом стоял мой любимый игровой компьютер: Ryzen 9 9950X3D и золотые 64 ГБ Kingston FURY Beast DDR5-6000 CL30. Память до 64 ГБ я докупал в 2026 году. Кто в этом году собирал комплуктер, тот понимает, почему я сейчас это упоминаю и ставлю на этом акцент.
К этому моменту я уже смотрел по квартире, у чего еще есть оперативка. Поглядывал дергающимся глазом на робот-пылесос и духовку. Если они подключаются к Wi-Fi, значит, они чем-то думают и чем-то помнят.

Но начал все-таки с ПК. Windows оставил на месте, потому что игры нужно на чем-то играть, поставил WSL с Ubuntu и отдельным Docker. Разработку оставил на мини-ПК, а тяжелые проверки решил вынести на игровую машину. Первый измеренный запуск PIT на ней занял больше девяти часов и закончился с ошибкой памяти. Это же разочарование я ловил много раз на мини-пк, разбирал по этому разочарованию логи, в течение десятков часов, в надежде исключить дефицит памяти. Смотрим.

Характеристика

Мини-ПК

Домашний ПК

Процессор

Intel Core i7-13620H

AMD Ryzen 9 9950X3D

Ядра и потоки

10 ядер, 16 потоков

16 ядер, 32 потока

Оперативная память

16 ГБ LPDDR5

64 ГБ DDR5-6000 CL30, 2 × 32 ГБ

Графика

Не имеет значения, мы тут не ИИ крутим

Не имеет значения, мы тут не ИИ крутим

Система для работы

Православная Ubuntu 24.04

Windows 11 и Ubuntu 24.04 в WSL 2

Памяти в ПК было в четыре раза больше. Видеокарта в PIT-прогоне не участвовала. Для этой задачи меня интересовали процессор, системная RAM и возможность освободить мини-ПК.

Зачем тестам понадобился околоигровой компьютер

Основная нагрузка состояла из Kotlin и Spring, интеграционных тестов с PostgreSQL и MinIO, frontend-проверок и PIT. У интеграционных классов были отдельные тестовые базы: по логам проверки изоляции таких классов было 144. Этот подход я сохранял при переносе.

Скрытый текст

PostgreSQL и MinIO запускались в контейнерах исключительно с тестовыми данными. Тащить прод домой = врать людям и нарушать закон. Рабочая среда продукта размещена в Selectel, ее базы и пользовательские данные на домашний ПК не переносились никогда и ни в каком виде.

Просто для справки: PIT намеренно меняет код и проверяет, замечают ли это тесты. Например, заменяет сравнение, убирает вызов метода или подставляет другое возвращаемое значение. Проходящий обычный тест при такой порче кода, очевидно, должен падать. Если он продолжает проходить, приходится разбираться: пропущена проверка поведения, измененный код недостижим или мутация эквивалентна исходнику. В выбранном для первых замеров файловом модуле получилось 622 мутации. PIT просмотрел 3 146 тестов при анализе покрытия, а во время проверки мутантов выполнил несколько тысяч тестовых запусков. Каждый такой запуск мог затронуть довольно тяжелую инфраструктуру. Количество ядер без достаточной памяти здесь быстро перестает помогать.

Винда остается

На ПК я установил WSL 2 с Ubuntu 24.04 и Docker Engine внутри Linux. Репозиторий, кеши сборки и данные контейнеров разместил в файловой системе WSL. Для Linux-инструментов Microsoft рекомендует хранить рабочие файлы именно там: доступ к каталогам Windows через /mnt/c добавляет накладные расходы.
Начал с лимита 40 ГБ памяти, 16 виртуальных процессоров и 4 ГБ swap. Для тяжелого PIT затем поднял лимит памяти WSL до 50 ГБ, потому что если нужно ехать, то нужно ехать быстро. Последняя конфигурация выглядела так:

[wsl2]
memory=50GB
processors=16
swap=4GB

Это содержимое .wslconfig в профиле пользователя Windows. Лимит задает потолок памяти всей WSL 2 VM, включая другие запущенные в ней дистрибы. Он не выдает по 50 ГБ каждому Java-процессу и гуляй рванина.

В подготовленный пусковой скрипт я добавил проверки ресурсов: не менее 48 ГБ общей памяти внутри Linux, не менее 45 ГБ MemAvailable и свободное место на диске хотя бы в размере видимой RAM плюс 2 ГиБ. Последнее нужно было для диагностики и архива отчетов. ГиБ здесь получаются из байтов /proc/meminfo; запись 50GB выше оставлена в синтаксисе .wslconfig. Такие проверки позволяют отказаться от запуска сразу. Иначе можно несколько часов гонять тесты в дистрибутиве, который после перезапуска получил совсем не те ресурсы, на которые рассчитывал.

Первый запуск закончился через девять часов

Первый измеренный прогон файлового модуля на игровом ПК занял 9 часов 9 минут 47 секунд. В конце Gradle написал BUILD SUCCESSFUL, команда вернула exit_code=0. Если смотреть только на эту часть лога, перенос можно было праздновать с шампанским и другим винищем.

В отчете при этом был один MEMORY_ERROR. Из 622 мутаций тесты убили 484, еще одна завершилась по таймауту, 121 выжила, у 15 не было покрытия. Сумма дает те же 622, но результат проверки с ошибкой памяти нельзя считать полностью достоверным измерением качества тестов.

BUILD SUCCESSFUL in 9h 9m 43s
...
mutants 622
  killed: 484
  memory_error: 1
  no_coverage: 15
  survived: 121
  timed_out: 1
...
real    549m46.783s
exit_code=0

Это сокращенный фрагмент первого отчета. Несколько секунд между строкой грэдл и real относятся к внешней обертке. По одной зеленой строке сборки здесь нельзя понять ни качество тестов, ни отсутствие инфраструктурных отказов. Сам MEMORY_ERROR тоже не сообщает, что на всей машине закончилась память. У дочерней JVM есть собственные ограничения. В использованном PIT 1.17.4 дополнительно работает MemoryWatchdog: он устанавливает порог 90% для поддерживающих уведомления пулов памяти и сообщает OUT_OF_MEMORY при его превышении. Это объясняет, почему такой статус в принципе может появиться до обычного OOMError и без ожидаемого heap dump. Точную причину конкретного отказа нужно копать по диагностике соответствующего процесса. Запись MEMORY_ERROR сама по себе не доказывает ни утечку, ни нехватку всей системной RAM.

Память WSL и память Java пришлось настраивать отдельно, но ладно

Первый режим использовал одного PIT worker с фиксированным heap 4 ГБ. Для следующего замера я перешел сразу на три workers и режим auto в своей обертке запуска. Вместо фиксированного -Xmx в нем оставил эргономический выбор heap с учетом доступных JVM ресурсов. В этой ветке обертка передавала -XX:+UseContainerSupport. Сам по себе флаг не является ускорителем и в современных HotSpot включен по умолчанию. Существенное изменение состояло в отказе от прежнего фиксированного heap. Размер, который выберет JVM, зависит в том числе от ограничений контейнера и настроек эргономики. Auto при этом не делит общую память между тремя воркерами.
Каждый процесс видит свои ограничения и принимает собственные решения. Помимо Java heap память нужна для Metaspace, потоков, нативных буферов, Gradle, PostgreSQL, MinIO и самой Linux. Поэтому умножать число процессов до количества логических CPU я не стал, ибо нет смысла. Фактические параметры работающего дочернего процесса можно посмотреть через jcmd. Ниже команды для диагностики; вместо PID нужно подставить идентификатор нужной JVM и выполнять их от подходящего пользователя в том же энвайрменте:

jcmd PID VM.flags
jcmd PID GC.heap_info

В VM.flags можно проверить MaxHeapSize, а GC.heap_info показывает состояние heap и Metaspace. Это помогает заметить ситуацию, когда WSL уже получила больше RAM, но потомок PIT продолжает работать со старым лимитом. Состояние Linux и Docker проверяется отдельно:

free -h
docker stats --no-stream
docker ps -a

Отдельный счетчик нужен и для swap: запись swap=4GB задает размер, а фактическое использование нужно наблюдать во время работы.

Что изменилось во втором прогоне

Во втором прогоне тот же файловый срез дал 622 мутации за 3 часа 29 минут 37 секунд полного времени команды. MEMORY_ERROR и RUN_ERROR в отчете отсутствовали. Повод открывать второе шампанское? Не очень.

Оба прогона ниже выполнены на домашнем ПК: первый на коммите 055293ed, второй на 91379646. Вместе с коммитом менялись память WSL, режим heap и число workers. Таблица показывает результат перехода между двумя рабочими конфигурациями. Изолированное влияние процессора, файловой системы или каждого параметра я не измерял.

Что

Первый прогон

Второй прогон

KILLED

484

500

SURVIVED

121

105

NO_COVERAGE

15

15

TIMED_OUT

1

2

MEMORY_ERROR

1

0

Итог

622

622

Полное время команды сократилось примерно в 2,62 раза, или на 61,9%. Освободилось 5 часов 40 минут. При этом подготовительный анализ покрытия почти не изменился: 13:45 и 13:55. Основное сокращение времени пришлось на сам мутационный анализ. Уменьшилось и число выполненных тестовых запусков: на 319. PIT может завершать проверку мутанта после обнаружившего его теста, поэтому одинаковое количество мутаций не обязано давать одинаковое количество запусков.

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

Что

Первый прогон

Второй прогон

Полное время команды

9:09:47

3:29:37

Анализ покрытия

13:45

13:55

PIT workers

1

3

Настройка heap

Фиксированные 4 ГБ

Автоматический выбор JVM

Во втором отчете было покрыто 852 из 876 строк выбранных классов, то есть около 97%. Мутационный результат с учетом двух TIMED_OUT составил 502 из 622, или 80,71%. И эти проценты отвечают на разные вопросы. Высокое покрытие, опять же, не отменило 105 выживших мутаций и 15 без покрытия. Выбранный срез теперь завершался без ошибок памяти и выполнения. Разбор оставшихся мутаций продолжался отдельно.

Для полного PIT всего репозитория требовался свой прогон.

Передача проверок на другую машину

Забыл почти о главном. После первых запусков стало неудобно вручную переносить изменения и потом вспоминать, какой код проверялся. Нет, у нас нет проблем с логированием и памятью (той, которая в голове). Когда речь идет о том, что можно оптимизировать - идем и оптимизируем.

На мини-ПК продолжалась разработка, а игровой компьютер должен был получить конкретную версию исходников, спокойно выполнить работу и вернуть отчет. Для этого появился отдельный worker. Мини-ПК хранит выбранного кандидата и принятые результаты. В WSL создается отдельный Git worktree под задание с UUID. Исходники передаются через Git bundle по SSH. Рабочую ветку мини-ПК при этом не требуется переключать, а изменения, которые еще не закоммичены, в таск не попадают. В запросе скриптом фиксируются полный SHA коммита, UUID и профиль проверок. После запуска сохраняются версия воркера, SHA исходников до и после, коды завершения, лог и backend-отчеты. Для обычных проверок настроен отдельный двухчасовой бюджет выполнения. Тяжелый PIT запускается отдельной явной командой, к этому бюджету он не относится.

Прогон репо с проверками:

Набор

Объем

Backend

222 класса, 2 055 тестов

Frontend

188 файлов, 1 367 тестов

Общее количество тестов в этих двух наборах

3 422

Backend failures/errors/скипнуто

0/0/0

Это результат конкретного кандидата 551305da. Число 3 422 относится к обычным проверкам этого кандидата. Число 3 146 из PIT выше относится к анализу другого прогона. Их нельзя складывать или считать расхождением одного отчета.
Проверку живого стенда я оставил отдельным этапом. Поэтому результат worker явно различает выполненные repository-проверки и приемку работающего окружения. Переезд тестов на домашний ПК не должен автоматически означать, что этот ПК получил доступ к данным стейджа и обязан воспроизвести всю его эксплуатацию.

Приемка

Удачный запуск на удаленной машине мало помогает, если назад пришел отчет от другого задания или прежнего коммита. Приемка сверяет UUID, SHA исходников, хеш запроса, контрольные суммы файлов и согласованность счетчиков backend XML с итогом в логе. Проверяются и признаки реального выполнения тестов: результат из кеша нельзя просто так незаметно выдать за новый прогон. Упрощенно основная проверка привязки выглядит так.

Имена героев изменены:

import hashlib

assert result["jobId"] == request["jobId"]
assert result["commit"] == request["commit"]
assert result["requestSha256"] == hashlib.sha256(request_bytes).hexdigest()
assert xml_counts == counts_from_log
assert source_before == source_after

Для самого worker добавились 28 контрактных тестов, еще 13 проверяли команду запуска. Четыре отрицательных контроля намеренно портили реализацию: позволяли принять чужой коммит, убирали сверку XML, разрешали snapshot от другого задания или превращали красный итог в зеленый после повторной доставки. В каждом случае последовательность была одной: исходная версия проходит, испорченная падает, восстановленная снова проходит.
Если связь оборвалась после завершения тестов, готовый результат сохраняется на ПК.
Повторный вызов сначала восстанавливает его доставку, без повторного запуска проверок.
Состояние выполнения и состояние доставки разделены: успешно доставленная ошибка всегда остается ошибкой. Отдельно обрабатывается неизвестный результат очистки контейнеров. В таком состоянии worker блокирует следующую работу до разбора. Иначе следующий прогон может унаследовать чужие процессы и данные, после чего его цифры будет трудно объяснить.

WSL нужно было оставить доступной

Еще одна часть переезда касалась SSH. В выбранном NAT-режиме WSL имеет свой адрес, и SSH, доступный из Windows, еще не означает просто так доступность с мини-ПК по локальной сети. Я использовал перенаправление TCP-порта (возьмите свое любимое число :)) на Windows в порт 22 внутри WSL. Firewall разрешает подключение только с адреса мини-ПК. Microsoft отдельно описывает такой вариант доступа из LAN (тут было убито два часа на актуализацию своих знаний о том "как это сейчас работает", ну да ладно).
Адрес WSL после перезапуска может измениться. Подготовленный PowerShell-скрипт определяет актуальный адрес, проверяет ожидаемый ключ SSH-сервера и обновляет перенаправление. Обслуживающий цикл выполняет проверку раз в 30 секунд. Задача Планировщика запускает его после входа пользователя в Windows и помогает восстановить его работу.

Эта задача обслуживает доступность SSH и не запускает тесты. Обычные проверки вызываются по необходимости, PIT запускается вручную. Так я могу пользоваться игровым ПК как своим компьютером, а не получать неожиданную многогигабайтную нагрузку по расписанию.
Для длительного PIT предусмотрен отдельный запрос Windows не засыпать на время работы. После завершения обертка снимает этот запрос. Постоянную политику сна ради тестов менять не потребовалось.

После переезда

Ага, первый же полномасштабный запуск упал как только я лег спать (через 2 часа 39 минут). Но про это как-нибудь потом.

В итоге у меня остались две машины с разными задачами. На CHUWI с его 16 ГБ продолжается разработка. На игровом ПК с 64 ГБ выполняются тяжелые проверки в WSL.
Windows на месте, отдельную машину покупать не пришлось (а очень хотелось, потому что это же новая железка).
Из полезных результатов у меня есть принятый обычный прогон на 3 422 теста и полный замер файлового среза из 622 мутаций без MEMORY_ERROR (УРА!) и RUN_ERROR. Последний занял 3 часа 29 минут вместо прежних 9 часов 9 минут на другом режиме той же машины.

Надеюсь, мой опыт сэкономит вам немного времени. Свои ошибки вы еще успеете сделать, повторять мои необязательно (но YOLO!).

Полезное и упомянутое

  1. Microsoft о хранении рабочих файлов в файловой системе WSL

  2. Настройки памяти и процессоров в .wslconfig

  3. MemoryWatchdog в исходниках PIT 1.17.4 (там еще есть про pitests глобально, интересно с инженерной точки зрения)

  4. Документация Java по UseContainerSupport и настройкам памяти

  5. Доступ к WSL из локальной сети

  6. Команды диагностики работающей JVM через jcmd (или как довести взрослого мужчину до слез)

  7. Кулстори про 3D печать.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.