Pull to refresh

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

Reading time 9 min
Views 5K
В предыдущих частях (часть 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. Об этом в следующей части...

Ну и в конце как обычно ссылочки:
Tags:
Hubs:
+8
Comments 0
Comments Leave a comment

Articles