diff --git a/src/TaskManager/Plug-ins/Argo/Controllers/TemplateController.cs b/src/TaskManager/Plug-ins/Argo/Controllers/TemplateController.cs index 92cf17a11..f64ed337f 100644 --- a/src/TaskManager/Plug-ins/Argo/Controllers/TemplateController.cs +++ b/src/TaskManager/Plug-ins/Argo/Controllers/TemplateController.cs @@ -56,7 +56,7 @@ public async Task> CreateArgoTemplate() if (string.IsNullOrWhiteSpace(value2)) { - return BadRequest("No file recieved"); + return BadRequest("No file received"); } WorkflowTemplate? workflowTemplate = null; try diff --git a/src/WorkflowManager/Database/Interfaces/ITaskExecutionStatsRepository.cs b/src/WorkflowManager/Database/Interfaces/ITaskExecutionStatsRepository.cs index 94ed069bf..e9bd9dbf3 100644 --- a/src/WorkflowManager/Database/Interfaces/ITaskExecutionStatsRepository.cs +++ b/src/WorkflowManager/Database/Interfaces/ITaskExecutionStatsRepository.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using System.Threading.Tasks; using Monai.Deploy.Messaging.Events; using Monai.Deploy.WorkflowManager.Contracts.Models; @@ -27,54 +28,60 @@ public interface ITaskExecutionStatsRepository /// /// Creates a task dispatch event in the database. /// - /// A TaskDispatchEvent to create. + /// + /// workflow id. + /// task id. /// Task CreateAsync(TaskExecution TaskExecutionInfo, string workflowId, string correlationId); /// /// Updates status of a task dispatch event in the database. /// - /// A TaskDispatchEvent to update. + /// + /// workflow id. + /// task id. /// Task UpdateExecutionStatsAsync(TaskExecution taskUpdateEvent, string workflowId, TaskExecutionStatus? status = null); /// /// Updates status of a task now its been canceled. /// - /// A TaskCanceledException to update. - /// A TaskCanceledException to update. + /// workflow id. + /// task id. + /// Task UpdateExecutionStatsAsync(TaskCancellationEvent taskCanceledEvent, string workflowId, string correlationId); /// - /// Returns paged entries between the two given dates. + /// Returns paged entries between the two given dates /// /// start of the range. /// end of the range. + /// + /// + /// optional workflow id. + /// optional task id. /// a collections of stats Task> GetStatsAsync(DateTime startTime, DateTime endTime, int PageSize = 10, int PageNumber = 1, string workflowId = "", string taskId = ""); /// - /// Return the total number of stats between the dates + /// Return the count of the entries with this status, or all if no status given. /// /// start of the range. /// end of the range. - /// The count of all records in range - //Task GetStatsCountAsync(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = ""); - - /// - /// Return the count of the entries with this status, or all if no status given - /// - /// start of the range. - /// end of the range. /// the status to get count of, or string.empty + /// optional workflow id. + /// optional task id. /// The count of all records in range - Task GetStatsStatusCountAsync(DateTime start, DateTime endTime, string status = "", string workflowId = "", string taskId = ""); + Task GetStatsStatusCountAsync(DateTime startTime, DateTime endTime, string status = "", string workflowId = "", string taskId = ""); /// /// Returns all stats in Succeeded status. /// /// start of the range. /// end of the range. + /// optional workflow id. + /// optional task id. /// All stats that succeeded Task GetStatsStatusSucceededCountAsync(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = ""); @@ -83,16 +90,43 @@ public interface ITaskExecutionStatsRepository /// /// start of the range. /// end of the range. + /// optional workflow id. + /// optional task id. /// All stats that failed or partially failed Task GetStatsStatusFailedCountAsync(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = ""); /// - /// Calculates the average exection time for the given range + /// Returns total ran executions status that have ran to completion. (not dispatched, created, accepted) + /// + /// start of the range. + /// end of the range. + /// optional workflow id. + /// optional task id. + /// All stats that failed or partially failed + Task GetStatsTotalCompleteExecutionsCountAsync(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = ""); + + + /// + /// Calculates the average execution time for the given range /// /// start of the range. /// end of the range. - /// the average exection times in the time range + /// optional workflow id. + /// optional task id. + /// the average execution times in the time range Task<(double avgTotalExecution, double avgArgoExecution)> GetAverageStats(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = ""); + + /// + /// Return the total number of stats between the dates with optional status filter. + /// + /// start of the range. + /// end of the range. + /// + /// optional workflow id. + /// optional task id. + /// The count of all records in range + /// + Task GetStatsCountAsync(DateTime startTime, DateTime endTime, Expression>? statusFilter = null, string workflowId = "", string taskId = ""); } } diff --git a/src/WorkflowManager/Database/Repositories/TaskExecutionStatsRepository.cs b/src/WorkflowManager/Database/Repositories/TaskExecutionStatsRepository.cs index 3bcce4957..4a69a8c23 100644 --- a/src/WorkflowManager/Database/Repositories/TaskExecutionStatsRepository.cs +++ b/src/WorkflowManager/Database/Repositories/TaskExecutionStatsRepository.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Ardalis.GuardClauses; using Microsoft.Extensions.Logging; @@ -159,14 +160,7 @@ public async Task> GetStatsAsync(DateTime startTime, T.StartedUTC >= startTime && T.StartedUTC <= endTime.ToUniversalTime() && (workflowNull || T.WorkflowId == workflowId) && - (taskIdNull || T.TaskId == taskId) - //&& - //( - // T.Status == TaskExecutionStatus.Succeeded.ToString() - // || T.Status == TaskExecutionStatus.Failed.ToString() - // || T.Status == TaskExecutionStatus.PartialFail.ToString() - // ) - ) + (taskIdNull || T.TaskId == taskId)) .Limit(PageSize) .Skip((PageNumber - 1) * PageSize) .ToListAsync(); @@ -187,66 +181,86 @@ private static ExecutionStats ExposeExecutionStats(ExecutionStats taskExecutionS var statKeys = taskUpdateEvent.ExecutionStats.Keys.Where(v => v.StartsWith("podStartTime") || v.StartsWith("podFinishTime")); if (statKeys.Any()) { - var start = DateTime.Now; - var end = new DateTime(); - foreach (var statKey in statKeys) - { - if (statKey.Contains("StartTime") && DateTime.TryParse(taskUpdateEvent.ExecutionStats[statKey], out var startTime)) - { - start = (startTime < start ? startTime : start); - } - else if (DateTime.TryParse(taskUpdateEvent.ExecutionStats[statKey], out var endTime)) - { - end = (endTime > end ? endTime : start); - } - } - taskExecutionStats.ExecutionTimeSeconds = (end - start).TotalMilliseconds / 1000; + CalculatePodExecutionTime(taskExecutionStats, taskUpdateEvent, statKeys); } } return taskExecutionStats; } - public async Task GetStatsStatusCountAsync(DateTime start, DateTime endTime, string status = "", string workflowId = "", string taskId = "") + /// + /// Calculates and sets ExecutionStats ExecutionTimeSeconds + /// + /// + /// + /// + private static void CalculatePodExecutionTime(ExecutionStats taskExecutionStats, TaskExecution taskUpdateEvent, IEnumerable statKeys) { - var statusNull = string.IsNullOrWhiteSpace(status); - var workflowNull = string.IsNullOrWhiteSpace(workflowId); - var taskIdNull = string.IsNullOrWhiteSpace(taskId); + var start = DateTime.Now; + var end = new DateTime(); + foreach (var statKey in statKeys) + { + if (statKey.Contains("StartTime") && DateTime.TryParse(taskUpdateEvent.ExecutionStats[statKey], out var startTime)) + { + start = (startTime < start ? startTime : start); + } + else if (DateTime.TryParse(taskUpdateEvent.ExecutionStats[statKey], out var endTime)) + { + end = (endTime > end ? endTime : start); + } + } + taskExecutionStats.ExecutionTimeSeconds = (end - start).TotalMilliseconds / 1000; + } - return await _taskExecutionStatsCollection.CountDocumentsAsync(T => - T.StartedUTC >= start.ToUniversalTime() && - T.StartedUTC <= endTime.ToUniversalTime() && - (workflowNull || T.WorkflowId == workflowId) && - (taskIdNull || T.TaskId == taskId) && - (statusNull || T.Status == status)); + public async Task GetStatsStatusCountAsync(DateTime startTime, DateTime endTime, string status = "", string workflowId = "", string taskId = "") + { + Expression>? statusFilter = null; + if (!string.IsNullOrWhiteSpace(status)) + { + statusFilter = t => t.Status == status; + } + return await GetStatsCountAsync(startTime, endTime, statusFilter, workflowId, taskId); } - public async Task GetStatsStatusSucceededCountAsync(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = "") + public async Task GetStatsCountAsync(DateTime startTime, DateTime endTime, Expression>? statusFilter = null, string workflowId = "", string taskId = "") { var workflowNull = string.IsNullOrWhiteSpace(workflowId); var taskIdNull = string.IsNullOrWhiteSpace(taskId); - return await _taskExecutionStatsCollection.CountDocumentsAsync(T => - T.StartedUTC >= startTime.ToUniversalTime() && - T.StartedUTC <= endTime.ToUniversalTime() && - (workflowNull || T.WorkflowId == workflowId) && - (taskIdNull || T.TaskId == taskId) && - T.Status == TaskExecutionStatus.Succeeded.ToString()); + var builder = Builders.Filter; + var filter = builder.Empty; + + filter &= builder.Where(t => t.StartedUTC >= startTime.ToUniversalTime()); + filter &= builder.Where(t => t.StartedUTC <= endTime.ToUniversalTime()); + filter &= builder.Where(t => workflowNull || t.WorkflowId == workflowId); + filter &= builder.Where(t => taskIdNull || t.TaskId == taskId); + if (statusFilter is not null) + { + filter &= builder.Where(statusFilter); + } + + return await _taskExecutionStatsCollection.CountDocumentsAsync(filter); } - public async Task GetStatsStatusFailedCountAsync(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = "") + public async Task GetStatsTotalCompleteExecutionsCountAsync(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = "") { - var workflowNull = string.IsNullOrWhiteSpace(workflowId); - var taskIdNull = string.IsNullOrWhiteSpace(taskId); + var dispatched = TaskExecutionStatus.Dispatched.ToString(); + var created = TaskExecutionStatus.Created.ToString(); + var accepted = TaskExecutionStatus.Accepted.ToString(); + Expression> statusFilter = t => t.Status != dispatched && t.Status != created && t.Status != accepted; + + return await GetStatsCountAsync(startTime, endTime, statusFilter, workflowId, taskId); + } - return await _taskExecutionStatsCollection.CountDocumentsAsync(T => - T.StartedUTC >= startTime.ToUniversalTime() && - T.StartedUTC <= endTime.ToUniversalTime() && - (workflowNull || T.WorkflowId == workflowId) && - (taskIdNull || T.TaskId == taskId) && - ( - T.Status == TaskExecutionStatus.Failed.ToString() || - T.Status == TaskExecutionStatus.PartialFail.ToString() - )); + public async Task GetStatsStatusSucceededCountAsync(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = "") + { + Expression> statusFilter = t => t.Status == TaskExecutionStatus.Succeeded.ToString(); + return await GetStatsCountAsync(startTime, endTime, statusFilter, workflowId, taskId); + } + + public async Task GetStatsStatusFailedCountAsync(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = "") + { + Expression> statusFilter = t => t.Status == TaskExecutionStatus.Failed.ToString() || t.Status == TaskExecutionStatus.PartialFail.ToString(); + return await GetStatsCountAsync(startTime, endTime, statusFilter, workflowId, taskId); } public async Task<(double avgTotalExecution, double avgArgoExecution)> GetAverageStats(DateTime startTime, DateTime endTime, string workflowId = "", string taskId = "") diff --git a/src/WorkflowManager/WorkflowManager/Controllers/TaskStatsController.cs b/src/WorkflowManager/WorkflowManager/Controllers/TaskStatsController.cs index e82d31f42..9a468e601 100644 --- a/src/WorkflowManager/WorkflowManager/Controllers/TaskStatsController.cs +++ b/src/WorkflowManager/WorkflowManager/Controllers/TaskStatsController.cs @@ -130,7 +130,7 @@ public async Task GetStatsAsync([FromQuery] TimeFilter filter, st { // both not empty but one is ! _logger.LogDebug($"{nameof(GetStatsAsync)} - Failed to validate WorkflowId or TaskId"); - return Problem($"Failed to validate ids, not a valid guid", $"tasks/stats/", BadRequest); + return Problem("Failed to validate ids, not a valid guid", "tasks/stats/", BadRequest); } if (filter.EndTime == default) @@ -150,12 +150,19 @@ public async Task GetStatsAsync([FromQuery] TimeFilter filter, st try { - var allStats = _repository.GetStatsAsync(filter.StartTime, filter.EndTime, pageSize, filter.PageNumber, workflowId ?? string.Empty, taskId ?? string.Empty); - var successes = _repository.GetStatsStatusSucceededCountAsync(filter.StartTime, filter.EndTime, workflowId ?? string.Empty, taskId ?? string.Empty); - var fails = _repository.GetStatsStatusFailedCountAsync(filter.StartTime, filter.EndTime, workflowId ?? string.Empty, taskId ?? string.Empty); - var rangeCount = _repository.GetStatsStatusCountAsync(filter.StartTime, filter.EndTime, string.Empty, workflowId ?? string.Empty, taskId ?? string.Empty); - var stats = _repository.GetAverageStats(filter.StartTime, filter.EndTime, workflowId ?? string.Empty, taskId ?? string.Empty); - var running = _repository.GetStatsStatusCountAsync(filter.StartTime, filter.EndTime, TaskExecutionStatus.Accepted.ToString()); + workflowId ??= string.Empty; + taskId ??= string.Empty; + var allStats = _repository.GetStatsAsync(filter.StartTime, filter.EndTime, pageSize, filter.PageNumber, workflowId, taskId); + + var successes = _repository.GetStatsStatusSucceededCountAsync(filter.StartTime, filter.EndTime, workflowId, taskId); + + var fails = _repository.GetStatsStatusFailedCountAsync(filter.StartTime, filter.EndTime, workflowId, taskId); + + var rangeCount = _repository.GetStatsTotalCompleteExecutionsCountAsync(filter.StartTime, filter.EndTime, workflowId, taskId); + + var stats = _repository.GetAverageStats(filter.StartTime, filter.EndTime, workflowId, taskId); + + var running = _repository.GetStatsStatusCountAsync(filter.StartTime, filter.EndTime, TaskExecutionStatus.Accepted.ToString(), workflowId, taskId); await Task.WhenAll(allStats, fails, rangeCount, stats, running); diff --git a/tests/UnitTests/Monai.Deploy.WorkflowManager.Shared.Tests/Monai.Deploy.WorkflowManager.Shared.Tests.csproj b/tests/UnitTests/Monai.Deploy.WorkflowManager.Shared.Tests/Monai.Deploy.WorkflowManager.Shared.Tests.csproj index 2279bdba6..a8ba5b6c8 100644 --- a/tests/UnitTests/Monai.Deploy.WorkflowManager.Shared.Tests/Monai.Deploy.WorkflowManager.Shared.Tests.csproj +++ b/tests/UnitTests/Monai.Deploy.WorkflowManager.Shared.Tests/Monai.Deploy.WorkflowManager.Shared.Tests.csproj @@ -36,6 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/tests/UnitTests/TaskManager.Email.Tests/Monai.Deploy.WorkflowManager.TaskManager.Email.Tests.csproj b/tests/UnitTests/TaskManager.Email.Tests/Monai.Deploy.WorkflowManager.TaskManager.Email.Tests.csproj index a5a7d6106..d386ec0c6 100644 --- a/tests/UnitTests/TaskManager.Email.Tests/Monai.Deploy.WorkflowManager.TaskManager.Email.Tests.csproj +++ b/tests/UnitTests/TaskManager.Email.Tests/Monai.Deploy.WorkflowManager.TaskManager.Email.Tests.csproj @@ -27,7 +27,6 @@ - runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/UnitTests/WorkflowManager.Tests/Controllers/TaskExecutionStatsControllerTests.cs b/tests/UnitTests/WorkflowManager.Tests/Controllers/TaskExecutionStatsControllerTests.cs index 424994737..e84b30da5 100644 --- a/tests/UnitTests/WorkflowManager.Tests/Controllers/TaskExecutionStatsControllerTests.cs +++ b/tests/UnitTests/WorkflowManager.Tests/Controllers/TaskExecutionStatsControllerTests.cs @@ -66,6 +66,7 @@ public ExecutionStatsControllerTests() }; _repo.Setup(w => w.GetStatsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(_executionStats); _repo.Setup(w => w.GetStatsStatusCountAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(_executionStats.Count()); + _repo.Setup(w => w.GetStatsTotalCompleteExecutionsCountAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(_executionStats.Count()); } [Fact] @@ -180,7 +181,7 @@ public async Task GetStatsAsync_Pass_All_Arguments_To_GetStatsCountAsync_in_Repo _repo.Verify(v => v.GetStatsStatusCountAsync( It.Is(d => d.Equals(startTime)), It.Is(d => d.Equals(endTime)), - It.Is(s => s.Equals("")), + It.Is(s => s.Equals("Accepted")), It.Is(s => s.Equals("workflow")), It.Is(s => s.Equals("ta")))); }