Элементы инфраструктуры приложений StromaV: примесное поведение приложения

/ / sw-sci :: , , ,

Планируется, что часть этой заметки я потом переведу на английский и размещу во внутренних Doxygen/(T)Wiki. API реализовано в библиотеке StromaV.

Заметка посвящена обзору вспомогательных классов и процедур образующих инфраструктуру системных приложений на основе библиотеки StromaV.

StromaV предлагает несколько генерализованных примесных классов приложения реализованных на основе общей виртуальной базы AbstractApplication, который в свою очередь основывается на абстрактной шаблонной базе goo::App параметризованной классом goo::dict::Configuration (оба последних класса предоставлены библиотекой goo).

Интерфейс взаимодействия с ОС: класс goo::App<...>

Параметризованный шаблон goo::App определяет основной интерфейс для обобщённой бизнес-логики приложения, включающей этап конфигурирования приложения согласно конфигурационным файлам и аргументам командной строки, а также этап исполнения. Класс-шаблон goo::App наследует методы класса goo::aux::iApp в которых определены основные процедуры взаимодействия приложения с системным окружением — выставление обработчиков сигналов, получение идентификатора процесса, чтение переменных окружения.

UML диаграмма классов.

goo::aux::iApp инкапсулирует эту функциональность, предоставляемую системными библиотеками и ядром ОС обычно в рамках структурной парадигмы, в парадигме ООП, в то время как шаблонный класс goo::App<...> реализует обобщённую логику журналирования и конфигурирования приложения на основе данных предоставляемых внешними источниками безотносительно их конкретных типов.

Интерфейс приложения (на диаграмме показаный как «Application») реализуется, обычно классами пользовательских приложений:

  1. Метод Config * construct_config_object(argc, argv) должен инициализировать объект конфигурации приложения на основе передаваемых ему аргументов командной строки int argc, const char * argv[] (аргументов стандартной точки входа C++-программ).
  2. Метод configure_application(Config) должен производить конфигурирование отдельных компонент приложения для последующей работы используя инициализированную конфигурацию.
  3. Метод LogStream * acquire_logstream() должен возвращать указатель на экземпляр потока в который будет производиться журналирование.
  4. Метод int run() должен осуществлять исполнение задач для которых реализовано приложение на основе выставленной конфигурации. Обычно он содержит цикл обработки событий, или делегирует выполнение другим компонентам системы. Возвращённое число затем передаётся оболочке как код результата выполнения приложения (обычно это EXIT_SUCCESS/EXIT_FAILURE ствндартной библиотеки C).

Целесообразность введения таких классов происходит из частой потребности сформулировать общение с ОС в терминах ООП, в то время как большинство этих функций реализованы в структурном подходе чистого C.

Абстрактная база AbstractApplication

AbstractApplication параметризует шаблон goo::App<...> следующими типами:

  • Класс Configuration из библиотеки goo представляет собой древовидную структуру выполняющую функции хранилища конфигурационных параметров, эффективно представленных как аннотированный словарь строго‐типизированных данных. В качестве типа конфигурации goo::App<...> могут выступать и другие подобные структуры, — так долгое время прежние версии StromaV использовали boost::variables_map.
  • Класс std::ostream задан в качестве базового типа для потока вывода в который выполняется журналирование процедур StromaV.

AbstractApplication содержит два семейства параметров.

  1. Первое семейство относится к временной конфигурации исполняемого приложения, и содержит параметры исполнения: уровень журналирования (verbosity level), выводные цели журналов, динамически‐загружаемые модули и т.д. Кроме того, пользовательское приложение может добавлять в этот словарь дополнительные параметры, специфичные задачам конкретного приложения (например, afpipe принимает адрес источника данных, имя его формата и список имён обработчиков из которых строится конвеер AnalysisPipeline). Для активного приложения это семейство параметров не может быть динамически изменено, и формируется в начале выполнения. Источником данных для этого семейства служат обычно параметры командной строки (помимо возможно‐определённых дефолтных значений).
  2. Второе семейство параметров называется общей конфигурацией (common config) и содержит множество параметров различных алгоритмов. Это семейство может быть расширено подгружаемыми модулями. Помимо значений по‐умолчанию, источником данных для этого семейства служат файлы конфигурации (в StromaV выраженные на языке YAML). Кроме того, приложения обычно предоставляют интерфейс для переопределения значений параметров (обычно, параметр командной строки -O,--override-opt).

Справка по обоим семействам доступна из командной строки. Для первого семейства — это стандартный для UNIX-утилит ключ -h,--help. Справка по второму семейству будет напечатана после загрузки всех модулей и базовой инициализации прилажения, если оно было запущено с ключом --inspect-config в виде ASCII-дерева с корневым элементом «sV-config»:

$ install/bin/afpipe-rdbg --inspect-config
[3   1.38] New constructors dictionary allocated (0x202e1c0).
[2   1.38] Constructor for type "svb":N2sV3aux14iEventSequenceE has been registered.
[2   1.38] Constructor for type "benchmarking":N2sV3aux15iEventProcessorE has been registered.
...
╔═ << sV-config >>
╠═╦ < buckets > 
║ ╚═╦ < generic > 
║   ╟─ maxBucketSize_events, type=`m', value set: "1000" Maximum ...
║   ╙─ maxBucketSize_kB, type=`m', value set: "16384" Maximum bucket ...
╠═╦ < logging > 
║ ╟─ families, type=`N4YAML4NodeE', value set: "<YAML-node>" List ...
║ ╚═╦ < Common >
...

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

Примесные классы приложений StromaV

Код конкретного приложения, помимо определения собственных атрибутов и методов, может включать в свою цепочку наследования различные вспомогательные классы‐примеси для того чтобы дополнять своё поведение в определённом контексте. Так, не всякое приложение для анализа должно использовать процедуры из пакета ROOT, работать с унифицированным представлением событий StromaV, основанном на Google Protocol Buffers, хотя подобные возможности присущи большинству практических случаев.

Основные примесные классы объявлены в директории app/mixins библиотеки StromaV. Примесное поведение реализуется в StromaV идиоматически, на основе виртуального наследования от общей базы, как показано на диаграмме.

UML диаграмма классов.

Мотивация к введению виртуального наследования происходит из необходимости многократного использования различных аспектов поведения приложения реализуемых в нескольких инструментальных утилитах — конкретных приложениях. Ни одна из примесей не реализует интерфейс «Application».

Примесь RootApplication

Актуальность этой примеси обусловлена главным образом популярностью пакета ROOT при обработке данных физического эксперимента. Сам ROOT, однако, является довольно обтрузивной и дорогой зависимостью.

UML диаграмма классов.

Многие внутренние процедуры ROOT предполагают наличие экземпляра класса TApplication, предоставляющего доступ к контексту выполнения определённому в ROOT: глобальным переменным, динамической конфигурации приложения, циклу обработки событий подсистемы графического пользовательского интерфейса и т.д. Этот экземпляр ассоциирован с примесью sV::mixins::RootApplication и должен быть инициализирован через публичный метод:

static void initialize_ROOT_system( uint8_t featuresEnabled );

где featuresEnabled содержит ИЛИ-конкатенацию флагов необходимых для работы пользовательского приложения:

  • enableCommonFile — включает использование общего файла, наличие которого подразумевают многие процедуры ROOT. Этот файл затем доступен через глобальную переменную gFile определённую в ROOT (и значение которой может быть переопределено при открытии нового TFile) и метод get_common_TFile(). В общую конфигурацию будет добавлен параметр пути этого файла "ROOT.output-file".
  • enablePlugins — включает использование ROOT-plug-ins (см. ROOT::PluginManager). StromaV предлагает небольшой набор собственных плагинов и должна дополнить список путей по которым процедуры ROOT отыскивают файлы расширения. Выставленный флаг добавит в конфигурацию приложения параметры "ROOT.plugin-handlers-file", значение которого затем будет отдано методу LoadHandlersFromEnv() класса TPluginManager.
  • enableDynamicPath — включает использование дополнительных путей по которым процедуры ROOT ищут файлы рзделяемых библиотек. Добавляет в общее семейство параметров запись "ROOT.dynamic-path".
  • enableTApplication — включает инстанцирование TApplication. Флаг важен, поскольку не всякое приложение работающее с библиотеками ROOT нуждается в экземпляре TApplication.

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

Будучи создан, экземпляр становится доступен через публичные методы:

const TApplication & get_TApplication() const;
TApplication & get_TApplication();

Частой потребность является сброс обработчиков сигналов, которые выставляет ROOT во время инициализации приложения:

static void reset_ROOT_signal_handlers();

Эта вспомогательная примесь обычно позволяет освободиться от некоторых наиболее непредсказуемых проблем в ROOT: перегрузка реакции на системные сигналы и операции с собственным глобальным указателем на текущий файл, выводя конфигурирование динамических путей в общую конфигурацию.

Примесь PBEventApp

Важнейшим средством улучшить производительность Google Protocol Buffers является использование arena allocator. Примесь PBEventApp предоставляет указатель на экземпляр аллокатора используемого для работы с событиями.

UML диаграмма классов.

Его время жизни соответствует времени исполнения приложения (что необходимо принимать во внимание во только время временной работы с данными большого размера, — в этом случае эффективнее иметь выделенный arena allocator).

static google::protobuf::Arena * arena_ptr();
template<typename MessageT> MessageT * new_msg_on_arena();

Кроме того, примесь PBEventApp предоставляет указатель на буфферное событие обчно используемое сессией приложения как «текущее». Так, например, AnalysisPipeline может использовать этот экземпляр в качестве временного изменяемого хранилища. Ссылка на экземпляр возвращается методом:

static UniEvent & c_event();

Использование arena allocator крайне рекомендуется для часто изменяемых структур поскольку минимизирует количество операций на системном аллокаторе и способно существенно сократить время выполнения для пакетных задач.

Примесь Geant4Application

Примесная функциональность класса Geant4Application главным образом посвящена обслуживанию фабрик процессов Geant4, виртуальных конструкторов его подклассов PhysicsList и виртуальных конструкторов частиц, определённых в Geant4, а так же настройкам парсера GDML и его расширений (включая, главным образом, ассоциацию с подклассами G4SensitiveDetector).

UML диаграмма классов.

Geant4 имеет довольно обширную подсистему run-time конфигурирования за счёт скриптов сценариев g4mac исполняемых внутренним run manager'ом пакета.

Примесь AlignmentApplication

Экспериментальная функциональность реализующая вспомогательные примитивы для alignment‐процедур и трекинга частиц, не вполне соответствующих процедурам анализа в широком смысле размещается здесь. По причине высокой вероятности дальнейших изменений в этом классе, я не вижу смысла документировать этот класс на момент публикации заметки.

Класс AnalysisApplication

Большинство пользовательских приложений так или иначе производящих анализ данных построено вокруг класса AnalysisPipeline и примесной функциональности PBEventApp. Базовым классом таких приложений является вспомогательный класс AnalysisApplication реализующий конвейер обработки как тело приложения.

Класс PythonSession

Данный класс‐наследник AnalysisApplication реализует некоторые методы для стабильной работы процедур StromaV под контролем интерактивной сессии языка Python или его скриптов. Этот класс также полностью реализует все интерфейсы приложения включённые в цепочку наследования, включая «Application», для которого метод int run() выполнен в виде «заглушки», поскольку бизнес-логика приложения в действительности делигируется обработчику событий интерпретатора Python.

Полная диаграмма классов приложения библиотеки StromaV

Инстанцирование подклассов AbstractApplication для приложений использующих StromaV, вообще говоря, не является обязательным, поскольку большинство компонент библиотеки подразумевают случай, когда соответствующий синглетон не представлен в программе. Однако, в этом случае не гарантируется стабильная работа компонент отмеченных как «экспериментальные».

UML диаграмма классов.

Пользовательский код может рассматривать приложение как необязательный элемент инфраструктуры и никогда не делегировать выполнение методу Application::run(). Этап инициализации и конфигурирования, однако, является довольно важным для удобства использования, поскольку только в этом случае возможно использовать обобщённый виртуальный конструктор классов StromaV (фабрики).


Comments