Выпуск 29. Июль 2015

(R)?ex на практике | Содержание | Обзор CPAN за июнь 2015 г.

Инструменты для поиска зависимостей в скрипте

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

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

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

Предыстория. Была у меня как-то очень простая задача — расширить функциональность программы, используя уже готовую библиотеку. Задача решилась, но скорость старта (не работы, а именно старта) упала до уровня, заметного человеку. Это очень подозрительно, если знаешь, что объективных причин на это нет. Не стоит игнорировать такие симптомы.

(Все представленные ниже примеры выдуманы из пальца и упрощены до предела для наглядности.)

Например, есть вот такая программа:

use strict; use warnings; use feature qw/say/;
use lib::abs qw/./;

# do something cool
say 'OK';

exit(0);

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

package Utils;
use strict; use warnings; use feature qw/say/;
use Common;

use Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(a);

sub a {
    say 'a';
}

1;

Любой нормальный человек подключает эту библиотеку и наслаждается жизнью с готовой функциональностью.

use Utils qw/a/;
a();
$ perl main.pl
a
OK

Для повышения запутанности связей модулей я сделала еще такой Common:

package Common;
use Abc;
use DateTime;
1;

и Abc:

package Abc;
use DateTime;
1;

Пример построен таким образом, что в результате формируются следующие зависимости:

main.pl
|-- Utils
    |-- Common
        |-- Datetime
        |-- Abc
            |-- Datetime

В реальности вам ничего о них неизвестно, поэтому представим, что мы о них не знаем.

perl -d

Если вы ничего не знаете о -d, тогда вам сюда: http://perldoc.perl.org/perldebug.html. Perl debugger отлично показывает все загруженные модули. Давайте начнем с общего списка:

$ perl -d main.pl
...
main::(main.pl:5):  a();

  DB<1> M

'Abc.pm' => '/home/wwax/Desktop/pragmatic/2/Abc.pm'
'Carp.pm' => '1.29 from /usr/share/perl/5.18/Carp.pm'
'Class/Singleton.pm' => '1.5 from /usr/local/share/perl/5.18.2/Class/Singleton.pm'
'Cwd.pm' => '3.40 from /usr/lib/perl/5.18/Cwd.pm'
'DateTime.pm' => '1.19 from /usr/local/lib/perl/5.18.2/DateTime.pm'
'DateTime/Duration.pm' => '1.19 from /usr/local/lib/perl/5.18.2/DateTime/Duration.pm'
'DateTime/Helpers.pm' => '1.19 from /usr/local/lib/perl/5.18.2/DateTime/Helpers.pm'

Команда M показывает список всех загруженных модулей. Но подождите! Я же только подключила Utils, а там нет никакого DateTime! Он мне вообще не нужен, поэтому надо бы избавиться, чтобы не притягивать лишнего.

Смотрим, где он у нас подключается.

$ perl -d main.pl
...

  DB<1> b load /usr/local/lib/perl/5.18.2/DateTime.pm
Will stop on load of '/usr/local/lib/perl/5.18.2/DateTime.pm'.

  DB<2> R
'/usr/local/lib/perl/5.18.2/DateTime.pm' loaded...

DateTime::CODE(0x1e58ce8)(/usr/local/lib/perl/5.18.2/DateTime.pm:9):
9:  our $VERSION = '1.19';

  DB<2> T
@ = DB::DB called from file '/usr/local/lib/perl/5.18.2/DateTime.pm' line 9
$ = require 'DateTime.pm' called from file '/home/wwax/Desktop/pragmatic/2/Abc.pm' line 2
....

b load /usr/local/lib/perl/5.18.2/DateTime.pm — установили точку останова на момент, когда будет выполняться первая инструкция этого модуля. Имя файла беру из списка загруженных модулей, полученных по команде M. R — перезапуск дебаггера, который затем остановился на строке 9 файла DateTime.pm. Т — трассировка вызовов. Во второй строке видно, что:

require 'DateTime.pm' called from file '/home/wwax/Desktop/pragmatic/2/Abc.pm' line 2

наш модуль подключается в другом модуле ABC, в строке 2.

Это отличный инструмент!

Закомметируем здесь #use DateTime;. Ну просто посмотреть, что будет. Опять смотрим в perl -d, команда M. Но не может быть! Кто-то еще подключил DateTime.pm. Опять точка останова, опять трассировка. Только в этот раз DateTime подключил какой-то другой модуль с именем Common.pm… И так можно продолжать довольно долго, быстро надоедает.

Visually graphing

Есть вот такой невероятный инструмент: App::PrereqGrapher, о котором можно почитать тут.

Запустим:

$ prereq-grapher -nc main.pl

Эта команда сгенерировала файл dependencies.dot, который можно превратить в png:

$ dot -Tpng dependencies.dot > output.png

Граф зависимостей в наших руках!

Граф зависимостей

Граф зависимостей

На графе очевидно, какой из модулей подключает DateTime, и сколько всего он еще тянет за собой. А все ради использования одной только функции a(). Ужас! Этот граф построен с использованием флага -nc, что значит — не выводить core modules.

Если ничего не помогает

Не так давно мне открылась мощь утилиты strace (понятное дело, что grep иногда дает ответ быстрее, но случаи бывают разные, и еще не всегда очевидно, где именно искать). Для меня, как человека, пишущего на С только в академическо-институтских целях, открылся новый мир средств отладки. (Если у меня здесь есть примеры неправильного использования, очень прошу мне о них сообщить.)

Запускаем и отправляем вывод в лог.

$ strace perl main.pl &> strace.log

Смотрим, какие вызовы обращались к чему-нибудь с DateTime:

$ cat strace.log | grep DateTime.pm -C 5 | less
stat("/home/wwax/Desktop/pragmatic/2/Abc.pm", {st_mode=S_IFREG|0664, st_size=30, ...}) = 0
open("/home/wwax/Desktop/pragmatic/2/Abc.pm", O_RDONLY) = 6
ioctl(6, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7ffdd665dd60) = -1 ENOTTY (Inappropriate ioctl for device)
lseek(6, 0, SEEK_CUR)                   = 0
read(6, "package Abc;\nuse DateTime;\n1;\n", 8192) = 30
...
stat("/usr/local/lib/perl/5.18.2/DateTime.pm", {st_mode=S_IFREG|0444, st_size=122107, ...}) = 0
open("/usr/local/lib/perl/5.18.2/DateTime.pm", O_RDONLY) = 7

Это, конечно, уже совсем тяжелый случай, если мы говорим о perl. Но средства отладки, на мой взгляд, это первые необходимые инструменты для упрощения своей жизни.

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

Наталья Савенкова


(R)?ex на практике | Содержание | Обзор CPAN за июнь 2015 г.
Нас уже 1393. Больше подписчиков — лучше выпуски!

Комментарии к статье