Server Debugger

Server Debugger

El_Neuman

Мод для власників серверів. Відповідає на запитання "чому мій сервер лагає" - і, якщо винен якийсь мод, називає його на ім'я. Окремий мод, нічого за собою не тягне. Ставиться тільки на сервер, гравцям завантажувати не потрібно.

Писався з розрахунком на те, що ти не програміст. Команди нижче пояснені простими словами: що робить, навіщо, і що покаже.


Загальна інформація - що взагалі відбувається, коли "сервер лагає"

 

Сервер раз на долі секунди робить "тік" - обчислює світ: мобів, багаття, температуру, все відразу. Якщо один тік займає не 0.05 секунди, а, скажімо, 5 секунд - для гравців це фриз: моби завмирають, блоки не ставляться, всіх "телепортує".

Причин довгого тіка небагато, і вони різні за лікуванням. Цей мод вміє відрізняти їх одна від одної та показувати, яка саме у тебе.

Дві речі, які треба знати, щоб розуміти виведення мода:

  • Сміття та його прибирання (GC). Гра і моди постійно створюють у пам'яті тимчасові об'єкти. Коли їх накопичується багато, система влаштовує "прибирання сміття" (GC - garbage collection): завмирає, проходить по всій пам'яті та викидає непотрібне. Поки йде прибирання - сервер стоїть. Зазвичай це долі секунди і непомітно. Але якщо в пам'яті скупчилися гігабайти, одне прибирання може зайняти секунди - ось і фриз.
  • Витік пам'яті. Прибирання викидає тільки те, що більше нікому не потрібно. Якщо якийсь мод помилково продовжує "тримати" вже мертвих мобів (не відпускає посилання на них) - прибирання їх викинути не може. Вони накопичуються. Пам'ять зростає, прибирання стають дедалі довшими, сервер лагає все сильніше. Це і є витік. Саме такий спіймав на своєму сервері - мод тримав десятки тисяч мобів, що давно зникли.

 


Чим Server Debugger відрізняється від MemLeakInspector?

MemLeakInspector - це спеціалізований інструмент для дослідження пам'яті. Він показує, які об'єкти знаходяться в пам'яті, дозволяє аналізувати їхній стан і порівнювати знімки пам'яті між собою. Однак він не визначає автоматично причину проблеми - інтерпретувати результати і шукати винуватця доводиться самостійно.

Server Debugger - це комплексний інструмент діагностики сервера. Він аналізує зависання main-потоку, лаги, паузи GC, проблеми з диском і витоки пам'яті. При виявленні витоку він відразу намагається визначити мод-винуватець, що значно прискорює пошук проблеми. При цьому він не надає глибокий аналіз об'єктів у пам'яті, тимчасові знімки або докладний дашборд використання пам'яті.

Іншими словами, Server Debugger відповідає на запитання "який мод викликає проблему?", а MemLeakInspector - "які об'єкти знаходяться в пам'яті і як змінюються з часом". Ці інструменти не замінюють один одного, а відмінно працюють разом.


Що мод вміє, а що ні

 

Ловить впевнено і сам

  • Витоки пам'яті. Коли якийсь мод помилково накопичує в пам'яті мертві ентіті (мобів) і не відпускає - пам'ять зростає, сервер лагає все сильніше. Мод знаходить це і називає винуватця на ім'я (команда whodunit). 
  • Зрозуміти, від чого взагалі лаг. На кожен фриз мод каже, куди пішов час: на прибирання сміття, на надто довгий код, або сервер просто чекав (диск, залізо). Напрямок вкаже завжди - не будеш гадати і копати не там.
  • Хто вручну смикає прибирання сміття. Іноді мод сам наказує "прибрати сміття зараз", і на великій пам'яті це фриз. Команда /sdebug gccallers показує, який мод це робить (докладно і з прикладом - у розділі про команди нижче).
  • Погані налаштування прибирання сміття. Команда /sdebug env покаже, чи увімкнено швидкий режим прибирання, і якщо ні - це можна виправити (приклад виведення і як лагодити - нижче).

Допомагає, але не назве винуватця сам

  • "Важкий код". Уяви: мод-автор помилився, і його мод кожен тік даремно перераховує всі 5000 грядок на сервері, як наприклад мод Wild Farming.  Пам'ять не зростає - код просто робить занадто багато роботи. Це НЕ витік, і whodunit тут марний.

  • Що мод зробить: stalls (див. нижче) точно напише "це важкий код, а не прибирання сміття" - тобто напрямок дасть вірний. А щоб знайти конкретный мод, є profile (див. нижче) - він заміряє час за шматками коду. Але він дивиться не на всі моди підряд, а на ті, що вказані в налаштуванні (або схожі на них). Тобто тут не "натиснув - отримав ім'я", як з витоком: можливо, доведеться підказати дебагеру, де дивитися. Допомога є, автоматизму - немає, доведеться пройтися по підозрілих модах.

Не вміє взагалі

  • Лаги через диск, мережу або слабке залізо хостингу. Мод скаже "причина не в коді модів" - але полагодити не зможе. Це до хостера.
  • Лаги у гравця (низький FPS, смикається картинка). Мод суто серверний, клієнт він не бачить взагалі.
  • "Важко, але правильно". Якщо ті ж 5000 грядок чесно треба обчислювати і в коді немає помилки - мод покаже, що час йде туди, але це вже не баг, а питання до того, як мод влаштований. Лікується не налаштуванням, а переробкою мода (або зменшенням навантаження на сервері).

Однією фразою

Витік пам'яті - знайде і назве винуватця сам. Все інше - покаже вірний напрямок ("це код", "це диск", "це прибирання сміття"), але далі вже думати тобі.


З чого починати


/sdebug stalls - розбір останніх фризів

Коли вводити: першою, як тільки сервер лагає або лагав недавно. Це точка входу в будь-який розбір - вона сама скаже, в який бік копати далі. На свіжозапущеному спокійному сервері покаже порожньо (фризів ще не було) - це нормально.

Мод ловить фризи сам і складає їх розбір. Ця команда показує останні (за замовчуванням 5). Кожен фриз - це блок, і останній рядок у ньому - вердикт простим текстом, куди пішов час. Приклад:

ServerDebugger: ЗАВИСАННЯ 
  Тривалість         : 5084 мс
  GC pause             : 5008 мс  (99%)
  ...
  ВЕРДИКТ : ПАУЗА GC. Сервер прибирав сміття. Перевір на витік: /sdebug leak

Або, якщо винне не сміття, а код:

  ВЕРДИКТ : ПРОЦЕС РЕАЛЬНО РАХУВАВ, GC ні до чого => гарячий код.
            Увімкніть /sdebug profile on, щоб дізнатися який.

  • Важливо і чесно: stalls сам не назве конкретний мод. Він говорить тільки напрямок - "це прибирання сміття" / "це код" / "це очікування диска". Хто саме винен, шукають наступні команди, кожна під свій напрямок. Куди йти після вердикту:вердикт "ПАУЗА GC" -> це пам'ять. Йди в блок "Про витоки" нижче (leak і whodunit), а заодно перевір gc.
  • вердикт "гарячий код" -> йди в profile / top.
  • вердикт "очікування диска / lock" -> це найчастіше залізо хостингу, кодом не лікується.

/sdebug env - загальний стан сервера

Коли вводити: у будь-який момент, відразу після stalls для загального огляду. Сервер повинен просто працювати, прибирання запускати не потрібно - команда читає поточний стан.

Покаже блок ось такого вигляду (зайве прибрав):

ServerDebugger: оточення 
  ProcessorCount       : 24
  Server GC            : False        <- ось цей рядок важливий
  Pause time %         : 18.5         <- і цей
  Memory load          : 6400 МБ / 16000 МБ

Що читати:

  • Server GC - чи увімкнено швидкий режим прибирання сміття. False = повільний однопотоковий, прибирання гальмує сильніше, ніж могло б. Це "погане налаштування" сервера. Як виправити - у розділі "Прискорити прибирання сміття" в самому кінці README. Після лагодження рядок стане True.
  • Pause time % - яка частка часу йде на прибирання сміття. Одиниці відсотків - норма. 15% і вище (як 18.5 у прикладі) - прибирання і є твоя проблема.
  • Memory load - скільки пам'яті зайнято. Близько до межі - пам'ять може стати причиною сама.

Про витоки - дві команди по порядку

Якщо stalls показав вердикт "ПАУЗА GC" - швидше за все це витік. Ловиться за два кроки: спочатку /sdebug leak перевіряє, чи є він узагалі, потім /sdebug whodunit називає винуватця. Йдуть саме в такому порядку.


/sdebug leak - КРОК 1: чи є витік узагалі?

Коли вводити: коли пам'ять сервера зростає з часом і лаги посилюються - і обов'язково давши серверу попрацювати (годину-дві після старту, або після ігрової ночі, коли пройшло багато мобів). На щойно запущеному сервері витік ще не накопичився, і тест покаже порожньо.

Команда проводить прибирання сміття вручну (сервер замре на пару секунд) і рахує, скільки мертвих мобів його пережили.

"Пережили" = мали зникнути, але хтось їх тримає, і прибирання не змогло викинути. Логіка проста: живих мобів прибирання не чіпає, а ось якщо давно зниклий моб "пережив" прибирання - значить його незаконно тримають, і це витік.

Що покаже: відсоток тих, хто вижив.

  • Близько нуля - витоку немає. На цьому з витоками закінчили, причину лагів шукай в іншому (див. stalls, env).
  • 80% і вище - витік є. Переходь до кроку 2 - /sdebug whodunit.

/sdebug whodunit - КРОК 2: хто винен у витоку?

Коли вводити: відразу після того, як /sdebug leak показав високий відсоток (витік підтверджено). Раніше сенсу немає - якщо витоку немає, шукати винуватця нема чого.

Ця команда так само робить:

  1. Проводить прибирання сміття вручну (сервер може завмерти на кілька секунд).
  2. Знаходить усіх мертвих мобів, яких хтось досі тримає.
  3. Простежує, хто саме їх тримає, і за яким модом проходить це посилання.
  4. Видає список: який мод скільки мертвих об'єктів утримує, з ім'ям файлу і місця в коді.

Налаштовувати заздалегідь нічого не треба. Результат виглядає так:

     ХТО ТРИМАЄ ВИТІК
  простежено 14203 об'єктів до тримача за 8 с
      утримано об'єктів, по модах    
    14203  <- Rust and Rustbound Creatures
        файл  : RustboundCreatures.dll
        тип   : RustCreaturesReworked.BowtornTuning
        поле  : MoveSpeedBaselines
       18  <- Інший мод
        3  <- ваніль / рушій

Перший рядок - головний винуватець. Далі вирішуєш: видалити мод, оновити його, або написати автору про проблему.

/sdebug entities - скільки всього живих мобів у світі

Коли вводити: у будь-який момент, коли хочеш зрозуміти, чи реально мобів багато у світі, чи вони тільки "висять" у пам'яті. Корисно поруч із /sdebug leak - разом вони відрізняють витік від справжнього напливу.

Показує, скільки істот гра сама вважає завантаженими, з розбивкою за видами і за найбільш "населеними" ділянками карти.

Навіщо: відрізнити витік від чесного напливу. Якщо команда показує 80 000 мобів - вони правда у світі, щось зламало обмеження спавну. Якщо показує 200, а /sdebug leak при цьому знаходить тисячі "мертвих" - значить моби давно зникли, але висять у пам'яті. Це витік.

Інше

  • /sdebug threshold [мс] - з якої тривалості фриз потрапляє в лог (за замовчуванням 500 мс = півсекунди).
  • /sdebug lang [код] - мова виведення (див. нижче).
  • /sdebug reset - очистити накопичену статистику.

Як читати блок "СТІЙЛО" (зависання сервера) або ж /sdebug stalls

 

Кожен такий блок - це один спійманий фриз. Нижче розібрано кожен рядок на реальному прикладі.

СТІЙЛО main-потоку
  Час                : 20:19:12.952
  Тривалість         : 751.7 мс

Час - коли трапився фриз. Тривалість - скільки сервер стояв. 751.7 мс = 0.75 секунди сервер не відповідав. Звичайний тік триває ~50 мс, тож це в 15 разів довше за норму - гравці це відчули як затримку.

куди пішов час (дельти за вікно стійла)
  GC pause             : 697.6 мс  (93%)

Найважливіше, розберемо докладно.

GC pause - це час, який сервер простояв через прибирання сміття (GC = garbage collector, збирач сміття). Прибирання - це коли система завмирає, проходить по пам'яті і викидає непотрібні об'єкти. Поки йде прибирання, сервер не тікає.

  • 697.6 мс - скільки саме тривало прибирання всередині цього фризу.
  • (93%) - яку частку всього фризу вона зайняла.

Читається так: фриз був 751.7 мс, і з них 697.6 мс (93%) - це прибирання сміття. Тобто майже весь фриз - це прибирання. Значить винне саме воно, а не щось інше. Якби тут стояло (5%) - прибирання було б ні до чого, копати треба було б в іншому місці.

  CPU процесу         : 187.5 мс  (25%)  <= РАХУВАВ або ЧЕКАВ: ось відповідь

CPU процесу - скільки за час фризу сервер реально працював процесором (рахував), а не просто стояв.

  • 187.5 мс - стільки сервер рахував.
  • (25%) - це 25% від тривалості фризу.

Ключова думка: фриз тривав 751 мс, а рахував сервер всього 187 мс із них. Куди поділися інші 564 мс? Сервер їх прочекав, нічого не роблячи. Це відрізняє «сервер працював» від «сервер завис в очікуванні». Якби тут було (95%) - сервер чесно гарував. А (25%) означає - здебільшого чекав.

  ThreadPool latency   : 5.6 мс

Наскільки затримувалися фонові завдання. Дрібниця, важлива рідко. Велике число (секунди) означало б, що фонові потоки забиті - тут усе в нормі.

  фон ЗА 9.3 с ДО стійла (ось справжнє навантаження)
  Алокації            : 23.0 МБ/с
  CPU процесу         : 20.6% від одного ядра

Це не про сам фриз, а про 9.3 секунди перед ним - щоб зрозуміти, що коїлося на сервері в спокійний момент до затримки.

  • Алокації 23.0 МБ/с - з якою швидкістю код створював у пам'яті нові тимчасові об'єкти. Чим вище, тим частіше система змушена прибирати сміття. 23 МБ/с - помірно. Сотні МБ/с - це вже мод-сміттяр, який завалює пам'ять і провокує часті прибирання.
  • CPU 20.6% від одного ядра - наскільки сервер був зайнятий перед фризом. 20% - розслаблений. Якби тут було під 100% - значить сервер і так пихтів, фриз був на межі.
  --- пам'ять ---
  Причина останньої GC : gen0, AllocSmall, NonConcurrent (BLOCKING)

Чому запустилося прибирання. Розшифровка технічних слів:

  • gen0 / gen1 / gen2 - "покоління", глибина прибирання. gen0 - легка, швидка, тільки свіже сміття. gen2 - повна і важка, проходить всю пам'ять (саме вона дає довгі паузи). Тут gen0 - найлегша.
  • AllocSmall - причина: "в пам'яті скінчилося місце під дрібні об'єкти". Це природне прибирання, пам'ять переповнилася сама. Протилежність - Induced, коли прибирання запустив якийсь мод вручну (це вже привід шукати винуватця).
  • BLOCKING - прибирання було "зупиняючим": сервер стояв, поки воно йшло. Буває Background - фонове, майже без зупинки.
  Збірки за вікно       : gen0 +1, gen1 +0, gen2 +0

Скільки прибирань кожного рівня сталося за фриз. gen0 +1 - одне легке прибирання. Якби тут було gen2 +1 - це було б важке повне прибирання, і паузу в сотні мс вона пояснювала б повністю.

  Алоковано у вікні  : 13 МБ  (при завмерлому main це майже завжди ~0 — не показник)

Скільки пам'яті створилося за час фризу. Зазвичай неінформативно (сервер же стояв), сам мод це й обумовлює - не звертай уваги.

  Heap                 : 4027 МБ -> 4031 МБ

Heap - загальний розмір пам'яті під об'єкти (купа) до і після фризу. Тут майже не змінився. Якби після прибирання купа помітно впала (наприклад, 4027 > 2800) - прибирання знайшло багато сміття і викинуло. Якщо не падає при великій купі - це ознака витоку (сміття є, але викинути не можна, його хтось тримає).

 Committed : 4226 МБ, фрагментовано 449 МБ (11% купи) 
  • Committed - скільки пам'яті сервер зарезервував у системи (зазвичай трохи більше heap).
  • Фрагментовано 449 МБ (11%) - "дірявість" пам'яті. 11% - терпимо. 25%+ - вже заважає.
  Working set / commit : 4396 МБ / 4509 МБ  (у RAM 97%)

Скільки пам'яті сервера реально лежить у швидкій оперативці (RAM), а не поїхало на повільний диск.

  • у RAM 97% - майже вся пам'ять сервера в оперативці. Це добре.
  • Якби тут було, скажімо, у RAM 60% - значить 40% пам'яті система вивантажила на диск (в pagefile), і прибирання змушене піднімати її назад із диска - повільно. Це ознака нестачі оперативки на машині.
  Page faults за вікно  : +1573

Скільки разів за фриз серверу довелося лізти за пам'яттю на диск, бо її не виявилося в оперативці. Трохи - норма. Тисячі-десятки тисяч - вірна ознака, що пам'яті на машині не вистачає і вона свопиться на диск.

  Memory load          : 113274 МБ / 39060 МБ  (поріг 124992 МБ, вільно -)

Наскільки забита пам'ять усієї машини (не тільки твого сервера - всієї фізичної ноди у хостера).

  • 113274 МБ - скільки зайнято на машині зараз.
  • поріг 124992 МБ - червона межа, після якої система починає панікувати і агресивно прибирати сміття.
  • Якщо перше число підбирається до порогу - пам'яті на машині обмаль. Це часто не ти, а сусідні сервери на тій самій залізяці.
  код
  Main був усередині      : (профайлер вимкнений)

Якби був увімкнений профайлер (/sdebug profile on), тут стояло б ім'я шматка коду, в якому сервер застряг у момент фризу. Вимкнений - значить інформації немає, це нормально для звичайного розбору.

  ВЕРДИКТ : GC ЧЕКАВ, а НЕ РАХУВАВ: 187.5 мс CPU за 751.7 мс паузи.
            Це hard page faults — купа вивантажена в pagefile,
            GC піднімає її з диска. Проблема в пам'яті МАШИНИ.

Готовий висновок простим текстом. Мод сам збирає його з рядків вище. Тут він каже: прибирання сміття йшло 751 мс, але сервер за цей час рахував лише 187 мс - решту чекав диск. Причина - пам'яті на машині не вистачило, частина купи поїхала на диск, і прибирання повзало, піднімаючи її назад. Це залізо хостингу, а не код мода.


Як читати вердикт у двох словах

Усе зводиться до порівняння двох чисел: GC pause (скільки стояв через прибирання) і CPU процесу (скільки при цьому реально рахував).

  • GC pause великий + CPU майже дорівнює йому > прибирання чесно працювало. Лікується зменшенням купи або прискоренням прибирання (Server GC).
  • GC pause великий, а CPU маленький (як тут: 697 мс паузи, 187 мс рахунку) > прибирання не рахувало, а чекало диск. Лікується додаванням пам'яті на машині - це до хостера.
  • GC pause ≈ 0, а CPU великий > прибирання ні до чого, винен важкий код. Шукай мод через /sdebug profile.
  • Усе маленьке > сервер просто чекав (диск або блокування).

Налаштування мода

 

При першому запуску створюється файл ModConfig/ServerDebuggerConfig.json:

{
  "Language": "en",
  "StallThresholdMs": 500,
  "WatchedModMarkers": [ "xskills", "xlib", "xleveling", "xeffects" ]
}
  • Language - мова виведення за замовчуванням.
  • StallThresholdMs - з якої тривалості (в мілісекундах) фриз вважається гідним запису в лог. 500 = півсекунди.
  • WatchedModMarkers - підказка для /sdebug profile і /sdebug findroot, на які моди дивитися за замовчуванням. Значення xskills, xlib тощо тут - просто приклад (мод спочатку писався при налагодженні цих модів). Це не залежність: сам ServerDebugger від xskills/xlib не залежить узагалі і працює на будь-якому сервері з будь-якими модами. Якщо хочеш - зітри їх і впиши свої, або залиш як є. Головні команди (whodunit, leak, entities, stalls, gc, gccallers, env) цей список ігнорують і працюють завжди. Звичайному користувачеві чіпати не обов'язково.

Мова

 

Змінити мову виведення на льоту:

/sdebug lang uk

Вибір зберігається. Без коду команда покаже поточну мову і список доступних.

Додати свою мову: скопіювати assets/serverdebugger/lang/en.json у файл зі своїм кодом (наприклад aaa.json) і перекласти значення. Плейсхолдери {0}, {1} і команди /sdebug ... чіпати не можна - тільки текст навколо. Файл підхопиться сам. Якщо якогось рядка у твоєму файлі немає - підставиться англійська, тож перекладати можна поступово.

Технічні слова (назви причин прибирання на кшталт AllocSmall, імена місць у коді у виведенні **findroot**) навмисно не перекладаються - це ідентифікатори, їх потрібно бачити як є.


 

Прискорити прибирання сміття (якщо env показав Server GC : False)

 

 

Це те саме "як виправити", про яке сказано в описі /sdebug env. Якщо команда показала Server GC : False на потужному сервері - прибирання сміття йде в один потік, повільно, і паузи довші, ніж могли б бути. Лікується редагуванням одного файлу налаштувань.


Що робити

  1. Через файловий менеджер хостингу знайди файл VintagestoryServer.runtimeconfig.json (лежить поруч із самим сервером).
  2. Спочатку зроби його копію - на випадок, якщо щось піде не так, повернеш як було.
  3. Відкрой файл. Усередині є розділ configProperties. Допиши в нього чотири рядки (не забудь кому в кінці попереднього рядка):
"System.GC.Server": true,
"System.GC.Concurrent": true,
"System.GC.HeapCount": 6,
"System.GC.HeapHardLimitPercent": 30

Файл цілком виглядатиме приблизно так:

{
  "runtimeOptions": {
    "tfm": "net10.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "10.0.0"
    },
    "configProperties": {
      "System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
      "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
      "System.Runtime.TieredPGO": true,
      "System.GC.Server": true,
      "System.GC.Concurrent": true,
      "System.GC.HeapCount": 6,
      "System.GC.HeapHardLimitPercent": 30
    }
  }
}
  1. Збережи файл і зроби повний перезапуск сервера - не "reload", а саме рестарт. Налаштування читається тільки при запуску, інакше не застосується.
  2. Перевір: введи /sdebug env. Рядок має стати Server GC : True, а Pause time % - помітно впасти.


Що означають ці рядки (простими словами)

  • Server GC - вмикає швидке прибирання сміття в кілька потоків замість одного. Це головне.
  • Concurrent - частина прибирання йде у фоні, не зупиняючи сервер.
  • HeapCount - на скільки ядер розкласти прибирання. Ставимо 6: швидко, але не занадто жадібно до пам'яті (без обмеження прибирання зайняло б куди більше пам'яті).
  • HeapHardLimitPercent - стеля пам'яті для сервера, страховка від розростання. Увага: на дешевому хостингу гра може бачити пам'ять усієї фізичної машини, а не тільки твою частку - тоді відсоток рахується від неї, і 30% може виявитися більше твого ліміту за тарифом. Якщо знаєш свій ліміт RAM і він невеликий - зменш це число (наприклад, до 12–15).

Чого очікувати

На реальному сервері це налаштування знизило час у паузах із 28% до менше 1%, а фризи від прибирання сміття стали майже непомітними. Це не лагодить витоки (витік треба прибирати окремо, через whodunit), але робить будь-яке прибирання в рази швидшим.


Report Page