diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatRobotTaskPeriodResponse.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatRobotTaskPeriodResponse.cs
index 3abdd1c6..38040dcf 100644
--- a/PARR.API/Contracts/V1/Responses/Statistics/StatRobotTaskPeriodResponse.cs
+++ b/PARR.API/Contracts/V1/Responses/Statistics/StatRobotTaskPeriodResponse.cs
@@ -23,5 +23,10 @@
/// Ошибок
///
public int Errors { get; set; }
+
+ ///
+ /// Среднее кол-во роботов работающих в течении часа
+ ///
+ public double AvgRobots { get; set; }
}
}
diff --git a/PARR.API/Controllers/V1/Statistics/StatRobotTaskController.cs b/PARR.API/Controllers/V1/Statistics/StatRobotTaskController.cs
index ba9862fb..b9543a58 100644
--- a/PARR.API/Controllers/V1/Statistics/StatRobotTaskController.cs
+++ b/PARR.API/Controllers/V1/Statistics/StatRobotTaskController.cs
@@ -8,7 +8,9 @@ using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Contracts.V1.Responses.Statistics;
using PARR.API.Controllers.V1.Base;
using PARR.Core.Repositories.Interfaces;
+using PARR.Core.Services.RobotSnapshotServices;
using PARR.Domain.Common.Roles;
+using PARR.Domain.DTOs.RobotSnapshotDTO;
using PARR.Domain.Enums;
namespace PARR.API.Controllers.V1.Statistics
@@ -24,13 +26,15 @@ namespace PARR.API.Controllers.V1.Statistics
private readonly ITaskStatusRepository taskStatusService;
private readonly IRobotHistoryRepository robotHistoryService;
private readonly IMapper mapper;
+ private readonly IRobotSnapshotService _robotSnapshotService;
public StatRobotTaskController(
IRobotRepository robotService,
IRobotConfigurationRepository robotConfigurationService,
ITaskStatusRepository taskStatusService,
IRobotHistoryRepository robotHistoryService,
- IMapper mapper
+ IMapper mapper,
+ IRobotSnapshotService robotSnapshotService
)
{
this.robotService = robotService;
@@ -38,6 +42,7 @@ namespace PARR.API.Controllers.V1.Statistics
this.taskStatusService = taskStatusService;
this.robotHistoryService = robotHistoryService;
this.mapper = mapper;
+ _robotSnapshotService = robotSnapshotService;
}
///
@@ -87,11 +92,11 @@ namespace PARR.API.Controllers.V1.Statistics
[HttpGet(ApiRoutes.StatRobotTask.GetPeriodStatistics)]
public async Task GetPeriodStatistics([FromRoute] RobotsEnum robot)
{
+ var offset = TimeSpan.FromHours(0);
+
int minusHour = 24;
var queryDate = DateTimeOffset.UtcNow.AddHours(-minusHour);
- queryDate = new DateTimeOffset(queryDate.Year, queryDate.Month, queryDate.Day, queryDate.Hour, 0, 0, new TimeSpan(0));
- //queryDate = queryDate.Date + new TimeSpan(queryDate.Hour, 0, 0);
- //queryDate=queryDate.ToOffset(TimeSpan.Zero);
+ queryDate = new DateTimeOffset(queryDate.Year, queryDate.Month, queryDate.Day, queryDate.Hour, 0, 0, offset);
var query = robotHistoryService.Get()
@@ -110,16 +115,24 @@ namespace PARR.API.Controllers.V1.Statistics
var statistics = await query.ToListAsync();
- //if (!statistics.Any())
- // return NoContent();
-
var periodList = GetPeriodList(queryDate, DateTimeOffset.UtcNow);
if (!periodList.Any())
return NoContent();
+ // Статистика по роботам (кол-во роботов)
+ var robotStats = await _robotSnapshotService.GetHourlyAnalyticsByRobotTypeAsync(new RobotAnalyticsRobotTypeQuery
+ {
+ DateStart = queryDate,
+ DateEnd = DateTimeOffset.UtcNow,
+ Offset = offset,
+ RobotType = robot
+ });
+
periodList.ForEach(item =>
{
+ var robotCount = robotStats.FirstOrDefault(t => t.Hour.Date == item.Date.Date && t.Hour.Hour == item.Date.Hour);
+
var statItem = statistics.FirstOrDefault(t => t.Date.Date == item.Date.Date && t.Date.Hour == item.Date.Hour);
if (statItem != null)
{
@@ -127,6 +140,7 @@ namespace PARR.API.Controllers.V1.Statistics
item.Creating = statItem.Creating;
item.Updating = statItem.Updating;
item.Ok = statItem.Ok;
+ item.AvgRobots = robotCount?.AvgRobots ?? 0;
}
});
@@ -150,6 +164,7 @@ namespace PARR.API.Controllers.V1.Statistics
Errors = 0,
Ok = 0,
Updating = 0,
+ AvgRobots = 0,
Date = new DateTimeOffset(periodDate.Date.Year, periodDate.Date.Month, periodDate.Date.Day, periodDate.Hour, 0, 0, new TimeSpan(0)),
});
diff --git a/PARR.API/Controllers/V1/TestController.cs b/PARR.API/Controllers/V1/TestController.cs
index e60bd570..c8462e56 100644
--- a/PARR.API/Controllers/V1/TestController.cs
+++ b/PARR.API/Controllers/V1/TestController.cs
@@ -173,14 +173,24 @@ namespace PARR.API.Controllers.V1
[HttpGet(ApiRoutes.Test.TestHandler)]
public async Task Test()
{
- var data = await _robotSnapshotService.GetHourlyAnalyticsAsync(new RobotAnalyticsQuery
- {
- DateStart = DateTimeOffset.UtcNow.AddHours(-1),
- DateEnd = DateTimeOffset.UtcNow,
- Offset = TimeSpan.FromMinutes(600)
- });
+ //var data = await _robotSnapshotService.GetHourlyAnalyticsAsync(new RobotAnalyticsQuery
+ //{
+ // DateStart = DateTimeOffset.UtcNow.AddHours(-1),
+ // DateEnd = DateTimeOffset.UtcNow,
+ // Offset = TimeSpan.FromMinutes(600)
+ //});
- return Ok(data);
+ //var data = await _robotSnapshotService.GetHourlyAnalyticsByRobotTypeAsync(new RobotAnalyticsRobotTypeQuery
+ //{
+ // DateStart = DateTimeOffset.UtcNow.AddHours(-12),
+ // DateEnd = DateTimeOffset.UtcNow,
+ // Offset = TimeSpan.FromMinutes(600),
+ // RobotType = PARR.Domain.Enums.RobotsEnum.TemplateOrder
+ //});
+
+ //return Ok(data);
+
+ return Ok();
}
diff --git a/PARR.Core/Services/RobotSnapshotServices/IRobotSnapshotService.cs b/PARR.Core/Services/RobotSnapshotServices/IRobotSnapshotService.cs
index 894e9dc2..3e6ab211 100644
--- a/PARR.Core/Services/RobotSnapshotServices/IRobotSnapshotService.cs
+++ b/PARR.Core/Services/RobotSnapshotServices/IRobotSnapshotService.cs
@@ -19,10 +19,19 @@ namespace PARR.Core.Services.RobotSnapshotServices
Task CreateAsync(CreateRobotSnapshot robotSnapshot);
///
- /// Получить аналитику за период
+ /// Получить аналитику за период с группировкой по ip серверов. Разбивка по часам.
+ /// Максимальная одновременная работа роботов. Среднее кол-во роботов в час. Максимально возможное кол-во роботов.
///
///
///
Task> GetHourlyAnalyticsAsync(RobotAnalyticsQuery queryDto);
+
+ ///
+ /// Получить аналитику за период по типу робота. Разбивка по часам.
+ /// Среднее кол-во роботов в час.
+ ///
+ ///
+ ///
+ Task> GetHourlyAnalyticsByRobotTypeAsync(RobotAnalyticsRobotTypeQuery queryDto);
}
}
diff --git a/PARR.Core/Services/RobotSnapshotServices/RobotSnapshotService.cs b/PARR.Core/Services/RobotSnapshotServices/RobotSnapshotService.cs
index d208cf8d..32156387 100644
--- a/PARR.Core/Services/RobotSnapshotServices/RobotSnapshotService.cs
+++ b/PARR.Core/Services/RobotSnapshotServices/RobotSnapshotService.cs
@@ -11,9 +11,9 @@ namespace PARR.Core.Services.RobotSnapshotServices
{
internal class RobotSnapshotService : IRobotSnapshotService
{
- private readonly IRobotSnapshotRepository robotSnapshotRepository;
- private readonly IUserRepository userRepository;
- private readonly IMapper mapper;
+ private readonly IRobotSnapshotRepository _robotSnapshotRepository;
+ private readonly IUserRepository _userRepository;
+ private readonly IMapper _mapper;
public RobotSnapshotService(
IRobotSnapshotRepository robotSnapshotRepository,
@@ -21,9 +21,9 @@ namespace PARR.Core.Services.RobotSnapshotServices
IMapper mapper
)
{
- this.robotSnapshotRepository = robotSnapshotRepository;
- this.userRepository = userRepository;
- this.mapper = mapper;
+ _robotSnapshotRepository = robotSnapshotRepository;
+ _userRepository = userRepository;
+ _mapper = mapper;
}
@@ -37,7 +37,7 @@ namespace PARR.Core.Services.RobotSnapshotServices
var dateEnd = DateTimeOffset.UtcNow;
var dateStart = dateEnd.AddMinutes(-minutes);
- var query = robotSnapshotRepository.Get().AsNoTracking()
+ var query = _robotSnapshotRepository.Get().AsNoTracking()
.Where(t => dateStart <= t.DateCreated && t.DateCreated <= dateEnd);
if (!string.IsNullOrEmpty(queryDto.Ip))
@@ -47,7 +47,7 @@ namespace PARR.Core.Services.RobotSnapshotServices
var robotIps = groupingSnapshots.Select(t => t.Key).ToHashSet();
- var robotList = await userRepository.Get()
+ var robotList = await _userRepository.Get()
.AsNoTracking()
.Where(t => robotIps.Contains(t.Ip))
.ToDictionaryAsync(t => t.Ip);
@@ -59,7 +59,7 @@ namespace PARR.Core.Services.RobotSnapshotServices
robotList.TryGetValue(item.Key, out var robot);
var userDto = new UserBaseDto { Ip = item.Key, Description = robot?.Description ?? string.Empty, Name = robot?.Name ?? string.Empty };
- var snapshots = mapper.Map>(item).OrderBy(t => t.DateCreated).ToList();
+ var snapshots = _mapper.Map>(item).OrderBy(t => t.DateCreated).ToList();
result.Add(new RobotSnapshotItemDto { Robot = userDto, Snapshots = snapshots });
}
@@ -70,7 +70,7 @@ namespace PARR.Core.Services.RobotSnapshotServices
public async Task> GetHourlyAnalyticsAsync(RobotAnalyticsQuery queryDto)
{
- var snapshotsQuery = robotSnapshotRepository.Get()
+ var snapshotsQuery = _robotSnapshotRepository.Get()
.AsNoTracking()
.Where(t => queryDto.DateStart <= t.DateCreated && t.DateCreated <= queryDto.DateEnd);
@@ -78,9 +78,6 @@ namespace PARR.Core.Services.RobotSnapshotServices
if (!string.IsNullOrEmpty(queryDto.Ip))
snapshotsQuery = snapshotsQuery.Where(t => t.Ip == queryDto.Ip);
- // Превращает TimeSpan в строку вида "+03:00" или "-05:00"
- string formattedOffset = (queryDto.Offset >= TimeSpan.Zero ? "+" : "-") +
- queryDto.Offset.ToString(@"hh\:mm");
// Группируем на стороне PostgreSQL с учетом часового пояса пользователя.
// Метод .Add(offset) сдвигает UTC-дату в Postgres на интервал времени пользователя,
@@ -117,12 +114,12 @@ namespace PARR.Core.Services.RobotSnapshotServices
// Получаем информацию о серверах
var serverIps = rawGroupedData.Select(g => g.Ip).ToHashSet();
- var serverDictionary = await userRepository.Get()
+ var serverDictionary = await _userRepository.Get()
.AsNoTracking()
.Where(t => serverIps.Contains(t.Ip))
.ToDictionaryAsync(t => t.Ip);
- // Группируем полученные агрегаты по серверам уже в памяти веб-сервера
+ // Группируем полученные агрегаты по серверам уже в памяти
var dataByServers = rawGroupedData.GroupBy(g => g.Ip);
var result = new List();
@@ -206,6 +203,103 @@ namespace PARR.Core.Services.RobotSnapshotServices
}
+ public async Task> GetHourlyAnalyticsByRobotTypeAsync(RobotAnalyticsRobotTypeQuery queryDto)
+ {
+ var snapshotsQuery = _robotSnapshotRepository.Get()
+ .AsNoTracking()
+ .Where(t => queryDto.DateStart <= t.DateCreated && t.DateCreated <= queryDto.DateEnd)
+ .GroupBy(t => new
+ {
+ // Явно сдвигаем дату
+ Year = (t.DateCreated + queryDto.Offset).DateTime.Year,
+ Month = (t.DateCreated + queryDto.Offset).DateTime.Month,
+ Day = (t.DateCreated + queryDto.Offset).DateTime.Day,
+ Hour = (t.DateCreated + queryDto.Offset).DateTime.Hour
+ });
+
+ IQueryable groupedQuery;
+
+ switch (queryDto.RobotType)
+ {
+ case Domain.Enums.RobotsEnum.TemplateOrder:
+ groupedQuery = snapshotsQuery.Select(g => new RobotSnapshotRobotTypeDbDto
+ {
+ // Вытаскиваем компоненты даты из уже смещенного локального времени
+ LocalYear = g.Key.Year,
+ LocalMonth = g.Key.Month,
+ LocalDay = g.Key.Day,
+ LocalHour = g.Key.Hour,
+
+ // Макс не правильно.
+ //MaxAllowed = g.Max(t => t.MaxRobots),
+ Sum = g.Sum(t => t.TemplateRobotsCount)
+ });
+ break;
+ case Domain.Enums.RobotsEnum.ScheduleOrder:
+ groupedQuery = snapshotsQuery.Select(g => new RobotSnapshotRobotTypeDbDto
+ {
+ // Вытаскиваем компоненты даты из уже смещенного локального времени
+ LocalYear = g.Key.Year,
+ LocalMonth = g.Key.Month,
+ LocalDay = g.Key.Day,
+ LocalHour = g.Key.Hour,
+
+ // Макс не правильно.
+ //MaxAllowed = g.Max(t => t.MaxRobots),
+ Sum = g.Sum(t => t.ScheduleRobotsCount)
+ });
+ break;
+ default:
+ throw new ArgumentException("Не обработано значение параметра", nameof(queryDto.RobotType));
+ }
+
+ var data = await groupedQuery.ToListAsync();
+
+ if (!data.Any())
+ return new List();
+
+ var dbDataDict = data.ToDictionary(
+ k => new DateTime(k.LocalYear, k.LocalMonth, k.LocalDay, k.LocalHour, 0, 0),
+ v => new
+ {
+ //MaxAllowedRobots = t.MaxAllowed,
+ AvgRobots = Math.Round(v.Sum / 60.0, 1)
+ });
+
+ // Заполняем модель
+ var statistics = new List();
+
+ #region Алгоритм заполнения пропусков во времени (если сервер был выключен)
+ // Превращаем границы в локальное время для генерации сетки часов
+ var currentHour = queryDto.DateStart.ToOffset(queryDto.Offset).DateTime;
+ var localEnd = queryDto.DateEnd.ToOffset(queryDto.Offset).DateTime;
+
+ // Срезаем минуты/секунды для старта
+ currentHour = new DateTime(currentHour.Year, currentHour.Month, currentHour.Day, currentHour.Hour, 0, 0);
+
+ while (currentHour <= localEnd)
+ {
+ // Ищем значение в словаре. Если нет — ставим 0
+ dbDataDict.TryGetValue(currentHour, out var statInfo);
+
+ // 1. Создаем DateTimeOffset для локального времени с правильным смещением
+ var localDateTimeOffset = new DateTimeOffset(currentHour, queryDto.Offset);
+
+ statistics.Add(new HourlyRobotTypeAnalyticsDto
+ {
+ // Возвращаем DateTimeOffset с нулевым смещением (как в вашем коде) или с queryDto.Offset
+ Hour = localDateTimeOffset.ToUniversalTime(),
+ AvgRobots = statInfo?.AvgRobots ?? 0
+ });
+
+ currentHour = currentHour.AddHours(1);
+ }
+ #endregion
+
+ return statistics;
+ }
+
+
public async Task CreateAsync(CreateRobotSnapshot robotSnapshot)
{
var snapshot = new RobotSnapshot
@@ -217,19 +311,19 @@ namespace PARR.Core.Services.RobotSnapshotServices
Ip = robotSnapshot.Ip
};
- var createdResult = await robotSnapshotRepository.CreateAsync(snapshot);
- var commitResult = await robotSnapshotRepository.CommitAsync();
+ var createdResult = await _robotSnapshotRepository.CreateAsync(snapshot);
+ var commitResult = await _robotSnapshotRepository.CommitAsync();
if (!createdResult || !commitResult)
throw new DbErrorException("Ошибка сохранения в БД");
- var user = await userRepository.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Ip == robotSnapshot.Ip);
+ var user = await _userRepository.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Ip == robotSnapshot.Ip);
var userDto = new UserBaseDto { Ip = robotSnapshot.Ip, Description = user?.Description ?? string.Empty, Name = user?.Name ?? string.Empty };
var result = new RobotSnapshotItemDto
{
Robot = userDto,
- Snapshots = mapper.Map>(new List { snapshot })
+ Snapshots = _mapper.Map>(new List { snapshot })
};
return result;
diff --git a/PARR.Domain/DTOs/RobotSnapshotDTO/HourlyRobotTypeAnalyticsDto.cs b/PARR.Domain/DTOs/RobotSnapshotDTO/HourlyRobotTypeAnalyticsDto.cs
new file mode 100644
index 00000000..9c0dc017
--- /dev/null
+++ b/PARR.Domain/DTOs/RobotSnapshotDTO/HourlyRobotTypeAnalyticsDto.cs
@@ -0,0 +1,17 @@
+namespace PARR.Domain.DTOs.RobotSnapshotDTO
+{
+ ///
+ /// Почасовая статистика по снапшотам роботов
+ ///
+ public record HourlyRobotTypeAnalyticsDto
+ {
+ public DateTimeOffset Hour { get; init; }
+
+ //public int MaxAllowedRobots { get; init; }
+
+ ///
+ /// Среднее кол-во роботов работающих в течении часа
+ ///
+ public double AvgRobots { get; init; }
+ }
+}
diff --git a/PARR.Domain/DTOs/RobotSnapshotDTO/RobotAnalyticsQuery.cs b/PARR.Domain/DTOs/RobotSnapshotDTO/RobotAnalyticsQuery.cs
index f9f54a81..6fbaef29 100644
--- a/PARR.Domain/DTOs/RobotSnapshotDTO/RobotAnalyticsQuery.cs
+++ b/PARR.Domain/DTOs/RobotSnapshotDTO/RobotAnalyticsQuery.cs
@@ -1,16 +1,30 @@
-namespace PARR.Domain.DTOs.RobotSnapshotDTO
+using PARR.Domain.Enums;
+
+namespace PARR.Domain.DTOs.RobotSnapshotDTO
{
- public record RobotAnalyticsQuery
+ public record RobotAnalyticsBaseQuery
{
public DateTimeOffset DateStart { get; init; }
-
+
public DateTimeOffset DateEnd { get; init; }
-
+
///
/// Смещение часового пояса
///
public TimeSpan Offset { get; init; }
+ }
+ public record RobotAnalyticsRobotTypeQuery : RobotAnalyticsBaseQuery
+ {
+ ///
+ /// Тип робота
+ ///
+ public RobotsEnum RobotType { get; init; }
+ }
+
+
+ public record RobotAnalyticsQuery : RobotAnalyticsBaseQuery
+ {
public string? Ip { get; init; }
}
}
diff --git a/PARR.Domain/DTOs/RobotSnapshotDTO/RobotSnapshotRobotTypeDbDto.cs b/PARR.Domain/DTOs/RobotSnapshotDTO/RobotSnapshotRobotTypeDbDto.cs
new file mode 100644
index 00000000..ccfc4f3e
--- /dev/null
+++ b/PARR.Domain/DTOs/RobotSnapshotDTO/RobotSnapshotRobotTypeDbDto.cs
@@ -0,0 +1,20 @@
+namespace PARR.Domain.DTOs.RobotSnapshotDTO
+{
+ ///
+ /// Селект из БД по снапшотам по типам роботов
+ ///
+ public record RobotSnapshotRobotTypeDbDto
+ {
+ public int LocalYear { get; init; }
+
+ public int LocalMonth { get; init; }
+
+ public int LocalDay { get; init; }
+
+ public int LocalHour { get; init; }
+
+ //public int MaxAllowed { get; init; }
+
+ public int Sum { get; init; }
+ }
+}
diff --git a/PARR.Domain/DTOs/RobotSnapshotDTO/ServerHourlyAnalyticsDto.cs b/PARR.Domain/DTOs/RobotSnapshotDTO/ServerHourlyAnalyticsDto.cs
index bca8ba7d..f0b948fe 100644
--- a/PARR.Domain/DTOs/RobotSnapshotDTO/ServerHourlyAnalyticsDto.cs
+++ b/PARR.Domain/DTOs/RobotSnapshotDTO/ServerHourlyAnalyticsDto.cs
@@ -3,7 +3,7 @@
namespace PARR.Domain.DTOs.RobotSnapshotDTO
{
///
- /// Аналитика по снапшотам роботов
+ /// Аналитика по снапшотам роботов с группировкой по IP робота
///
public record ServerHourlyAnalyticsDto
{