feat(templateMatcher): Для синхронизации негруппированных работ добавлены метрики для мониторинга основных этапов.

This commit is contained in:
Mikhail Kuznetsov
2026-06-10 12:08:07 +10:00
parent 535e87ec21
commit a64ab59de1
3 changed files with 188 additions and 102 deletions

View File

@@ -18,6 +18,7 @@ using PARR.Domain.Settings;
using PARR.TemplateMatcher.Models;
using PARR.TemplateMatcher.Services.Interfaces;
using PARR.TemplateMatcher.Settings;
using System.Diagnostics;
namespace PARR.TemplateMatcher.Services.Implementations;
@@ -99,8 +100,8 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
return;
}
// === Обычная логика для всех остальных Job ===
logger.LogDebug("Начало синхронизации шаблонов для Job {JobId}", jobId);
var totalSw = Stopwatch.StartNew();
logger.LogInformation("Начало синхронизации шаблонов для Job {JobId}", jobId);
// === Проверка: уже запущена? ===
var existingStatus = await matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
@@ -126,6 +127,8 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
try
{
// === ЭТАП 1: Загрузка Job ===
var stageSw = Stopwatch.StartNew();
var job = await jobService.Get()
.AsNoTracking()
.Include(j => j.AutoControl)
@@ -142,48 +145,22 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
await UpdateMatchingStatusAsync(jobId, "Job не найден");
return;
}
stageSw.Stop();
logger.LogDebug("[Perf] Job {JobId} | Этап: Загрузка Job | Время: {Ms} мс",
jobId, stageSw.ElapsedMilliseconds);
// === Получение отфильтрованных юнитов с полной информацией ===
// === ЭТАП 2: Фильтрация юнитов ===
stageSw.Restart();
var filteredUnits = await unitFilterService.GetUnitsByJobFilterAsync(jobId);
if (filteredUnits == null || !filteredUnits.Any())
{
logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
stageSw.Stop();
var filterCount = filteredUnits?.Count() ?? 0;
logger.LogDebug("[Perf] Job {JobId} | Этап: Фильтрация юнитов | Время: {Ms} мс | Результат: {Count}",
jobId, stageSw.ElapsedMilliseconds, filterCount);
var existingTemplatesForDeactivation = await templateService.Get()
.AsNoTracking()
.Include(t => t.UnitsInTemplate)
.Where(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used)
.ToListAsync();
await UpdateMatchingStatusAsync(jobId, $"Нет Unit'ов. Деактивация {existingTemplatesForDeactivation.Count} шаблонов...");
foreach (var unusedTemplate in existingTemplatesForDeactivation)
{
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId);
await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator);
}
await UpdateMatchingStatusAsync(jobId, "Синхронизация завершена: нет Unit'ов");
await matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
logger.LogInformation("Синхронизация шаблонов завершена для Job {JobId}.", jobId);
return;
}
// Извлекаем ID юнитов для последующих операций
var unitIds = filteredUnits.Select(u => u.Id).ToList();
#if DEBUG
// Отладка: проверить, есть ли юнит в unitIds
if (unitIds.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIds.", targetUnitId);
}
#endif
var unitIds = filteredUnits?.Select(u => u.Id).ToHashSet() ?? new HashSet<Guid>();
// === ЭТАП 3: Загрузка существующих шаблонов ===
stageSw.Restart();
var existingTemplates = await templateService.Get()
.Include(t => t.UnitsInTemplate)
.Include(t => t.Job)
@@ -199,25 +176,48 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
.Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used)
.ToList();
var existingUnitIds = existingUsedTemplates
.Select(t => t.UnitId)
.ToHashSet();
var existingUnitIds = existingUsedTemplates.Select(t => t.UnitId).ToHashSet();
stageSw.Stop();
logger.LogDebug("[Perf] Job {JobId} | Этап: Загрузка шаблонов | Время: {Ms} мс | Используется: {Count}",
jobId, stageSw.ElapsedMilliseconds, existingUsedTemplates.Count);
// === ЭТАП 4: Расчёт диффа (создание / деактивация / переименование) ===
stageSw.Restart();
var newUnitIds = unitIds.Except(existingUnitIds).ToList();
var unusedTemplates = existingUsedTemplates
.Where(t => !unitIds.Contains(t.UnitId))
.ToList();
// Проверка имён существующих шаблонов
var templatesToRename = new List<(Template Template, string ExpectedName)>();
foreach (var template in existingUsedTemplates)
{
if (!unitIds.Contains(template.UnitId))
continue;
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
{
templatesToRename.Add((template, expectedName));
}
}
stageSw.Stop();
logger.LogDebug("[Perf] Job {JobId} | Этап: Расчёт диффа | Время: {Ms} мс | Создать: {Create}, Деактивировать: {Deactivate}, Переименовать: {Rename}",
jobId, stageSw.ElapsedMilliseconds, newUnitIds.Count, unusedTemplates.Count, templatesToRename.Count);
// === ЭТАП 5: Создание новых шаблонов ===
stageSw.Restart();
foreach (var unitId in newUnitIds)
{
// Вычисляем флаги активности из конфигурации автоконтрола
var isActiveTemplate = job.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState;
var isActiveSchedule = job.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState;
var request = new TemplateAllocationRequest(
TargetJob: job,
TargetUnitId: unitId,
TargetUnit: null, // Для простого шаблона не критично, нормализатор загрузит при необходимости
TargetUnit: null,
Index: null,
UnitsInTemplate: new List<UnitInTemplateMessage>(),
IsActiveTemplate: isActiveTemplate,
@@ -226,58 +226,68 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
await templateAllocationService.AllocateAsync(request);
}
stageSw.Stop();
logger.LogDebug("[Perf] Job {JobId} | Этап: Создание шаблонов | Время: {Ms} мс | Количество: {Count}",
jobId, stageSw.ElapsedMilliseconds, newUnitIds.Count);
// === Обработка существующих шаблонов (проверка имени) ===
foreach (var template in existingUsedTemplates)
// === ЭТАП 6: Обновление имён существующих шаблонов ===
stageSw.Restart();
foreach (var (template, expectedName) in templatesToRename)
{
if (unitIds.Contains(template.UnitId))
logger.LogDebug("Шаблон {TemplateId} требует обновления имени: '{OldName}' → '{NewName}'",
template.Id, template.Name, expectedName);
var updateRequest = new TemplateUpdaterMessage
{
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
{
logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", template.Id, template.Name, expectedName);
TemplateId = template.Id,
JobId = jobId,
UnitId = template.UnitId,
Name = expectedName,
IsActiveTemplate = template.IsActiveTemplate,
IsActiveSchedule = template.IsActiveSchedule,
IsNew = false,
Index = template.Index,
StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator,
UnitsInTemplate = new List<UnitInTemplateMessage>()
};
var updateRequest = new TemplateUpdaterMessage
{
TemplateId = template.Id,
JobId = jobId,
UnitId = template.UnitId,
Name = expectedName,
IsActiveTemplate = template.IsActiveTemplate,
IsActiveSchedule = template.IsActiveSchedule,
IsNew = false,
Index = template.Index,
StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator,
UnitsInTemplate = new List<UnitInTemplateMessage>() // для простого шаблона
};
await templateMqPublisher.PublishUpdateAsync(updateRequest);
}
}
await templateMqPublisher.PublishUpdateAsync(updateRequest);
}
stageSw.Stop();
logger.LogDebug("[Perf] Job {JobId} | Этап: Обновление имён | Время: {Ms} мс | Количество: {Count}",
jobId, stageSw.ElapsedMilliseconds, templatesToRename.Count);
// === Деактивация лишних шаблонов ===
// === ЭТАП 7: Деактивация лишних шаблонов ===
stageSw.Restart();
foreach (var unusedTemplate in unusedTemplates)
{
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId);
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).",
unusedTemplate.Id, jobId, unusedTemplate.UnitId);
await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator);
}
stageSw.Stop();
logger.LogDebug("[Perf] Job {JobId} | Этап: Деактивация | Время: {Ms} мс | Количество: {Count}",
jobId, stageSw.ElapsedMilliseconds, unusedTemplates.Count);
// === ИТОГО ===
totalSw.Stop();
logger.LogInformation("[Perf] Job {JobId} | ИТОГО: {TotalMs} мс", jobId, totalSw.ElapsedMilliseconds);
// === Успешное завершение ===
await UpdateMatchingStatusAsync(jobId, "Синхронизация завершена успешно");
await matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
logger.LogInformation("Синхронизация шаблонов завершена для Job {JobId}.", jobId);
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при синхронизации Job {JobId}", jobId);
totalSw.Stop();
logger.LogError(ex, "Ошибка при синхронизации Job {JobId} через {ElapsedMs} мс", jobId, totalSw.ElapsedMilliseconds);
await UpdateMatchingStatusAsync(jobId, $"Ошибка: {ex.Message}");
throw;
}
}
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
{
logger.LogWarning("SimpleTemplateSynchronizer: SyncTemplatesForJobGroup вызван для JobGroup {JobGroupId}. Это не поддерживаемая операция.", jobGroupId);