upd. 2018.12.19
обновил главный файл сбора данных - зафиксировал, что регулярное выражение покрывало не все сеансы.
Имплементация системы мониторинга кластера 1С (и лицензий).
Для успешного приготовления нам потребуются:
-
1С Предприятие нужного Вам релиза.
-
-
Grafana* ( я использую версию 5.2.4)
-
-
*Вопросы установки Linux, Grafana и Postgre Pro в данной статье не будут рассмотрены.
Сначала коснёмся 1С. Для сбора данных через RAS нам нужно:
-
Установить(и запустить) службу RAS на серверах приложений 1С под Windows. Через RAS мы будем получать данные от этого СП 1С. Установить службу можно сделать такой командой:
"C:\Windows\System32\sc.exe" create "1C:Remote Administation Serive (RAS)" binPath= "\"C:\Program Files\1cv8\8.3.12.1685\bin\ras.exe\" cluster --service --port=1545 ИМЯ_СЕРВЕРА:1540" start= auto
Для автоматической установки службы RAS можно воспользоваться скриптом func.install_ras.ps1 из раздела загрузки.
-
Установить необходимые компоненты 1С rac на Linux.
Для CentOS потребуется потребуется скачать с https://releases.1c.ru/ Сервер 1С:Предприятия (64-bit) для RPM-based Linux-систем.
Порядок установки пакетов, на примере релиза 8.3.12.1685 для моей ОС следующий:
yum install -y 1C_Enterprise83-common-8.3.12-1685.x86_64.rpm
yum install -y 1C_Enterprise83-common-nls-8.3.12-1685.x86_64.rpm
yum install -y 1C_Enterprise83-server-8.3.12-1685.x86_64.rpm
yum install -y 1C_Enterprise83-server-nls-8.3.12-1685.x86_64.rpm
После этого rac будет доступен из /opt/1C/v8.3/x86_64/rac.
-
Проверить корректность установки можно подключившись к RAS.
/opt/1C/v8.3/x86_64/rac cluster list ИМЯ_СЕРВЕРА:1545
Если всё правильно, то мы увидим подобное:
Теперь подготовим отдельную базу в Postgre.
В ней нам потребуется несколько таблиц:
-
Таблица для хранения списка серверов RAS, к которым мы будет подключаться за данными.
-
Вспомогательные таблицы для экономии места
-
Таблица данных о сеансах.
Подключаемся к postgre из консоли:
Создаём пользователя для работы со своей базой данных:
CREATE ROLE "racer" LOGIN PASSWORD 'my_evil_password';
Создаём отдельную базу данных:
CREATE DATABASE "racerDB"
WITH
OWNER = "racer"
ENCODING = 'UTF8'
LC_COLLATE = 'ru_RU.UTF-8@icu.58.0.0.50'
LC_CTYPE = 'ru_RU.UTF-8'
TABLESPACE = "pg_default"
;
\connect racerDB;
Создаём таблицу для хранения списка RAS-серверов, к которым будем подключаться:
CREATE TABLE "public"."ras_servers" (
"id" SERIAL PRIMARY KEY,
"host_name" varchar(255) NOT NULL,
"ras_port" int2 NOT NULL
);
ALTER TABLE "public"."ras_servers"
OWNER TO "racer";
И заполняем её списком своих RAS-серверов:
INSERT INTO "public"."ras_servers"("host_name", "ras_port") VALUES ('ИМЯ_СЕРВЕРА_1', 1545) RETURNING *;
INSERT INTO "public"."ras_servers"("host_name", "ras_port") VALUES ('ИМЯ_СЕРВЕРА_2', 1545) RETURNING *;
...
INSERT INTO "public"."ras_servers"("host_name", "ras_port") VALUES ('ИМЯ_СЕРВЕРА_N', 1545) RETURNING *;
Теперь нам нужно создать несколько служебных таблиц, чтобы хранить в них ссылки на данные из главной таблицы соединений.
CREATE TABLE "public"."users" (
"id" SERIAL PRIMARY KEY,
"name" varchar(255) NOT NULL
);
ALTER TABLE "public"."users"
OWNER TO "racer";
CREATE TABLE "public"."infobases" (
"id" SERIAL PRIMARY KEY,
"uuid" varchar(36) NOT NULL,
"name" varchar(255) NOT NULL,
"descr" varchar(1024)
);
ALTER TABLE "public"."infobases"
OWNER TO "racer";
CREATE TABLE "public"."hosts" (
"id" SERIAL PRIMARY KEY,
"name" varchar(255) NOT NULL
);
ALTER TABLE "public"."hosts"
OWNER TO "racer";
CREATE TABLE "public"."licenses_db" (
"id" SERIAL PRIMARY KEY,
"name" varchar(255) NOT NULL,
"type" varchar(4) NOT NULL,
"max" int2 NOT NULL
);
ALTER TABLE "public"."licenses_db"
OWNER TO "racer";
CREATE TABLE "public"."apps" (
"id" SERIAL PRIMARY KEY,
"name" varchar(255) NOT NULL,
"description" varchar(255)
);
ALTER TABLE "public"."apps"
OWNER TO "racer";
--и сразу заполним её
INSERT INTO "public"."apps"("name", "description") VALUES ('1CV8', 'Толстый клиент') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('1CV8C', 'Тонкий клиент') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('WebClient', 'Веб-клиент') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('Designer', 'Конфигуратор') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('COMConnection', 'COM-соединение') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('WSConnection', 'Сессия веб-сервиса') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('BackgroundJob', 'Фоновое задание') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('SystemBackgroundJob ', 'Системное фоновое задание') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('SrvrConsole', 'Консоль кластера') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('COMConsole', 'COM-консоль кластера') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('JobScheduler ', 'Планировщик') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('Debugger', 'Отладчик') RETURNING *;
INSERT INTO "public"."apps"("name", "description") VALUES ('RAS', 'Сервер администрирования') RETURNING *;
CREATE TABLE "public"."processes" (
"id" SERIAL PRIMARY KEY,
"uuid" varchar(36) NOT NULL,
"host" varchar(255),
"port" int4,
"pid" int4,
"started_at" timestamp
);
ALTER TABLE "public"."processes"
OWNER TO "racer";
Теперь создаём таблицу, в которой будем хранить информацию о соединениях и их лицензиях.
CREATE TABLE "public"."sessions" (
"ras_server_by_id" int2 NOT NULL, --ссылка на имя RAS-сервера, с которого были получены данные
FOREIGN KEY("ras_server_by_id") REFERENCES hosts(id) ON DELETE SET DEFAULT, --ссылка на имя RAS-сервера, с которого были получены данные
"datetime" timestamp NOT NULL, --дата и время получения среза в UTC
"session-id_nmb" int4 NOT NULL, --номер сессии
"infobase_by_id" int2 NOT NULL, --id информационной базы из таблицы infobases
FOREIGN KEY("infobase_by_id") REFERENCES infobases(id) ON DELETE SET DEFAULT, --id информационной базы из таблицы infobases
"process_by_id" int4 , --id процесс СП 1C из таблицы processes
FOREIGN KEY("process_by_id") REFERENCES processes(id) ON DELETE SET DEFAULT, --id процесс СП 1C из таблицы processes
"user-name_by_id" int2 NOT NULL, --id пользователя из таблицы users
FOREIGN KEY("user-name_by_id") REFERENCES users(id) ON DELETE SET DEFAULT, --id пользователя из таблицы users
"host_by_id" int2 NOT NULL, --id хоста пользователя
FOREIGN KEY("host_by_id") REFERENCES hosts(id) ON DELETE SET DEFAULT, --id хоста пользователя
"app-id_by_id" int2 NOT NULL, --id приложения из таблицы apps
FOREIGN KEY("app-id_by_id") REFERENCES apps(id) ON DELETE SET DEFAULT, --id приложения из таблицы apps
"started-at" timestamp NOT NULL, --дата и время начала сеанса в UTC
"last-active-at" timestamp NOT NULL, --последняя активность
"hibernate" bool NOT NULL, --спящий ли сеанс
"passive-session-hibernate-time" int4 NOT NULL, --
"hibernate-session-terminate-time" int4 NOT NULL, --
"blocked-by-dbms" int4 NOT NULL, --
"blocked-by-ls" int4 NOT NULL, --
"bytes-all" int8 NOT NULL, --
"bytes-last-5min" int8 NOT NULL, --
"calls-all" int4 NOT NULL, --
"calls-last-5min" int4 NOT NULL, --
"dbms-bytes-all" int8 NOT NULL, --
"dbms-bytes-last-5min" int8 NOT NULL, --
"db-proc-info" int4 NOT NULL, --
"db-proc-took" int4 NOT NULL, --
"db-proc-took-at" timestamp , --
"duration-all" int4 NOT NULL, --
"duration-all-dbms" int4 NOT NULL, --
"duration-current" int4 NOT NULL, --
"duration-current-dbms" int4 NOT NULL, --
"duration-last-5min" int4 NOT NULL, --
"duration-last-5min-dbms" int4 NOT NULL, --
"memory-current" int8 NOT NULL, --
"memory-last-5min" int8 NOT NULL, --
"memory-total" int8 NOT NULL, --
"read-current" int8 NOT NULL, --
"read-last-5min" int8 NOT NULL, --
"read-total" int8 NOT NULL, --
"write-current" int8 NOT NULL, --
"write-last-5min" int8 NOT NULL, --
"write-total" int8 NOT NULL, --
"duration-current-service" int4 NOT NULL, --
"duration-last-5min-service" int4 NOT NULL, --
"duration-all-service" int4 NOT NULL, --
"license_by_id" int2 , --размер ключа (на сколько он пользователей)
FOREIGN KEY("license_by_id") REFERENCES licenses_db(id) ON DELETE SET DEFAULT, --размер ключа (на сколько он пользователей)
"license_issued_by_server" bool , --признак выдачи лицензии сервером
"license_net" bool , --сетевая ли лицензия
"rmngr_by_id" int2 , --хост, выдавший лицензию
FOREIGN KEY("rmngr_by_id") REFERENCES hosts(id) ON DELETE SET DEFAULT --хост, выдавший лицензию
);
ALTER TABLE "public"."sessions"
OWNER TO "racer";
CREATE INDEX "by_date" ON "public"."sessions" USING btree (
"datetime" "pg_catalog"."timestamp_ops" ASC NULLS LAST
);
Так как в таблице хранятся ссылки, а не абсолютные значения, создадим для удобства просмотров, агрегированный view_sessions, содержащий значения всех всех собранных данных:
CREATE VIEW "public"."view_sessions" AS
SELECT
h1.name AS ras_host
, s.datetime AS datetime
, s."session-id_nmb" AS session_id
, i.name AS infobase_name
, p.host AS process_host
, p.port AS process_port
, p.pid AS process_pid
, p.started_at AS process_started
, u.name AS user_name
, h2.name AS user_host
, a.name AS app
, s."started-at" AS started_at
, s."last-active-at" AS last_active_at
, s.hibernate AS hibernate
, s."passive-session-hibernate-time" AS passive_session_hibernate_time
, s."hibernate-session-terminate-time" AS hibernate_session_terminate_time
, s."blocked-by-dbms" AS blocked_by_dbms
, s."blocked-by-ls" AS blocked_by_ls
, s."bytes-all" AS bytes_all
, s."bytes-last-5min" AS bytes_last_5min
, s."calls-all" AS calls_all
, s."calls-last-5min" AS calls_last_5min
, s."dbms-bytes-all" AS dbms_bytes_all
, s."dbms-bytes-last-5min" AS dbms_bytes_last_5min
, s."db-proc-info" AS db_proc_info
, s."db-proc-took" AS db_proc_took
, s."db-proc-took-at" AS db_proc_took_at
, s."duration-all" AS duration_all
, s."duration-all-dbms" AS duration_all_dbms
, s."duration-current" AS duration_current
, s."duration-current-dbms" AS duration_current_dbms
, s."duration-last-5min" AS duration_last_5min
, s."duration-last-5min-dbms" AS duration_last_5min_dbms
, s."memory-current" AS memory_current
, s."memory-last-5min" AS memory_last_5min
, s."memory-total" AS memory_total
, s."read-current" AS read_current
, s."read-last-5min" AS read_last_5min
, s."read-total" AS read_total
, s."write-current" AS write_current
, s."write-last-5min" AS write_last_5min
, s."write-total" AS write_total
, s."duration-current-service" AS duration_current_service
, s."duration-last-5min-service" AS duration_last_5min_service
, s."duration-all-service" AS duration_all_service
, l.name AS license_series
, l.type AS license_type
, l.max AS license_max
, s.license_issued_by_server AS license_issued_by_server
, s.license_net AS license_net
, h3.name AS license_rmngr
FROM
sessions s
LEFT JOIN hosts h1 ON s.ras_server_by_id = h1.id
LEFT JOIN infobases i ON s.infobase_by_id = i.id
LEFT JOIN processes p ON s.process_by_id = p.id
LEFT JOIN users u ON s."user-name_by_id" = u.id
LEFT JOIN hosts h2 ON s.host_by_id = h2.id
LEFT JOIN apps a ON s."app-id_by_id" = a.id
LEFT JOIN licenses_db l ON s.license_by_id = l.id
LEFT JOIN hosts h3 ON s.rmngr_by_id = h3.id
;
ALTER TABLE "public"."view_sessions"
OWNER TO "racer";
Для выборки данных о лицензиях нам понадобится отдельный View, ибо с использованием лицензий не всё так просто.
1С утилизирует лицензии полученные клиентом и выданные сервером по разному:
-
при выдаче сервером для каждого сеанса выдаётся одна программная лицензия;
-
при выдаче сервером для каждого сеанса выдаётся одна аппаратная лицензия;
-
при выдаче клиентскому приложению службой HASP LM для каждого пользовательского сеанса(сессия RDP или локальное подключение Windows) на клиенте выдаётся одна аппаратная лицензия. При этом с этой одной лицензией можно работать в разных базах.
-
как считать программные лицензии, полученные локально клиентским приложением я ещё не определился, к ним применяется такой же порядок, как для аппаратных, выданных клиенту.
Из-за этих особенностей в отборе применяется три вида группировки:
-
для серверов, не являющихся терминальными (отбираются через view_not_terminal_ids) агрегируются все подключения с каждого хоста для каждого уникального ключа;
-
для терминальных серверов (view_terminal_ids) применяется дополнительная агрегация и по имени пользователя - применяется допущение, что пользователь, работающий из терминальной сессии в разных базах, будет работать там под одним именем;
-
и наконец, для лицензий, выданных сервером, не применяется никакой группировки.
Сначала определимся со списком рабочих станций, не являющихся терминальными серверами:
CREATE VIEW view_not_terminal_ids AS
SELECT h.id from HOSTS h
WHERE
(UPPER (( h.NAME ) :: TEXT ) !~~ 'TERM%' :: TEXT)
AND
(UPPER (( h.NAME ) :: TEXT ) !~~ 'APP%' :: TEXT)
;
ALTER TABLE "public"."view_not_terminal_ids"
OWNER TO "racer";
Потом - со списком терминальных серверов:
CREATE VIEW view_terminal_ids AS
SELECT h.id from HOSTS h
WHERE
(UPPER (( h.NAME ) :: TEXT ) ~~ 'TERM%' :: TEXT)
OR
(UPPER (( h.NAME ) :: TEXT ) ~~ 'APP%' :: TEXT)
;
ALTER TABLE "public"."view_terminal_ids"
OWNER TO "racer";
Теперь можно создавать View с группировкой использования лицензий:
CREATE VIEW "public"."view_licenses" AS
SELECT
s.datetime
, '' :: TEXT AS user_name
, UPPER (( h.NAME ) :: TEXT ) AS user_host
, '' :: TEXT AS appid
, l.NAME AS license_series
, s.license_issued_by_server AS license_issued_by_server
, s.license_net AS license_net
, l.TYPE AS license_type
, '' AS license_rmngr --у клиентских не будет RMNGR
, l.MAX AS license_max
, COUNT ( * ) AS seanses_count --сеансов на эту лицензию
FROM
sessions s
LEFT JOIN hosts h ON s.host_by_id = h.ID
LEFT JOIN licenses_db l ON s.license_by_id = l.ID
LEFT JOIN users u ON s."user-name_by_id" = u.ID
WHERE
(s.license_issued_by_server = FALSE) --только лицензии, не выданные сервером
AND
(s.host_by_id in (select id from view_not_terminal_ids)) --и не с терминальных серверов
GROUP BY --группируем по
s.datetime
, ( UPPER (( h.NAME ) :: TEXT )) --имени хоста
, l.NAME --серии лицензии
, s.license_issued_by_server --признаку выдачи сервером
, s.license_net --является ли лицензия сетевой
, l.TYPE --типу - приграммная/аппаратная
, l.MAX --размеру ключа
UNION ALL
SELECT
s.datetime
, UPPER (( u.NAME ) :: TEXT ) AS user_name
, UPPER (( h.NAME ) :: TEXT ) AS user_host
, '' :: TEXT AS appid
, l.NAME AS license_series
, s.license_issued_by_server AS license_issued_by_server
, s.license_net AS license_net
, l.TYPE AS license_type
, '' AS license_rmngr --у клиентских не будет RMNGR
, l.MAX AS license_max
, COUNT ( * ) AS seanses_count --сеансов на эту лицензию
FROM
sessions s
LEFT JOIN hosts h ON s.host_by_id = h.ID
LEFT JOIN licenses_db l ON s.license_by_id = l.ID
LEFT JOIN users u ON s."user-name_by_id" = u.ID
WHERE
(s.license_issued_by_server = FALSE) --только лицензии, не выданные сервером
AND
(s.host_by_id in (select id from view_terminal_ids)) --с терминальных серверов
GROUP BY
s.datetime
, ( UPPER (( u.NAME ) :: TEXT )) --добавляем группировку по имени пользователя
, ( UPPER (( h.NAME ) :: TEXT )) --имени хоста
, l.NAME --серии лицензии
, s.license_issued_by_server --признаку выдачи сервером
, s.license_net --является ли лицензия сетевой
, l.TYPE --типу - приграммная/аппаратная
, l.MAX --размеру ключа
UNION ALL
SELECT
s.datetime
, UPPER (( u.NAME ) :: TEXT ) AS user_name
, UPPER (( h.NAME ) :: TEXT ) AS user_host
, A.NAME AS appid
, l.NAME AS license_series
, s.license_issued_by_server AS license_issued_by_server
, s.license_net AS license_net
, l.TYPE AS license_type
, UPPER (( h2.NAME ) :: TEXT )AS license_rmngr --а здесь RMNGR будет
, l.MAX AS license_max
, '1' :: BIGINT AS seanses_count --сеансов на эту лицензию
FROM
sessions s
LEFT JOIN hosts h ON s.host_by_id = h.ID
LEFT JOIN licenses_db l ON s.license_by_id = l.ID
LEFT JOIN users u ON s."user-name_by_id" = u.ID
LEFT JOIN apps a ON s."app-id_by_id" = A.ID
LEFT JOIN hosts h2 ON s.rmngr_by_id = h2.ID
WHERE
s.license_issued_by_server = TRUE --все лицензии выданные сервером не группируются
;
ALTER TABLE "public"."view_licenses"
OWNER TO "racer";
Подготовка хранилища данных завершена.
Собираем данные
Сначала о командах, используемых для получения информации:
-
/opt/1C/v8.3/x86_64/rac cluster list ИМЯ_СЕРВЕРА:1545 #так мы будем получать список кластеров из сервера RAS
-
/opt/1C/v8.3/x86_64/rac infobase summary list --cluster=GUID_Кластера ИМЯ_СЕРВЕРА:1545 #здесь мы, используя GUID кластера из первой команды, получим список баз
-
/opt/1C/v8.3/x86_64/rac process list --cluster=GUID_Кластера ИМЯ_СЕРВЕРА:1545 #а так мы получим информацию о списке рабочих процессов
-
/opt/1C/v8.3/x86_64/rac session list --cluster=GUID_Кластера ИМЯ_СЕРВЕРА:1545 #с помощью этой команды можно получить список сеансов, но здесь есть не вся информация о лицензии
-
/opt/1C/v8.3/x86_64/rac session list --licenses --cluster=GUID_Кластера ИМЯ_СЕРВЕРА:1545 #а тут мы получаем недостающую информацию о лицензиях
Общий порядок работы скрипта такой:
-
Получаем список кластеров сервера RAS из таблицы ras_servers в базе Postre
-
Получаем список информационных баз кластера
-
Получаем список процессов кластера
-
Получаем список сеансов и их лицензий
-
Сводим все данные и заносим в целевую таблицу
Проверим, что у нас установлен Python 3.6
yum list installed | grep python36
Если его нет, то нужно его поставить:
yum install -y https://centos7.iuscommunity.org/ius-release.rpm
yum install -y python36u python36u-devel python36u-pip
И, в любом случае, нужно поставить два используемых модуля Python:
pip3.6 install psycopg2
pip3.6 install pytz
После этого нужно настроить скрипт сбора данных (см. раздел загружаемых файлов) - отредактировать его и поменять параметры подключения к Postre.
После этого можно смело создавать новый Dashboard и добавлять на него первый Graph
Выбираете свой Datasource на вкладке Metrics и можно редактировать тексты запросов.
Отдельный график можно сделать и по сеансам с программными и аппаратными лицензиями:
И посмотреть утилизацию ключей по типам (обратите внимание, что многопользовательские ключи на 20, 50 и 100 лицензий обладают одинаковым идентификатором - ORGL8. А ещё для клиентских лицензий мы не может узнать какой HASP LM их выдал, поэтому они агрегируются сразу по всем ключам одного типа и значение для графика таких ключей может быть больше ёмкости одного ключа):
На выходе получаются вот такие прекрасные графики.