воскресенье, 4 декабря 2011 г.

Вышел Geant4 9.5

Вышла новая версия Geant4 - программы для моделирования физических процессов в веществе. Чейнджлог здесь. В новой версии было решено отказаться от скрипта Configure при конфигурации билда, вместо этого настройка всех параметров производится с помощью cmake. Подробное руководство по настройке и компиляции Geant4 можно найти в файле cmake/INSTALL.g4cmake после распаковки тарбола с исходниками. Поскольку во время построения Geant4 я столкнулся с проблемами, то изложу все подробности здесь.

Итак, сначала, в соответствии с инструкциями руководства, создаем новую директорию geant4.9.5-builld/ на том же уровне файловой иерархии, что и корневая директория распакованных исходников (хотя конкретные название и местоположение новой директории не имеют особого значения). После этого переходим в эту директорию. Теперь следует сконфигурировать билд с помощью cmake. Я использовал для этого стандартную псевдографическую оболочку ccmake - в ней удобней настраивать параметры билда:
ccmake ../geant4.9.5
В открывшемся окне выбираем те параметры, которые следует изменить. Клавиша Enter позволяет переключать toggle-параметры и переводит в режим редактирования строковые параметры. В частности, я изменил значение CMAKE_INSTALL_PREFIX с /usr/local на /usr/local/geant4, кроме того, я установил параметры GEANT4_INSTALL_DATA, GEANT4_USE_GDML, GEANT4_USE_OPENGL_X11 и GEANT4_USE_QT в ON. Второй параметр в этом списке нужен для компиляции модуля GDML, а последние два - для компиляции модулей OpenGL и Qt. Забегая вперед отмечу, что GDML будет нужен для компиляции одного из advanced examples - ChargeExchangeMC  (GDML используется в нем для описания геометрии физической установки), а OpenGL и Qt нужны для качественной визуализации установки и частиц (картинка, приведенная ниже, создана с помощью драйвера визуализации OpenGL/Qt). После настройки всех параметров жмем на клавишу с и ждем, пока cmake сконфигурирует билд. Если в процессе конфигурации не возникло ошибок, то в нижнем окне ccmake появится подсказка Press [g] to generate and exit. Далее жмем на g, а после выхода из ccmake запускаем make и make install.

Все, Geant4 построен и установлен. Теперь нужно сделать так, чтобы конфигурация Geant4 (переменные среды и т.п.) автоматически настраивалась при входе в систему. Для этого необходимо, чтобы в файле $HOME/.bash_profile (если вы используете bash в качестве оболочки) сорсились файлы /usr/local/geant4/bin/geant4.sh и /usr/local/geant4/share/Geant4-9.5.0/geant4make/geant4make.sh. И здесь возникает первая проблема: из-за бага в исходном коде Geant4, файл /usr/local/geant4/bin/geant4.sh содержит мусор в shebang-строке (самой первой строке скрипта). Соответственно
#!@GEANT4_TC_SHELL_PROGRAM
следует заменить на
#!/bin/sh
В файл $HOME/.bash_profile помещаем следующие строки:
export G4ROOT=/usr/local/geant4
export G4ROOTMAKE=$G4ROOT/share/Geant4-9.5.0/geant4make
export G4WORKDIR=$HOME/geant4

[ -f $G4ROOT/bin/geant4.sh ] && . $G4ROOT/bin/geant4.sh
[ -f $G4ROOTMAKE/geant4make.sh ] && . $G4ROOTMAKE/geant4make.sh
Вторая проблема заключается в том, что в файле /usr/local/geant4/share/Geant4-9.5.0/geant4make/geant4make.sh настраивается переменная среды LD_LIBRARY_PATH, но почему-то не настраивается PATH, а эта переменная нужна для поиска пользовательских программ и скомпилированных примеров из исходного кода Geant4. Поэтому ниже в $HOME/.bash_profile добавляем строку
PATH=$PATH:$G4WORKDIR/bin/$G4SYSTEM
Теперь вернемся к проекту ChargeExchangeMC, исходный код которого поставляется вместе с исходниками Geant4 в директории examples/advanced/ChargeExchangeMC. Эта программа использовалась для моделирования отклика детекторов в реальных физических экспериментах по изучению реакции пион-нуклонной перезарядки. Однако, программа может быть легко адаптирована для изучения фактически любых адронных реакций  на почти произвольных физических установках (так, геометрия установки описывается в формате GDML и ее легко изменять без перекомпиляции программы). Подробности здесь.

В обновленной версии ChargeExchangeMC проведена серьезная работа по улучшению производительности программы, в частности, значительно оптимизирован расчет физических величин, исправлены ошибки, добавлены новые алгоритмы реконструкции событий. Одна из важных новинок - специализированный фреймворк для создания и отображения произвольных графических примитивов (scene primitives). Фреймворк основан на run-duration модели G4VModel. В рамках этого фреймворка реализованы 3 графических примитива: подсветка центральных кристаллов детекторов, вспомогательные радиальные линии, которые могут быть размещены на графической сцене в любом количестве под разными углами относительно центра мишени, маркер центра мишени.


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

понедельник, 28 ноября 2011 г.

vim: TagHighlight и tagbar - немного магии цвета

Про плагин TagHighlight (который раньше назывался ctags_highlighting) я уже писал (см. здесь). C тех пор в TagHighlight многое изменилось, прежде всего сам движок, но кроме этого было добавлено большое число новых фич. Самая главная фича - это возможность использовать разные подсветки для разных проектов. Однако для ранжира подсветок по проектам я по-прежнему использую скрипт MakeVimHlTags - так уж у меня сложилось - который, естественно, тоже обновился (новая версия доступна для скачивания здесь). В данном посте я хочу продемонстрировать другую возможность нового TagHighlight - подсветку буферов вспомогательного типа (FileType) тегами из некоторого проекта. Такими буферами могут быть, например, поля редактирования при коммите svn (в этом случае буфер имеет тип svn). Чтобы понять, что это такое и для чего это нужно представьте, что вы делаете svn commit, открывается ваш любимый редактор vim, вы вводите сообщение об изменениях, и названия объектов тут же подсвечиваются в соответствии с тегами для данного проекта - на мой взгляд это очень удобно.

Другой вспомогательный тип буфера - и тут я плавно перехожу ко второму герою повествования - это тип tagbar. Плагин tagbar - это современная замена taglist. Когда я решил посмотреть, что он из себя представляет, он сразу стал моим фаворитом. Это действительно лучший вариант taglist. Во-первых, у него нет глюков, связанных с переходом между табами. Во-вторых, он представляет информацию о тегах в более систематизированном виде. В-третьих, имеет очень приличный look-n-feel. Ну и то, что он предоставляет собственный FileType, можно использовать для подсветки TagHighlight!

Кстати, для того, чтобы tagbar не сортировал теги по алфавиту, а показывал их в той последовательности, в которой они объявлены в исходном файле, добавьте в .vimrc строку:
let g:tagbar_sort = 0
Итак, вот картинка (кликните для увеличения):


На этой картинке представлено два обычных буфера - исходники на C++, и буфер tagbar (самое правое окно). Все три буфера расцвечены с помощью TagHighlight. Для создания тегов в MakeVimHlTags использовался пропатченный ctags (см. здесь), который я настоятельно рекомендую, так как за время его использования c августа этого года никаких ошибок я не обнаружил, а вот проблемы с пропуском тегов исчезли.

Теперь о том, как добиться подсветки tagbar c помощью TagHighlight. Я по-прежнему предполагаю (по крайней мере так сделано у меня), что мы используем понятие проектов, которые находятся в директории, на которую ссылается переменная окружения $TAGSDIR (лично я установил ее в файле .bash_profile в значение $HOME/opt/tags), и состоят из файлов с тегами ctags и cscope (для TagHighlight эти файлы не нужны - просто они упоминаются в коде, который я приведу ниже), и файла с подсветкой для TagHighlight, который был создан с помощью MakeVimHlTags и имеет имя типа projectname_c.vim, где projectname - имя конкретного проекта. Итак, вот участок кода, который отвечает за запуск подсветок для проектов (вы можете вставить его в ваш .vimrc, лично я положил его в отдельный файл $HOME/.vim-myprojects.vim, который сорсится в конце .vimrc):

" ---- Setting tags and tag highlights
" ----
fun<SID>SetTags(ctags_db, cscope_db)
    if filereadable(a:ctags_db)
        execute 'set tags+=' . a:ctags_db
    endif
    if filereadable(a:cscope_db)
        execute 'cscope add ' . a:cscope_db
    endif
endfun

fun<SID>SetCTagsHl(ctagshl_db, skipft)
    if !exists('g:loaded_TagHighlight')
        return
    endif
    if index(split(a:skipft), &ft) < 0
        "let g:TagHighlightSettings['DebugLevel'] = 'Information'
        let g:TagHighlightSettings['UserLibraries'= [a:ctagshl_db]
        " add 'FileType' to LanguageDetectionMethods to see highlights when
        " committed to svn (to achieve this, this function must be triggered
        " with BufReadPre event), tagbar is also to be highlighted
        let g:TagHighlightSettings['LanguageDetectionMethods'=
                                                \ ['Extension''FileType']
        let g:TagHighlightSettings['FileTypeLanguageOverrides'=
                                                \ {'svn''c''tagbar''c'}
    endif
endfun

fun<SID>SkipCTagsHlForFt(skipft)
    if !exists('g:loaded_TagHighlight')
        return
    endif
    if index(split(a:skipft), &ft) >= 0
        let g:TagHighlightSettings['UserLibraries'= []
        " trigger FileType event forcibly
        exe "set ft=" . &ft
        "ReadTypes
    endif
endfun

let mpr_def_skipft = 'perl sh'

let mpr_tags =
    \ [
        \ ['*/geant4/pnpi/*''pnpi', mpr_def_skipft],
        \ ['*/geant4/src/geant4*''geant4', mpr_def_skipft],
    \ ]

for item in mpr_tags
    let mpr_tagname = item[1]
    let mpr_hltags  = $TAGSDIR."/".mpr_tagname."_c.vim"
    let mpr_ctags   = $TAGSDIR."/".mpr_tagname.".ctags"
    let mpr_cstags  = $TAGSDIR."/".mpr_tagname.".cscope_out"
    if filereadable(mpr_hltags)
        exe "autocmd BufReadPre,BufNewFile ".item[0].
                    \ " call <SID>SetCTagsHl('".mpr_hltags."', '".item[2]."')"
        exe "autocmd BufRead ".item[0].
                    \ " call <SID>SkipCTagsHlForFt('".item[2]."')"
    endif
    exe "autocmd VimEnter ".item[0].
                \ " silent call <SID>SetTags('".mpr_ctags."', '".mpr_cstags."')"
endfor

Функция <SID>SetTags() к нашему разговору не относится - она нужна для загрузки тех самых ctags и cscope файлов проекта, о которых я упомянул выше, а они отвечают не за подсветку тегов, а за навигацию по ним. Интерес представляют функция <SID>SetCTagsHl(), объекты mpr_def_skipft, mpr_tags и цикл for по mpr_tags. Объект-строка mpr_def_skipft определяет, файлы каких типов (в данном случае perl и sh) не должны подсвечиваться TagHighlight. В объекте-коллекции mpr_tags указан полный список всех проектов, которые могут быть загружены из директории $TAGSDIR. В данном случае я утверждаю, что в директории $TAGSDIR (привязка к которой осуществляется ниже - в цикле for) находятся два проекта с именами pnpi и geant4, рабочие директории проектов (которые указаны в списках autocmd BufReadPre, BufNewFile, BufRead и VimEnter цикла for в качестве первого аргумента - через item[0] - и являются триггером для чтения тегов и запуска подсветки TagHighlight) - это */geant4/pnpi/* и */geant4/src/geant4/* соответственно. В качестве третьего параметра в элементах коллекции mpr_tags указан mpr_def_skipft - он будет использован в цикле for для отключения подсветки TagHighlight для файлов типа perl и sh (путем вызова функции <SID>SkipCTagsHlForFt() из autocmd BufRead).

Вызов подсветки tagbar происходит внутри функции <SID>SetCTagsHl() с помощью всего двух объявлений:
        let g:TagHighlightSettings['LanguageDetectionMethods'=
                                                \ ['Extension''FileType']
        let g:TagHighlightSettings['FileTypeLanguageOverrides'=
                                                \ {'svn''c''tagbar''c'}
В первой строке мы говорим, что будем определять нужна ли подсветка TagHighlight как по расширению читаемого файла, так и по заданному для него типу FileType, который устанавливается в недрах движка vim. Во второй строке мы проассоциировали FileType tagbar с FileType c, и это значит, что в окне tagbar появится подсветка, определенная для C-проектов, а это то, чего мы и добивались. Кроме того, мы проассоциировали FileType svn с FileType c - это нужно для подсветки коммитов svn, о чем я говорил раньше.

пятница, 28 октября 2011 г.

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

nginx - популярный и очень быстрый веб-сервер, который легко настраивается в качестве реверсного http-прокси. Это означает, что nginx может быть настроен таким образом, чтобы в соответствии с правилами маршрутизации, которые администратор прописывает в конфигурационных файлах, внешние http запросы направлялись в различные участки внутренней сети. Самый удобный способ определить такие участки внутри сети - это создать апстримы (upstreams), в которые будут входить один или несколько концептуально (или географически, или как-нибудь еще) связанных серверов.

Апстрим - одна из основных концепций nginx - это просто коллекция серверов, осуществляющих реальную обработку http-запросов, поступающих из внешней сети на вход nginx, работающего в данном контексте как http-прокси. Выбор конкретного сервера из апстрима для обработки очередного запроса определяется опциями, заданными для всех серверов апстрима внутри конфигурационного файла, чаще всего это простая round-robin модель.

К сожалению, внутри определения апстрима невозможно сослаться на другой апстрим, составив таким образом комбинацию апстримов. Однако было бы чрезвычайно удобно иметь такую возможность. Представьте, что мы определили апстрим u1 для серверов особого типа, находящихся в Москве, через некоторое время мы добавляем еще один набор серверов такого же типа u2, размещенных, например, во Владивостоке. А теперь представим, что наши правила маршрутизации требуют создания комбинированного апстрима ucombined, состоящего из всех серверов данного типа, находящихся в Москве и Владивостоке. Очевидное решение - включить данные из u1 и u2 в ucombined. Это можно сделать с помощью директивы include, но это очень неудобно, так как данные о серверах придется помещать в отдельные файлы, и, к тому же, что нам делать, если все сервера из Владивостока нужно пометить как backup? Остается единственный выход - продублировать объявления серверов в апстриме ucombined. А это уже совсем нехорошо - лишний копи-паст может легко стать источником ошибок, если в одной из его инстанций сделать изменения, а в других - забыть.

В представленном модуле к существующим директивам, доступным на уровне блока upstream, добавляется еще одна - add_upstream, которая включает в текущий апстрим серверы из другого, уже объявленного апстрима. Директива add_upstream - полноценный член сообщества директив для upstream, поэтому внутри блока upstream ее можно использовать наряду с любыми другими директивами, доступными на этом уровне. Вот пример, который мы будем тестировать ниже:
    upstream u1 {
        server localhost:8020;
    }
    upstream u2 {
        server localhost:8030;
    }
    upstream ucombined {
        server localhost:8030;
        add_upstream u1;
        add_upstream u2 backup;
    }
Апстрим ucombined включает серверы localhost:8020 и localhost:8030 c помощью директив add_upstream u1 и add_upstream u2 backup. Во втором случае используется единственная доступная для add_upstream опция backup, которая помечает все серверы из апстрима u2 как backup. Все опции (backup, weight и т.д.) серверов, переданных из u1 и u2 в ucombined, будут сохранены в ucombined. В данном примере в апстрим ucombined дополнительно, с помощью директивы server, включен сервер localhost:8030. Поскольку он также входит в апстрим u1, но при этом с помощью опции add_upstream backup помечается как backup, то общий вес его остается 1.

Теперь перейдем к описанию модуля с точки зрения программирования и сборки. Отличным пособием по созданию модулей nginx могут служить два ресурса от Эвана Миллера (здесь и здесь). Поэтому я не буду поднимать общие вопросы, а перейду сразу к деталям данного модуля.

Прежде всего обратимся к процедуре сборки модуля. Данный модуль состоит из одного файла-исходника на языке C, в котором определена всего одна функция, и вспомогательного файла config, который нужен nginx во время сборки. Да, это может показаться неудобным, но все модули nginx должны линковаться статически во время сборки самого nginx. Файл config содержит мета-информацию о модуле (имя, пути, дополнительные подключаемые библиотеки и т.д.). Вот его содержимое:
ngx_addon_name=ngx_http_upstream_add_upstream_module
HTTP_MODULES="$HTTP_MODULES $ngx_addon_name"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_upstream_add_upstream_module.c"
Название ngx_http_upstream_add_upstream_module выглядит неуклюжим, но зато говорит само за себя и соответствует правилу, принятому в nginx для именования модулей. Кроме того, из значения определения NGX_ADDON_SRCS видно, что имя нашего исходного файла ngx_http_upstream_add_upstream_module.c. Директория, в которой размещены файл config и исходник, может быть произвольной, она указывается в опции --add-module скрипта configure во время сборки nginx. Итак, нам понадобятся исходники nginx, которые можно взять на официальном сайте. Кроме того, в целях тестирования я добавлю замечательный модуль ngx_echo, который следует загрузить отдельно.

После загрузки и распаковки nginx и ngx_echo, конфигурируем nginx следующим образом:
./configure --add-module=/path/to/nginx_http_upstream_add_upstream_module --add-module=/path/to/ngx_echo
(вместо /path/to нужно подставить реальные пути к директориям модулей). Затем стандартная процедура make, make install и можно начинать тестирование. Но перед этим я хочу прокомментировать содержание исходного файла.

Как я уже отметил, модуль ngx_http_upstream_add_upstream_module очень простой и состоит всего из одной функции, в которой описаны действия nginx при чтении директивы add_upstream. Кроме этой единственной функции в файле ngx_http_upstream_add_upstream_module.c объявлены объекты, необходимые nginx при инициализации модуля. Вот они:
static ngx_command_t  ngx_http_upstream_add_upstream_commands[] = {

    { ngx_string("add_upstream"),
      NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
      ngx_http_upstream_add_upstream,
      0,
      0,
      NULL },

      ngx_null_command

};

static ngx_http_module_t  ngx_http_upstream_add_upstream_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    NULL,                                  /* create location configuration */
    NULL                                   /* merge location configuration */
};

ngx_module_t  ngx_http_upstream_add_upstream_module = {
    NGX_MODULE_V1,
    &ngx_http_upstream_add_upstream_module_ctx, /* module context */
    ngx_http_upstream_add_upstream_commands,    /* module directives */
    NGX_HTTP_MODULE,                            /* module type */
    NULL,                                       /* init master */
    NULL,                                       /* init module */
    NULL,                                       /* init process */
    NULL,                                       /* init thread */
    NULL,                                       /* exit thread */
    NULL,                                       /* exit process */
    NULL,                                       /* exit master */
    NGX_MODULE_V1_PADDING
};
В массиве ngx_http_upstream_add_upstream_commands определен список директив, которые предоставляет данный модуль. В нашем модуле определена единственная директива add_upstream, которая может быть использована внутри блока upstream (что отражено включением флага NGX_HTTP_UPS_CONF), и поддерживает один или более параметров (что отражено включением флага NGX_CONF_1MORE). Обработчиком директивы при чтении конфигурации является функция ngx_http_upstream_add_upstream() (та самая единственная функция модуля о которой мы говорили). Структура ngx_http_upstream_add_upstream_module_ctx описывает действия, которые nginx будет выполнять с конфигурациями модуля (или, другими словами, персистентными состояниями модуля) на различных этапах обращения к ним (создание, слияние (merge) конфигурации при переходе парсера с высокого уровня конфигурации (http или server) на уровень ниже (server или location) и т.п.). Наш модуль не имеет состояний (в самом деле, все данные, необходимые для наполнения блока апстрима серверами из другого апстрима с помощью директивы add_upstream доступны в момент чтения конфигурации парсером и не нуждаются в сохранении), поэтому все элементы структуры ngx_http_upstream_add_upstream_module_ctx пусты. И, наконец, в последней - главной структуре модуля - ngx_http_upstream_add_upstream_module, определены ссылки на только что заданный контекст, директивы и хуки модуля на различных этапах его существования (инициализация мастер-процесса, модуля, завершение работы мастер-процесса и т.д.). В нашем простом модуле все эти хуки тоже не нужны.

Ниже приводится текст функции ngx_http_upstream_add_upstream(), которая определяет действия nginx в момент чтения директивы add_upstream. Для удобства комментирования кода строки пронумерованы.
 86 static char *
 87 ngx_http_upstream_add_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 88 {
 89     ngx_uint_t                      i, j;
 90     ngx_http_upstream_main_conf_t  *usmf;
 91     ngx_http_upstream_srv_conf_t   *uscf, **uscfp;
 92     ngx_http_upstream_server_t     *us;
 93     ngx_str_t                      *value;
 94     ngx_uint_t                      backup = 0;
 95 
 96     usmf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);
 97     uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
 98     uscfp = usmf->upstreams.elts;
 99     value = cf->args->elts;
100 
101     if (cf->args->nelts > 3) {
102         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
103                            "number of parameters must be 1 or 2");
104         return NGX_CONF_ERROR;
105     }
106 
107     if (value[1].len == uscf->host.len &&
108         ngx_strncasecmp(value[1].data, uscf->host.data, value[1].len) == 0) {
109         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
110                            "upstream \"%V\" makes recursion", &value[1]);
111         return NGX_CONF_ERROR;
112     }
113 
114     if (cf->args->nelts == 3) {
115         if (ngx_strncmp(value[2].data, "backup", 6) == 0) {
116             backup = 1;
117             uscf->flags |= NGX_HTTP_UPSTREAM_BACKUP;
118         } else {
119             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
120                                &value[2]);
121             return NGX_CONF_ERROR;
122         }
123     }
124 
125     for (= 0; i < usmf->upstreams.nelts; i++) {
126         if (uscfp[i]->host.len == value[1].len &&
127             ngx_strncasecmp(uscfp[i]->host.data,
128                             value[1].data, value[1].len) == 0) {
129             if (uscf->servers == NULL) {
130                 uscf->servers = ngx_array_create(cf->pool, 4,
131                                             sizeof(ngx_http_upstream_server_t));
132                 if (uscf->servers == NULL) {
133                     return NGX_CONF_ERROR;
134                 }
135             }
136             us = ngx_array_push_n(uscf->servers, uscfp[i]->servers->nelts);
137             if (us == NULL) {
138                 return NGX_CONF_ERROR;
139             }
140             ngx_memcpy(us, uscfp[i]->servers->elts,
141                 sizeof(ngx_http_upstream_server_t) * uscfp[i]->servers->nelts);
142             uscf->flags |= uscfp[i]->flags;
143             if (backup) {
144                 for (= 0; j < uscfp[i]->servers->nelts; j++) {
145                     us[j].backup = 1;
146                 }
147             }
148             return NGX_CONF_OK;
149         }
150     }
151 
152     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "upstream \"%V\" not found",
153                        &value[1]);
154     return NGX_CONF_ERROR;
155 }
В строках 89-99 определены объекты, с которыми данная функция будет работать. В частности usmf - указатель на главную конфигурацию модуля ngx_http_upstream_module, из которой нам понадобится получить список всех апстримов, прочитанных парсером на данный момент. Этот список хранится в элементе upstreams типа ngx_array_t. Тип ngx_array_t - это простой массив, который предоставляет информацию о количестве элементов в значении nelts, а сами элементы массива доступны через указатель elts. Для удобства определена переменная uscfp, которая представляет собой указатель на начало списка апстримов из usmf->upstreams. Переменная uscf инициализируется указателем на текущий апстрим. Переменная us будет инициализирована указателем на список серверов, который будет создан для добавления в текущий апстрим, если все пойдет хорошо. Переменная value - это указатель типа ngx_str_t, который инициализируется указателем на начало строки директивы. Так, если наша директива записывается как add_upstream u1;, то value[0] будет соответствовать строка add_upstream, а value[1] - строка u1. Тип ngx_str_t представляет собой быструю строку с двумя элементами - len типа size_t и data типа u_char*; понятно, что элемент len должен соответствовать длине строке, а указатель data - началу строки, при этом сама строка не обязана заканчиваться нулевым символом. Переменная backup понадобится для определения была ли задана одноименная опция директивы add_upstream.

Дальше все просто. В строках 101-112 проверяются недопустимые условия, в частности количество заданных опций и возможная недопустимая рекурсивность текущего апстрима. В строках 114-123 проверяется, что если опция директивы задана, то она может быть только backup.

В строках 125-150 происходит самое интересное. Проходим по всем апстримам, прочитанным парсером на данный момент и находим апстрим, название которого совпадает с первым параметром директивы (т.е. со значением value[1]). В случае совпадения в строках 129-135 проверяем, была ли выделена память под список серверов текущего апстрима (память уже выделена, если первой директивой в блоке апстрима была директива server, в противном случае, если это первая директива add_upstream в блоке - нет). Если память не была выделена, выделяем ее с помощью функции ngx_array_create(). Память выделяется в пуле и за ее освобождением проследит nginx.

В строках 136-141 добавляем необходимое количество элементов, равное количеству серверов, объявленных в апстриме, на который ссылается данная директива add_upstream, и копируем элементы-серверы из этого апстрима в выделенный участок. Очевидно, что все опции, установленные в оригинальных серверах, скопируются в новый апстрим. В строке 142 добавляем флаги апстрима, из которого были скопированы серверы, в текущий апстрим. В строках 143-147 добавляем ко всем серверам, скопированным из апстрима-источника флаг backup в том случае, если текущая директива add_upstream имеет опцию backup. В строке 148 успешно выходим. Если исполняемый поток по какой-то причине не достигнет строки 148, то в строках 152-154 будет выведено диагностическое сообщение о том, что апстрим, на который ссылается директива add_upstream не найден, а функция вернет значение NGX_CONF_ERROR. Фактически это будет означать, что конфигурационный файл содержит ошибки и nginx не запустится.

Это все. А теперь немного потестируем наш модуль. Для этого в конфигурационном файле создадим три виртуальных сервера с именами main, server1 и server2.
    server {
        listen       8010;
        server_name  main;
        location / {
            proxy_pass http://ucombined;
        }
    }
    server {
        listen       8020;
        server_name  server1;
        location / {
            echo "Passed to $server_name";
        }
    }
    server {
        listen       8030;
        server_name  server2;
        location / {
            echo "Passed to $server_name";
        }
    }
Сервер main при обращении на него должен перенаправлять запрос на серверы server1 и server2 в соответствии с правилами, определенными в комбинированном апстриме ucombined. В случае конфигурации, приведенной выше, сервера server1 и server2 будут иметь одинаковые веса, поскольку апстрим u2 в ucombined забекаплен, но при этом он все равно в игре за счет первой директивы server. Сервер server1 входит в ucombined за счет директивы add_upstream u1 и имеет тот же вес, что и u2. Соответственно последовательные запросы к серверу main должны проксироваться попеременно на server1 и server2. Запускаем nginx с указанной конфигурацией и проверяем (с помощью curl):
$ curl http://localhost:8010
Passed to server1
$ curl http://localhost:8010
Passed to server2
$ curl http://localhost:8010
Passed to server1
$ curl http://localhost:8010
Passed to server2
$ curl http://localhost:8010
Passed to server1
$ curl http://localhost:8010
Passed to server2
$
Отлично. Убираем опцию backup в директиве add_upstream u2. Теперь вес сервера server2 становится 2, а вес сервера server1 по-прежнему 1, соответственно server2 должен срабатывать последовательно 2 раза, а server1 - один раз. Перезапускаем nginx и проверяем:
$ curl http://localhost:8010
Passed to server2
$ curl http://localhost:8010
Passed to server1
$ curl http://localhost:8010
Passed to server2
$ curl http://localhost:8010
Passed to server2
$ curl http://localhost:8010
Passed to server1
$ curl http://localhost:8010
Passed to server2
$ curl http://localhost:8010
Passed to server2
$ curl http://localhost:8010
Passed to server1
$
Все работает правильно. Исходный код модуля, а также тестовый образец конфигурации можно взять здесь.

Update. Залил наработки из этой статьи на гитхаб. Теперь модуль называется nginx-combined-upstreams-module и кроме директивы add_upstreams в нем объявлена еще одна директива combine_server_singlets. Ее функции описаны в README.

понедельник, 19 сентября 2011 г.

Цветной ngrep (в продолжение предпоследнего поста)

ngrep - это что-то среднее между tcpdump и wireshark. Он может показаться не таким удобным как wireshark, зато превосходит по удобству использования tcpdump, поскольку основной особенностью ngrep является вывод на экран полезного содержимого пакетов (то, что обычно называют payload), а не всевозможных сетевых деталей. Кроме того, ngrep работает в терминале, а это, на мой взгляд, огромный плюс.

Итак, сначала картинка:


А теперь настройки .hlrc:
snippet ngrep   -191 '^\s*(?:POST|GET)\s+' '^[ITU]\s+' -211 '\[[A-Z]+\]' \
                -120 '^[A-Z]\S+: ' -130 '^#+' \
                -210 '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+' \
                -140 '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' -190 '^HTTP.*'
и .hl_functions:
function ngrep()
{
    sudo `env which ngrep` $@ | hl -sngrep
}
Чтобы этот пост не получился совсем уж скучным и банальным, ниже я покажу как предоставить права на запуск ngrep обычному пользователю (заметили sudo в функции ngrep?).

Итак, в обычной ситуации обычный пользователь не имеет права на запуск ngrep, поскольку может получить доступ к информации, для него не предназначенной. Но что если нам часто требуется получать доступ к содержимому сетевых пакетов (например, при тестировании какого-нибудь веб-приложения), или мы работаем на домашнем компьютере и доступ к информации о том, что происходит в нашей сетке не только не ослабляет безопасность системы, а наоборот повышает ее? В этом случае нужно добавить себя в список sudoers. Я не стану добавлять записи в файл /etc/sudoers, а создам новый файл (назовем его users) в директории /etc/sudoers.d/. Итак, от имени суперпользователя открываем файл /etc/sudoers.d/users и записываем в него следующие строки:
Cmnd_Alias  NETTASKS = /usr/sbin/ngrep, /usr/sbin/wireshark, /usr/sbin/tcpdump

user1      ALL = NOPASSWD: NETTASKS
Вместо user1 нужно поставить ваше настоящее регистрационное имя. После завершения редактирования файла необходимо изменить его атрибуты доступа:
chmod 0440 /etc/sudoers.d/users
Если этого не сделать, sudo откажется выполнять инструкции данного файла с соответствующей диагностикой. Ввиду данного изменения, при дальнейшем редактировании /etc/sudoers.d/users придется подтверждать внесение изменений при записи на диск (в vim это делается командой :w!), но лучше всего воспользоваться командой visudo -f /etc/sudoers.d/users, которая знает, как работать с файлами sudoers

Кроме права на запуск ngrep я наделил пользователя user1 правами на запуск wireshark и tcpdump. Теперь для запуска этих двух команд данный пользователь может ввести sudo wireshark и sudo tcpdump, а для запуска ngrep достаточно просто ввести ngrep, поскольку в интерактивной среде ngrep - это функция, записанная нами в файл .hl_functions, которая через sudo вызывает настоящий ngrep.

Заметим также, что ввод пароля пользователем при запуске этих команд не требуется, так как в файле /etc/sudoers.d/users перед задачами NETTASKS  для пользователя user1 указана опция NOPASSWD. Кроме того, слово ALL указывает на то, что пользователь user1 имеет право запускать команды с любого хоста. В целях безопасности, наверное имеет смысл заменить ALL на localhost, но в этом случае user1 не сможет запускать команды из NETTASKS удаленно.

Update. Не сразу заметил, что функция ngrep() в приведенном выше виде,  работает неверно для многих достаточно типичных случаев, в частности, когда паттерн для поиска пуст (например, в случае ngrep '' 'tcp port 80'). Проблема в том, что переменная $@ внутри ngrep() удаляет все кавычки, которые в случае пустого паттерна (или паттернов, содержащих пробелы) имеют значение. К счастью, если переменную $@ взять в двойные кавычки, то информация о пустых аргументах и аргументах, содержащих пробелы, не теряется, и ее можно восстановить. Я не знаю, существует ли в sh или bash штатный способ вернуть кавычки в этом случае на место, скорее всего нет, поэтому я написал отдельную функцию quote_args_if_needed() и поместил ее в начале файла .hl_functions.
function quote_args_if_needed()
{
    local result=''
    for i in "$@" ; do
        if [[ -z "$i" || "$i" =~ ' ' ]] ; then
            result=$result" '"$i"'"
            continue
        fi
        result=$result' '$i
    done
    echo $result
}
Функция проходит по переданным ей аргументам и добавляет их (через пробел) к изначально пустой переменной result. Если очередной аргумент пуст, либо содержит пробелы, то он обертывается одинарными кавычками. Мы будем передавать в quote_args_if_needed() значение "$@". Переменная $@, взятая в двойные кавычки представляет собой правильный массив переданных аргументов, в котором те аргументы, которые были заключены в кавычки, представляют единое целое (в отличие от бескавычечной версии, в которой все содержимое переданных аргументов разбивается по пробельным символам). Теперь нужно использовать новую функцию в ngrep():
function ngrep()
{
    eval sudo `env which ngrep` `quote_args_if_needed "$@"` | hl -sngrep
}
Как видите, мне пришлось обернуть все в eval. Это потому, что кавычки для аргументов, которые нам удалось вернуть с помощью quote_args_if_needed(), будут переданы без eval в `env which ngrep` дословно, и он будет ругаться на syntax error. Команда eval очистит нашу строку от кавычек, и при этом сохранит пустые аргументы и аргументы, содержащие пробелы.

суббота, 10 сентября 2011 г.

Переносы русских слов в texlive

У меня просто нет слов! После каждого очередного обновления texlive у меня напрочь слетают переносы русских слов в latex. И каждый раз приходится долго и мучительно вспоминать, что я такое делал, чтобы это исправить. Чтобы в следующий раз не мучаться, запишу-ка я необходимые действия сюда.

Итак, после обновления texlive открываем файл /usr/share/texlive/texmf/tex/generic/config/language.dat (конкретный путь зависит от дистрибутива и репозитория) и добавляем в него строки
ruseng ruenhyph.tex
=russian
=english
сразу же за подобными определениями для english. После этого запускаем в терминале от имени root
fmtutil-sys --all
Теперь переносы должны работать.

Использование пользовательской подсветки команд в терминале (на примере make)

Сформулируем задачу следующим образом: мы хотим обеспечить собственные правила подсветки вывода произвольной команды в терминале. Для простоты будем считать, что наш терминал поддерживает 256 цветов (что уже давно справедливо и для vte, и для konsole, и для xterm) и мы используем bash. Задача абсолютно нетривиальная хотя бы потому, что нормального полноценного решения ее, насколько я знаю, пока не существует. Если бы вышеперечисленные терминалы поддерживали систему плагинов, то решить ее было бы, наверное, просто, но на данный момент это не так.

Стоит отметить, что существует целый ряд решений, которые могут помочь с расцвечиванием консоли для разных программ: это colordiff, GNU souce-highlight (который я часто использую для подсветки кода в этом блоге) и другие, а также использование различных цветовых опций общеизвестных команд, таких как ls, tree, grep и т.д., например в моем файле .bashrc определены алиасы
alias ls='ls -F --color=tty'
alias tree='tree -C'
Недостатки этих программ очевидны: собственно, они не решают поставленную нами задачу, так как предназначены для подсветки вывода заранее определенных программ по заранее определенным правилам.

Совсем недавно я прочитал про программу grc (generic colouriser). Точный ресурс сейчас точно не вспомню, но из свеженайденных - например здесь. Это уже что-то: в конфигах grc можно задать правила подсветки для вывода произвольной программы, основанные на регулярных выражениях. Я не стал устанавливать данную программу, поскольку имею собственное решение, очень похожее по смыслу и, надеюсь, не уступающее по возможностям. На нем и остановимся поподробнее.

Программа была написана на perl еще в 2007 или 2006 году. Она состоит из двух частей: модуля Term::Highlight, реализующего основную работу по поиску вхождений, соответствующих заданным регулярным выражениям, и вставке цветовых тегов на их границы, и скрипта hl, в котором определяются режимы работы программы и куда передаются аргументы, определяющие подсветку. В пакет программы входят справочные страницы man, поэтому разобраться в ней будет несложно. Скачать программу можно с sourceforge.net (здесь), или с cpan.org (здесь).

Изначально я планировал использовать Term::Highlight как фильтр для подсветки слов или регулярных выражений в длинном выводе программ, например так:
$ cat some_long_file | hl -100 some_word -101 another_word
Здесь осуществляется вывод на экран длинного файла some_long_file, при этом слова some_word и another_word будут подсвечены цветами, соответствующими значениям 100 и 101 в 256-цветной палитре терминала. Term::Highlight прекрасно справляется в ситуации, когда участки текста, соответствующие заданным словам или регулярным выражениям в hl оказываются вложены или пересекаются (причем многократно) друг с другом, например в случае
$ cat some_long_file | hl -100 some_word -101 'w\w+'
внутри подсвеченного цветом 100 слова some_word будет подсвечен цветом 101 участок, соответствующий выражению 'w\w+', т.е. слово word. Кроме этого, Term::Highlight поддерживает 8-цветные консоли, поиск в двоичных файлах, установку жирного текста, поиск без учета регистра и др. В дальнейшем я реализовал режим цветного grep и поддержку сниппетов.

Теперь подробнее о сниппетах. Очевидно, что для сложной подсветки, например синтаксиса какого-нибудь языка, нам понадобится передать в hl огромное число аргументов - это не удобно, а главное, система скорее всего не справится с такой длинной строкой и придется использовать xargs, а это очень неудобно. Поэтому в  версии 1.7 была добавлена возможность записывать именованные сниппеты в файле $HOME/.hlrc. Сниппеты содержат аргументы подсветки hl и их можно передавать по имени с помощью опции -s. Ниже мы напишем сниппет по имени make для подсветки вывода одноименной команды.

Буквально вчера я внес в Term::Highlight изменение, связанное с безопасным использованием hl в пайпах. Новая версия имеет номер 1.7.1. Отныне hl знает, куда направлен ее вывод, и если это не stdout, то цветовые теги вставляться не будут. Это поможет использовать hl для подсветки вывода произвольных программ и не беспокоиться в том случае, если вывод подсвечиваемой программы направлен через пайп на вход другой программы. Напомню, что точно так же работает ls --color=tty: если ее  вывод перенаправить в файл или на вход другой программы, или раздвоить с помощью tee , то цветовые теги исчезнут.

Итак, теперь нужно сделать так, чтобы при наборе make в командной строке терминала вместо реальной программы make подставлялось нечто, фильтрующее ее вывод с помощью hl. Первое, что приходит на ум - использовать алиас - не годится. Алиасы bash очень примитивны и мы не сможем с их помощью передать все возможные аргументы make, а затем через пайп установить фильтр hl. Поэтому будем использовать функцию с именем make, которая будет подменять настоящий make в интерактивной среде.

Я предлагаю сделать так: в файле $HOME/.bash_profile установить переменную HL_ALIASES, которая будет указывать на путь к файлу, содержащему нашу функцию. Пусть этот файл называется .hl_functions. Тогда в .bash_profile записываем строку
export HL_ALIASES=$HOME/.hl_functions
Затем в $HOME/.bashrc считываем содержимое этого файла:
[ -n "$HL_ALIASES" ] && [ -f "$HL_ALIASES" ] && . $HL_ALIASES
В файл .hl_functions поместим функцию make (в дальнейшем туда же можно поместить функции, определяющие подсветку вывода других программ):
function make()
{
    `env which make` $@ 2>&1 | hl -smake
}
Все очень просто: передаем stdout и stderr настоящего make (`env which make`) со всеми аргументами на вход команды hl, которая преобразует его на основании сниппета make (hl -smake). Такой подход безопасен в случае использования вывода подсвечиваемой программы для передачи на вход другой: hl не будет подставлять цветовые теги в этом случае. Более того, поскольку функция make() определена в .bashrc, то скриптам она будет неизвестна, поскольку объекты .bashrc определены для интерактивного использования. Соответственно можно совершенно не беспокоиться, если подсвечиваемая команда будет использоваться в каком-нибудь скрипте: в этом случае всегда будет вызываться настоящая программа. (Если вы действительно хотите использовать функции подсветки в скриптах, чего я делать крайне не рекомендую, то сорсить .hl_functions следует не в .bashrc, а в .bash_profile, а затем экспортировать определенные функции с помощью export -f).

Теперь приведем определение сниппета make, которое следует поместить в $HOME/.hlrc:
snippet make    -b -215 '^\s*gcc\b' '^\s*g\+\+(?=\s)' '^\s*libtool:\s*\w+:' \
                '^s*\/bin\/sh\s+[\w/.]+' -rb -108 '[\w/.-]+\.c\b(?!:)' \
                -119 '[\w/.-]+\.(cc|cpp|cxx|c\+\+)\b(?!:)' \
                -30 '\s*[\w/.*-]+\.o\b' -203 '(?<=\s-o)\s*[^-][\w/.-]*' \
                -42 '^\s*make\[\d+\]' -64 '(?:^|\s+)\-l[\w/+-]+' \
                -50 '^[\w/.-]+\.\w+:(?:\d+:\d+:)?' \
                -204 '^\s*(?:rm|mv|ln|cp)\s+(?:-\w+)?' \
                -196 '(?:О|о)шибка( \d+)?' -202 '(?:П|п)редупреждение( \d+)?' \
                -120 'Выход из.*$' 'Вход в.*$'
С помощью числовых тегов здесь определены цвета для следующих за ними регулярных выражений, на которых я останавливаться не буду - безусловно, для того чтобы эффективно использовать этот подход, вам самим необходимо знать, как составляются регулярные выражения perl. Тег (или опция) -b устанавливает, что следующие за ним слова и выражения нужно печатать жирным шрифтом, тег -rb отменяет жирное начертание. Подробнее о различных опциях hl можно прочитать на странице справки man hl. В приведенных выше правилах сниппета make задаются цвета подсветки команд gcc, g++, libtool и др., исходников C и C++, объектных файлов с суффиксом .o, предупреждений и ошибок компилятора (на русском языке), сообщений о входе и выходе make в директории, подключаемых с помощью опции -l библиотек, названий файлов, получаемых на выходе, которые задаются с помощью опции компилятора -o и других артефактов. Самое замечательное, что данные опции можно изменять и добавлять и они сразу становятся эффективным без ре-сорсинга файла .hl_functions.

А теперь главное - картинка. Это пример работы функции make при компиляции исходников sane-backends (картинка кликабельна):