Регламентное задание бывает включено, расписание у него верное, ошибок в журнале ноль — а оно не выполняется. Ошибке неоткуда взяться, если код задания не запускался вовсе. Консоль заданий показывает настройки и дату последнего запуска, но не говорит, что задание давно должно было выполниться: она отвечает на вопрос «как задание настроено», а не «работает ли оно».
Обработка отвечает на второй вопрос. Историю запусков она берёт из журнала регистрации и рисует диаграммой Ганта, когда каждое задание было в порядке, когда упало и когда молчало, хотя по расписанию должно было работать. Форма управляемая, БСП не нужна — только методы платформы, поэтому обработка открывается и в типовой конфигурации, и в самописной. В базу она ничего не пишет.
Как это выглядит
График работы
Главная закладка. Каждая строка — регламентное задание, по горизонтали — период журнала. Зелёный — задание в порядке: последний запуск прошёл, и следующий по расписанию ещё не опоздал. Оранжевый — просрочено: расписание требовало запуска, а его не было. Красный — последний запуск упал. Серым закрыто время, о котором журнал ничего не знает: до его первой записи и после момента чтения.
График работы за неделю: у «Отправки серверных оповещений» в субботу днём — оранжевый участок, задание несколько часов не запускалось.
Наведите мышь на отрезок — подсказка покажет его состояние и точное время начала и конца.

Фактические запуски
Второй вид отвечает на вопрос, когда задание выполнялось на самом деле. Под строкой — синее окно, в которое расписание вообще разрешает запуск; над ним — клетки по часу. Зелёная клетка — в этот час были запуски без ошибок, красная — была ошибка, оранжевая — пропуск: в окне по расписанию запуска не было.
Фактические запуски: синим — когда расписание разрешает запуск, красным — часы с ошибкой, оранжевым — пропуск.
Масштаб «Один день» показывает выбранные сутки с клетками по десять минут — видно каждый запуск частого задания.

Список заданий, история и текст ошибки
На первой закладке — все регламентные задания базы с одним словом о каждом: работает, молчит, ошибки, выключено. Отбор «Проблемные» оставляет молчащие и упавшие, у каждого положения отбора — число заданий. Под списком — история выбранного задания: каждый запуск с началом, концом, длительностью и результатом. Справа — полный текст последней ошибки, с модулем и строкой.
«Проверка кэша состояний ФНС»: пять ошибок за неделю, последний запуск прошёл — вердикт «работает», а текст ошибки справаФоновые задания сейчас
Что выполняется в базе прямо сейчас, сколько идёт и что упало. Отборы «Выполняются» и «С ошибкой», текст ошибки выбранного задания справа.

Как устроено
История — из журнала регистрации, с отбором
Менеджер фоновых заданий хранит только последние выполнения: на базе, где задания идут раз в минуту, это считанные часы. Журнал регистрации помнит столько, сколько его настроили хранить, поэтому историю обработка берёт из него.
Читать весь журнал и фильтровать в цикле незачем — у ВыгрузитьЖурналРегистрации есть отбор. Обработка передаёт в него события заданий и массив метаданных всех регламентных заданий конфигурации:
События = СтрРазделить("_$Job$_.Start,_$Job$_.Finish,_$Job$_.Error,_$Job$_.Cancel,_$Job$_.Terminate", ",");
ЗаданияМД = Новый Массив;
Для Каждого ЗаданиеМД Из Метаданные.РегламентныеЗадания Цикл
ЗаданияМД.Добавить(ЗаданиеМД);
КонецЦикла;
Отбор = Новый Структура;
Отбор.Вставить("Событие", События);
Отбор.Вставить("Метаданные", ЗаданияМД);
Отбор.Вставить("ДатаНачала", Период.ДатаНачала);
Журнал = Новый ТаблицаЗначений;
ВыгрузитьЖурналРегистрации(Журнал, Отбор, "Дата,Событие,Метаданные,Комментарий,Сеанс");
Журнал.Сортировать("Дата");
Отбор по метаданным важен не меньше, чем по событиям. Те же пять событий пишут и фоновые задания, запущенные кодом без регламентного, и на живой базе их бывает больше, чем регламентных. С массивом метаданных платформа их просто не отдаёт. Журнал.Сортировать("Дата") тоже нужен: в облачной базе записи приходят не строго по времени, а на порядок опирается всё дальнейшее.
Запуск и его завершение
В журнале запуск и завершение — две отдельные записи. Связывает их номер сеанса: у каждого выполнения задания он свой. Обработка идёт по журналу от старых записей к новым, запуск открывает строку, а завершение того же сеанса её закрывает:
Открытые = Новый Соответствие;
Для Каждого Запись Из Журнал Цикл
ИмяЗадания = Сред(Запись.Метаданные, СтрНайти(Запись.Метаданные, ".") + 1);
Если Запись.Событие = "_$Job$_.Start" Тогда
Запуск = Запуски.Добавить();
Запуск.Задание = ИмяЗадания;
Запуск.Начало = Запись.Дата;
Запуск.Итог = "идёт";
Открытые.Вставить(Запись.Сеанс, Запуск);
Иначе
Запуск = Открытые[Запись.Сеанс];
Если Запуск <> Неопределено Тогда
Открытые.Удалить(Запись.Сеанс);
Запуск.Конец = Запись.Дата;
Запуск.Итог = ?(Запись.Событие = "_$Job$_.Error" ИЛИ Запись.Событие = "_$Job$_.Terminate",
"ошибка", "успех");
КонецЕсли;
КонецЕсли;
КонецЦикла;
Имя задания — всё после точки в колонке метаданных: там лежит строка вида «РегламентноеЗадание.ОбработкаБотов». Запуск без пары остаётся «идёт»: задание ещё выполняется или его сеанс оборвался. Таблицу запусков обработка кладёт во временное хранилище формы, и при переключении видов и отборов журнал заново не читается.
«Молчит»: спрашиваем само расписание
Сравнивать дату последнего запуска с расписанием вручную — значит разбирать дни недели, месяцы, повторы внутри дня и время окончания. У платформы на это есть готовый метод: РасписаниеРегламентногоЗадания.ТребуетсяВыполнение отвечает, должно ли задание выполниться на указанную дату с учётом последнего запуска.
НачалоОтсчёта = ?(ЗначениеЗаполнено(ПоследнийЗапуск), ПоследнийЗапуск, ПерваяЗаписьЖурнала);
КонецОтсчёта = Макс(ПоследнийУспех, ДатаОшибки);
// Допуск: задание раз в минуту между двумя запусками не должно числиться молчащим
ДатаПроверки = ТекущаяДата() - 600;
Если НачалоОтсчёта < ДатаПроверки
И Расписание.ТребуетсяВыполнение(ДатаПроверки, НачалоОтсчёта, КонецОтсчёта) Тогда
Возврат "молчит";
КонецЕсли;
Если запусков в журнале нет, отсчёт идёт от первой записи журнала, а не от начала периода: журнал бывает моложе выбранной недели, и «не запускалось за неделю» было бы неправдой. Вердикт «ошибки» проще: последнее завершение — ошибка, и успеха после неё не было.
Состояние по времени, как в сервисах мониторинга
Для диаграммы нужно состояние в каждый момент. Правило — как у сервисов мониторинга cron-задач: после успешного запуска задание в порядке до следующего ожидаемого запуска плюс льгота; не дождались — просрочено до следующего фактического запуска; упало — ошибка до следующего запуска.
Момент просрочки обработка находит тем же ТребуетсяВыполнение: шагает от последнего запуска вперёд, пока расписание не скажет «пора», и прибавляет льготу:
Льгота = 600;
Проверка = НачалоЗапуска + 600;
Пока Проверка + Льгота < Граница Цикл
Если Расписание.ТребуетсяВыполнение(Проверка, НачалоЗапуска, КонецЗапуска) Тогда
Возврат Проверка + Льгота;
КонецЕсли;
Проверка = Проверка + 600;
КонецЦикла;
Возврат Неопределено; // до границы задание не опоздало
В обработке функция ещё уточняет найденную десятиминутку до минуты тем же вопросом к расписанию. Граница — момент, когда обработка прочитала журнал, а не текущее время: запусков после чтения в данных нет, и хвост до «сейчас» выглядел бы ложной просрочкой. Из запусков задания и этих моментов функция СостоянияЗадания собирает отрезки «в порядке», «просрочено» и «ошибка» и сливает соседние отрезки одного вида в один.
Диаграмма Ганта в управляемой форме
Диаграмма — реквизит формы типа ДиаграммаГанта и поле вида ПолеДиаграммыГанта. Строка — точка, отрезок состояния — интервал со своим цветом и текстом, который и показывается подсказкой:
Точка = Диаграмма.УстановитьТочку(ИмяЗадания);
Точка.Текст = Синоним + " — " + Вердикт;
Для Каждого Отрезок Из Отрезки Цикл
Интервал = Диаграмма.ПолучитьЗначение(Точка, Серия).Добавить();
Интервал.Начало = Отрезок.Начало;
Интервал.Конец = Отрезок.Конец;
Интервал.Цвет = ЦветСостояния(Отрезок.Вид);
Интервал.Текст = Отрезок.Вид + ": " + Формат(Отрезок.Начало, "ДФ='дд.ММ ЧЧ:мм'")
+ " – " + Формат(Отрезок.Конец, "ДФ='дд.ММ ЧЧ:мм'");
КонецЦикла;
Обработка задаёт период шкалы явно, подписывает строки сама и выключает встроенную легенду: та показывает серии, а цвет здесь у интервалов. Свою легенду обработка рисует надписью с ФорматированнаяСтрока:
Диаграмма.АвтоОпределениеПолногоИнтервала = Ложь;
Диаграмма.УстановитьПолныйИнтервал(ОтМомента, ДоМомента);
Диаграмма.АвтоУстановкаТекстаТочек = Ложь;
Диаграмма.ОбластьЛегенды.Расположение = РасположениеЛегендыДиаграммы.Нет;
Две ловушки веб-клиента. Шкалу времени без явного формата веб-клиент оставляет без подписей, поэтому формат нужно задать каждому элементу шкалы. А интервал фона, упёртый ровно в правую границу шкалы, веб-клиент не рисует, и обработка ставит его конец на секунду раньше:
ЭлементыШкалы[0].Единица = ТипЕдиницыШкалыВремени.День;
ЭлементыШкалы[0].Формат = "ДФ='ддд дд.ММ'";
Часы = ЭлементыШкалы.Добавить();
Часы.Единица = ТипЕдиницыШкалыВремени.Час;
Часы.Формат = "ДФ=ЧЧ";
Фон = Диаграмма.ИнтервалыФона.Добавить(МоментЧтения, ДоМомента - 1);
Фон.Цвет = WebЦвета.Серебряный;
Окно по расписанию и клетки запусков
Окно на виде «Запуски» обработка получает тем же методом, но без последнего запуска: Расписание.ТребуетсяВыполнение(Дата) отвечает, подходит ли дата под расписание вообще. Обработка спрашивает его о середине каждой клетки и склеивает подряд идущие «да» в один синий интервал. Запуски она раскладывает по клеткам одним проходом, а пропуском помечает клетки внутри отрезков «просрочено» — там, где расписание разрешает запуск.
История выбранного задания
По заданию из списка обработка читает журнал ещё раз, уже с отбором по его метаданным, и склеивает запуски так же, по сеансу. Последний параметр выгрузки ограничивает её последними событиями: у задания раз в минуту за неделю записей многие тысячи, а человеку нужны свежие.
Отбор.Вставить("Метаданные", ЗаданиеМД);
ВыгрузитьЖурналРегистрации(Журнал, Отбор, "Дата,Событие,Комментарий,Сеанс", , 1000);
Текст ошибки лежит в комментарии записи журнала — целиком, с модулем и номером строки.
Фоновые задания без тормозов
Консоль, которая у каждого фонового задания обращается к его регламентному, на нагруженной базе открывает список долго. Обработка берёт только свойства самого фонового задания:
Для Каждого Задание Из ФоновыеЗадания.ПолучитьФоновыеЗадания() Цикл
// Состояние сравниваем значением: строкой оно на языке интерфейса
Идёт = (Задание.Состояние = СостояниеФоновогоЗадания.Активно);
Упало = (Задание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно);
...
Если Задание.ИнформацияОбОшибке <> Неопределено Тогда
Запись.ТекстОшибки = ПодробноеПредставлениеОшибки(Задание.ИнформацияОбОшибке);
КонецЕсли;
КонецЦикла;
Историю эта закладка не заменяет: платформа хранит только последние фоновые задания. За историей — на первую закладку, к журналу.
Сценарии
- Задание «вроде работает», а данные не обновляются: на графике работы видно, с какого момента оно молчит.
- Задание падает через раз: в списке видно, сколько было запусков и сколько с ошибкой, в истории — какие запуски упали, справа — текст ошибки с модулем и строкой.
- После обновления или переноса базы — отбор «Проблемные» показывает, что не запустилось.
- Пользователи жалуются на тормоза: на закладке фоновых видно, что выполняется прямо сейчас и сколько идёт.
Поставка
KontrolReglamentnykhZadaniy.epf — внешняя обработка. Код открыт и лежит в модуле формы. Список заданий и журнал регистрации платформа отдаёт только администратору, поэтому открывать обработку нужно под пользователем с правом «Администрирование».
Проверено на следующих конфигурациях и релизах:
- Управление нашей фирмой, редакция 3.0, релизы 3.0.14.159
Вступайте в нашу телеграмм-группу Инфостарт

