четверг, 21 августа 2014 г.

Еще один фильтр для pandoc

На этот раз будем решать следующую задачу. Требуется стилизовать отдельные (standalone) изображения (такие обычно помещаются внутрь HTML тэгов <div> … </div>) в документе, представляющем собой отдельный фрагмент HTML (то есть inlined HTML — без поддержки CSS). Я не взял эту задачу с потолка. Именно такие фрагменты я вставляю в свой блог каждый раз, когда пишу очередную статью. Непосредственно HTML код статьи генерируется из простого текстового документа с помощью pandoc и дополнительных фильтров (я писал о фильтрах, которые я использую здесь и здесь). Фильтры pandoc — замечательная вещь! Но у них есть небольшой недостаток. Если запускать фильтр с помощью опций pandoc -F или --filter, то в них нельзя будет передать их собственные опции, которые, как у каждой отдельной программы, могут быть предусмотрены — это несмотря на обратное утверждение в документации к классу ToJSONFilter. Обойти это можно разными способами: пропускать фильтр через пайпы без применения опций -F или --filter, передавать настройки с помощью переменных среды, в конце концов, создать несколько фильтров в которых захардкодить нужные настройки. Но все это совсем некрасиво. Наш фильтр должен уметь принимать разные стили изображений. При этом он не должен читать их как опции командной строки, поскольку pandoc их не сможет передать. К счастью, в pandoc есть одна хитрая лазейка на этот случай — метаданные документа. Они не отображаются в сгенерированных документах и при этом в них можно записать все что угодно. Фильтры pandoc имеют доступ к метаданным исходного документа! Вот исходный код фильтра (я назвал его imgStyleFromMeta), а ниже пояснения.
-- imgStyleFromMeta.hs

{-# OPTIONS_HADDOCK prune, ignore-exports #-}

import Text.Pandoc.JSON
import Text.Pandoc.Walk (walk)
import qualified Data.Map as M
import Data.String.Utils (replace)

-- | Applies image style found in the metadata of the document
--
-- Finds field /img_style/ in the document metadata and applies its value
-- to all standalone images found in the document. Field /img_style/ may be
-- declared inside YAML block on the top of the document like
--
-- > ---
-- > img_style : |
-- >  <div class="figure" style="clear: both; text-align: center;">
-- >  <a href="$SRC$" style="margin-left: 1em; margin-right: 1em;">
-- >  <img border="0" src="$SRC$" /></a></div>
-- > ...
--
-- Additionally placeholders /$SRC$/ and /$TITLE$/ in the /img_style/ will
-- be replaced by actual source and title specified in an image parameters.
--
imgStyleFromMeta :: Maybe Format -> Pandoc -> IO Pandoc
imgStyleFromMeta (Just (Format "html")) p@(Pandoc m bs) =
    return $ case (M.lookup "img_style" (unMeta m)) of
                 Nothing -> p
                 Just (MetaBlocks [b]) -> Pandoc m (walk (substImgParams b) bs)
                 Just _ -> p
imgStyleFromMeta _ p = return p

substImgParams :: Block -> Block -> Block
substImgParams b (Para [Image _ (src, title)]) =
    walk substImgParams' b
    where substImgParams' (RawInline f s) =
                RawInline f (foldr (\(a, b) -> replace a b) s
                                   [("$SRC$", src), ("$TITLE$", title)])
          substImgParams' b = b
substImgParams _ b = b

main :: IO ()
main = toJSONFilter imgStyleFromMeta
Как установить стиль изображения сказано в комментарии внутри приведенного кода. Функция imgStyleFromMeta пытается найти в метаданных исходного документа поле img_style и, в случае успеха, проходится (walk) по блокам документа bs, подставляя найденный результат. Но не все так просто: функция substImgParams также проходится по объектам, которые нужно вставить, заменяя в них плейсхолдеры $SRC$ и $TITLE$ на результаты совпадения (matches) src и title в Para [Image _ (src, title)]. Кстати, этот паттерн и определяет, к каким типам изображений из AST-представления исходного документа будут применены подстановки стиля. Фильтр paraToSpanBlock из этой статьи преобразует любые блоки, соответствующие паттерну Para contents (ему же соответствуют и блоки с отдельными изображениями) в Plain [Span ... contents] — а это уже не соответствует отдельным изображениям. Соответственно, если сначала пройтись по исходному документу фильтром paraToSpanBlock, а затем фильтром imgStyleFromMeta, то новые стили не будут применены. Исправить это просто: paraToSpanBlock не должен преобразовывать отдельные изображения в спаны — в конце концов они и не должны находиться внутри спанов. Вот исправленный код paraToSpanBlock.
-- paraToSpanBlock.hs
import Text.Pandoc.JSON

paraToSpanBlock :: Maybe Format -> Block -> IO Block
paraToSpanBlock (Just (Format "html")) b@(Para [Image _ _]) =
    return b
paraToSpanBlock (Just (Format "html")) (Para contents) =
    return $ Plain [Span ("", [], [("style", style)]) contents]
    where style = "display: block; margin-bottom: 16px;\
                 \ font-family: Arial, Helvetica, sans-serif;"
paraToSpanBlock _ b = return b

main :: IO ()
main = toJSONFilter paraToSpanBlock
Кстати, здесь тоже есть захардкоженный стиль, так что и этот фильтр можно переписать в стиле imgStyleFromMeta. Update. Запилил новый фильтр styleFromMeta, который умеет делать то же самое, что и imgStyleFromMeta (при этом с возможностью применять разные стили для разных изображений и форматов документа), и paraToSpanBlock. Кроме того, в новом фильтре доступны настройки стилей встроенных (inline) изображений и ссылок. Подробности на странице проекта.

среда, 13 августа 2014 г.

Наклонное начертание шрифта (italic) в gnome-terminal и vim

Наклонное начертание поддерживается в последних версиях gnome-terminal (вроде бы, начиная с версии 3.6.1). Вот картинка-подтверждение.
Выглядит круто! К сожалению, сия красота в mate-terminal не доступна. Для того, чтобы наклонный шрифт заработал в vim, просто запустить vim в gnome-terminal недостаточно. Вот здесь отличное руководство (см. секцию vim). Я, однако, не советую просто вписать новое значение TERM=xterm-256color-italic в .bashrc, если вы пользуетесь другими терминалами. Вместо этого лучше записать туда строки
[ "$COLORTERM" = "gnome-terminal" ] &&
    toe -a | grep xterm-256color-italic > /dev/null 2>&1 &&
    TERM=xterm-256color-italic
Teперь переменная среды TERM будет автоматически устанавливаться в новое значение только внутри gnome-terminal и только в случае наличия в системе или в вашей домашней директории скомпилированного файла terminfo xterm-256color-italic. Update. Сегодня на своей Fedora 20 обновил MATE desktop до версии 1.8. Наклонных шрифтов в mate-terminal не появилось. В общем, собрал патч для старого vte, проверил на своем компьютере и послал в багзиллу Red Hat, вот сюда. Так что пользуйтесь, кому надо. Патч накладывается на версию vte 0.28.2 и совместим с SRPM пакетом Fedora 20. Мои настройки .bashrc теперь выглядят так:
case $COLORTERM in
    gnome*|mate*)
        toe -a | grep xterm-256color-italic > /dev/null 2>&1 &&
            TERM=xterm-256color-italic || TERM=xterm-256color
        ;;
    konsole*)
        TERM=xterm-256color
        ;;
esac

пятница, 8 августа 2014 г.

Корректный подсчет числа вариативных параметров в BOOST_PP_VARIADIC_SIZE

Макроопределение BOOST_PP_VARIADIC_SIZE из библиотеки Boost Preprocessor никогда не возвращает значение 0, это видно из его определения.
#define BOOST_PP_VARIADIC_SIZE(...)                                       \
        BOOST_PP_VARIADIC_SIZE_I(__VA_ARGS__, 64, 63, 62, 61, 60, 59,     \
                                 58, 57, 56, 55, 54, 53, 52, 51, 50, 49,  \
                                 48, 47, 46, 45, 44, 43, 42, 41, 40, 39,  \
                                 38, 37, 36, 35, 34, 33, 32, 31, 30, 29,  \
                                 28, 27, 26, 25, 24, 23, 22, 21, 20, 19,  \
                                 18, 17, 16, 15, 14, 13, 12, 11, 10, 9,   \
                                 8, 7, 6, 5, 4, 3, 2, 1,)

#define BOOST_PP_VARIADIC_SIZE_I(e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,  \
                                 e10, e11, e12, e13, e14, e15, e16, e17,  \
                                 e18, e19, e20, e21, e22, e23, e24, e25,  \
                                 e26, e27, e28, e29, e30, e31, e32, e33,  \
                                 e34, e35, e36, e37, e38, e39, e40, e41,  \
                                 e42, e43, e44, e45, e46, e47, e48, e49,  \
                                 e50, e51, e52, e53, e54, e55, e56, e57,  \
                                 e58, e59, e60, e61, e62, e63, size,      \
                                 ...) size
Я позволил себе немного видоизменить определения из boost/preprocessor/variadic/size.hpp: разбил их на несколько строк, убрал обрамляющий служебный макрос BOOST_PP_VARIADICS и проигнорировал вариант для Microsoft Visual C. Видите, убывающий список чисел начинается с 64 и заканчивается единицей: нулевое значение не предусмотрено. Почему так сделано? Потому что препроцессор все равно не в состоянии стандартным образом вычислить этот 0. Представьте, что значение __VA_ARGS__ как первый аргумент в макросе BOOST_PP_VARIADIC_SIZE_I пустое. По-хорошему, мы бы хотели, чтобы на месте e0 из его определения оказалось значение 64, тогда при условии, что убывающий список оканчивался бы нулем, мы получили бы желаемый результат 0. Но этого не произойдет потому, что за пустым __VA_ARGS__ следует вполне четкая запятая, и не важно, что перед ней пусто: препроцессор даже это пустое значение сопоставит с e0! Таким образом, случаи, когда __VA_ARGS__ пуст либо содержит лишь один элемент, неразличимы для этого определения BOOST_PP_VARIADIC_SIZE! Вот почему в нем нет нулевого значения. Итак, главный враг на пути к корректному определению BOOST_PP_VARIADIC_SIZE — это запятая после __VA_ARGS__. Первое, что приходит на ум для борьбы с ней — это использование макроопределения BOOST_PP_COMMA_IF из той же библиотеки Boost Preprocessor. Однако, для того, чтобы определить, ставить запятую или нет, нужно сначала выяснить, пуст ли __VA_ARGS__, другими словами, посчитать число его элементов. Таким образом, мы вернулись к исходной задаче, а значит этот подход не годится. Я нашел в интернете несколько предложений решения этой проблемы (например, здесь и здесь), но все они кажутся какими-то очень громоздкими. Между тем, gcc предлагает в виде расширения для __VA_ARGS__ оператор ##, который умеет съедать предваряющую __VA_ARGS__ запятую, если тот оказывается пустым (см. документацию gcc по вариативным макроопределениям). Это похоже на то, что нам нужно, и это и есть то, что нам нужно. Именно для решения подобных проблем разработчики gcc представили это расширение. Теперь внутри вызова BOOST_PP_VARIADIC_SIZE_I вместо __VA_ARGS__, мы можем записать , ##__VA_ARGS__,, добавить в конец убывающего списка чисел 0, а в определение BOOST_PP_VARIADIC_SIZE_I — еще один элемент e64.
#define FROM_BOOST_PP_VARIADIC_SIZE(...)                                       \
        FROM_BOOST_PP_VARIADIC_SIZE_I(, ##__VA_ARGS__, 64, 63, 62, 61, 60, 59, \
                                      58, 57, 56, 55, 54, 53, 52, 51, 50, 49,  \
                                      48, 47, 46, 45, 44, 43, 42, 41, 40, 39,  \
                                      38, 37, 36, 35, 34, 33, 32, 31, 30, 29,  \
                                      28, 27, 26, 25, 24, 23, 22, 21, 20, 19,  \
                                      18, 17, 16, 15, 14, 13, 12, 11, 10, 9,   \
                                      8, 7, 6, 5, 4, 3, 2, 1, 0)

#define FROM_BOOST_PP_VARIADIC_SIZE_I(e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,  \
                                      e10, e11, e12, e13, e14, e15, e16, e17,  \
                                      e18, e19, e20, e21, e22, e23, e24, e25,  \
                                      e26, e27, e28, e29, e30, e31, e32, e33,  \
                                      e34, e35, e36, e37, e38, e39, e40, e41,  \
                                      e42, e43, e44, e45, e46, e47, e48, e49,  \
                                      e50, e51, e52, e53, e54, e55, e56, e57,  \
                                      e58, e59, e60, e61, e62, e63, e64, size, \
                                      ...) size
В случае, если __VA_ARGS__ окажется пуст, мы получим одну запятую перед элементом 64 (то есть эффективно первый дополнительный элемент) и, за счет добавления e64, size в BOOST_PP_VARIADIC_SIZE_I окажется равным 0. Если в __VA_ARGS__ будет один элемент, то это равносильно двум дополнительным элементам перед значением 64 (несъеденная первая запятая и запятая после единственного элемента из __VA_ARGS__), и на месте size окажется значение 1. Дальнейшее увеличение количества элементов в __VA_ARGS__ на единицу будет сдвигать убывающий список чисел на единицу и в месте, соответствующем size, будет каждый раз оказываться число, большее на единицу. Проверяем на следующей простой программе.
#define PRINT( A, ... ) \
        printf( A ": %d\n", FROM_BOOST_PP_VARIADIC_SIZE( __VA_ARGS__ ))

int  main( void )
{
    PRINT( "Number of arguments" );
    PRINT( "Number of arguments", hahgsagsgdg );
    PRINT( "Number of arguments", 576oo, 3354 );
}
Программа выведет на экран
Number of arguments: 0
Number of arguments: 1
Number of arguments: 2
Мне кажется, что если бы оператор ## для __VA_ARGS__ был стандартизован, то макроопределение BOOST_PP_VARIADIC_SIZE в Boost Preprocessor было бы реализовано именно таким способом.

среда, 6 августа 2014 г.

SystemTap: пример трассировки в пространстве пользователя

SystemTap был разработан прежде всего для упрощения отладки и трассировки ядерных функций Linux (kernel space). Однако, с появлением в ядре механизма uprobes (в мэйнстриме ядра он присутствует начиная с версии 3.5, а в системах, связанных с Red Hat, таких как Fedora, CentOS и RHEL, он уже существует на протяжении нескольких лет), SystemTap можно использовать и для трассировки в пространстве пользователя (user space). На этой странице можно найти огромное количество примеров скриптов для SystemTap, которые дают представление о мощи этого инструмента. Я взял за основу один из них (уже не помню какой) и значительно переделал, чтобы показать, как работает SystemTap в пространстве пользователя. Мы не станем запускать SystemTap от имени суперпользователя, а для этого нам придется кое-что подготовить. Во-первых, необходимо установить пакет с серверной частью SystemTap. В моей системе Fedora 20 это делается так:
yum install systemtap-server
Затем нужно добавить вашу учетную запись в группу stapusr и запустить сервер.
usermod -a -G stapusr your-username
service stap-server start
Зачем такие сложности. Дело в том, что SystemTap при запуске нового скрипта строит и устанавливает в ядро Linux новый временный модуль, который задействует механизм uprobes для установки в интересующие нас точки программы интересующих нас обработчиков событий. По завершении работы SystemTap удаляет временный модуль из ядра. Очевидно, что для этого нужны привилегии суперпользователя, которыми обладает stap-server. Итак, перейдем к примеру. Возьмем простое приложение, написанное на C++ (файл test.cc).
#include <iostream>
#include <unistd.h>

namespace
{
    void  print( const char *  lbl )
    {
        std::cout << lbl << std::endl;
    }
}

class  A
{
    public:
        virtual void  print( void ) const
        {
            ::print( "A" );
        }
};

class  B : public A
{
    public:
        void  print( void ) const
        {
            ::print( "B" );
        }
};

namespace
{
    void  cycle( const A *  a, const A *  b, int  i )
    {
        if ( i % 3 )
            a->print();
        else
            b->print();

        sleep( i / 2 );
    }
}

int  main( void )
{
    A *  a( new A );
    A *  b( new B );

    for ( int  i( 0 ); i < 10; ++i )
        cycle( a, b, i );

    delete a;
    delete b;

    return 0;
}
Имеются два класса: A и его наследник B. В классе A определена виртуальная фукция print(), которая с помощью свободной функции print(), объявленной в анонимном пространстве имен, выводит на экран строку A. В классе B виртуальная функция print() переопределена: она выводит на экран строку B с помощью той же свободной функции print(). В функции main() создаются два полиморфных объекта a и b типов A и B соответственно. Затем в цикле от 1 до 10 эти объекты передаются в свободную функцию cycle(), которая вызывает виртуальный метод print() попеременно для a и b в зависимости от номера цикла, причем общее число вызовов a->print() будет превышать число вызовов b->print() в два раза. Кроме этого, функция cycle() умеет зависать, причем чем больше номер цикла, тем дольше длительность зависания. Все эти сложности я ввел с той целью, чтобы показать, как будет работать типичный сценарий SystemTap. Итак, давайте посмотрим, сколько раз будет вызвана виртуальная функция A::print(), сколько времени займут эти вызовы (мы соберем статистику и даже выведем гистограмму!), а также будем фиксировать зависания функции cycle() выше заданного порога. Для написания скрипта нам понадобятся имена функций, которые в C++ обычно не соответствуют именам, объявленным в исходном коде. Чтобы выяснить их значения, можно просто скомпилировать программу test.
g++ -o test test.cc
Заметьте, что мы скомпилировали программу без отладочной информации — SystemTap она не нужна! Теперь, с помощью SystemTap, можно вывести значения имен функций.
stap -l 'process("./test").function("*")' -c ./test
process("/home/lyokha/tmp/stap-example/test").function("_GLOBAL__sub_I_main")
process("/home/lyokha/tmp/stap-example/test").function("_Z41__static_initialization_and_destruction_0ii")
process("/home/lyokha/tmp/stap-example/test").function("_ZN12_GLOBAL__N_15cycleEPK1AS2_i")
process("/home/lyokha/tmp/stap-example/test").function("_ZN12_GLOBAL__N_15printEPKc")
process("/home/lyokha/tmp/stap-example/test").function("_ZN1AC2Ev")
process("/home/lyokha/tmp/stap-example/test").function("_ZN1BC1Ev")
process("/home/lyokha/tmp/stap-example/test").function("_ZNK1A5printEv")
process("/home/lyokha/tmp/stap-example/test").function("_ZNK1B5printEv")
process("/home/lyokha/tmp/stap-example/test").function("_ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4")
process("/home/lyokha/tmp/stap-example/test").function("_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@@GLIBCXX_3.4")
process("/home/lyokha/tmp/stap-example/test").function("_ZdlPv@@GLIBCXX_3.4")
process("/home/lyokha/tmp/stap-example/test").function("__do_global_dtors_aux")
process("/home/lyokha/tmp/stap-example/test").function("__libc_csu_fini")
process("/home/lyokha/tmp/stap-example/test").function("__libc_csu_init")
process("/home/lyokha/tmp/stap-example/test").function("_fini")
process("/home/lyokha/tmp/stap-example/test").function("_init")
process("/home/lyokha/tmp/stap-example/test").function("_start")
process("/home/lyokha/tmp/stap-example/test").function("deregister_tm_clones")
process("/home/lyokha/tmp/stap-example/test").function("frame_dummy")
process("/home/lyokha/tmp/stap-example/test").function("main")
process("/home/lyokha/tmp/stap-example/test").function("register_tm_clones")
Отлично, функции _ZNK1A5printEv и _ZNK1B5printEv — это и есть наши A::print() и B::print(), а функция _ZN12_GLOBAL__N_15cycleEPK1AS2_i — это свободная функция cycle() из анонимного пространства имен. Вот исходный код скрипта (файл test.stp, объяснения ниже).
#!/usr/bin/env stap

global startA, startCycle, intervals, hdr = "<---> "

probe process(@1).function("_ZNK1A5printEv")
{
  startA[tid()] = gettimeofday_us()
}
probe process(@1).function("_ZNK1A5printEv").return
{
  t = gettimeofday_us()
  old_t = startA[tid()]
  if (old_t) intervals <<< t - old_t
  delete startA[tid()]
}
probe process(@1).function("_ZN12_GLOBAL__N_15cycleEPK1AS2_i")
{
  startCycle[tid()] = gettimeofday_us()
}
probe process(@1).function("_ZN12_GLOBAL__N_15cycleEPK1AS2_i").return
{
  delete startCycle[tid()]
}
function dump_intervals(print_hist)
{
  if (@count(intervals))
  {
    printf("%s Intervals min:%dus avg:%dus max:%dus count:%d\n", hdr,
           @min(intervals), @avg(intervals), @max(intervals),
           @count(intervals))
    if (print_hist)
    {
      print(@hist_log(intervals))
    }
  }
}
probe timer.s(2)
{
  foreach (tid in startCycle)
  {
    old_t = startCycle[tid]
    if (old_t > 0)
    {
      t = gettimeofday_us()
      if (t - old_t > $2 * 1000000)
      {
        printf("%s Pending call detected: %dus (tid %d)\n", hdr, t - old_t, tid)
      }
    }
  }
  dump_intervals(0)
}
probe end
{
  println()
  dump_intervals(1)
}
Первая строка — shebang — понятно. Далее идет объявление четырех глобальных переменных: startA, startCycle, intervals и hdr, которые будут доступны из всех объявлений probe и function в скрипте. Переменная startA — это массив, параметризованный значениями идентификаторов программного потока, возвращаемыми встроенной функцией tid(). Мы будем использовать startA как временну́ю метку входа в функцию A::print() в соответствующем программном потоке. На самом деле, в программе всего лишь один поток, а параметризация переменных-массивов startA и startCycle по идентификаторам потоков сделана просто для обобщения подхода. Переменная startCycle — тоже временна́я метка входа в функцию cycle(). Кстати, как SystemTap определяет, что переменные startA и startCycle являются массивами? Ведь в их объявлении это никак не указано. Дело в том, что парсер SystemTap способен выводить (infer) тип переменной при ее первом использовании. В переменной intervals мы будем собирать временну́ю статистику пребывания внутри функции A::print(). Переменная hdr — простой префикс для строк, выводимых SystemTap, она инициализируется в месте объявления. Итак, первое объявление probe process(@1).function("_ZNK1A5printEv") читается так: для имени процесса, переданного SystemTap в первом аргументе командной строки, при входе в функцию _ZNK1A5printEv, выполнить тело данного probe, которое находится ниже, внутри фигурных скобок. Мы уже применяли подобные объявления, когда выводили список функций в программе test. Тогда это было process("./test").function("*"), то есть мы использовали имя процесса ./test. Поскольку опция -l не требовала использования сервера SystemTap, мы просто указали в имени процесса текущую директорию. Однако, когда мы запустим наш скрипт, сервер должен будет знать полный путь к программе test, поэтому мы передадим его через аргумент командной строки @1 (в SystemTap есть переменные вида $N, где N — некоторое число, которое, в отличие от bash, может превышать 9 — они подставляют N-ый аргумент командной строки как есть, и @1 — они оборачивают N-ый аргумент командной строки в двойные кавычки). Внутри probe process(@1).function("_ZNK1A5printEv") мы присваиваем элементу массива startA[tid()] значение gettimeofday_us(), которое соответствует числу микросекунд, прошедших с начала Эпохи UNIX. В свою очередь, probe process(@1).function("_ZNK1A5printEv").return устанавливает обработчик на выходе из A::print(). В нем, при помощи оператора <<<, в массив intervals добавляется новый элемент, равный разности между текущим значением gettimeofday_us() и значением startA[tid()]. Оператор <<< можно рассматривать как аналог оператора вывода в поток << из C++. На выходе из обработчика элемент startA[tid()] удаляется. Следующие два probe соответствуют входу и выходу в/из cycle(): они, соответственно, устанавливают и удаляют элемент массива startCycle[tid()]. В probe timer.s(2) каждые 2 секунды осуществляется проход по элементам startCycle и в случае, если разность между текущим значением gettimeofday_us() и значением данного элемента превышает значение в секундах, заданное во втором аргументе командной строки ($2), на экран выводится предупреждающее сообщение, в котором указано время пребывания внутри функции cycle() (собственно, эта разность) и идентификатор потока. В конце данного обработчика вызывается function dump_intervals(print_hist), которая выводит статистику, накопленную в intervals, на экран. В случае, когда аргумент print_hist не нулевой, на экран выводится гистограмма распределения событий из intervals. Мы не собираемся выводить гистограмму каждые две секунды, мы ее выведем в завершающем обработчике probe end. Итак, запускаем программу test под SystemTap.
stap test.stp -c ./test `pwd`/test 3
B
A
A
B
<--->  Intervals min:6us avg:7us max:8us count:2
A
<--->  Intervals min:6us avg:14us max:28us count:3
A
<--->  Intervals min:6us avg:17us max:28us count:4
B
<--->  Intervals min:6us avg:17us max:28us count:4
A
<--->  Intervals min:6us avg:21us max:37us count:5
<--->  Intervals min:6us avg:21us max:37us count:5
A
<--->  Intervals min:6us avg:21us max:37us count:6
<--->  Pending call detected: 3977735us (tid 4949)
<--->  Intervals min:6us avg:21us max:37us count:6
B
<--->  Intervals min:6us avg:21us max:37us count:6
<--->  Pending call detected: 3977592us (tid 4949)
<--->  Intervals min:6us avg:21us max:37us count:6

<--->  Intervals min:6us avg:21us max:37us count:6
value |-------------------------------------------------- count
    1 |                                                   0
    2 |                                                   0
    4 |@                                                  1
    8 |@                                                  1
   16 |@@@                                                3
   32 |@                                                  1
   64 |                                                   0
  128 |                                                   0
Опция -c ./test говорит о том, что SystemTap должен запустить отдельный процесс и выполнить в нем программу test. (SystemTap также поддерживает опцию -x PID, которая позволяет прикрепиться к уже существующему процессу: такой механизм лучше подходит для трассировки в продакшн системе, но я не буду на этом останавливаться.) Первый свободный аргумент командной строки — `pwd`/test — это полный путь к исполняемому файлу test, второй свободный аргумент — таймаут пребывания внутри тела функции cycle(). Вывод программы не принес неожиданностей: функция A::print() вызывалась 6 раз, B::print() — 4 раза, это видно из вывода собственно программы test. SystemTap выводил статистику по intervals каждые 2 секунды, в завершении была выведена соответствующая гистограмма распределения времен пребывания внутри функции A::print() в микросекундах. Кроме того, обработка функции cycle() два раза превышала установленный предел в 3 секунды и SystemTap вывел соответствующие сообщения. Давайте усложним задачу. Теперь нас дополнительно будет интересовать количество вызовов свободной функции print() из A::print() (их должно оказаться 6), а также значение переменной i на входе в функцию cycle(). Первая задача подразумевает использование механизма process().function().callee() (эта запись условна, так как формально во всех круглых скобках нужно указывать какие-либо строковые значения). Механизм callee требует, чтобы программа test содержала символы DWARF, то есть была скомпилирована с опцией -g. Да и вторая задача тоже требует DWARF. Вот какие изменения нужно внести в test.stp.
...

global startA, startCycle, intervals, printcalls = 0, hdr = "<---> "

...

probe process(@1).function("_ZN12_GLOBAL__N_15cycleEPK1AS2_i")
{
  startCycle[tid()] = gettimeofday_us()
  if (@defined($i))
  {
    printf("%s Stack variable i = %d\n", hdr, $i)
  }
}

...

probe process(@1).function("_ZNK1A5printEv").callee("*print*") ?
{
  printcalls += 1
}

...

probe end
{
  println()
  dump_intervals(1)
  printf("%s Function print() was called from A %d times\n", hdr, printcalls)
}
Добавлена новая глобальная переменная printcalls, которая будет увеличиваться на единицу при вызове функции print() из A::print(): за это будет отвечать новый обработчик process(@1).function("_ZNK1A5printEv").callee("*print*") ?. Вопросительный знак в конце имени обработчика говорит о том, что он опционален: мы хотим использовать новый скрипт в том числе и когда программа test не будет содержать отладочной информации. Я не стал записывать полное имя функции в значение аргумента callee, а ограничился глоб-паттерном: они прекрасно работают в скриптах SystemTap. В обработчик входа в функцию cycle() добавлен вывод значения стековой переменной i, а в probe end — вывод значения printcalls. Компилируем с опцией -g и запускаем SystemTap.
g++ -g -o test test.cc
stap test.stp -c ./test `pwd`/test 3
B
A
A
B
<--->  Intervals min:8us avg:9us max:10us count:2
A
<--->  Intervals min:8us avg:14us max:25us count:3
A
<--->  Intervals min:8us avg:17us max:26us count:4
B
<--->  Intervals min:8us avg:17us max:26us count:4
A
<--->  Intervals min:8us avg:20us max:32us count:5
<--->  Intervals min:8us avg:20us max:32us count:5
A
<--->  Intervals min:8us avg:21us max:32us count:6
<--->  Pending call detected: 3977681us (tid 1682)
<--->  Intervals min:8us avg:21us max:32us count:6
B
<--->  Intervals min:8us avg:21us max:32us count:6
<--->  Pending call detected: 3977528us (tid 1682)
<--->  Intervals min:8us avg:21us max:32us count:6

<--->  Intervals min:8us avg:21us max:32us count:6
value |-------------------------------------------------- count
    2 |                                                   0
    4 |                                                   0
    8 |@@                                                 2
   16 |@@@                                                3
   32 |@                                                  1
   64 |                                                   0
  128 |                                                   0

<--->  Function print() was called from A 0 times
Сообщения о переменной i не выводятся, printcalls не увеличивается. Что-то не так. Посмотрим как теперь называются наши функции.
stap -l 'process("./test").function("*")' -c ./test
process("/home/lyokha/tmp/stap-example/test").function("A@/home/lyokha/tmp/stap-example/test.cc:30")
process("/home/lyokha/tmp/stap-example/test").function("B@/home/lyokha/tmp/stap-example/test.cc:39")
process("/home/lyokha/tmp/stap-example/test").function("_GLOBAL__sub_I_main@<unknown source>")
process("/home/lyokha/tmp/stap-example/test").function("_ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4")
process("/home/lyokha/tmp/stap-example/test").function("_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@@GLIBCXX_3.4")
process("/home/lyokha/tmp/stap-example/test").function("_ZdlPv@@GLIBCXX_3.4")
process("/home/lyokha/tmp/stap-example/test").function("__do_global_dtors_aux")
process("/home/lyokha/tmp/stap-example/test").function("__libc_csu_fini")
process("/home/lyokha/tmp/stap-example/test").function("__libc_csu_init")
process("/home/lyokha/tmp/stap-example/test").function("__static_initialization_and_destruction_0@<unknown source>")
process("/home/lyokha/tmp/stap-example/test").function("_fini")
process("/home/lyokha/tmp/stap-example/test").function("_init")
process("/home/lyokha/tmp/stap-example/test").function("_start")
process("/home/lyokha/tmp/stap-example/test").function("cycle@/home/lyokha/tmp/stap-example/test.cc:50")
process("/home/lyokha/tmp/stap-example/test").function("deregister_tm_clones")
process("/home/lyokha/tmp/stap-example/test").function("frame_dummy")
process("/home/lyokha/tmp/stap-example/test").function("main@/home/lyokha/tmp/stap-example/test.cc:61")
process("/home/lyokha/tmp/stap-example/test").function("print@/home/lyokha/tmp/stap-example/test.cc:24")
process("/home/lyokha/tmp/stap-example/test").function("print@/home/lyokha/tmp/stap-example/test.cc:33")
process("/home/lyokha/tmp/stap-example/test").function("print@/home/lyokha/tmp/stap-example/test.cc:42")
process("/home/lyokha/tmp/stap-example/test").function("register_tm_clones")
Ага, функции стали называться иначе. Давайте я сразу приведу готовый скрипт с учетом всех переименований и возможностью запуска с отладочными символами в программе test и без них.
#!/usr/bin/env stap

global startA, startCycle, intervals, printcalls = 0, hdr = "<---> "

probe process(@1).function("_ZNK1A5printEv")
{
  startA[tid()] = gettimeofday_us()
}
probe process(@1).function("_ZNK1A5printEv").return
{
  t = gettimeofday_us()
  old_t = startA[tid()]
  if (old_t) intervals <<< t - old_t
  delete startA[tid()]
}
probe process(@1).function("_ZN12_GLOBAL__N_15cycleEPK1AS2_i") ?
{
  startCycle[tid()] = gettimeofday_us()
}
probe process(@1).function("_ZN12_GLOBAL__N_15cycleEPK1AS2_i").return ?
{
  delete startCycle[tid()]
}
probe process(@1).function("cycle") ?
{
  startCycle[tid()] = gettimeofday_us()
  if (@defined($i))
  {
    printf("%s Stack variable i = %d\n", hdr, $i)
  }
}
probe process(@1).function("cycle").return ?
{
  delete startCycle[tid()]
}
probe process(@1).function("print@*test.cc:33").callee("*print*") ?
{
  printcalls += 1
}
function dump_intervals(print_hist)
{
  if (@count(intervals))
  {
    printf("%s Intervals min:%dus avg:%dus max:%dus count:%d\n", hdr,
           @min(intervals), @avg(intervals), @max(intervals),
           @count(intervals))
    if (print_hist)
    {
      print(@hist_log(intervals))
    }
  }
}
probe timer.s(2)
{
  foreach (tid in startCycle)
  {
    old_t = startCycle[tid]
    if (old_t > 0)
    {
      t = gettimeofday_us()
      if (t - old_t > $2 * 1000000)
      {
        printf("%s Pending call detected: %dus (tid %d)\n", hdr, t - old_t, tid)
      }
    }
  }
  dump_intervals(0)
}
probe end
{
  println()
  dump_intervals(1)
  printf("%s Function print() was called from A %d times\n", hdr, printcalls)
}
Я добавил опциональные probe с function(“cycle”) и function(“print@*test.cc:33”). Поскольку существует единственная функция cycle(), уточнение адреса не требуется. Другое дело — имя print, которое соответствует сразу трем функциям. Для разрешения адреса функции A::print() я воспользовался глоб-паттерном print@*test.cc:33. Важно отметить, что имена функций в случае введения отладочной информации могут сильно зависеть как от опций оптимизации gcc, так и от его версии. Ситуация осложняется наличием багов (например, вот этот), возможностью встраивания функций при оптимизации (inlining) и другими подобными факторами. В общем, правильно определить имя функции для использования в скрипте SystemTap и в итоге получить нужную трассировку может оказаться непростой задачей. Запустим скрипт test.stp снова.
stap test.stp -c ./test `pwd`/test 3
WARNING: For probing a particular line, use a .statement() probe, not .function(): keyword at test.stp:36:1
 source: probe process(@1).function("print@*test.cc:33").callee("*print*") ?
         ^
B
A
A
<--->  Stack variable i = 0
<--->  Stack variable i = 1
<--->  Stack variable i = 2
B
<--->  Stack variable i = 3
<--->  Intervals min:12us avg:13us max:14us count:2
A
<--->  Stack variable i = 4
<--->  Intervals min:12us avg:17us max:27us count:3
A
<--->  Stack variable i = 5
<--->  Intervals min:12us avg:19us max:27us count:4
B
<--->  Stack variable i = 6
<--->  Intervals min:12us avg:19us max:27us count:4
A
<--->  Stack variable i = 7
<--->  Intervals min:12us avg:21us max:28us count:5
<--->  Intervals min:12us avg:21us max:28us count:5
A
<--->  Stack variable i = 8
<--->  Intervals min:12us avg:21us max:28us count:6
<--->  Pending call detected: 3977342us (tid 32523)
<--->  Intervals min:12us avg:21us max:28us count:6
B
<--->  Stack variable i = 9
<--->  Intervals min:12us avg:21us max:28us count:6
<--->  Pending call detected: 3977157us (tid 32523)
<--->  Intervals min:12us avg:21us max:28us count:6

<--->  Intervals min:12us avg:21us max:28us count:6
value |-------------------------------------------------- count
    2 |                                                   0
    4 |                                                   0
    8 |@@                                                 2
   16 |@@@@                                               4
   32 |                                                   0
   64 |                                                   0

<--->  Function print() was called from A 6 times
Прикольно. Сам выводил print@…test.cc:33 как имя функции, а теперь говорит используйте .statement(). В данном случае мы не можем использовать statement, поскольку callee можно присоединить только к function. Ну да это просто предупреждение. Главное, все заработало! Мы стали получать значение переменной i и printcalls оказалась равной 6, как мы и ожидали.