Планируется, что часть этой заметки я потом переведу на английский и размещу во внутренних Doxygen/(T)Wiki. API реализовано в библиотеке StromaV.
Заметка посвящена обзору вспомогательных классов и процедур образующих инфраструктуру системных приложений на основе библиотеки StromaV.
StromaV предлагает несколько генерализованных примесных классов приложения
реализованных на основе общей виртуальной базы AbstractApplication
, который
в свою очередь основывается на абстрактной шаблонной базе
goo::App
параметризованной классом goo::dict::Configuration
(оба последних
класса предоставлены библиотекой goo).
goo::App<...>
Параметризованный шаблон goo::App
определяет основной интерфейс для
обобщённой бизнес-логики приложения, включающей этап конфигурирования
приложения согласно конфигурационным файлам и аргументам командной строки, а
также этап исполнения. Класс-шаблон goo::App
наследует методы класса
goo::aux::iApp
в которых определены основные процедуры взаимодействия
приложения с системным окружением — выставление обработчиков сигналов,
получение идентификатора процесса, чтение переменных окружения.
goo::aux::iApp
инкапсулирует эту функциональность, предоставляемую системными
библиотеками и ядром ОС обычно в рамках структурной парадигмы, в парадигме ООП,
в то время как шаблонный класс goo::App<...>
реализует обобщённую логику
журналирования и конфигурирования приложения на основе данных предоставляемых
внешними источниками безотносительно их конкретных типов.
Интерфейс приложения (на диаграмме показаный как «Application») реализуется, обычно классами пользовательских приложений:
Config * construct_config_object(argc, argv)
должен инициализировать
объект конфигурации приложения на основе передаваемых ему аргументов командной
строки int argc, const char * argv[]
(аргументов стандартной точки входа
C++-программ).configure_application(Config)
должен производить конфигурирование
отдельных компонент приложения для последующей работы используя
инициализированную конфигурацию.LogStream * acquire_logstream()
должен возвращать указатель на
экземпляр потока в который будет производиться журналирование.int run()
должен осуществлять исполнение задач для которых
реализовано приложение на основе выставленной конфигурации. Обычно он содержит
цикл обработки событий, или делегирует выполнение другим компонентам системы.
Возвращённое число затем передаётся оболочке как код результата выполнения
приложения (обычно это EXIT_SUCCESS
/EXIT_FAILURE
ствндартной библиотеки C).Целесообразность введения таких классов происходит из частой потребности сформулировать общение с ОС в терминах ООП, в то время как большинство этих функций реализованы в структурном подходе чистого C.
AbstractApplication
AbstractApplication
параметризует шаблон goo::App<...>
следующими типами:
Configuration
из библиотеки goo представляет собой древовидную
структуру выполняющую функции хранилища конфигурационных параметров, эффективно
представленных как аннотированный словарь строго‐типизированных данных. В
качестве типа конфигурации goo::App<...>
могут выступать и другие подобные
структуры, — так долгое время прежние версии StromaV использовали
boost::variables_map
.std::ostream
задан в качестве базового типа для потока вывода в
который выполняется журналирование процедур StromaV.AbstractApplication
содержит два семейства параметров.
afpipe
принимает адрес источника
данных, имя его формата и список имён обработчиков из которых строится конвеер
AnalysisPipeline
). Для активного приложения это семейство
параметров не может быть динамически изменено, и формируется в начале
выполнения. Источником данных для этого семейства служат обычно параметры
командной строки (помимо возможно‐определённых дефолтных значений).-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-систем.
Код конкретного приложения, помимо определения собственных атрибутов и методов, может включать в свою цепочку наследования различные вспомогательные классы‐примеси для того чтобы дополнять своё поведение в определённом контексте. Так, не всякое приложение для анализа должно использовать процедуры из пакета ROOT, работать с унифицированным представлением событий StromaV, основанном на Google Protocol Buffers, хотя подобные возможности присущи большинству практических случаев.
Основные примесные классы объявлены в директории app/mixins
библиотеки
StromaV. Примесное поведение реализуется в StromaV идиоматически, на основе
виртуального наследования от общей базы, как показано на диаграмме.
Мотивация к введению виртуального наследования происходит из необходимости многократного использования различных аспектов поведения приложения реализуемых в нескольких инструментальных утилитах — конкретных приложениях. Ни одна из примесей не реализует интерфейс «Application».
RootApplication
Актуальность этой примеси обусловлена главным образом популярностью пакета ROOT при обработке данных физического эксперимента. Сам ROOT, однако, является довольно обтрузивной и дорогой зависимостью.
Многие внутренние процедуры 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
предоставляет указатель на экземпляр аллокатора
используемого для работы с событиями.
Его время жизни соответствует времени исполнения приложения (что необходимо принимать во внимание во только время временной работы с данными большого размера, — в этом случае эффективнее иметь выделенный 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
).
Geant4 имеет довольно обширную подсистему run-time конфигурирования за счёт скриптов сценариев g4mac исполняемых внутренним run manager'ом пакета.
AlignmentApplication
Экспериментальная функциональность реализующая вспомогательные примитивы для alignment‐процедур и трекинга частиц, не вполне соответствующих процедурам анализа в широком смысле размещается здесь. По причине высокой вероятности дальнейших изменений в этом классе, я не вижу смысла документировать этот класс на момент публикации заметки.
AnalysisApplication
Большинство пользовательских приложений так или иначе производящих анализ
данных построено вокруг класса AnalysisPipeline
и примесной функциональности
PBEventApp
. Базовым классом таких приложений является вспомогательный класс
AnalysisApplication
реализующий конвейер обработки как тело приложения.
PythonSession
Данный класс‐наследник AnalysisApplication
реализует некоторые методы для
стабильной работы процедур StromaV под контролем интерактивной сессии языка
Python или его скриптов. Этот класс также полностью реализует все интерфейсы
приложения включённые в цепочку наследования, включая «Application», для
которого метод int run()
выполнен в виде «заглушки», поскольку бизнес-логика
приложения в действительности делигируется обработчику событий интерпретатора
Python.
Инстанцирование подклассов AbstractApplication
для приложений использующих
StromaV, вообще говоря, не является обязательным, поскольку большинство
компонент библиотеки подразумевают случай, когда соответствующий синглетон не
представлен в программе. Однако, в этом случае не гарантируется стабильная
работа компонент отмеченных как «экспериментальные».
Пользовательский код может рассматривать приложение как необязательный элемент
инфраструктуры и никогда не делегировать выполнение методу
Application::run()
. Этап инициализации и конфигурирования, однако, является
довольно важным для удобства использования, поскольку только в этом случае
возможно использовать обобщённый виртуальный конструктор классов StromaV
(фабрики).