libuniset2 — библиотека для создания АСУ. Лучше один раз увидеть…Часть 4 (Наладка)

    В предыдущих частях (часть 1, часть 2, часть 3) было описано создание двух процессов: имитатора и процесса управления… Теперь же настало время наладки.

    Итак, на текущий момент у нас уже реализованы и запускаются следующие процессы:

    можно начинать наладку…

    Наладка. Вводная часть.


    Пришло время небольшой поясняющей картинки, чтобы понять что у нас тут происходит…
    Структура обмена

    На самом деле всё конечно немного сложнее, но рисунок призван помочь понять, как у нас устроены «информационные потоки».
    Итак..
    • Всё взаимодействие идёт через SharedMemory
    • Процесс управления получает сохраняет в SM команды, а от SM получает уведомления об изменении датчика уровня(Level_s)
    • Имитатор управления получает от SM уведомления об изменении команд, а в SM сохраняет имитируемое состояние уровня
    • Всё взаимодействие идёт через датчики


    Раз всё взаимодействие происходит через датчики, то наладка, в целом, это «выставление датчиков» и «отслеживание текущего состояния датчиков». Для этих целей в libuniset2-utils входит несколько утилит:
    • uniset2-admin — это многофункциональная утилита, но в данном случае позволяет ещё и выставлять (setValue) и смотреть текущее состояние датчиков (getValue)
    • uniset2-smviewer — утилита, позволяющая посмотреть сразу состояние всех датчиков, зарегистрированных в SM
    • uniset2-smonit — утилита, «следящая» (мониторинг) за изменением указанных датчиков

    Все эти утилиты активно используются в наладке, но это инструменты отслеживания «внешней» жизни процессов. А есть ещё два дополнительных механизма, которые позволяют наблюдать «жизнь процессов» изнутри:
    • vmonit — мониторинг внутренних переменных объекта
    • LogServer — удалённое чтение логов процесса

    Конечно можно было бы начать наладку, запустив сразу два процесса, и смотреть, что там происходит. Но правильнее, если это возможно, проводить наладку процессов отдельно. Давайте начнём с имитатора.

    Отладка работы имитатора


    • Запускаем SM — Входим в каталог src/Services/SharedMemory/. Запускаем скрипт start_fg.sh
    • Запускаем имитатор — Входим в каталог src/Algorithms/Imitator/. Запускаем скрипт start_fg.sh


    Проверяем, что объекты доступны. Заходим в src/Services/Administrator/ и запускаем ./exist
    Должны увидеть следующее
    Вывод на экране
    [pv@pvbook Administrator]$ ./exist
    
    ||=======********  UNISET-EXAMPLE/Services  ********=========||
    
    пусто!!!!!!
    
    ||=======********  UNISET-EXAMPLE/Controllers  ********=========||
    
    (22000 )SharedMemory1                                             <--- exist ok
    
    ||=======********  UNISET-EXAMPLE/Objects  ********=========||
    
    (20001 )Imitator1                                                 <--- exist ok
    [pv@pvbook Administrator]$ 
    


    Теперь возвращаемся к началу и вспоминаем, что же должен делать имитатор.
    Имитатор должен по приходу команды cmdLoad_C=1 начать имитировать наполнение цистерны (рост датчика Level_AS), а по приходу команды cmdUnload_C=1 — имитировать опустошение цистерны (уменьшать датчик Level_AS).

    А значит мы должны
    • выставить датчик cmdLoad_C=1 и увидеть нарастание Level_AS
    • выставить датчик cmdUnload_C=1 и увидеть уменьшение Level_AS

    Давайте посмотрим вообще текущее состояние датчиков. Воспользуемся утилитой uniset2-smviewer.
    Входим в каталог src/Services/SMViewer и запускаем ./start_fg.sh
    Видим это…
    Вывод на экране
    [pv@pvbook SMViewer]$ ./start_fg.sh
    
    ======================================================
    SharedMemory1    Датчики
    ------------------------------------------------------
    (  101) | AI |                                                     Level_AS   |     0
    (  100) | DI |                                                  OnControl_S   |     0
    ------------------------------------------------------
    
    ======================================================
    SharedMemory1    Выходы
    ------------------------------------------------------
    (  103) | DO |                                                  CmdUnload_C   |     0
    (  102) | DO |                                                    CmdLoad_C   |     0
    ------------------------------------------------------
    
    ======================================================
    SharedMemory1    Пороговые датчики
    ------------------------------------------------------
    


    Как видно, всё по нулям… Конечно же в реальном проекте датчиков будет ОЧЕНЬ много, и поэтому можно (и нужно) пользоваться uniset2-smviewer вместе с grep если хочется как-то фильтровать вывод…

    Вторая утилита, которая нам понадобится — это uniset2-smonit, чтобы посмотреть, как датчик уровня будет меняться. Давайте запустим её. Заходим в src/Services/SMonit/ и…
    Небольшая тонкость при использовании smonit
    Т.к. uniset2-smonit запускается и отслеживает изменение указанных датчиков, он должен иметь «обратный адрес». Для uniset-системы, таким адресом является идентификатор. По умолчанию, uniset2-smonit пытается запускаться с именем TestProc. Т.е. подразумевается, что в configure.xml в секции objects объявлен объект с name=«TestProc». Но если по каким-то причинам в вашем проекте не хочется иметь такой объект, то uniset2-smonit можно запустить с параметром --name XXX и указать любое имя из существующих в проекте объектов (не задействованных в текущий момент).

    Для этой утилиты нужно указать, за каким датчиками мы хотим следить, поэтому у неё есть ключик --sid
    (для простоты он вписан сразу в start_fg.sh). В качестве параметра --sid можно указать идентификатор, а можно указать имя датчика. Мы укажем имя Level_AS.
    ./start_fg.sh Level_AS
    

    Вывод команды


    smonit запускается и висит ждёт изменений. При этом в начале выводится текущее состояние датчика. Из вывода можно увидеть название, время последнего изменения (включая микросекунды), идентификатор процесса, который сохранил этот датчик в SM (в данном случае это Imitator1), и текущее значение value (и в виде float — fvalue).

    Всё вроде бы готово, выставляем датчик cmdLoad_C=1 и смотрим, как побежал меняться датчик Level_AS.
    Для выставления как раз воспользуемся admin-ом.
    [pv@pvbook Administrator]$ ./setValue CmdLoad_C=1
    

    и переключившись в консоль, где у нас запущен smonit, смотрим как побежал датчик (от 0 до 100).
    Вывод на экран smonit


    Значит, увеличение работает. Уменьшение проверяется аналогично… предварительно надо не забыть сбросить предыдущую команду, и мы сделаем это «одним махом»
    [pv@pvbook Administrator]$ ./setValue CmdLoad_C=0,CmdUnload_C=1
    

    smonit побежал в обратную сторону (от 100 до 0)
    Вывод на экране



    Мониторинг внутренних переменных объекта (vmonit)


    Теперь я опишу механизм, который позволяет посмотреть внутренние переменные объекта. Вообще, всё очень просто. Зная идентификатор или имя объекта, можно просто запросить у него информацию.
    Итак, у нас всё запущено и работает. Давайте посмотрим, что объект Imitator1 нам покажет.
    Заходим в src/Services/Administrator и запускаем команду ./oinfo Imitator1
    Вывод на экране


    Как видно из вывода, команда oinfo позволяет увидеть
    • Состояние всех входов и выходов объекта с привязками к датчикам (внутренних in_, out_ переменных)
    • Текущий список работающих таймеров, с оставшимся временем работы и т.п.
    • Значения всех переменных, с которыми запущен процесс (объявленных в src.xml)
    • Внутреннюю информацию по объекту (размер очереди сообщений, какой был максимум, были ли переполнения)
    • А также пользовательская информация

    О пользовательской информации скажу немного подробнее…
    У каждого объекта (точнее у скелета класса) есть специальная функция
    virtual std::string getMonitInfo() override;
    

    переопределив которую, можно выводить свою информацию, в виде текста (строки). В данном случае имитатор, например, пишет «Текущий режим работы: наполняем» (или «опустошаем»). Можно писать и что-то более сложное.
    Пример реализации функции в имитаторе
    string Imitator::getMonitInfo()
    {
    	ostringstream s;
    
    	s << "Текущий режим работы: " ;
    
    	if( in_cmdLoad_c )
    		s << " наполяем.." << endl;
    	else if( in_cmdUnload_c )
    		s << " опустошаем.." << endl;
    
    	return std::move(s.str());
    }
    


    Добавление в информационный вывод своих переменных

    Конечно, когда Вы пишете свой процесс управления, скорее всего у Вас будут, помимо переменных объявленных в xml-файле, ещё какие-то свои поля класса. И конечно захочется так же следить (выводить информацию) о текущем состоянии и своих переменных. Нет ничего проще. Допустим в имитатор мы добавим два счётчика команд.
    Добавление в Imitator.h
          ...
    	private:
    		unsigned int numCmdLoad = { 0 };
    		unsigned int numCmdUnload = { 0 };
    


    Тогда, если вы хотите увидеть их в выводе oinfo, просто в конструкторе сделаем два волшебных вызова:
    Добавление в Imitator.cc
    Imitator::Imitator( UniSetTypes::ObjectId id, xmlNode* cnode, const string& prefix ):
    	Imitator_SK(id, cnode, prefix)
    {
    	...
    	vmonit(numCmdLoad);
    	vmonit(numCmdUnload);
    }
    


    Т.е. просто обернули свои переменные в vmonit(xxx). Тут конечно не обошлось без макросной магии, но наверно это не сильно напрягает…
    В итоге на экране мы уже увидим и наши переменные (затесавшиеся среди прочих).
    Вывод на экран (повторный вызов ./oinfo)


    ВАЖНО: поддерживаются пока только стандартные простые типы: bool,int,long и т.п., для всего остального есть универсальная функция getMonitInfo()

    Удалённое чтение логов (встроенный LogServer)


    Как известно, сколько бы механизмов отладки ни существовало, а любимый cout(или же не любимый printf) всё равно будет использован. Ну что ж, libuniset предоставляет и этот способ. На самом деле, тема очень обширная, если раскрывать все возможности и детали, то это тема для отдельной статьи. Поэтому я покажу применение и расскажу немного деталей…
    В сгенерированном скелете класса, есть специальный объект для логов — log. Он по сути имеет интерфейс как cout, только это shared_ptr, поэтому пользоваться им нужно как указателем. Например
    log->info() << "......information.." << endl;
    

    или
    log->crit() << "......critical" << endl;
    

    У лога есть 15 уровней, включать их можно «параллельно» (т.е. например info,warn,crit), ему можно указать файл, куда писать логи, можно включать и отключать вывод даты и времени в начале каждой строки и т.п. Вообщем много стандартных возможностей. Для каждого объекта можно включать или отключать логи просто указав при запуске аргумент командной строки
    Управление логами через аргументы командной строки
    --ObjectName-log-add-levels info,warn,crit,level1,... - это добавление логов (к уже включённым)
    --ObjectName-log-del-levels info,warn,crit,level1,...  - это удаление  логов (из включённых)
    --ObjectName-log-set-levels info,warn,crit,level1,...  - это установка логов (взамен текущих)
    


    Но всё это было бы не так интересно, если бы не наличие такого механизма, как LogServer. В каждом объекте есть встроенный LogServer, который по умолчанию не запускается, соответственно ресурсов не потребляет и вообще не виден никак. Но простым аргументом командной строки
    --ObjectName-run-logserver
    

    мы можем его активировать. По умолчанию он запускается на localhost, а в качестве порта использует идентификатор объекта. Но можно и принудительно указать host и port запуска.
    Команды для переопределения host и port
    --ObjectName-logserver-host  xxx
    --ObjectName-logserver-port  zzz
    


    После того как у объекта запущен LogServer, мы можем читать его логи, причём удалённо. Просто подключившись по указанному хосту и порту. Для чтения логов существует специальная утилита uniset2-log. При помощи неё можно помимо чтения логов, так же и управлять уровнем вывода логов, запись в файл и т.п., т.е. осуществлять полный контроль над логами объекта. Это очень удобный механизм, т.к. позволяет включать и отключать логи без перезапуска программы (а ведь часто нельзя остановить процесс, но очень нужно посмотреть, что там внутри происходит).
    … давайте просто я покажу...
    Итак, у нас всё запущено, причём мы добавили в start_fg.sh имитатора строчку
    --Imitator1-run-logserver

    Кстати в выводе ./oinfo, если кто не заметил, выводится информация о том, запущен ли LogServer. Но давайте я покажу ещё раз (зайдём в каталог src/Services/Administator/ и запустим команду ./oinfo Imitator1).
    Вывод информации об объекте (обратите внимание на LogServer)


    Итак logserver запущен на localhost и порт 20001. Но по умолчанию (если, конечно, разработчик их принудительно не включит), логи отключены. Соответственно, мы не просто подключимся, а сразу ещё и включим все(any) логи, чтобы сразу начать их видеть. Давайте подключимся (добавим ключик -v, чтобы увидеть отладочную информацию о том, к кому мы подключаемся)
    uniset2-log -i localhost -p 20001 -v -a any
    

    Я добавил в функцию таймера лог (уровень 3), для вывода, чтобы продемонстрировать работу.
    Добавка в Imitator.cc (mylog3)
    void Imitator::timerInfo( const UniSetTypes::TimerMessage* tm )
    {
    	if( tm->id == tmStep )
    	{
    		if( in_cmdLoad_c ) // значит наполняем..
    		{
    			mylog3 << myname << "(timerInfo): таймер(" << tmStep << ").. наполняем" << endl;
    			out_Level_s += stepVal;
    			if( out_Level_s >= maxLevel )
    			{
    				out_Level_s = maxLevel;
    				askTimer(tmStep,0); // останавливаем таймер (и работу)
    			}
    			return;
    		}
    
    		if( in_cmdUnload_c ) // значит опустошаем
    		{
    			mylog3 << myname << "(timerInfo): таймер(" << tmStep << ")... опустошаем" << endl;
    			out_Level_s -= stepVal;
    			if( out_Level_s <= minLevel )
    			{
    				out_Level_s = minLevel;
    				askTimer(tmStep,0); // останавливаем таймер (и работу)
    			}
    			return;
    		}
    	}
    }
    


    Тогда подключаемся (как указано выше) и в другой консоли (заходим в src/Services/Administrator) выставляем команду
    ./setValue CmdLoad_C=1,CmdUnload_C=0
    
    … а через некоторое время наоборот
    ./setValue CmdLoad_C=0,CmdUnload_C=1
    

    А вот что мы увидим в логах (удалённо читаемых)
    Чтение логов


    Пользуясь возможностью удалённо читать логи, важно не забывать, что если вы их включили, то неплохо бы и уходя, отключать, потому что в реальной системе логи не должны работать, ввод/вывод — дорогое удовольствие. И ещё, я немного укажу возможности, которые не раскрыл ранее
    • LogAgregator — объект, позволяющий агрегировать в себе логи от нескольких объектов и управлять ими «централизованно»
    • Поддержка в LogAgregatore регулярных выражений (C++11), позволяющих более гибко выбирать какие логи (от каких объектов) мы хотим читать
    • Возможность в uniset2-log указать сразу несколько команд для включения одних логов, отключения других и, например, чтения третьих. Всё это одной командой.

    В реальном применении LogServer конечно запускается один на все объекты (в рамках одного запускаемого файла), которые при этом выстраиваются в иерархию при помощи LogAgregator и можно ими гибко управлять.

    Небольшой итог


    Каждый раз рассказывая о каких-то механизмах, я пытаюсь соблюсти баланс между кучей подробностей внутреннего функционирования и простотой внешнего применения. Т.е. «вот готовые команды, берите и пользуйтесь — они работают из коробки». Поэтому я многое не рассказал, и может что-то осталось не очевидным… Постараюсь ответить на ваши вопросы.
    В целом, если не считать, что нормальное тестирование — это гораздо больше всяких «тестов» (граничные случаи, задание max, min, одновременное выставление команд и т.п.), то с наладкой имитатора мы закончили. Главное было продемонстрировать инструменты для наладки, входящие в libuniset2:
    • утилиты для работы с датчиками и мониторинга их состояния
    • механизм для удалённого просмотра состояния внутренних переменных объекта
    • механизм удалённого чтения и управления логированием (у каждого объекта)

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

    Ну и в конце как обычно ссылочки:
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 0

    Only users with full accounts can post comments. Log in, please.