Зовсім недавно на одному з клієнтських проектів (http://www.ihoppers.com) ми мали проблемку із зависанням Python сервера. Під “зависанням” мається на увазі ситуація, коли процес “з’їдає” весь ресурс процесора і забирає необмежену кількість часу. Після того як усі потоки в межах даного процесу сервера зайняті аплікація повністю перестає обслуговувати відвідувачів.

Debugging with GDB

Фото взято з uplifted.net

Проект написаний на Plone CMS і обслуговується на Ubuntu сервері. Тому дана техніка дебагу була застосована саме для дослідження Python процесу на Лінуксі.

Зазвичай, коли є потреба подебажити те чи інше місце в Пітон коді я використовую pdb – Python дебагер. З допомогою нього ставлю точку зупинки в програмі, і вже там на місці досліджую змінні середовища, що не так, і т.д… Зазвичай працює добре! Чому ж тоді цього разу я звернувся за допомогою до GDB інструменту?

Проблему із зависанням процесу було важко відтворити як на розробницьких так і на продакшин машинах з реальною базою даних. Могло бути так що 3 рази на день трапляється після того як хтось поредагував контент на сайті, а могло бути так що 2 тижні все спокійно і ніяких глюків.

Тому прийшлось шукати інших способів розбору проблеми, зокрема можливості залісти напряму в процес, що завис і використовує 100% процесора, без його попередньої зупинки чи рестарту.

Для цього я дізнався про новий для себе інструмент – GDB – GNU Project Debugger. Є кілька сценаріїв як можна використовувати цей інструмент, але для моєї задачі я його використовував саме для того, щоб залізти всередину запущеного процесу і подивитися, що ж там відбувається.

Далі даю інструкцію як я це зробив для дебагу Пітон процесу на Лінуксі (Ubuntu). Вкінці статті ви знайдете наглядний скрінкаст усієї процедури.

1. існталюємо наш GNU GDB дебагер. Зауважте: весь процес дебагу на лінуксовій машині я робив під рутовим (root) користувачем. Я не впевнений чи можна обійтися тут без рутового користувача. Якщо знаєте більше про це – будь-ласка, коментуйте і діліться вашим досвідом.

2. запускаємо дебагер і приєднуємося до нашого процесу, який власне хочемо дебажити

  • з допомогою команди top визначаємо програму, яка запустила наш процес, а також id процесу. У моєму випадку це був Python інтерпретатор, який постійно висів у самому верху списку процесів посортованому по CPU Usage % колонці:
GDB Define PID and Executable

Визначаємо PID і програмку процесу

у даному прикладі це /home/devel/python-2.6.7/bin/python Пітон інтерпретатор запустив наш процес під ID 16355.

  • запускаємо gdb з назвою або шляхом до програми що запустила наш процес:
  •  тепер приєднаємо наш дебагер до конкретного процесу запущеного нашими Пітон інтерпретатором. ID процесу ми визначили на попередньому кроці.
якщо все йде добре, ви повинні побачити велику кількість стрічок на екрані, які тим чи іншим чином пов’язані із запущеними модулями і пакетами даним процесом.

3. тепер знаходимо “завислий” потік у процесі:

  • виводимо список усіх потоків у даному процесі
  • якщо потоків більше, ніж один, тоді або треба пройтись по кожному; або, якщо ви маєте уявлення про нутрощі аплікації/сервера, що дебажите – по перших стрічках навпроти виводу кожного потоку вище вказаною командою ви зможете побачити чи це нормальний стан потоку, а чи він зайнятий незвичною для себе річчю. Наприклад, у моєму випадку, працюючи з Python сервером Plone CMS я знаю, що вивід типу: sigsuspend(), poll(), or select() вказує на правильну поведінку потоку.
  • отже, вибираємо перший з потоків, спробуємо почати з нього і розібрати чи у ньому зараз проблема. Наступна команда переключить нас у контекст конкретного потоку:
4. Виводимо у файл те, чим зайнятий даний потік (стек викликів). В пітоні це зробити досить легко. Для цього ми використовуємо той факт, що Python інтерпретатор написаний на мові C, і тому ми маємо потужні інструменти звернення прямо до Python модулів та функцій прямо з C середовища:
Дана команда, використовуючи Пітон модуль traceback, запусує у файл /tmp/tb поточний стек викликів нашого потоку.

Ось як це виглядало у моєму випадку:

gdb Output Stack

GDB Output Stack

Після цього я зміг визначити, що є проблемка у функції _placeLink всередині модуля events.py пакету ihoppers.contenttypes. А саме – зависає складний регулярний пітонівський вираз. Це допомогло вирішити проблему!

І коротке відео-скрінкаст як це виглядає:

Був цей пост корисний вам? Якщо так – тисність Like 🙂

А як ви вирішуєте, розрулюєте подібні проблеми?