From 673cb8960cd92ae5037700f362575618e1d387d8 Mon Sep 17 00:00:00 2001 From: Frolov-Nikita Date: Sun, 8 Oct 2023 13:09:09 +0500 Subject: [PATCH 01/13] =?UTF-8?q?WorkBase=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5?= =?UTF-8?q?=20=D0=B4=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE=D1=81=D1=82=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=BE=D0=B9=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D0=B0=D1=86=D0=B8=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Background/BackgroundWorker.cs | 26 +--- AsbCloudInfrastructure/Background/WorkBase.cs | 113 ++++++++++++++++-- .../Background/WorkPeriodic.cs | 2 +- .../Background/WorkQueue.cs | 2 - AsbCloudInfrastructure/Background/todo.md | 9 +- .../DrillingProgram/DrillingProgramService.cs | 1 - .../BackgroundWorkerServiceTest.cs | 2 +- 7 files changed, 120 insertions(+), 35 deletions(-) diff --git a/AsbCloudInfrastructure/Background/BackgroundWorker.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs index 7c88fade..4ba3d535 100644 --- a/AsbCloudInfrastructure/Background/BackgroundWorker.cs +++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs @@ -14,10 +14,10 @@ namespace AsbCloudInfrastructure.Background { private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2); - private static readonly TimeSpan exceptionHandleTimeout = TimeSpan.FromSeconds(2); private readonly IServiceProvider serviceProvider; private readonly WorkQueue workQueue = new WorkQueue(); public string? CurrentWorkId; + public BackgroundWorker(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; @@ -58,36 +58,18 @@ namespace AsbCloudInfrastructure.Background { while (!token.IsCancellationRequested) { - var dateStart = DateTime.Now; var work = workQueue.Pop(); if (work is null) { await Task.Delay(executePeriod, token); continue; } + CurrentWorkId = work.Id; using var scope = serviceProvider.CreateScope(); - - try - { - Trace.TraceInformation($"Backgroud work:\"{work.Id}\" start."); - var task = work.ActionAsync(work.Id, scope.ServiceProvider, token); - await task.WaitAsync(work.Timeout, token); - work.ExecutionTime = DateTime.Now - dateStart; - Trace.TraceInformation($"Backgroud work:\"{work.Id}\" done. ExecutionTime: {work.ExecutionTime:hh\\:mm\\:ss\\.fff}"); - } - catch (Exception exception) - { - Trace.TraceError($"Backgroud work:\"{work.Id}\" throw exception: {exception.Message}"); - if (work.OnErrorAsync is not null) - { - using var task = Task.Run( - async () => await work.OnErrorAsync(work.Id, exception, token), - token); - await task.WaitAsync(exceptionHandleTimeout, token); - } - } + await work.Start(scope.ServiceProvider, token); + CurrentWorkId = null; await Task.Delay(minDelay, token); } diff --git a/AsbCloudInfrastructure/Background/WorkBase.cs b/AsbCloudInfrastructure/Background/WorkBase.cs index e8adf04c..ef238a85 100644 --- a/AsbCloudInfrastructure/Background/WorkBase.cs +++ b/AsbCloudInfrastructure/Background/WorkBase.cs @@ -1,10 +1,11 @@ using System; +using System.Diagnostics; +using System.Text; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Background { - /// /// Класс разовой работы. /// Разовая работа приоритетнее периодической. @@ -14,7 +15,7 @@ namespace AsbCloudInfrastructure.Background /// /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. /// - public string Id { get; private set; } + public string Id { get; } /// /// Делегат работы. @@ -36,7 +37,7 @@ namespace AsbCloudInfrastructure.Background /// /// /// - internal Func ActionAsync { get; set; } + internal Func ActionAsync { get; } /// /// Делегат обработки ошибки. @@ -50,20 +51,118 @@ namespace AsbCloudInfrastructure.Background public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); /// - /// Фактическое время успешного выполнения работы + /// Продолжительность последнего выполнения /// - public TimeSpan? ExecutionTime { get; internal set; } + public TimeSpan? LastExecutionTime { get; private set; } + + /// + /// Текущее время выполнения + /// + public TimeSpan? CurrentExecutionTime => CurrentStart.HasValue + ? DateTime.Now - CurrentStart.Value + : null; /// /// Время последнего запуска /// - public DateTime LastStart { get; set; } + public DateTime? CurrentStart { get; private set; } + + /// + /// Текстовое описание того, что происходит в задаче. + /// + public string? CurrentStatus { get; private set; } + + /// + /// Время последнего запуска + /// + public DateTime? CurrentStatusUpdate { get; private set; } + + /// + /// Последняя ошибка + /// + public string? LastErrorMessage { get; private set; } + + /// + /// Дата последнего запуска + /// + public DateTime? LastStart { get; private set; } + + /// + /// Дата последней ошибки + /// + public DateTime? LastError { get; private set; } + + /// + /// Дата завершения последнего выполнения + /// + public DateTime? LastComplete { get; private set; } + + /// + /// Кол-во завершений + /// + public int CountComplete { get; private set; } + + /// + /// Кол-во ошибок + /// + public int CountErrors { get; private set; } + + private string WorkNameForTrace => $"Backgroud work:\"{Id}\""; public WorkBase(string id, Func actionAsync) { Id = id; ActionAsync = actionAsync; } - } + + public async Task Start(IServiceProvider services, CancellationToken token) + { + CurrentStart = DateTime.Now; + LastStart = DateTime.Now; + try + { + SetStatus(" start"); + var task = ActionAsync(Id, services, token); + await task.WaitAsync(Timeout, token); + LastComplete = DateTime.Now; + CountComplete++; + SetStatus($" {task.Status}. ExecutionTime: {CurrentExecutionTime:hh\\:mm\\:ss\\.fff}"); + } + catch (Exception exception) + { + SetError(exception.Message); + if (OnErrorAsync is not null) + { + var task = Task.Run( + async () => await OnErrorAsync(Id, exception, token), + token); + await task.WaitAsync(Timeout, token); + } + } + LastExecutionTime = CurrentExecutionTime; + CurrentStart = null; + SetStatus(null); + } + + protected void SetStatus(string? newStatus) + { + CurrentStatus = newStatus; + if (newStatus is not null) + { + CurrentStatusUpdate = DateTime.Now; + Trace.TraceInformation($"{WorkNameForTrace} state: {newStatus}"); + } + else + CurrentStatusUpdate = null; + } + + private void SetError(string? errorMessage) + { + CountErrors++; + LastErrorMessage = errorMessage; + LastError = DateTime.Now; + Trace.TraceError($"{WorkNameForTrace} throw exception[{CountErrors}]: {errorMessage}"); + } + } } diff --git a/AsbCloudInfrastructure/Background/WorkPeriodic.cs b/AsbCloudInfrastructure/Background/WorkPeriodic.cs index cbd34fec..5c2b0e23 100644 --- a/AsbCloudInfrastructure/Background/WorkPeriodic.cs +++ b/AsbCloudInfrastructure/Background/WorkPeriodic.cs @@ -18,7 +18,7 @@ namespace AsbCloudInfrastructure.Background /// /// Время следующего запуска /// - public DateTime NextStart => LastStart + Period; + public DateTime NextStart => LastStart??DateTime.MinValue + Period; /// /// Класс периодической работы diff --git a/AsbCloudInfrastructure/Background/WorkQueue.cs b/AsbCloudInfrastructure/Background/WorkQueue.cs index ce77fa94..e925916b 100644 --- a/AsbCloudInfrastructure/Background/WorkQueue.cs +++ b/AsbCloudInfrastructure/Background/WorkQueue.cs @@ -4,7 +4,6 @@ using System.Linq; namespace AsbCloudInfrastructure.Background { - /// /// /// Очередь работ @@ -90,7 +89,6 @@ namespace AsbCloudInfrastructure.Background if (work is null || work.NextStart > DateTime.Now) return null; - work.LastStart = DateTime.Now; return work; } diff --git a/AsbCloudInfrastructure/Background/todo.md b/AsbCloudInfrastructure/Background/todo.md index f30ce6c4..b8b30852 100644 --- a/AsbCloudInfrastructure/Background/todo.md +++ b/AsbCloudInfrastructure/Background/todo.md @@ -1,5 +1,12 @@ # -- +- . + - , + - , + - , - . . - / - / + +# +- dto +- , . diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index adb410d6..9dc20155 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -541,7 +541,6 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram var work = new WorkBase(workId, workAction) { - ExecutionTime = TimeSpan.FromMinutes(1), OnErrorAsync = onErrorAction }; diff --git a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs index 133f6b36..f4a03f2f 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs @@ -88,7 +88,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(10); - Assert.True(work.ExecutionTime > TimeSpan.Zero); + Assert.True(work.LastExecutionTime > TimeSpan.Zero); } [Fact] From 724c7b0cd8028ff59c0b925a130d899a5b4fe87c Mon Sep 17 00:00:00 2001 From: Frolov-Nikita Date: Sun, 8 Oct 2023 19:45:21 +0500 Subject: [PATCH 02/13] BackgroudWork Add onprogres callback --- .../Background/BackgroudWorkDto.cs | 170 ++++++++++++++++++ .../Background/BackgroundWorker.cs | 2 +- .../Background/OrderedList.cs | 41 +++++ AsbCloudInfrastructure/Background/WorkBase.cs | 149 ++++----------- .../Background/WorkPeriodic.cs | 13 +- .../Background/{WorkQueue.cs => WorkStore.cs} | 42 +++-- .../OperationDetectionWorkFactory.cs | 8 +- .../DrillingProgram/DrillingProgramService.cs | 3 +- .../EmailNotificationTransportService.cs | 5 +- .../LimitingParameterBackgroundService.cs | 5 +- .../Services/ReportService.cs | 6 +- .../Services/SAUB/TelemetryDataCache.cs | 10 +- .../SubsystemOperationTimeCalcWorkFactory.cs | 5 +- .../Services/WellInfoService.cs | 7 +- AsbCloudInfrastructure/Startup.cs | 2 +- .../SignalRNotificationTransportService.cs | 4 +- 16 files changed, 321 insertions(+), 151 deletions(-) create mode 100644 AsbCloudInfrastructure/Background/BackgroudWorkDto.cs create mode 100644 AsbCloudInfrastructure/Background/OrderedList.cs rename AsbCloudInfrastructure/Background/{WorkQueue.cs => WorkStore.cs} (70%) diff --git a/AsbCloudInfrastructure/Background/BackgroudWorkDto.cs b/AsbCloudInfrastructure/Background/BackgroudWorkDto.cs new file mode 100644 index 00000000..873217eb --- /dev/null +++ b/AsbCloudInfrastructure/Background/BackgroudWorkDto.cs @@ -0,0 +1,170 @@ +using System; +using System.Diagnostics; + +namespace AsbCloudInfrastructure.Background +{ + public class BackgroudWorkDto + { + /// + /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. + /// + public string Id { get; init; } = null!; + + public class CurrentStateInfo + { + private string state = "start"; + + /// + /// Время последнего запуска + /// + public DateTime Start { get; } = DateTime.Now; + + /// + /// Текущее время выполнения + /// + public TimeSpan ExecutionTime => DateTime.Now - Start; + + /// + /// Текстовое описание того, что происходит в задаче. + /// + public string State + { + get => state; + internal set + { + state = value; + StateUpdate = DateTime.Now; + } + } + + public double Progress { get; internal set; } = 0; + + /// + /// Время последнего запуска + /// + public DateTime StateUpdate { get; private set; } = DateTime.Now; + } + + public class LastErrorInfo: LastCompleteInfo + { + public LastErrorInfo(CurrentStateInfo state, string errorText) + : base(state) + { + ErrorText = errorText; + } + + /// + /// Последняя ошибка + /// + public string ErrorText { get; init; } = null!; + } + + public class LastCompleteInfo + { + /// + /// Дата запуска + /// + public DateTime Start {get; init;} + + /// + /// Дата завершения + /// + public DateTime End { get; init; } + + /// + /// Продолжительность последнего выполнения + /// + public TimeSpan ExecutionTime => End - Start; + + /// + /// Состояние на момент завершения + /// + public string State { get; init; } + + public LastCompleteInfo(CurrentStateInfo state) + { + Start = state.Start; + End = DateTime.Now; + State = state.State; + } + } + + /// + /// Текущее состояние + /// + public CurrentStateInfo? CurrentState { get; private set; } + + /// + /// Последняя ошибка + /// + public LastErrorInfo? LastError { get; private set; } + + /// + /// Последняя завершенная + /// + public LastCompleteInfo? LastComplete { get; private set; } + + /// + /// Кол-во запусков + /// + public int CountStart { get; private set; } + + /// + /// Кол-во завершений + /// + public int CountComplete { get; private set; } + + /// + /// Кол-во ошибок + /// + public int CountErrors { get; private set; } + + /// + /// Максимально допустимое время выполнения работы + /// + public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); + + private string WorkNameForTrace => $"Backgroud work:\"{Id}\""; + + protected void SetStatusStart() + { + CurrentState = new(); + CountStart++; + Trace.TraceInformation($"{WorkNameForTrace} state: starting"); + } + + protected void UpdateStatus(string newState, double? progress) + { + if (CurrentState is null) + return; + + CurrentState.State = newState; + if (progress.HasValue) + CurrentState.Progress = progress.Value; + + Trace.TraceInformation($"{WorkNameForTrace} state: {newState}"); + } + + protected void SetStatusComplete(System.Threading.Tasks.TaskStatus status) + { + if (CurrentState is null) + return; + + LastComplete = new (CurrentState); + CurrentState = null; + CountComplete++; + Trace.TraceInformation($"{WorkNameForTrace} state: completed"); + } + + protected void SetLastError(string errorMessage) + { + if (CurrentState is null) + return; + + LastError = new LastErrorInfo(CurrentState, errorMessage); + CurrentState = null; + CountErrors++; + Trace.TraceError($"{WorkNameForTrace} throw exception[{CountErrors}]: {errorMessage}"); + } + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Background/BackgroundWorker.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs index 4ba3d535..fad61edf 100644 --- a/AsbCloudInfrastructure/Background/BackgroundWorker.cs +++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs @@ -15,7 +15,7 @@ namespace AsbCloudInfrastructure.Background private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2); private readonly IServiceProvider serviceProvider; - private readonly WorkQueue workQueue = new WorkQueue(); + private readonly WorkStore workQueue = new WorkStore(); public string? CurrentWorkId; public BackgroundWorker(IServiceProvider serviceProvider) diff --git a/AsbCloudInfrastructure/Background/OrderedList.cs b/AsbCloudInfrastructure/Background/OrderedList.cs new file mode 100644 index 00000000..1f583a82 --- /dev/null +++ b/AsbCloudInfrastructure/Background/OrderedList.cs @@ -0,0 +1,41 @@ +using System.Linq; + +namespace System.Collections.Generic +{ + public class OrderedList: IEnumerable, ICollection + where T : notnull + { + private readonly List list = new List(); + + private readonly Func keySelector; + private readonly bool isDescending = false; + + private IOrderedEnumerable OrdredList => isDescending + ? list.OrderByDescending(keySelector) + : list.OrderBy(keySelector); + + public int Count => list.Count; + + public bool IsReadOnly => false; + + public OrderedList(Func keySelector, bool isDescending = false) + { + this.keySelector = keySelector; + this.isDescending = isDescending; + } + + public void Add(T item) => list.Add(item); + + public void Clear()=> list.Clear(); + + public bool Contains(T item)=> list.Contains(item); + + public void CopyTo(T[] array, int arrayIndex)=> list.CopyTo(array, arrayIndex); + + public bool Remove(T item)=> list.Remove(item); + + public IEnumerator GetEnumerator() => OrdredList.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/AsbCloudInfrastructure/Background/WorkBase.cs b/AsbCloudInfrastructure/Background/WorkBase.cs index ef238a85..e1b83053 100644 --- a/AsbCloudInfrastructure/Background/WorkBase.cs +++ b/AsbCloudInfrastructure/Background/WorkBase.cs @@ -1,6 +1,4 @@ using System; -using System.Diagnostics; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -10,14 +8,23 @@ namespace AsbCloudInfrastructure.Background /// Класс разовой работы. /// Разовая работа приоритетнее периодической. /// - public class WorkBase + public class WorkBase : BackgroudWorkDto { - /// - /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. - /// - public string Id { get; } + internal Func, CancellationToken, Task> ActionAsync { get; } /// + /// Делегат обработки ошибки. + /// Не должен выполняться долго. + /// + public Func? OnErrorAsync { get; set; } + + public TimeSpan OnErrorHandlerTimeout { get; set; } = TimeSpan.FromSeconds(5); + + /// + /// Базовая работа + /// + /// + /// /// Делегат работы. /// /// Параметры: @@ -31,138 +38,50 @@ namespace AsbCloudInfrastructure.Background /// Поставщик сервисов /// /// + /// Action<string, double?> + /// on progress callback. String - new state text. double? - optional progress 0-100%. + /// + /// /// CancellationToken /// Токен отмены задачи /// /// /// - /// - internal Func ActionAsync { get; } - - /// - /// Делегат обработки ошибки. - /// Не должен выполняться долго. - /// - public Func? OnErrorAsync { get; set; } - - /// - /// максимально допустимое время выполнения работы - /// - public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); - - /// - /// Продолжительность последнего выполнения - /// - public TimeSpan? LastExecutionTime { get; private set; } - - /// - /// Текущее время выполнения - /// - public TimeSpan? CurrentExecutionTime => CurrentStart.HasValue - ? DateTime.Now - CurrentStart.Value - : null; - - /// - /// Время последнего запуска - /// - public DateTime? CurrentStart { get; private set; } - - /// - /// Текстовое описание того, что происходит в задаче. - /// - public string? CurrentStatus { get; private set; } - - /// - /// Время последнего запуска - /// - public DateTime? CurrentStatusUpdate { get; private set; } - - /// - /// Последняя ошибка - /// - public string? LastErrorMessage { get; private set; } - - /// - /// Дата последнего запуска - /// - public DateTime? LastStart { get; private set; } - - /// - /// Дата последней ошибки - /// - public DateTime? LastError { get; private set; } - - /// - /// Дата завершения последнего выполнения - /// - public DateTime? LastComplete { get; private set; } - - /// - /// Кол-во завершений - /// - public int CountComplete { get; private set; } - - /// - /// Кол-во ошибок - /// - public int CountErrors { get; private set; } - - private string WorkNameForTrace => $"Backgroud work:\"{Id}\""; - - public WorkBase(string id, Func actionAsync) + /// + public WorkBase(string id, Func, CancellationToken, Task> actionAsync) { Id = id; ActionAsync = actionAsync; } - public async Task Start(IServiceProvider services, CancellationToken token) + /// + /// Запустить работу + /// + /// + /// + /// True - susess, False = Fail + public async Task Start(IServiceProvider services, CancellationToken token) { - CurrentStart = DateTime.Now; - LastStart = DateTime.Now; + SetStatusStart(); try { - SetStatus(" start"); - var task = ActionAsync(Id, services, token); + var task = ActionAsync(Id, services, UpdateStatus, token); await task.WaitAsync(Timeout, token); - LastComplete = DateTime.Now; - CountComplete++; - SetStatus($" {task.Status}. ExecutionTime: {CurrentExecutionTime:hh\\:mm\\:ss\\.fff}"); + SetStatusComplete(task.Status); + return true; } catch (Exception exception) { - SetError(exception.Message); + SetLastError(exception.Message); if (OnErrorAsync is not null) { var task = Task.Run( async () => await OnErrorAsync(Id, exception, token), token); - await task.WaitAsync(Timeout, token); + await task.WaitAsync(OnErrorHandlerTimeout, token); } } - - LastExecutionTime = CurrentExecutionTime; - CurrentStart = null; - SetStatus(null); - } - - protected void SetStatus(string? newStatus) - { - CurrentStatus = newStatus; - if (newStatus is not null) - { - CurrentStatusUpdate = DateTime.Now; - Trace.TraceInformation($"{WorkNameForTrace} state: {newStatus}"); - } - else - CurrentStatusUpdate = null; - } - - private void SetError(string? errorMessage) - { - CountErrors++; - LastErrorMessage = errorMessage; - LastError = DateTime.Now; - Trace.TraceError($"{WorkNameForTrace} throw exception[{CountErrors}]: {errorMessage}"); + return false; } } } diff --git a/AsbCloudInfrastructure/Background/WorkPeriodic.cs b/AsbCloudInfrastructure/Background/WorkPeriodic.cs index 5c2b0e23..71d580ed 100644 --- a/AsbCloudInfrastructure/Background/WorkPeriodic.cs +++ b/AsbCloudInfrastructure/Background/WorkPeriodic.cs @@ -18,7 +18,16 @@ namespace AsbCloudInfrastructure.Background /// /// Время следующего запуска /// - public DateTime NextStart => LastStart??DateTime.MinValue + Period; + public DateTime NextStart + { + get + { + var lastStart = LastComplete?.Start ?? DateTime.MinValue; + if (LastError?.Start > lastStart) + lastStart = LastError.Start; + return lastStart + Period; + } + } /// /// Класс периодической работы @@ -26,7 +35,7 @@ namespace AsbCloudInfrastructure.Background /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки /// Делегат работы /// Период выполнения задачи - public WorkPeriodic(string id, Func actionAsync, TimeSpan period) + public WorkPeriodic(string id, Func, CancellationToken, Task> actionAsync, TimeSpan period) : base(id, actionAsync) { Period = period; diff --git a/AsbCloudInfrastructure/Background/WorkQueue.cs b/AsbCloudInfrastructure/Background/WorkStore.cs similarity index 70% rename from AsbCloudInfrastructure/Background/WorkQueue.cs rename to AsbCloudInfrastructure/Background/WorkStore.cs index e925916b..590ad8a6 100644 --- a/AsbCloudInfrastructure/Background/WorkQueue.cs +++ b/AsbCloudInfrastructure/Background/WorkStore.cs @@ -10,11 +10,25 @@ namespace AsbCloudInfrastructure.Background /// /// Не периодические задачи будут возвращаться первыми, как самые приоритетные. /// - class WorkQueue + class WorkStore { - private Queue Primary = new(8); private readonly List Periodic = new(8); + /// + /// Работы выполняемые один раз + /// + public Queue RunOnceQueue { get; } = new(8); + + /// + /// Работы выполняемые периодически + /// + public IOrderedEnumerable Periodics => Periodic.OrderBy(work => work.NextStart); + + /// + /// Завершывшиеся с ошибкой + /// + public CyclycArray Falled { get; } = new(16); + /// /// Добавление работы. /// @@ -25,8 +39,8 @@ namespace AsbCloudInfrastructure.Background if (Periodic.Any(w => w.Id == work.Id)) throw new ArgumentException("work.Id is not unique", nameof(work)); - if (Primary.Any(w => w.Id == work.Id)) - throw new ArgumentException("work.Id is not unique", nameof(work)); + //if (Primary.Any(w => w.Id == work.Id)) + // throw new ArgumentException("work.Id is not unique", nameof(work)); if (work is WorkPeriodic workPeriodic) { @@ -34,7 +48,7 @@ namespace AsbCloudInfrastructure.Background return; } - Primary.Enqueue(work); + //Primary.Enqueue(work); } /// @@ -51,19 +65,19 @@ namespace AsbCloudInfrastructure.Background return true; } - var work = Primary.FirstOrDefault(w => w.Id == id); - if (work is not null) - { - Primary = new Queue(Primary.Where(w => w.Id != id)); - return true; - } + //var work = Primary.FirstOrDefault(w => w.Id == id); + //if (work is not null) + //{ + // Primary = new Queue(Primary.Where(w => w.Id != id)); + // return true; + //} return false; } public bool Contains(string id) { - var result = Periodic.Any(w => w.Id == id) || Primary.Any(w => w.Id == id); + var result = false;//Periodic.Any(w => w.Id == id) || Primary.Any(w => w.Id == id); return result; } @@ -82,8 +96,8 @@ namespace AsbCloudInfrastructure.Background /// public WorkBase? Pop() { - if (Primary.Any()) - return Primary.Dequeue(); + //if (Primary.Any()) + // return Primary.Dequeue(); var work = GetNextPeriodic(); if (work is null || work.NextStart > DateTime.Now) diff --git a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs index da171f13..350ad6b7 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs @@ -46,7 +46,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations } // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. - private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token) + private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) { using var db = serviceProvider.GetRequiredService(); @@ -75,10 +75,14 @@ namespace AsbCloudInfrastructure.Services.DetectOperations }); var affected = 0; + var count = joinedlastDetectedDates.Count(); + var i = 0; foreach (var item in joinedlastDetectedDates) { var stopwatch = Stopwatch.StartNew(); - var newOperations = await DetectOperationsAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); + var startDate = item.LastDate ?? DateTimeOffset.MinValue; + onProgress($"start detecting telemetry: {item.IdTelemetry} from {startDate}", i++ / count); + var newOperations = await DetectOperationsAsync(item.IdTelemetry, startDate, db, token); stopwatch.Stop(); if (newOperations.Any()) { diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index 9dc20155..cf41ab7c 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -519,11 +519,12 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.pdf"; var convertedFilesDir = Path.Combine(Path.GetTempPath(), "drillingProgram", $"{well.Cluster}_{well.Caption}"); var tempResultFilePath = Path.Combine(convertedFilesDir, resultFileName); - var workAction = async (string workId, IServiceProvider serviceProvider, CancellationToken token) => + var workAction = async (string workId, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) => { var context = serviceProvider.GetRequiredService(); var fileService = serviceProvider.GetRequiredService(); var files = state.Parts.Select(p => fileService.GetUrl(p.File!)); + onProgress($"Start converting {files.Count()} files to PDF.", null); await ConvertToPdf.GetConverteAndMergedFileAsync(files, tempResultFilePath, convertedFilesDir, token); await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token); }; diff --git a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs index 26eed2be..412a4354 100644 --- a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs +++ b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs @@ -10,6 +10,7 @@ using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Services.Notifications; using AsbCloudInfrastructure.Background; +using DocumentFormat.OpenXml.Presentation; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -70,9 +71,9 @@ namespace AsbCloudInfrastructure.Services.Email return Task.WhenAll(tasks); } - private Func MakeEmailSendWorkAction(NotificationDto notification) + private Func, CancellationToken, Task> MakeEmailSendWorkAction(NotificationDto notification) { - return async (_, serviceProvider, token) => + return async (_, serviceProvider, onProgress, token) => { var notificationRepository = serviceProvider.GetRequiredService(); var userRepository = serviceProvider.GetRequiredService(); diff --git a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs b/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs index 7c875e70..6c4f94c1 100644 --- a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs +++ b/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs @@ -28,7 +28,7 @@ namespace AsbCloudInfrastructure.Services } // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. - private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token) + private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) { using var db = serviceProvider.GetRequiredService(); var lastDetectedDates = await db.LimitingParameter @@ -55,8 +55,11 @@ namespace AsbCloudInfrastructure.Services inner.SingleOrDefault()?.LastDate, }); + var count = telemetryLastDetectedDates.Count(); + var i = 0; foreach (var item in telemetryLastDetectedDates) { + onProgress($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++/count); var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); if (newLimitingParameters?.Any() == true) { diff --git a/AsbCloudInfrastructure/Services/ReportService.cs b/AsbCloudInfrastructure/Services/ReportService.cs index eca8f84e..95851c74 100644 --- a/AsbCloudInfrastructure/Services/ReportService.cs +++ b/AsbCloudInfrastructure/Services/ReportService.cs @@ -51,7 +51,7 @@ namespace AsbCloudInfrastructure.Services var workId = $"create report by wellid:{idWell} for userid:{idUser} requested at {DateTime.Now}"; - var workAction = async (string id, IServiceProvider serviceProvider, CancellationToken token) => + var workAction = async (string id, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) => { using var context = serviceProvider.GetRequiredService(); var fileService = serviceProvider.GetRequiredService(); @@ -64,7 +64,9 @@ namespace AsbCloudInfrastructure.Services generator.OnProgress += (s, e) => { - progressHandler.Invoke(e.Adapt(), id); + var arg = e.Adapt(); + onProgress(arg.Operation?? string.Empty, arg.Progress); + progressHandler.Invoke(arg, id); }; generator.Make(reportFileName); diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs index d1a7fea1..8868fc24 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs @@ -48,9 +48,9 @@ namespace AsbCloudInfrastructure.Services.SAUB instance = new TelemetryDataCache(); var worker = provider.GetRequiredService(); var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}"; - var work = new WorkBase(workId, async (workId, provider, token) => { + var work = new WorkBase(workId, async (workId, provider, onProgress, token) => { var db = provider.GetRequiredService(); - await instance.InitializeCacheFromDBAsync(db, token); + await instance.InitializeCacheFromDBAsync(db, onProgress, token); }); worker.Push(work); @@ -150,7 +150,7 @@ namespace AsbCloudInfrastructure.Services.SAUB return new DatesRangeDto { From = from.Value, To = to }; } - private async Task InitializeCacheFromDBAsync(IAsbCloudDbContext db, CancellationToken token) + private async Task InitializeCacheFromDBAsync(IAsbCloudDbContext db, Action onProgress, CancellationToken token) where TEntity : class, AsbCloudDb.Model.ITelemetryData { if (isLoading) @@ -168,6 +168,8 @@ namespace AsbCloudInfrastructure.Services.SAUB .Where(well => well.IdTelemetry != null) .ToArrayAsync(token); + var count = wells.Count(); + var i = 0; foreach (Well well in wells) { var capacity = well.IdState == 1 @@ -176,7 +178,7 @@ namespace AsbCloudInfrastructure.Services.SAUB var idTelemetry = well.IdTelemetry!.Value; var hoursOffset = well.Timezone.Hours; - + // TODO: remove traces System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}>: Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}"); var cacheItem = await GetOrDefaultCacheDataFromDbAsync(db, idTelemetry, capacity, hoursOffset, token); if(cacheItem is not null) diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs index d5b86803..bbbc1fb5 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs @@ -36,7 +36,7 @@ namespace AsbCloudInfrastructure.Services.Subsystems } // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. - private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token) + private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) { using var db = serviceProvider.GetRequiredService(); @@ -64,8 +64,11 @@ namespace AsbCloudInfrastructure.Services.Subsystems inner.SingleOrDefault()?.LastDate, }); + var count = telemetryLastDetectedDates.Count(); + var i = 0; foreach (var item in telemetryLastDetectedDates) { + onProgress($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count); var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); if (newOperationsSaub?.Any() == true) { diff --git a/AsbCloudInfrastructure/Services/WellInfoService.cs b/AsbCloudInfrastructure/Services/WellInfoService.cs index eea573cc..0d6951b2 100644 --- a/AsbCloudInfrastructure/Services/WellInfoService.cs +++ b/AsbCloudInfrastructure/Services/WellInfoService.cs @@ -62,7 +62,7 @@ namespace AsbCloudInfrastructure.Services return workPeriodic; } - private static async Task WorkAction(string workName, IServiceProvider serviceProvider, CancellationToken token) + private static async Task WorkAction(string workName, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) { var wellService = serviceProvider.GetRequiredService(); var operationsStatService = serviceProvider.GetRequiredService(); @@ -90,11 +90,12 @@ namespace AsbCloudInfrastructure.Services var subsystemStat = await subsystemOperationTimeService .GetStatByActiveWells(wellsIds, token); - + var count = wells.Count(); + var i = 0; WellMapInfo = wells.Select(well => { var wellMapInfo = well.Adapt(); wellMapInfo.IdState = well.IdState; - + onProgress($"Start updating info by well({well.Id}): {well.Caption}", i++ / count); double? currentDepth = null; TelemetryDataSaubDto? lastSaubTelemetry = null; diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index c1c3cc8f..54d5d080 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -51,7 +51,7 @@ namespace AsbCloudInfrastructure static WorkPeriodic MakeMemoryMonitoringWork() { var workId = "Memory monitoring"; - var workAction = (string _, IServiceProvider _, CancellationToken _) => { + var workAction = (string _, IServiceProvider _, Action _, CancellationToken _) => { var bytes = GC.GetTotalMemory(false); var bytesString = FromatBytes(bytes); System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes. DbContext count is:{AsbCloudDbContext.ReferenceCount}"); diff --git a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs index b418c0b5..9633eb1c 100644 --- a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs +++ b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs @@ -39,9 +39,9 @@ public class SignalRNotificationTransportService : INotificationTransportService return Task.CompletedTask; } - private Func MakeSignalRSendWorkAction(IEnumerable notifications) + private Func, CancellationToken, Task> MakeSignalRSendWorkAction(IEnumerable notifications) { - return async (_, serviceProvider, cancellationToken) => + return async (_, serviceProvider, onProgress, cancellationToken) => { var notificationPublisher = serviceProvider.GetRequiredService(); From 1560c6bf91c75bb0f5cc35f0977a34ff018b862a Mon Sep 17 00:00:00 2001 From: Frolov-Nikita Date: Sun, 8 Oct 2023 21:20:28 +0500 Subject: [PATCH 03/13] Refactor webStore --- .../Data}/BackgroudWorkDto.cs | 41 +++- .../Background/BackgroundWorker.cs | 97 ++++------ .../Background/{WorkBase.cs => Work.cs} | 11 +- .../Background/WorkPeriodic.cs | 63 +++--- .../Background/WorkStore.cs | 183 +++++++----------- .../OperationDetectionWorkFactory.cs | 19 +- .../DrillingProgram/DrillingProgramService.cs | 8 +- .../EmailNotificationTransportService.cs | 6 +- .../LimitingParameterBackgroundService.cs | 7 +- .../Services/ReportService.cs | 4 +- .../Services/SAUB/TelemetryDataCache.cs | 6 +- .../SubsystemOperationTimeCalcWorkFactory.cs | 13 +- .../Services/WellInfoService.cs | 13 +- AsbCloudInfrastructure/Startup.cs | 17 +- .../DrillingProgramServiceTest.cs | 2 +- .../SignalRNotificationTransportService.cs | 6 +- 16 files changed, 219 insertions(+), 277 deletions(-) rename {AsbCloudInfrastructure/Background => AsbCloudApp/Data}/BackgroudWorkDto.cs (79%) rename AsbCloudInfrastructure/Background/{WorkBase.cs => Work.cs} (88%) diff --git a/AsbCloudInfrastructure/Background/BackgroudWorkDto.cs b/AsbCloudApp/Data/BackgroudWorkDto.cs similarity index 79% rename from AsbCloudInfrastructure/Background/BackgroudWorkDto.cs rename to AsbCloudApp/Data/BackgroudWorkDto.cs index 873217eb..64451d26 100644 --- a/AsbCloudInfrastructure/Background/BackgroudWorkDto.cs +++ b/AsbCloudApp/Data/BackgroudWorkDto.cs @@ -1,8 +1,11 @@ using System; using System.Diagnostics; -namespace AsbCloudInfrastructure.Background +namespace AsbCloudApp.Data { + /// + /// Информация о фоновой работе + /// public class BackgroudWorkDto { /// @@ -10,6 +13,9 @@ namespace AsbCloudInfrastructure.Background /// public string Id { get; init; } = null!; + /// + /// Класс описания состояния + /// public class CurrentStateInfo { private string state = "start"; @@ -37,6 +43,9 @@ namespace AsbCloudInfrastructure.Background } } + /// + /// Прогресс + /// public double Progress { get; internal set; } = 0; /// @@ -45,9 +54,12 @@ namespace AsbCloudInfrastructure.Background public DateTime StateUpdate { get; private set; } = DateTime.Now; } - public class LastErrorInfo: LastCompleteInfo + /// + /// Инфо о последней ошибке + /// + public class LastErrorInfo : LastCompleteInfo { - public LastErrorInfo(CurrentStateInfo state, string errorText) + public LastErrorInfo(CurrentStateInfo state, string errorText) : base(state) { ErrorText = errorText; @@ -59,12 +71,15 @@ namespace AsbCloudInfrastructure.Background public string ErrorText { get; init; } = null!; } + /// + /// Инфо о последнем завершении + /// public class LastCompleteInfo { /// /// Дата запуска /// - public DateTime Start {get; init;} + public DateTime Start { get; init; } /// /// Дата завершения @@ -123,9 +138,12 @@ namespace AsbCloudInfrastructure.Background /// Максимально допустимое время выполнения работы /// public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); - + private string WorkNameForTrace => $"Backgroud work:\"{Id}\""; + /// + /// Обновления состояния при запуске работы + /// protected void SetStatusStart() { CurrentState = new(); @@ -133,6 +151,9 @@ namespace AsbCloudInfrastructure.Background Trace.TraceInformation($"{WorkNameForTrace} state: starting"); } + /// + /// Обновления состояния в процессе работы + /// protected void UpdateStatus(string newState, double? progress) { if (CurrentState is null) @@ -145,17 +166,23 @@ namespace AsbCloudInfrastructure.Background Trace.TraceInformation($"{WorkNameForTrace} state: {newState}"); } - protected void SetStatusComplete(System.Threading.Tasks.TaskStatus status) + /// + /// Обновления состояния при успешном завершении работы + /// + protected void SetStatusComplete() { if (CurrentState is null) return; - LastComplete = new (CurrentState); + LastComplete = new(CurrentState); CurrentState = null; CountComplete++; Trace.TraceInformation($"{WorkNameForTrace} state: completed"); } + /// + /// Обновления состояния при ошибке в работе + /// protected void SetLastError(string errorMessage) { if (CurrentState is null) diff --git a/AsbCloudInfrastructure/Background/BackgroundWorker.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs index fad61edf..f4ef8c0e 100644 --- a/AsbCloudInfrastructure/Background/BackgroundWorker.cs +++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs @@ -1,78 +1,49 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Background +namespace AsbCloudInfrastructure.Background; + +/// +/// Сервис для фонового выполнения работы +/// +public class BackgroundWorker : BackgroundService { - /// - /// Сервис для фонового выполнения работы - /// - public class BackgroundWorker : BackgroundService + private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); + private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2); + private readonly IServiceProvider serviceProvider; + + public WorkStore WorkStore { get; } = new WorkStore(); + public Work? CurrentWork; + + public BackgroundWorker(IServiceProvider serviceProvider) { - private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); - private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2); - private readonly IServiceProvider serviceProvider; - private readonly WorkStore workQueue = new WorkStore(); - public string? CurrentWorkId; + this.serviceProvider = serviceProvider; + } - public BackgroundWorker(IServiceProvider serviceProvider) + protected override async Task ExecuteAsync(CancellationToken token) + { + while (!token.IsCancellationRequested) { - this.serviceProvider = serviceProvider; - } - - /// - /// Добавление задачи в очередь. - /// Не периодические задачи будут выполняться вперед. - /// - /// - /// Id mast be unique - public void Push(WorkBase work) - { - workQueue.Push(work); - } - - /// - /// Проверяет наличие работы с указанным Id - /// - /// - /// - public bool Contains(string id) - { - return workQueue.Contains(id); - } - - /// - /// Удаление работы по ID - /// - /// - /// - public bool Delete(string id) - { - return workQueue.Delete(id); - } - - protected override async Task ExecuteAsync(CancellationToken token) - { - while (!token.IsCancellationRequested) + var work = WorkStore.GetNext(); + if (work is null) { - var work = workQueue.Pop(); - if (work is null) - { - await Task.Delay(executePeriod, token); - continue; - } - - CurrentWorkId = work.Id; - using var scope = serviceProvider.CreateScope(); - - await work.Start(scope.ServiceProvider, token); - - CurrentWorkId = null; - await Task.Delay(minDelay, token); + await Task.Delay(executePeriod, token); + continue; } + + CurrentWork = work; + using var scope = serviceProvider.CreateScope(); + + var result = await work.Start(scope.ServiceProvider, token); + + if (!result) + WorkStore.Falled.Add(work); + + CurrentWork = null; + await Task.Delay(minDelay, token); } } } diff --git a/AsbCloudInfrastructure/Background/WorkBase.cs b/AsbCloudInfrastructure/Background/Work.cs similarity index 88% rename from AsbCloudInfrastructure/Background/WorkBase.cs rename to AsbCloudInfrastructure/Background/Work.cs index e1b83053..84135707 100644 --- a/AsbCloudInfrastructure/Background/WorkBase.cs +++ b/AsbCloudInfrastructure/Background/Work.cs @@ -1,4 +1,5 @@ -using System; +using AsbCloudApp.Data; +using System; using System.Threading; using System.Threading.Tasks; @@ -8,9 +9,9 @@ namespace AsbCloudInfrastructure.Background /// Класс разовой работы. /// Разовая работа приоритетнее периодической. /// - public class WorkBase : BackgroudWorkDto + public class Work : BackgroudWorkDto { - internal Func, CancellationToken, Task> ActionAsync { get; } + private Func, CancellationToken, Task> ActionAsync { get; } /// /// Делегат обработки ошибки. @@ -48,7 +49,7 @@ namespace AsbCloudInfrastructure.Background /// /// /// - public WorkBase(string id, Func, CancellationToken, Task> actionAsync) + public Work(string id, Func, CancellationToken, Task> actionAsync) { Id = id; ActionAsync = actionAsync; @@ -67,7 +68,7 @@ namespace AsbCloudInfrastructure.Background { var task = ActionAsync(Id, services, UpdateStatus, token); await task.WaitAsync(Timeout, token); - SetStatusComplete(task.Status); + SetStatusComplete(); return true; } catch (Exception exception) diff --git a/AsbCloudInfrastructure/Background/WorkPeriodic.cs b/AsbCloudInfrastructure/Background/WorkPeriodic.cs index 71d580ed..4cd22af6 100644 --- a/AsbCloudInfrastructure/Background/WorkPeriodic.cs +++ b/AsbCloudInfrastructure/Background/WorkPeriodic.cs @@ -1,45 +1,42 @@ using System; -using System.Threading; -using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Background +namespace AsbCloudInfrastructure.Background; + +/// +/// Класс периодической работы. +/// +public class WorkPeriodic { + public Work Work { get; } /// - /// Класс периодической работы. + /// Период выполнения задачи /// - public class WorkPeriodic : WorkBase + public TimeSpan Period { get; set; } + + /// + /// Время следующего запуска + /// + public DateTime NextStart { - /// - /// Период выполнения задачи - /// - public TimeSpan Period { get; set; } - - /// - /// Время следующего запуска - /// - public DateTime NextStart + get { - get - { - var lastStart = LastComplete?.Start ?? DateTime.MinValue; - if (LastError?.Start > lastStart) - lastStart = LastError.Start; - return lastStart + Period; - } - } - - /// - /// Класс периодической работы - /// - /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки - /// Делегат работы - /// Период выполнения задачи - public WorkPeriodic(string id, Func, CancellationToken, Task> actionAsync, TimeSpan period) - : base(id, actionAsync) - { - Period = period; + var lastStart = Work.LastComplete?.Start ?? DateTime.MinValue; + if (Work.LastError?.Start > lastStart) + lastStart = Work.LastError.Start; + return lastStart + Period; } } + /// + /// Класс периодической работы + /// + /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки + /// Делегат работы + /// Период выполнения задачи + public WorkPeriodic(Work work, TimeSpan period) + { + Work = work; + Period = period; + } } diff --git a/AsbCloudInfrastructure/Background/WorkStore.cs b/AsbCloudInfrastructure/Background/WorkStore.cs index 590ad8a6..dbb1e464 100644 --- a/AsbCloudInfrastructure/Background/WorkStore.cs +++ b/AsbCloudInfrastructure/Background/WorkStore.cs @@ -2,118 +2,85 @@ using System.Collections.Generic; using System.Linq; -namespace AsbCloudInfrastructure.Background +namespace AsbCloudInfrastructure.Background; + +/// +/// +/// Очередь работ +/// +/// Не периодические задачи будут возвращаться первыми, как самые приоритетные. +/// +public class WorkStore { + private readonly List periodics = new(8); + /// - /// - /// Очередь работ - /// - /// Не периодические задачи будут возвращаться первыми, как самые приоритетные. + /// Список периодических задач /// - class WorkStore + public IEnumerable Periodics => periodics; + /// + /// Работы выполняемые один раз + /// + public Queue RunOnceQueue { get; private set; } = new(8); + + /// + /// Завершывшиеся с ошибкой + /// + public CyclycArray Falled { get; } = new(16); + + public void AddPeriodic(Work work, TimeSpan period) { - private readonly List Periodic = new(8); - - /// - /// Работы выполняемые один раз - /// - public Queue RunOnceQueue { get; } = new(8); - - /// - /// Работы выполняемые периодически - /// - public IOrderedEnumerable Periodics => Periodic.OrderBy(work => work.NextStart); - - /// - /// Завершывшиеся с ошибкой - /// - public CyclycArray Falled { get; } = new(16); - - /// - /// Добавление работы. - /// - /// - /// Id mast be unique - public void Push(WorkBase work) - { - if (Periodic.Any(w => w.Id == work.Id)) - throw new ArgumentException("work.Id is not unique", nameof(work)); - - //if (Primary.Any(w => w.Id == work.Id)) - // throw new ArgumentException("work.Id is not unique", nameof(work)); - - if (work is WorkPeriodic workPeriodic) - { - Periodic.Add(workPeriodic); - return; - } - - //Primary.Enqueue(work); - } - - /// - /// Удаление работы по ID - /// - /// - /// - public bool Delete(string id) - { - var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id); - if (workPeriodic is not null) - { - Periodic.Remove(workPeriodic); - return true; - } - - //var work = Primary.FirstOrDefault(w => w.Id == id); - //if (work is not null) - //{ - // Primary = new Queue(Primary.Where(w => w.Id != id)); - // return true; - //} - - return false; - } - - public bool Contains(string id) - { - var result = false;//Periodic.Any(w => w.Id == id) || Primary.Any(w => w.Id == id); - return result; - } - - /// - /// - /// Возвращает приоритетную задачу. - /// - /// - /// Если приоритетные закончились, то ищет ближайшую периодическую. - /// Если до старта ближайшей периодической работы меньше 20 сек, - /// то этой задаче устанавливается время последнего запуска в now и она возвращается. - /// Если больше 20 сек, то возвращается null. - /// - /// - /// - /// - public WorkBase? Pop() - { - //if (Primary.Any()) - // return Primary.Dequeue(); - - var work = GetNextPeriodic(); - if (work is null || work.NextStart > DateTime.Now) - return null; - - return work; - } - - private WorkPeriodic? GetNextPeriodic() - { - var work = Periodic - .OrderBy(w => w.NextStart) - .ThenByDescending(w => w.Period) - .FirstOrDefault(); - return work; - } + var periodic = new WorkPeriodic(work, period); + periodics.Add(periodic); } + /// + /// Удаление работы по ID из одноразовой очереди + /// + /// + /// + public bool TryRemoveFromRunOnceQueue(string id) + { + var work = RunOnceQueue.FirstOrDefault(w => w.Id == id); + if (work is not null) + { + RunOnceQueue = new Queue(RunOnceQueue.Where(w => w.Id != id)); + return true; + } + + return false; + } + + /// + /// + /// Возвращает приоритетную задачу. + /// + /// + /// Если приоритетные закончились, то ищет ближайшую периодическую. + /// Если до старта ближайшей периодической работы меньше 20 сек, + /// то этой задаче устанавливается время последнего запуска в now и она возвращается. + /// Если больше 20 сек, то возвращается null. + /// + /// + /// + /// + public Work? GetNext() + { + if (RunOnceQueue.Any()) + return RunOnceQueue.Dequeue(); + + var work = GetNextPeriodic(); + if (work is null || work.NextStart > DateTime.Now) + return null; + + return work.Work; + } + + private WorkPeriodic? GetNextPeriodic() + { + var work = Periodics + .OrderBy(w => w.NextStart) + .FirstOrDefault(); + return work; + } } diff --git a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs index 350ad6b7..0c9bb3dd 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs @@ -16,7 +16,6 @@ namespace AsbCloudInfrastructure.Services.DetectOperations public static class OperationDetectionWorkFactory { private const string workId = "Operation detection"; - private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); private static string progress = "no progress"; private static readonly DetectorAbstract[] detectors = new DetectorAbstract[] @@ -32,18 +31,16 @@ namespace AsbCloudInfrastructure.Services.DetectOperations //new DetectorTemplatingWhileDrilling(), }; - public static WorkPeriodic MakeWork() - { - var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod); - workPeriodic.Timeout = TimeSpan.FromSeconds(15 * 60); - workPeriodic.OnErrorAsync = (id, exception, token) => + public static Work MakeWork() => new Work(workId, WorkAction) { - var text = $"work {id}, when {progress}, throw error:{exception.Message}"; - Trace.TraceWarning(text); - return Task.CompletedTask; + Timeout = TimeSpan.FromMinutes(20), + OnErrorAsync = (id, exception, token) => + { + var text = $"work {id}, when {progress}, throw error:{exception.Message}"; + Trace.TraceWarning(text); + return Task.CompletedTask; + } }; - return workPeriodic; - } // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index cf41ab7c..402a89ed 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -513,7 +513,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram if (state.IdState == idStateCreating) { var workId = MakeWorkId(idWell); - if (!backgroundWorker.Contains(workId)) + if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId)) { var well = (await wellService.GetOrDefaultAsync(idWell, token))!; var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.pdf"; @@ -540,12 +540,12 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram return Task.CompletedTask; }; - var work = new WorkBase(workId, workAction) + var work = new Work(workId, workAction) { OnErrorAsync = onErrorAction }; - backgroundWorker.Push(work); + backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work); } } } @@ -559,7 +559,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private async Task RemoveDrillingProgramAsync(int idWell, CancellationToken token) { var workId = MakeWorkId(idWell); - backgroundWorker.Delete(workId); + backgroundWorker.WorkStore.TryRemoveFromRunOnceQueue(workId); var filesIds = await context.Files .Where(f => f.IdWell == idWell && diff --git a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs index 412a4354..33f011bf 100644 --- a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs +++ b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs @@ -52,12 +52,12 @@ namespace AsbCloudInfrastructure.Services.Email } var workId = MakeWorkId(notification.IdUser, notification.Title, notification.Message); - if (!backgroundWorker.Contains(workId)) + if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w=>w.Id==workId)) { var workAction = MakeEmailSendWorkAction(notification); - var work = new WorkBase(workId, workAction); - backgroundWorker.Push(work); + var work = new Work(workId, workAction); + backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work); } return Task.CompletedTask; diff --git a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs b/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs index 6c4f94c1..07a17369 100644 --- a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs +++ b/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs @@ -16,16 +16,11 @@ namespace AsbCloudInfrastructure.Services internal static class LimitingParameterCalcWorkFactory { private const string workId = "Limiting parameter calc"; - private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); - public static WorkPeriodic MakeWork() - { - var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod) + public static Work MakeWork() => new Work(workId, WorkAction) { Timeout = TimeSpan.FromMinutes(30) }; - return workPeriodic; - } // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) diff --git a/AsbCloudInfrastructure/Services/ReportService.cs b/AsbCloudInfrastructure/Services/ReportService.cs index 95851c74..f3ab6cb8 100644 --- a/AsbCloudInfrastructure/Services/ReportService.cs +++ b/AsbCloudInfrastructure/Services/ReportService.cs @@ -94,8 +94,8 @@ namespace AsbCloudInfrastructure.Services context.SaveChanges(); }; - var work = new WorkBase(workId, workAction); - backgroundWorkerService.Push(work); + var work = new Work(workId, workAction); + backgroundWorkerService.WorkStore.RunOnceQueue.Enqueue(work); progressHandler.Invoke(new ReportProgressDto { diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs index 8868fc24..daaa2dcf 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs @@ -48,12 +48,12 @@ namespace AsbCloudInfrastructure.Services.SAUB instance = new TelemetryDataCache(); var worker = provider.GetRequiredService(); var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}"; - var work = new WorkBase(workId, async (workId, provider, onProgress, token) => { + var work = new Work(workId, async (workId, provider, onProgress, token) => { var db = provider.GetRequiredService(); await instance.InitializeCacheFromDBAsync(db, onProgress, token); }); - worker.Push(work); + worker.WorkStore.RunOnceQueue.Enqueue(work); } instance.provider = provider; return instance; @@ -168,7 +168,7 @@ namespace AsbCloudInfrastructure.Services.SAUB .Where(well => well.IdTelemetry != null) .ToArrayAsync(token); - var count = wells.Count(); + var count = wells.Length; var i = 0; foreach (Well well in wells) { diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs index bbbc1fb5..451ca633 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs @@ -18,7 +18,6 @@ namespace AsbCloudInfrastructure.Services.Subsystems internal static class SubsystemOperationTimeCalcWorkFactory { private const string workId = "Subsystem operation time calc"; - private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); private const int idSubsytemTorqueMaster = 65537; private const int idSubsytemSpinMaster = 65536; @@ -26,14 +25,10 @@ namespace AsbCloudInfrastructure.Services.Subsystems private const int idSubsystemAPDSlide = 12; private const int idSubsytemMse = 2; - public static WorkPeriodic MakeWork() - { - var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod) - { - Timeout = TimeSpan.FromMinutes(30) - }; - return workPeriodic; - } + public static Work MakeWork() => new Work(workId, WorkAction) + { + Timeout = TimeSpan.FromMinutes(20) + }; // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) diff --git a/AsbCloudInfrastructure/Services/WellInfoService.cs b/AsbCloudInfrastructure/Services/WellInfoService.cs index 0d6951b2..80456db9 100644 --- a/AsbCloudInfrastructure/Services/WellInfoService.cs +++ b/AsbCloudInfrastructure/Services/WellInfoService.cs @@ -29,7 +29,6 @@ namespace AsbCloudInfrastructure.Services } private const string workId = "Well statistics update"; - private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); private readonly TelemetryDataCache telemetryDataSaubCache; private readonly TelemetryDataCache telemetryDataSpinCache; @@ -53,14 +52,10 @@ namespace AsbCloudInfrastructure.Services this.gtrRepository = gtrRepository; } - public static WorkPeriodic MakeWork() - { - var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod) - { - Timeout = TimeSpan.FromMinutes(30) - }; - return workPeriodic; - } + public static Work MakeWork() => new Work(workId, WorkAction) + { + Timeout = TimeSpan.FromMinutes(20) + }; private static async Task WorkAction(string workName, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) { diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index 54d5d080..604a4f69 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -15,7 +15,6 @@ using AsbCloudInfrastructure.Services.SAUB; namespace AsbCloudInfrastructure { - public class Startup { public static void BeforeRunHandler(IHost host) @@ -32,11 +31,11 @@ namespace AsbCloudInfrastructure _ = provider.GetRequiredService>(); var backgroundWorker = provider.GetRequiredService(); - backgroundWorker.Push(WellInfoService.MakeWork()); - backgroundWorker.Push(OperationDetectionWorkFactory.MakeWork()); - backgroundWorker.Push(SubsystemOperationTimeCalcWorkFactory.MakeWork()); - backgroundWorker.Push(LimitingParameterCalcWorkFactory.MakeWork()); - backgroundWorker.Push(MakeMemoryMonitoringWork()); + backgroundWorker.WorkStore.AddPeriodic(WellInfoService.MakeWork(), TimeSpan.FromMinutes(30)); + backgroundWorker.WorkStore.AddPeriodic(OperationDetectionWorkFactory.MakeWork(), TimeSpan.FromMinutes(15)); + backgroundWorker.WorkStore.AddPeriodic(SubsystemOperationTimeCalcWorkFactory.MakeWork(), TimeSpan.FromMinutes(30)); + backgroundWorker.WorkStore.AddPeriodic(LimitingParameterCalcWorkFactory.MakeWork(), TimeSpan.FromMinutes(30)); + backgroundWorker.WorkStore.AddPeriodic(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1)); var notificationBackgroundWorker = provider.GetRequiredService(); @@ -48,17 +47,15 @@ namespace AsbCloudInfrastructure }); } - static WorkPeriodic MakeMemoryMonitoringWork() + static Work MakeMemoryMonitoringWork() { - var workId = "Memory monitoring"; var workAction = (string _, IServiceProvider _, Action _, CancellationToken _) => { var bytes = GC.GetTotalMemory(false); var bytesString = FromatBytes(bytes); System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes. DbContext count is:{AsbCloudDbContext.ReferenceCount}"); return Task.CompletedTask; }; - var workPeriod = TimeSpan.FromMinutes(1); - var work = new WorkPeriodic(workId, workAction, workPeriod); + var work = new Work("Memory monitoring", workAction); return work; } diff --git a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs index 12bc81a2..54f5d19c 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs @@ -366,7 +366,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); Assert.Equal(2, state.IdState); - backgroundWorkerMock.Verify(s => s.Push(It.IsAny())); + backgroundWorkerMock.Verify(s => s.Push(It.IsAny())); } [Fact] diff --git a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs index 9633eb1c..143055e5 100644 --- a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs +++ b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs @@ -29,12 +29,12 @@ public class SignalRNotificationTransportService : INotificationTransportService { var workId = HashCode.Combine(notifications.Select(n => n.Id)).ToString("x"); - if (backgroundWorker.Contains(workId)) + if (backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId)) return Task.CompletedTask; var workAction = MakeSignalRSendWorkAction(notifications); - var work = new WorkBase(workId, workAction); - backgroundWorker.Push(work); + var work = new Work(workId, workAction); + backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work); return Task.CompletedTask; } From c28315b795488bb04febae5a302027e33b53f1ab Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Mon, 9 Oct 2023 13:12:45 +0500 Subject: [PATCH 04/13] Add backgroundController for monitoring works states --- ...ckgroudWorkDto.cs => BackgroundWorkDto.cs} | 11 +- AsbCloudApp/Data/WellboreDto.cs | 5 +- .../Notifications/NotificationService.cs | 1 - .../Background/BackgroundWorker.cs | 2 +- AsbCloudInfrastructure/Background/Work.cs | 160 +++++----- .../Background/WorkStore.cs | 25 +- .../AutoGeneratedDailyReportService.cs | 2 +- .../OperationDetectionWorkFactory.cs | 164 ---------- .../WorkOperationDetection.cs | 157 +++++++++ .../DrillingProgram/DrillingProgramService.cs | 7 +- .../EmailNotificationTransportService.cs | 2 +- .../LimitingParameterBackgroundService.cs | 149 --------- .../Services/ReportService.cs | 2 +- .../Services/SAUB/TelemetryDataCache.cs | 17 +- .../SubsystemOperationTimeCalcWorkFactory.cs | 298 ------------------ .../WorkSubsystemOperationTimeCalc.cs | 294 +++++++++++++++++ .../Services/WellInfoService.cs | 173 +++++----- .../Services/WorkLimitingParameterCalc.cs | 144 +++++++++ AsbCloudInfrastructure/Startup.cs | 10 +- .../UserConnectionsLimitMiddlwareTest.cs | 16 +- AsbCloudWebApi/Controllers/BackgroundWork.cs | 34 ++ .../SignalRNotificationTransportService.cs | 2 +- ConsoleApp1/DebugWellOperationsStatService.cs | 40 --- 23 files changed, 867 insertions(+), 848 deletions(-) rename AsbCloudApp/Data/{BackgroudWorkDto.cs => BackgroundWorkDto.cs} (95%) delete mode 100644 AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs create mode 100644 AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs delete mode 100644 AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs delete mode 100644 AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs create mode 100644 AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs create mode 100644 AsbCloudInfrastructure/Services/WorkLimitingParameterCalc.cs create mode 100644 AsbCloudWebApi/Controllers/BackgroundWork.cs delete mode 100644 ConsoleApp1/DebugWellOperationsStatService.cs diff --git a/AsbCloudApp/Data/BackgroudWorkDto.cs b/AsbCloudApp/Data/BackgroundWorkDto.cs similarity index 95% rename from AsbCloudApp/Data/BackgroudWorkDto.cs rename to AsbCloudApp/Data/BackgroundWorkDto.cs index 64451d26..b2a3d161 100644 --- a/AsbCloudApp/Data/BackgroudWorkDto.cs +++ b/AsbCloudApp/Data/BackgroundWorkDto.cs @@ -6,7 +6,7 @@ namespace AsbCloudApp.Data /// /// Информация о фоновой работе /// - public class BackgroudWorkDto + public class BackgroundWorkDto { /// /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. @@ -59,6 +59,11 @@ namespace AsbCloudApp.Data /// public class LastErrorInfo : LastCompleteInfo { + /// + /// + /// + /// + /// public LastErrorInfo(CurrentStateInfo state, string errorText) : base(state) { @@ -96,6 +101,10 @@ namespace AsbCloudApp.Data /// public string State { get; init; } + /// + /// ctor + /// + /// public LastCompleteInfo(CurrentStateInfo state) { Start = state.Start; diff --git a/AsbCloudApp/Data/WellboreDto.cs b/AsbCloudApp/Data/WellboreDto.cs index dbd9b697..c153b14f 100644 --- a/AsbCloudApp/Data/WellboreDto.cs +++ b/AsbCloudApp/Data/WellboreDto.cs @@ -7,7 +7,10 @@ namespace AsbCloudApp.Data; /// public class WellboreDto { - public WellWithTimezoneDto Well { get; set; } + /// + /// Скважина + /// + public WellWithTimezoneDto Well { get; set; } = null!; /// /// Идентификатор diff --git a/AsbCloudApp/Services/Notifications/NotificationService.cs b/AsbCloudApp/Services/Notifications/NotificationService.cs index 85c521e3..4537f04e 100644 --- a/AsbCloudApp/Services/Notifications/NotificationService.cs +++ b/AsbCloudApp/Services/Notifications/NotificationService.cs @@ -90,7 +90,6 @@ public class NotificationService /// Отправка уведомлений, которые не были отправлены /// /// - /// /// /// public async Task RenotifyAsync(int idUser, CancellationToken cancellationToken) diff --git a/AsbCloudInfrastructure/Background/BackgroundWorker.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs index f4ef8c0e..cacb5a60 100644 --- a/AsbCloudInfrastructure/Background/BackgroundWorker.cs +++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs @@ -40,7 +40,7 @@ public class BackgroundWorker : BackgroundService var result = await work.Start(scope.ServiceProvider, token); if (!result) - WorkStore.Falled.Add(work); + WorkStore.Felled.Add(work); CurrentWork = null; await Task.Delay(minDelay, token); diff --git a/AsbCloudInfrastructure/Background/Work.cs b/AsbCloudInfrastructure/Background/Work.cs index 84135707..9372343b 100644 --- a/AsbCloudInfrastructure/Background/Work.cs +++ b/AsbCloudInfrastructure/Background/Work.cs @@ -3,86 +3,96 @@ using System; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Background +namespace AsbCloudInfrastructure.Background; + +/// +/// Класс разовой работы. +/// Разовая работа приоритетнее периодической. +/// +public abstract class Work : BackgroundWorkDto { - /// - /// Класс разовой работы. - /// Разовая работа приоритетнее периодической. - /// - public class Work : BackgroudWorkDto + private sealed class WorkBase : Work { private Func, CancellationToken, Task> ActionAsync { get; } - - /// - /// Делегат обработки ошибки. - /// Не должен выполняться долго. - /// - public Func? OnErrorAsync { get; set; } - - public TimeSpan OnErrorHandlerTimeout { get; set; } = TimeSpan.FromSeconds(5); - - /// - /// Базовая работа - /// - /// - /// - /// Делегат работы. - /// - /// Параметры: - /// - /// - /// string - /// Id Идентификатор работы - /// - /// - /// IServiceProvider - /// Поставщик сервисов - /// - /// - /// Action<string, double?> - /// on progress callback. String - new state text. double? - optional progress 0-100%. - /// - /// - /// CancellationToken - /// Токен отмены задачи - /// - /// - /// - /// - public Work(string id, Func, CancellationToken, Task> actionAsync) + public WorkBase(string id, Func, CancellationToken, Task> actionAsync) + : base(id) { - Id = id; ActionAsync = actionAsync; } - - /// - /// Запустить работу - /// - /// - /// - /// True - susess, False = Fail - public async Task Start(IServiceProvider services, CancellationToken token) - { - SetStatusStart(); - try - { - var task = ActionAsync(Id, services, UpdateStatus, token); - await task.WaitAsync(Timeout, token); - SetStatusComplete(); - return true; - } - catch (Exception exception) - { - SetLastError(exception.Message); - if (OnErrorAsync is not null) - { - var task = Task.Run( - async () => await OnErrorAsync(Id, exception, token), - token); - await task.WaitAsync(OnErrorHandlerTimeout, token); - } - } - return false; - } + + protected override Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) + => ActionAsync(id, services, onProgressCallback, token); } + + /// + /// Делегат обработки ошибки. + /// Не должен выполняться долго. + /// + public Func? OnErrorAsync { get; set; } + + /// + /// макс продолжительность обработки исключения + /// + public TimeSpan OnErrorHandlerTimeout { get; set; } = TimeSpan.FromSeconds(5); + + /// + /// Базовая работа + /// + /// + public Work(string id) + { + Id = id; + } + + /// + /// Создать работу на основе делегата + /// + /// + /// + /// + [Obsolete("Use implement Work class")] + public static Work CreateByDelegate(string id, Func, CancellationToken, Task> actionAsync) + { + return new WorkBase(id, actionAsync); + } + + /// + /// Запустить работу + /// + /// + /// + /// True - success, False = fail + public async Task Start(IServiceProvider services, CancellationToken token) + { + SetStatusStart(); + try + { + var task = Action(Id, services, UpdateStatus, token); + await task.WaitAsync(Timeout, token); + SetStatusComplete(); + return true; + } + catch (Exception exception) + { + SetLastError(exception.Message); + if (OnErrorAsync is not null) + { + var task = Task.Run( + async () => await OnErrorAsync(Id, exception, token), + token); + await task.WaitAsync(OnErrorHandlerTimeout, token); + } + } + return false; + } + + /// + /// делегат фоновой работы + /// + /// Идентификатор работы + /// Поставщик сервисов + /// on progress callback. String - new state text. double? - optional progress 0-100% + /// + /// + protected abstract Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token); } diff --git a/AsbCloudInfrastructure/Background/WorkStore.cs b/AsbCloudInfrastructure/Background/WorkStore.cs index dbb1e464..b57fc104 100644 --- a/AsbCloudInfrastructure/Background/WorkStore.cs +++ b/AsbCloudInfrastructure/Background/WorkStore.cs @@ -18,16 +18,35 @@ public class WorkStore /// Список периодических задач /// public IEnumerable Periodics => periodics; + /// /// Работы выполняемые один раз /// public Queue RunOnceQueue { get; private set; } = new(8); /// - /// Завершывшиеся с ошибкой + /// Завершившиеся с ошибкой /// - public CyclycArray Falled { get; } = new(16); - + public CyclycArray Felled { get; } = new(16); + + /// + /// Добавить фоновую работу выполняющуюся с заданным периодом + /// + /// + /// + public void AddPeriodic(TimeSpan period) + where T : Work, new() + { + var work = new T(); + var periodic = new WorkPeriodic(work, period); + periodics.Add(periodic); + } + + /// + /// Добавить фоновую работу выполняющуюся с заданным периодом + /// + /// + /// public void AddPeriodic(Work work, TimeSpan period) { var periodic = new WorkPeriodic(work, period); diff --git a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs index 2f402285..03ed6e07 100644 --- a/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs +++ b/AsbCloudInfrastructure/Services/AutoGeneratedDailyReports/AutoGeneratedDailyReportService.cs @@ -226,7 +226,7 @@ public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService .OrderBy(w => w.DateStart); } - private Task?> GetSubsystemStatsAsync(int idWell, DateTime startDate, + private Task> GetSubsystemStatsAsync(int idWell, DateTime startDate, DateTime finishDate, CancellationToken cancellationToken) { var request = new SubsystemOperationTimeRequest diff --git a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs deleted file mode 100644 index 0c9bb3dd..00000000 --- a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs +++ /dev/null @@ -1,164 +0,0 @@ -using AsbCloudDb.Model; -using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AsbCloudInfrastructure.Services.DetectOperations.Detectors; -using AsbCloudInfrastructure.Background; -using Microsoft.Extensions.DependencyInjection; - -namespace AsbCloudInfrastructure.Services.DetectOperations -{ - - public static class OperationDetectionWorkFactory - { - private const string workId = "Operation detection"; - private static string progress = "no progress"; - - private static readonly DetectorAbstract[] detectors = new DetectorAbstract[] - { - new DetectorRotor(), - new DetectorSlide(), - //new DetectorDevelopment(), - //new DetectorTemplating(), - new DetectorSlipsTime(), - //new DetectorStaticSurveying(), - //new DetectorFlashingBeforeConnection(), - //new DetectorFlashing(), - //new DetectorTemplatingWhileDrilling(), - }; - - public static Work MakeWork() => new Work(workId, WorkAction) - { - Timeout = TimeSpan.FromMinutes(20), - OnErrorAsync = (id, exception, token) => - { - var text = $"work {id}, when {progress}, throw error:{exception.Message}"; - Trace.TraceWarning(text); - return Task.CompletedTask; - } - }; - - // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. - private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) - { - using var db = serviceProvider.GetRequiredService(); - - var lastDetectedDates = await db.DetectedOperations - .GroupBy(o => o.IdTelemetry) - .Select(g => new - { - IdTelemetry = g.Key, - LastDate = g.Max(o => o.DateEnd) - }) - .ToListAsync(token); - - var telemetryIds = await db.Telemetries - .Where(t => t.Info != null && t.TimeZone != null) - .Select(t => t.Id) - .ToListAsync(token); - - var joinedlastDetectedDates = telemetryIds - .GroupJoin(lastDetectedDates, - t => t, - o => o.IdTelemetry, - (outer, inner) => new - { - IdTelemetry = outer, - inner.SingleOrDefault()?.LastDate, - }); - - var affected = 0; - var count = joinedlastDetectedDates.Count(); - var i = 0; - foreach (var item in joinedlastDetectedDates) - { - var stopwatch = Stopwatch.StartNew(); - var startDate = item.LastDate ?? DateTimeOffset.MinValue; - onProgress($"start detecting telemetry: {item.IdTelemetry} from {startDate}", i++ / count); - var newOperations = await DetectOperationsAsync(item.IdTelemetry, startDate, db, token); - stopwatch.Stop(); - if (newOperations.Any()) - { - db.DetectedOperations.AddRange(newOperations); - affected += await db.SaveChangesAsync(token); - } - } - } - - private static async Task> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) - { - var query = db.TelemetryDataSaub - .AsNoTracking() - .Where(d => d.IdTelemetry == idTelemetry) - .Where(d => d.BlockPosition >= 0) - .Select(d => new DetectableTelemetry - { - DateTime = d.DateTime, - IdUser = d.IdUser, - WellDepth = d.WellDepth ?? float.NaN, - Pressure = d.Pressure ?? float.NaN, - HookWeight = d.HookWeight ?? float.NaN, - BlockPosition = d.BlockPosition ?? float.NaN, - BitDepth = d.BitDepth ?? float.NaN, - RotorSpeed = d.RotorSpeed ?? float.NaN, - }) - .OrderBy(d => d.DateTime); - - var take = 4 * 86_400; // 4 дня - var startDate = begin; - var detectedOperations = new List(8); - DetectedOperation? lastDetectedOperation = null; - const int minOperationLength = 5; - const int maxDetectorsInterpolationFrameLength = 30; - const int gap = maxDetectorsInterpolationFrameLength + minOperationLength; - - while (true) - { - var data = await query - .Where(d => d.DateTime > startDate) - .Take(take) - .ToArrayAsync(token); - - if (data.Length < gap) - break; - - var isDetected = false; - var positionBegin = 0; - var positionEnd = data.Length - gap; - var step = 10; - while (positionEnd > positionBegin) - { - step ++; - for (int i = 0; i < detectors.Length; i++) - { - progress = $"telemetry:{idTelemetry}, date:{startDate}, pos:{positionBegin}, detector:{detectors[i]}"; - if (detectors[i].TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out OperationDetectorResult? result)) - { - detectedOperations.Add(result!.Operation); - lastDetectedOperation = result.Operation; - isDetected = true; - step = 1; - positionBegin = result.TelemetryEnd; - break; - } - } - if (step > 20) - step = 10; - positionBegin += step; - } - - if (isDetected) - startDate = lastDetectedOperation!.DateEnd; - else - startDate = data[positionEnd].DateTime; - } - - return detectedOperations; - } - } - -} diff --git a/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs b/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs new file mode 100644 index 00000000..eef423b9 --- /dev/null +++ b/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs @@ -0,0 +1,157 @@ +using AsbCloudDb.Model; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudInfrastructure.Services.DetectOperations.Detectors; +using AsbCloudInfrastructure.Background; +using Microsoft.Extensions.DependencyInjection; + +namespace AsbCloudInfrastructure.Services.DetectOperations; + +public class WorkOperationDetection: Work +{ + private static readonly DetectorAbstract[] detectors = new DetectorAbstract[] + { + new DetectorRotor(), + new DetectorSlide(), + //new DetectorDevelopment(), + //new DetectorTemplating(), + new DetectorSlipsTime(), + //new DetectorStaticSurveying(), + //new DetectorFlashingBeforeConnection(), + //new DetectorFlashing(), + //new DetectorTemplatingWhileDrilling(), + }; + + public WorkOperationDetection() + :base("Operation detection") + { + Timeout = TimeSpan.FromMinutes(20); + OnErrorAsync = (id, exception, token) => + { + var text = $"work {id}, when {CurrentState?.State}, throw error:{exception.Message}"; + Trace.TraceWarning(text); + return Task.CompletedTask; + }; + } + + protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) + { + using var db = services.GetRequiredService(); + + var lastDetectedDates = await db.DetectedOperations + .GroupBy(o => o.IdTelemetry) + .Select(g => new + { + IdTelemetry = g.Key, + LastDate = g.Max(o => o.DateEnd) + }) + .ToListAsync(token); + + var telemetryIds = await db.Telemetries + .Where(t => t.Info != null && t.TimeZone != null) + .Select(t => t.Id) + .ToListAsync(token); + + var joinedlastDetectedDates = telemetryIds + .GroupJoin(lastDetectedDates, + t => t, + o => o.IdTelemetry, + (outer, inner) => new + { + IdTelemetry = outer, + inner.SingleOrDefault()?.LastDate, + }); + + var affected = 0; + var count = joinedlastDetectedDates.Count(); + var i = 0d; + foreach (var item in joinedlastDetectedDates) + { + var stopwatch = Stopwatch.StartNew(); + var startDate = item.LastDate ?? DateTimeOffset.MinValue; + onProgressCallback($"start detecting telemetry: {item.IdTelemetry} from {startDate}", i++ / count); + var newOperations = await DetectOperationsAsync(item.IdTelemetry, startDate, db, token); + stopwatch.Stop(); + if (newOperations.Any()) + { + db.DetectedOperations.AddRange(newOperations); + affected += await db.SaveChangesAsync(token); + } + } + } + + private static async Task> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) + { + var query = db.TelemetryDataSaub + .AsNoTracking() + .Where(d => d.IdTelemetry == idTelemetry) + .Where(d => d.BlockPosition >= 0) + .Select(d => new DetectableTelemetry + { + DateTime = d.DateTime, + IdUser = d.IdUser, + WellDepth = d.WellDepth ?? float.NaN, + Pressure = d.Pressure ?? float.NaN, + HookWeight = d.HookWeight ?? float.NaN, + BlockPosition = d.BlockPosition ?? float.NaN, + BitDepth = d.BitDepth ?? float.NaN, + RotorSpeed = d.RotorSpeed ?? float.NaN, + }) + .OrderBy(d => d.DateTime); + + var take = 4 * 86_400; // 4 дня + var startDate = begin; + var detectedOperations = new List(8); + DetectedOperation? lastDetectedOperation = null; + const int minOperationLength = 5; + const int maxDetectorsInterpolationFrameLength = 30; + const int gap = maxDetectorsInterpolationFrameLength + minOperationLength; + + while (true) + { + var data = await query + .Where(d => d.DateTime > startDate) + .Take(take) + .ToArrayAsync(token); + + if (data.Length < gap) + break; + + var isDetected = false; + var positionBegin = 0; + var positionEnd = data.Length - gap; + var step = 10; + while (positionEnd > positionBegin) + { + step ++; + for (int i = 0; i < detectors.Length; i++) + { + if (detectors[i].TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out OperationDetectorResult? result)) + { + detectedOperations.Add(result!.Operation); + lastDetectedOperation = result.Operation; + isDetected = true; + step = 1; + positionBegin = result.TelemetryEnd; + break; + } + } + if (step > 20) + step = 10; + positionBegin += step; + } + + if (isDetected) + startDate = lastDetectedOperation!.DateEnd; + else + startDate = data[positionEnd].DateTime; + } + + return detectedOperations; + } +} diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index 402a89ed..228a3564 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -540,11 +540,8 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram return Task.CompletedTask; }; - var work = new Work(workId, workAction) - { - OnErrorAsync = onErrorAction - }; - + var work = Work.CreateByDelegate(workId, workAction); + work.OnErrorAsync = onErrorAction; backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work); } } diff --git a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs index 33f011bf..bbbc8196 100644 --- a/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs +++ b/AsbCloudInfrastructure/Services/Email/EmailNotificationTransportService.cs @@ -56,7 +56,7 @@ namespace AsbCloudInfrastructure.Services.Email { var workAction = MakeEmailSendWorkAction(notification); - var work = new Work(workId, workAction); + var work = Work.CreateByDelegate(workId, workAction); backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work); } diff --git a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs b/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs deleted file mode 100644 index 07a17369..00000000 --- a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs +++ /dev/null @@ -1,149 +0,0 @@ -using AsbCloudDb.Model; -using Microsoft.EntityFrameworkCore; -using System; -using System.Data.Common; -using System.Data; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using AsbCloudInfrastructure.Background; -using Microsoft.Extensions.DependencyInjection; - -namespace AsbCloudInfrastructure.Services -{ - - internal static class LimitingParameterCalcWorkFactory - { - private const string workId = "Limiting parameter calc"; - - public static Work MakeWork() => new Work(workId, WorkAction) - { - Timeout = TimeSpan.FromMinutes(30) - }; - - // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. - private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) - { - using var db = serviceProvider.GetRequiredService(); - var lastDetectedDates = await db.LimitingParameter - .GroupBy(o => o.IdTelemetry) - .Select(g => new - { - IdTelemetry = g.Key, - LastDate = g.Max(o => o.DateEnd) - }) - .ToListAsync(token); - - var telemetryIds = await db.Telemetries - .Where(t => t.Info != null && t.TimeZone != null) - .Select(t => t.Id) - .ToListAsync(token); - - var telemetryLastDetectedDates = telemetryIds - .GroupJoin(lastDetectedDates, - t => t, - o => o.IdTelemetry, - (outer, inner) => new - { - IdTelemetry = outer, - inner.SingleOrDefault()?.LastDate, - }); - - var count = telemetryLastDetectedDates.Count(); - var i = 0; - foreach (var item in telemetryLastDetectedDates) - { - onProgress($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++/count); - var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); - if (newLimitingParameters?.Any() == true) - { - db.LimitingParameter.AddRange(newLimitingParameters); - await db.SaveChangesAsync(token); - } - } - } - - private static async Task> GetLimitingParameterAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) - { - var query = - $"select " + - $"limiting_parameters.date, limiting_parameters.id_feed_regulator, limiting_parameters.well_depth " + - $"from ( " + - $"select " + - $"date, id_feed_regulator, well_depth, " + - $"lag(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lag, " + - $"lead(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lead " + - $"from t_telemetry_data_saub " + - $"where id_feed_regulator is not null " + - $"and id_telemetry = {idTelemetry}" + - $"and date >= '{begin:u}'" + - $"order by date) as limiting_parameters " + - $"where id_feed_regulator_lag is null " + - $"or (id_feed_regulator != id_feed_regulator_lag and id_feed_regulator_lead != id_feed_regulator_lag) " + - $"order by date;"; - - var limitingParameters = new List(32); - using (var result = await ExecuteReaderAsync(db, query, token)) - { - LimitingParameter? limitingLast = null; - while (result.Read()) - { - var date = result.GetFieldValue(0); - var idLimiting = result.GetFieldValue(1); - var wellDepth = result.GetFieldValue(2); - - if (limitingLast is null) - { - limitingLast = new LimitingParameter - { - DateStart = date, - DepthStart = wellDepth, - IdFeedRegulator = idLimiting - }; - } - - if (limitingLast.IdFeedRegulator != idLimiting || limitingLast.DepthStart < wellDepth) - { - limitingParameters.Add(new LimitingParameter { - IdTelemetry = idTelemetry, - IdFeedRegulator = limitingLast.IdFeedRegulator, - DateStart = limitingLast.DateStart, - DateEnd = date, - DepthStart = limitingLast.DepthStart, - DepthEnd = wellDepth - }); - - limitingLast = new LimitingParameter - { - DateStart = date, - DepthStart = wellDepth, - IdFeedRegulator = idLimiting - }; - } - } - } - - return limitingParameters; - } - - private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) - { - var connection = db.Database.GetDbConnection(); - if ( - connection?.State is null || - connection.State == ConnectionState.Broken || - connection.State == ConnectionState.Closed) - { - await db.Database.OpenConnectionAsync(token); - connection = db.Database.GetDbConnection(); - } - using var command = connection.CreateCommand(); - command.CommandText = query; - - var result = await command.ExecuteReaderAsync(token); - return result; - } - } - -} diff --git a/AsbCloudInfrastructure/Services/ReportService.cs b/AsbCloudInfrastructure/Services/ReportService.cs index f3ab6cb8..6a66c30e 100644 --- a/AsbCloudInfrastructure/Services/ReportService.cs +++ b/AsbCloudInfrastructure/Services/ReportService.cs @@ -94,7 +94,7 @@ namespace AsbCloudInfrastructure.Services context.SaveChanges(); }; - var work = new Work(workId, workAction); + var work = Work.CreateByDelegate(workId, workAction); backgroundWorkerService.WorkStore.RunOnceQueue.Enqueue(work); progressHandler.Invoke(new ReportProgressDto diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs index daaa2dcf..9bb2dc79 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs @@ -48,7 +48,7 @@ namespace AsbCloudInfrastructure.Services.SAUB instance = new TelemetryDataCache(); var worker = provider.GetRequiredService(); var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}"; - var work = new Work(workId, async (workId, provider, onProgress, token) => { + var work = Work.CreateByDelegate(workId, async (workId, provider, onProgress, token) => { var db = provider.GetRequiredService(); await instance.InitializeCacheFromDBAsync(db, onProgress, token); }); @@ -159,7 +159,6 @@ namespace AsbCloudInfrastructure.Services.SAUB isLoading = true; var defaultTimeout = db.Database.GetCommandTimeout(); - System.Diagnostics.Trace.TraceInformation($"cache loading starting. Setting CommandTimeout 90s ({defaultTimeout})"); db.Database.SetCommandTimeout(TimeSpan.FromSeconds(90)); Well[] wells = await db.Set() @@ -169,7 +168,7 @@ namespace AsbCloudInfrastructure.Services.SAUB .ToArrayAsync(token); var count = wells.Length; - var i = 0; + var i = 0d; foreach (Well well in wells) { var capacity = well.IdState == 1 @@ -178,21 +177,13 @@ namespace AsbCloudInfrastructure.Services.SAUB var idTelemetry = well.IdTelemetry!.Value; var hoursOffset = well.Timezone.Hours; - // TODO: remove traces - System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}>: Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}"); + + onProgress($"Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}", i++/count); var cacheItem = await GetOrDefaultCacheDataFromDbAsync(db, idTelemetry, capacity, hoursOffset, token); if(cacheItem is not null) - { caches.TryAdd(idTelemetry, cacheItem); - System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> for well: {well.Cluster?.Caption}/{well.Caption} loaded"); - } - else - { - System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> for well: {well.Cluster?.Caption}/{well.Caption} has no data"); - } } - System.Diagnostics.Trace.TraceInformation($"cache<{typeof(TDto).Name}> load complete"); isLoading = false; db.Database.SetCommandTimeout(defaultTimeout); } diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs deleted file mode 100644 index 451ca633..00000000 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs +++ /dev/null @@ -1,298 +0,0 @@ -using AsbCloudDb.Model; -using AsbCloudDb.Model.Subsystems; -using AsbCloudInfrastructure.Background; -using AsbCloudInfrastructure.Services.Subsystems.Utils; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudInfrastructure.Services.Subsystems -{ - internal static class SubsystemOperationTimeCalcWorkFactory - { - private const string workId = "Subsystem operation time calc"; - - private const int idSubsytemTorqueMaster = 65537; - private const int idSubsytemSpinMaster = 65536; - private const int idSubsystemAPDRotor = 11; - private const int idSubsystemAPDSlide = 12; - private const int idSubsytemMse = 2; - - public static Work MakeWork() => new Work(workId, WorkAction) - { - Timeout = TimeSpan.FromMinutes(20) - }; - - // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. - private static async Task WorkAction(string _, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) - { - using var db = serviceProvider.GetRequiredService(); - - var lastDetectedDates = await db.SubsystemOperationTimes - .GroupBy(o => o.IdTelemetry) - .Select(g => new - { - IdTelemetry = g.Key, - LastDate = g.Max(o => o.DateEnd) - }) - .ToListAsync(token); - - var telemetryIds = await db.Telemetries - .Where(t => t.Info != null && t.TimeZone != null) - .Select(t => t.Id) - .ToListAsync(token); - - var telemetryLastDetectedDates = telemetryIds - .GroupJoin(lastDetectedDates, - t => t, - o => o.IdTelemetry, - (outer, inner) => new - { - IdTelemetry = outer, - inner.SingleOrDefault()?.LastDate, - }); - - var count = telemetryLastDetectedDates.Count(); - var i = 0; - foreach (var item in telemetryLastDetectedDates) - { - onProgress($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count); - var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); - if (newOperationsSaub?.Any() == true) - { - db.SubsystemOperationTimes.AddRange(newOperationsSaub); - await db.SaveChangesAsync(token); - } - var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); - if (newOperationsSpin?.Any() == true) - { - db.SubsystemOperationTimes.AddRange(newOperationsSpin); - await db.SaveChangesAsync(token); - } - } - } - - private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) - { - var connection = db.Database.GetDbConnection(); - if ( - connection?.State is null || - connection.State == ConnectionState.Broken || - connection.State == ConnectionState.Closed) - { - await db.Database.OpenConnectionAsync(token); - connection = db.Database.GetDbConnection(); - } - using var command = connection.CreateCommand(); - command.CommandText = query; - - var result = await command.ExecuteReaderAsync(token); - return result; - } - - private static async Task> OperationTimeSaubAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) - { - static bool isSubsytemAkbRotor(short? mode) => mode == 1; - - static bool isSubsytemAkbSlide(short? mode) => mode == 3; - - static bool IsSubsystemMse(short? state) => (state & 1) > 0; - - var query = - $"select tt.date, tt.mode, tt.well_depth, tt.mse_state " + - $"from ( " + - $" select " + - $" date, " + - $" mode, " + - $" mse_state, " + - $" well_depth, " + - $" lag(mode,1) over (order by date) as mode_lag, " + - $" lead(mode,1) over (order by date) as mode_lead " + - $" from t_telemetry_data_saub " + - $" where id_telemetry = {idTelemetry} and well_depth is not null and well_depth > 0" + - $" order by date ) as tt " + - $"where (tt.mode_lag is null or (tt.mode != tt.mode_lag and tt.mode_lead != tt.mode_lag)) and tt.date >= '{begin:u}' " + - $"order by tt.date;"; - - using var result = await ExecuteReaderAsync(db, query, token); - - var subsystemsOperationTimes = new List(); - var detectorRotor = new SubsystemDetector(idTelemetry, idSubsystemAPDRotor, isSubsytemAkbRotor, IsValid); - var detectorSlide = new SubsystemDetector(idTelemetry, idSubsystemAPDSlide, isSubsytemAkbSlide, IsValid); - var detectorMse = new SubsystemDetector(idTelemetry, idSubsytemMse, IsSubsystemMse, IsValid); - - while (result.Read()) - { - var mode = result.GetFieldValue(1); - var state = result.GetFieldValue(3); - - var isAkbRotorEnable = isSubsytemAkbRotor(mode); - var isAkbSlideEnable = isSubsytemAkbSlide(mode); - var isMseEnable = IsSubsystemMse(state); - var date = result.GetFieldValue(0); - var depth = result.GetFieldValue(2); - - if (detectorRotor.TryDetect(mode, date, depth, out var detectedRotor)) - subsystemsOperationTimes.Add(detectedRotor!); - - if (detectorSlide.TryDetect(mode, date, depth, out var detectedSlide)) - subsystemsOperationTimes.Add(detectedSlide!); - - if (detectorMse.TryDetect(mode, date, depth, out var detectedMse)) - subsystemsOperationTimes.Add(detectedMse!); - } - - return subsystemsOperationTimes; - } - - private static async Task> OperationTimeSpinAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) - { - static int? GetSubsytemId(short? mode, int? state) - { - // При изменении следующего кода сообщи в Vladimir.Sobolev@nedra.digital - if (state == 7 && (mode & 2) > 0) - return idSubsytemTorqueMaster;// демпфер - - if (state != 0 && state != 5 && state != 6 && state != 7) - return idSubsytemSpinMaster;// осцилляция - - return null; - } - - var querySpin = - $"select " + - $" tspin.date, " + - $" tspin.mode, " + - $" tspin.state " + - $"from ( " + - $" select " + - $" date, " + - $" mode, " + - $" lag(mode, 1) over (order by date) as mode_lag, " + - $" lead(mode, 1) over (order by date) as mode_lead, " + - $" state, " + - $" lag(state, 1) over (order by date) as state_lag " + - $" from t_telemetry_data_spin " + - $" where id_telemetry = {idTelemetry} and date >= '{begin:u}'" + - $" order by date ) as tspin " + - $"where mode_lag is null or state_lag is null or (mode != mode_lag and mode_lead != mode_lag) or state != state_lag " + - $"order by date;"; - - var rows = new List<(int? IdSubsystem, DateTimeOffset Date)>(32); - - using var resultSpin = await ExecuteReaderAsync(db, querySpin, token); - int? idSubsystemLast = null; - while (resultSpin.Read()) - { - var mode = resultSpin.GetFieldValue(1); - var state = resultSpin.GetFieldValue(2); - var idSubsystem = GetSubsytemId(mode, state); - if (idSubsystemLast != idSubsystem) - { - idSubsystemLast = idSubsystem; - var date = resultSpin.GetFieldValue(0); - rows.Add((idSubsystem, date)); - } - } - await resultSpin.DisposeAsync(); - - if (rows.Count < 2) - return Enumerable.Empty(); - - var minSpinDate = rows.Min(i => i.Date); - var maxSpinDate = rows.Max(i => i.Date); - var depthInterpolation = await GetInterpolation(db, idTelemetry, minSpinDate, maxSpinDate, token); - - if (depthInterpolation is null) - return Enumerable.Empty(); - - var subsystemsOperationTimes = new List(32); - - for (int i = 1; i < rows.Count; i++) - { - var r0 = rows[i - 1]; - var r1 = rows[i]; - if (r0.IdSubsystem is not null && r0.IdSubsystem != r1.IdSubsystem) - { - var subsystemOperationTime = new SubsystemOperationTime() - { - IdTelemetry = idTelemetry, - IdSubsystem = r0.IdSubsystem.Value, - DateStart = r0.Date, - DateEnd = r1.Date, - DepthStart = depthInterpolation.GetDepth(r0.Date), - DepthEnd = depthInterpolation.GetDepth(r1.Date), - }; - - if (IsValid(subsystemOperationTime)) - subsystemsOperationTimes.Add(subsystemOperationTime); - } - } - - return subsystemsOperationTimes; - } - - private static bool IsValid(SubsystemOperationTime item) - { - var validateCode = GetValidateErrorCode(item); - if (validateCode != 0) - { - var str = System.Text.Json.JsonSerializer.Serialize(item); - Trace.TraceWarning($"Wrong({validateCode}) SubsystemOperationTime: {str}"); - } - return validateCode == 0; - } - - private static int GetValidateErrorCode(SubsystemOperationTime item) - { - if (item.DateStart > item.DateEnd) - return -1; - if ((item.DateEnd - item.DateStart).TotalHours > 48) - return -2; - if (item.DepthEnd < item.DepthStart) - return -3; - if (item.DepthEnd - item.DepthStart > 2000d) - return -4; - if (item.DepthEnd < 0d) - return -5; - if (item.DepthStart < 0d) - return -6; - if (item.DepthEnd > 24_0000d) - return -7; - if (item.DepthStart > 24_0000d) - return -8; - return 0; - } - - private static async Task GetInterpolation(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) - { - var dataDepthFromSaub = await db.TelemetryDataSaub - .Where(d => d.IdTelemetry == idTelemetry) - .Where(d => d.DateTime >= dateBegin) - .Where(d => d.DateTime <= dateEnd) - .Where(d => d.WellDepth != null) - .Where(d => d.WellDepth > 0) - .GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10)) - .Select(g => new { - DateMin = g.Min(d => d.DateTime), - DepthMin = g.Min(d => d.WellDepth) ?? 0, - }) - .OrderBy(i => i.DateMin) - .ToArrayAsync(token); - - if (!dataDepthFromSaub.Any()) - return null; - - var depthInterpolation = new DepthInterpolation(dataDepthFromSaub.Select(i => (i.DateMin, i.DepthMin))); - return depthInterpolation; - } - } - -} diff --git a/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs b/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs new file mode 100644 index 00000000..bda4bbe4 --- /dev/null +++ b/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs @@ -0,0 +1,294 @@ +using AsbCloudDb.Model; +using AsbCloudDb.Model.Subsystems; +using AsbCloudInfrastructure.Background; +using AsbCloudInfrastructure.Services.Subsystems.Utils; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services.Subsystems; + +public class WorkSubsystemOperationTimeCalc: Work +{ + private const int idSubsytemTorqueMaster = 65537; + private const int idSubsytemSpinMaster = 65536; + private const int idSubsystemAPDRotor = 11; + private const int idSubsystemAPDSlide = 12; + private const int idSubsytemMse = 2; + + public WorkSubsystemOperationTimeCalc() + : base("Subsystem operation time calc") + { + Timeout = TimeSpan.FromMinutes(20); + } + + protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) + { + using var db = services.GetRequiredService(); + + var lastDetectedDates = await db.SubsystemOperationTimes + .GroupBy(o => o.IdTelemetry) + .Select(g => new + { + IdTelemetry = g.Key, + LastDate = g.Max(o => o.DateEnd) + }) + .ToListAsync(token); + + var telemetryIds = await db.Telemetries + .Where(t => t.Info != null && t.TimeZone != null) + .Select(t => t.Id) + .ToListAsync(token); + + var telemetryLastDetectedDates = telemetryIds + .GroupJoin(lastDetectedDates, + t => t, + o => o.IdTelemetry, + (outer, inner) => new + { + IdTelemetry = outer, + inner.SingleOrDefault()?.LastDate, + }); + + var count = telemetryLastDetectedDates.Count(); + var i = 0d; + foreach (var item in telemetryLastDetectedDates) + { + onProgressCallback($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count); + var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); + if (newOperationsSaub?.Any() == true) + { + db.SubsystemOperationTimes.AddRange(newOperationsSaub); + await db.SaveChangesAsync(token); + } + var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); + if (newOperationsSpin?.Any() == true) + { + db.SubsystemOperationTimes.AddRange(newOperationsSpin); + await db.SaveChangesAsync(token); + } + } + } + + private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) + { + var connection = db.Database.GetDbConnection(); + if ( + connection?.State is null || + connection.State == ConnectionState.Broken || + connection.State == ConnectionState.Closed) + { + await db.Database.OpenConnectionAsync(token); + connection = db.Database.GetDbConnection(); + } + using var command = connection.CreateCommand(); + command.CommandText = query; + + var result = await command.ExecuteReaderAsync(token); + return result; + } + + private static async Task> OperationTimeSaubAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) + { + static bool isSubsytemAkbRotor(short? mode) => mode == 1; + + static bool isSubsytemAkbSlide(short? mode) => mode == 3; + + static bool IsSubsystemMse(short? state) => (state & 1) > 0; + + var query = + $"select tt.date, tt.mode, tt.well_depth, tt.mse_state " + + $"from ( " + + $" select " + + $" date, " + + $" mode, " + + $" mse_state, " + + $" well_depth, " + + $" lag(mode,1) over (order by date) as mode_lag, " + + $" lead(mode,1) over (order by date) as mode_lead " + + $" from t_telemetry_data_saub " + + $" where id_telemetry = {idTelemetry} and well_depth is not null and well_depth > 0" + + $" order by date ) as tt " + + $"where (tt.mode_lag is null or (tt.mode != tt.mode_lag and tt.mode_lead != tt.mode_lag)) and tt.date >= '{begin:u}' " + + $"order by tt.date;"; + + using var result = await ExecuteReaderAsync(db, query, token); + + var subsystemsOperationTimes = new List(); + var detectorRotor = new SubsystemDetector(idTelemetry, idSubsystemAPDRotor, isSubsytemAkbRotor, IsValid); + var detectorSlide = new SubsystemDetector(idTelemetry, idSubsystemAPDSlide, isSubsytemAkbSlide, IsValid); + var detectorMse = new SubsystemDetector(idTelemetry, idSubsytemMse, IsSubsystemMse, IsValid); + + while (result.Read()) + { + var mode = result.GetFieldValue(1); + var state = result.GetFieldValue(3); + + var isAkbRotorEnable = isSubsytemAkbRotor(mode); + var isAkbSlideEnable = isSubsytemAkbSlide(mode); + var isMseEnable = IsSubsystemMse(state); + var date = result.GetFieldValue(0); + var depth = result.GetFieldValue(2); + + if (detectorRotor.TryDetect(mode, date, depth, out var detectedRotor)) + subsystemsOperationTimes.Add(detectedRotor!); + + if (detectorSlide.TryDetect(mode, date, depth, out var detectedSlide)) + subsystemsOperationTimes.Add(detectedSlide!); + + if (detectorMse.TryDetect(mode, date, depth, out var detectedMse)) + subsystemsOperationTimes.Add(detectedMse!); + } + + return subsystemsOperationTimes; + } + + private static async Task> OperationTimeSpinAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) + { + static int? GetSubsytemId(short? mode, int? state) + { + // При изменении следующего кода сообщи в Vladimir.Sobolev@nedra.digital + if (state == 7 && (mode & 2) > 0) + return idSubsytemTorqueMaster;// демпфер + + if (state != 0 && state != 5 && state != 6 && state != 7) + return idSubsytemSpinMaster;// осцилляция + + return null; + } + + var querySpin = + $"select " + + $" tspin.date, " + + $" tspin.mode, " + + $" tspin.state " + + $"from ( " + + $" select " + + $" date, " + + $" mode, " + + $" lag(mode, 1) over (order by date) as mode_lag, " + + $" lead(mode, 1) over (order by date) as mode_lead, " + + $" state, " + + $" lag(state, 1) over (order by date) as state_lag " + + $" from t_telemetry_data_spin " + + $" where id_telemetry = {idTelemetry} and date >= '{begin:u}'" + + $" order by date ) as tspin " + + $"where mode_lag is null or state_lag is null or (mode != mode_lag and mode_lead != mode_lag) or state != state_lag " + + $"order by date;"; + + var rows = new List<(int? IdSubsystem, DateTimeOffset Date)>(32); + + using var resultSpin = await ExecuteReaderAsync(db, querySpin, token); + int? idSubsystemLast = null; + while (resultSpin.Read()) + { + var mode = resultSpin.GetFieldValue(1); + var state = resultSpin.GetFieldValue(2); + var idSubsystem = GetSubsytemId(mode, state); + if (idSubsystemLast != idSubsystem) + { + idSubsystemLast = idSubsystem; + var date = resultSpin.GetFieldValue(0); + rows.Add((idSubsystem, date)); + } + } + await resultSpin.DisposeAsync(); + + if (rows.Count < 2) + return Enumerable.Empty(); + + var minSpinDate = rows.Min(i => i.Date); + var maxSpinDate = rows.Max(i => i.Date); + var depthInterpolation = await GetInterpolation(db, idTelemetry, minSpinDate, maxSpinDate, token); + + if (depthInterpolation is null) + return Enumerable.Empty(); + + var subsystemsOperationTimes = new List(32); + + for (int i = 1; i < rows.Count; i++) + { + var r0 = rows[i - 1]; + var r1 = rows[i]; + if (r0.IdSubsystem is not null && r0.IdSubsystem != r1.IdSubsystem) + { + var subsystemOperationTime = new SubsystemOperationTime() + { + IdTelemetry = idTelemetry, + IdSubsystem = r0.IdSubsystem.Value, + DateStart = r0.Date, + DateEnd = r1.Date, + DepthStart = depthInterpolation.GetDepth(r0.Date), + DepthEnd = depthInterpolation.GetDepth(r1.Date), + }; + + if (IsValid(subsystemOperationTime)) + subsystemsOperationTimes.Add(subsystemOperationTime); + } + } + + return subsystemsOperationTimes; + } + + private static bool IsValid(SubsystemOperationTime item) + { + var validateCode = GetValidateErrorCode(item); + if (validateCode != 0) + { + var str = System.Text.Json.JsonSerializer.Serialize(item); + Trace.TraceWarning($"Wrong({validateCode}) SubsystemOperationTime: {str}"); + } + return validateCode == 0; + } + + private static int GetValidateErrorCode(SubsystemOperationTime item) + { + if (item.DateStart > item.DateEnd) + return -1; + if ((item.DateEnd - item.DateStart).TotalHours > 48) + return -2; + if (item.DepthEnd < item.DepthStart) + return -3; + if (item.DepthEnd - item.DepthStart > 2000d) + return -4; + if (item.DepthEnd < 0d) + return -5; + if (item.DepthStart < 0d) + return -6; + if (item.DepthEnd > 24_0000d) + return -7; + if (item.DepthStart > 24_0000d) + return -8; + return 0; + } + + private static async Task GetInterpolation(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) + { + var dataDepthFromSaub = await db.TelemetryDataSaub + .Where(d => d.IdTelemetry == idTelemetry) + .Where(d => d.DateTime >= dateBegin) + .Where(d => d.DateTime <= dateEnd) + .Where(d => d.WellDepth != null) + .Where(d => d.WellDepth > 0) + .GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10)) + .Select(g => new { + DateMin = g.Min(d => d.DateTime), + DepthMin = g.Min(d => d.WellDepth) ?? 0, + }) + .OrderBy(i => i.DateMin) + .ToArrayAsync(token); + + if (!dataDepthFromSaub.Any()) + return null; + + var depthInterpolation = new DepthInterpolation(dataDepthFromSaub.Select(i => (i.DateMin, i.DepthMin))); + return depthInterpolation; + } +} diff --git a/AsbCloudInfrastructure/Services/WellInfoService.cs b/AsbCloudInfrastructure/Services/WellInfoService.cs index 80456db9..7e4cd8ce 100644 --- a/AsbCloudInfrastructure/Services/WellInfoService.cs +++ b/AsbCloudInfrastructure/Services/WellInfoService.cs @@ -18,53 +18,26 @@ using System.Threading.Tasks; using AsbCloudApp.IntegrationEvents; using AsbCloudApp.IntegrationEvents.Interfaces; -namespace AsbCloudInfrastructure.Services +namespace AsbCloudInfrastructure.Services; + +public class WellInfoService { - public class WellInfoService + public class WorkWellInfoUpdate : Work { - class WellMapInfoWithComanies : WellMapInfoDto + public WorkWellInfoUpdate() + : base("Well statistics update") { - public int? IdTelemetry { get; set; } - public IEnumerable IdsCompanies { get; set; } = null!; + Timeout = TimeSpan.FromMinutes(20); } - private const string workId = "Well statistics update"; - - private readonly TelemetryDataCache telemetryDataSaubCache; - private readonly TelemetryDataCache telemetryDataSpinCache; - private readonly IWitsRecordRepository witsRecord7Repository; - private readonly IWitsRecordRepository witsRecord1Repository; - private readonly IGtrRepository gtrRepository; - private static IEnumerable WellMapInfo = Enumerable.Empty(); - - public WellInfoService( - TelemetryDataCache telemetryDataSaubCache, - TelemetryDataCache telemetryDataSpinCache, - IWitsRecordRepository witsRecord7Repository, - IWitsRecordRepository witsRecord1Repository, - IGtrRepository gtrRepository) + protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) { - this.telemetryDataSaubCache = telemetryDataSaubCache; - this.telemetryDataSpinCache = telemetryDataSpinCache; - - this.witsRecord7Repository = witsRecord7Repository; - this.witsRecord1Repository = witsRecord1Repository; - this.gtrRepository = gtrRepository; - } - - public static Work MakeWork() => new Work(workId, WorkAction) - { - Timeout = TimeSpan.FromMinutes(20) - }; - - private static async Task WorkAction(string workName, IServiceProvider serviceProvider, Action onProgress, CancellationToken token) - { - var wellService = serviceProvider.GetRequiredService(); - var operationsStatService = serviceProvider.GetRequiredService(); - var processMapRepository = serviceProvider.GetRequiredService(); - var subsystemOperationTimeService = serviceProvider.GetRequiredService(); - var telemetryDataSaubCache = serviceProvider.GetRequiredService>(); - var messageHub = serviceProvider.GetRequiredService>(); + var wellService = services.GetRequiredService(); + var operationsStatService = services.GetRequiredService(); + var processMapRepository = services.GetRequiredService(); + var subsystemOperationTimeService = services.GetRequiredService(); + var telemetryDataSaubCache = services.GetRequiredService>(); + var messageHub = services.GetRequiredService>(); var wells = await wellService.GetAllAsync(token); @@ -82,30 +55,30 @@ namespace AsbCloudInfrastructure.Services }); var operationsStat = await operationsStatService.GetWellsStatAsync(wellsIds, token); - + var subsystemStat = await subsystemOperationTimeService .GetStatByActiveWells(wellsIds, token); var count = wells.Count(); - var i = 0; + var i = 0d; WellMapInfo = wells.Select(well => { var wellMapInfo = well.Adapt(); wellMapInfo.IdState = well.IdState; - onProgress($"Start updating info by well({well.Id}): {well.Caption}", i++ / count); + onProgressCallback($"Start updating info by well({well.Id}): {well.Caption}", i++ / count); double? currentDepth = null; TelemetryDataSaubDto? lastSaubTelemetry = null; - + if (well.IdTelemetry.HasValue) { wellMapInfo.IdTelemetry = well.IdTelemetry.Value; lastSaubTelemetry = telemetryDataSaubCache.GetLastOrDefault(well.IdTelemetry.Value); - if(lastSaubTelemetry is not null) + if (lastSaubTelemetry is not null) { currentDepth = lastSaubTelemetry.WellDepth; } } - var wellOperationsStat = operationsStat.FirstOrDefault(s => s.Id == well.Id); + var wellOperationsStat = operationsStat.FirstOrDefault(s => s.Id == well.Id); var wellLastFactSection = wellOperationsStat?.Sections.LastOrDefault(s => s.Fact is not null); currentDepth ??= wellLastFactSection?.Fact?.WellDepthEnd; @@ -120,7 +93,7 @@ namespace AsbCloudInfrastructure.Services { wellProcessMap = wellProcessMaps.FirstOrDefault(p => p.IdWellSectionType == idSection); } - else if(currentDepth.HasValue) + else if (currentDepth.HasValue) { wellProcessMap = wellProcessMaps.FirstOrDefault(p => p.DepthStart <= currentDepth.Value && p.DepthEnd >= currentDepth.Value); } @@ -130,8 +103,8 @@ namespace AsbCloudInfrastructure.Services planTotalDepth ??= wellOperationsStat?.Total.Plan?.WellDepthEnd; wellMapInfo.Section = wellLastFactSection?.Caption; - - wellMapInfo.FirstFactOperationDateStart = wellOperationsStat?.Total.Fact?.Start + + wellMapInfo.FirstFactOperationDateStart = wellOperationsStat?.Total.Fact?.Start ?? wellOperationsStat?.Total.Plan?.Start; wellMapInfo.LastPredictOperationDateEnd = wellOperationsStat?.Total.Plan?.End; @@ -159,7 +132,7 @@ namespace AsbCloudInfrastructure.Services Plan = wellProcessMap?.Pressure.Plan, Fact = lastSaubTelemetry?.Pressure }; - + wellMapInfo.PressureSp = lastSaubTelemetry?.PressureSp; wellMapInfo.WellDepth = new() @@ -191,51 +164,79 @@ namespace AsbCloudInfrastructure.Services return wellMapInfo; }).ToArray(); - var updateWellInfoEventTasks = wellsIds.Select(idWell => + var updateWellInfoEventTasks = wellsIds.Select(idWell => messageHub.HandleAsync(new UpdateWellInfoEvent(idWell), token)); - + await Task.WhenAll(updateWellInfoEventTasks); } + } - private WellMapInfoWithTelemetryStat Convert(WellMapInfoWithComanies wellInfo) + class WellMapInfoWithComanies : WellMapInfoDto + { + public int? IdTelemetry { get; set; } + public IEnumerable IdsCompanies { get; set; } = null!; + } + + private readonly TelemetryDataCache telemetryDataSaubCache; + private readonly TelemetryDataCache telemetryDataSpinCache; + private readonly IWitsRecordRepository witsRecord7Repository; + private readonly IWitsRecordRepository witsRecord1Repository; + private readonly IGtrRepository gtrRepository; + private static IEnumerable WellMapInfo = Enumerable.Empty(); + + public WellInfoService( + TelemetryDataCache telemetryDataSaubCache, + TelemetryDataCache telemetryDataSpinCache, + IWitsRecordRepository witsRecord7Repository, + IWitsRecordRepository witsRecord1Repository, + IGtrRepository gtrRepository) + { + this.telemetryDataSaubCache = telemetryDataSaubCache; + this.telemetryDataSpinCache = telemetryDataSpinCache; + + this.witsRecord7Repository = witsRecord7Repository; + this.witsRecord1Repository = witsRecord1Repository; + this.gtrRepository = gtrRepository; + } + + private WellMapInfoWithTelemetryStat Convert(WellMapInfoWithComanies wellInfo) + { + var result = wellInfo.Adapt(); + if (wellInfo.IdTelemetry.HasValue) { - var result = wellInfo.Adapt(); - if (wellInfo.IdTelemetry.HasValue) - { - var idTelemetry = wellInfo.IdTelemetry.Value; - result.LastDataSaub = telemetryDataSaubCache.GetLastOrDefault(idTelemetry); - result.LastDataSpin = telemetryDataSpinCache.GetLastOrDefault(idTelemetry); - result.LastDataDdsDate = GetLastOrDefaultDdsTelemetry(idTelemetry); - result.LastDataGtrDate = gtrRepository.GetLastData(wellInfo.Id) - .MaxOrDefault(item => item.Date); - result.LastDataDpcsDate = null; - result.LastDataDpcsDate = null; - } - - return result; + var idTelemetry = wellInfo.IdTelemetry.Value; + result.LastDataSaub = telemetryDataSaubCache.GetLastOrDefault(idTelemetry); + result.LastDataSpin = telemetryDataSpinCache.GetLastOrDefault(idTelemetry); + result.LastDataDdsDate = GetLastOrDefaultDdsTelemetry(idTelemetry); + result.LastDataGtrDate = gtrRepository.GetLastData(wellInfo.Id) + .MaxOrDefault(item => item.Date); + result.LastDataDpcsDate = null; + result.LastDataDpcsDate = null; } - private DateTime? GetLastOrDefaultDdsTelemetry(int idTelemetry) - { - var lastDdsRecord1Date = witsRecord1Repository.GetLastOrDefault(idTelemetry)?.DateTime; - var lastDdsRecord7Date = witsRecord7Repository.GetLastOrDefault(idTelemetry)?.DateTime; + return result; + } - if (lastDdsRecord1Date.HasValue && lastDdsRecord7Date.HasValue) - if (lastDdsRecord1Date.Value > lastDdsRecord7Date.Value) - return lastDdsRecord1Date.Value; - else - return lastDdsRecord7Date.Value; + private DateTime? GetLastOrDefaultDdsTelemetry(int idTelemetry) + { + var lastDdsRecord1Date = witsRecord1Repository.GetLastOrDefault(idTelemetry)?.DateTime; + var lastDdsRecord7Date = witsRecord7Repository.GetLastOrDefault(idTelemetry)?.DateTime; - return lastDdsRecord1Date ?? lastDdsRecord7Date; - } + if (lastDdsRecord1Date.HasValue && lastDdsRecord7Date.HasValue) + if (lastDdsRecord1Date.Value > lastDdsRecord7Date.Value) + return lastDdsRecord1Date.Value; + else + return lastDdsRecord7Date.Value; - public WellMapInfoWithTelemetryStat? FirstOrDefault(Func predicate) - { - var first = WellMapInfo.FirstOrDefault(predicate); - if (first is WellMapInfoWithComanies wellMapInfoWithComanies) - return Convert(wellMapInfoWithComanies); + return lastDdsRecord1Date ?? lastDdsRecord7Date; + } - return null; - } + public WellMapInfoWithTelemetryStat? FirstOrDefault(Func predicate) + { + var first = WellMapInfo.FirstOrDefault(predicate); + if (first is WellMapInfoWithComanies wellMapInfoWithComanies) + return Convert(wellMapInfoWithComanies); + + return null; } } diff --git a/AsbCloudInfrastructure/Services/WorkLimitingParameterCalc.cs b/AsbCloudInfrastructure/Services/WorkLimitingParameterCalc.cs new file mode 100644 index 00000000..3ab159e0 --- /dev/null +++ b/AsbCloudInfrastructure/Services/WorkLimitingParameterCalc.cs @@ -0,0 +1,144 @@ +using AsbCloudDb.Model; +using Microsoft.EntityFrameworkCore; +using System; +using System.Data.Common; +using System.Data; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using AsbCloudInfrastructure.Background; +using Microsoft.Extensions.DependencyInjection; + +namespace AsbCloudInfrastructure.Services; + +public class WorkLimitingParameterCalc : Work +{ + public WorkLimitingParameterCalc() + : base("Limiting parameter calc") + { + Timeout = TimeSpan.FromMinutes(30); + } + + protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) + { + using var db = services.GetRequiredService(); + var lastDetectedDates = await db.LimitingParameter + .GroupBy(o => o.IdTelemetry) + .Select(g => new + { + IdTelemetry = g.Key, + LastDate = g.Max(o => o.DateEnd) + }) + .ToListAsync(token); + + var telemetryIds = await db.Telemetries + .Where(t => t.Info != null && t.TimeZone != null) + .Select(t => t.Id) + .ToListAsync(token); + + var telemetryLastDetectedDates = telemetryIds + .GroupJoin(lastDetectedDates, + t => t, + o => o.IdTelemetry, + (outer, inner) => new + { + IdTelemetry = outer, + inner.SingleOrDefault()?.LastDate, + }); + + var count = telemetryLastDetectedDates.Count(); + var i = 0d; + foreach (var item in telemetryLastDetectedDates) + { + onProgressCallback($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++/count); + var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); + if (newLimitingParameters?.Any() == true) + { + db.LimitingParameter.AddRange(newLimitingParameters); + await db.SaveChangesAsync(token); + } + } + } + + private static async Task> GetLimitingParameterAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) + { + var query = + $"select " + + $"limiting_parameters.date, limiting_parameters.id_feed_regulator, limiting_parameters.well_depth " + + $"from ( " + + $"select " + + $"date, id_feed_regulator, well_depth, " + + $"lag(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lag, " + + $"lead(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lead " + + $"from t_telemetry_data_saub " + + $"where id_feed_regulator is not null " + + $"and id_telemetry = {idTelemetry}" + + $"and date >= '{begin:u}'" + + $"order by date) as limiting_parameters " + + $"where id_feed_regulator_lag is null " + + $"or (id_feed_regulator != id_feed_regulator_lag and id_feed_regulator_lead != id_feed_regulator_lag) " + + $"order by date;"; + + var limitingParameters = new List(32); + using (var result = await ExecuteReaderAsync(db, query, token)) + { + LimitingParameter? limitingLast = null; + while (result.Read()) + { + var date = result.GetFieldValue(0); + var idLimiting = result.GetFieldValue(1); + var wellDepth = result.GetFieldValue(2); + + if (limitingLast is null) + { + limitingLast = new LimitingParameter + { + DateStart = date, + DepthStart = wellDepth, + IdFeedRegulator = idLimiting + }; + } + + if (limitingLast.IdFeedRegulator != idLimiting || limitingLast.DepthStart < wellDepth) + { + limitingParameters.Add(new LimitingParameter { + IdTelemetry = idTelemetry, + IdFeedRegulator = limitingLast.IdFeedRegulator, + DateStart = limitingLast.DateStart, + DateEnd = date, + DepthStart = limitingLast.DepthStart, + DepthEnd = wellDepth + }); + + limitingLast = new LimitingParameter + { + DateStart = date, + DepthStart = wellDepth, + IdFeedRegulator = idLimiting + }; + } + } + } + + return limitingParameters; + } + + private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) + { + var connection = db.Database.GetDbConnection(); + if ( + connection?.State is null || + connection.State == ConnectionState.Broken || + connection.State == ConnectionState.Closed) + { + await db.Database.OpenConnectionAsync(token); + connection = db.Database.GetDbConnection(); + } + using var command = connection.CreateCommand(); + command.CommandText = query; + + var result = await command.ExecuteReaderAsync(token); + return result; + } +} diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index 604a4f69..d00de488 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -31,10 +31,10 @@ namespace AsbCloudInfrastructure _ = provider.GetRequiredService>(); var backgroundWorker = provider.GetRequiredService(); - backgroundWorker.WorkStore.AddPeriodic(WellInfoService.MakeWork(), TimeSpan.FromMinutes(30)); - backgroundWorker.WorkStore.AddPeriodic(OperationDetectionWorkFactory.MakeWork(), TimeSpan.FromMinutes(15)); - backgroundWorker.WorkStore.AddPeriodic(SubsystemOperationTimeCalcWorkFactory.MakeWork(), TimeSpan.FromMinutes(30)); - backgroundWorker.WorkStore.AddPeriodic(LimitingParameterCalcWorkFactory.MakeWork(), TimeSpan.FromMinutes(30)); + backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30)); + backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(15)); + backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30)); + backgroundWorker.WorkStore.AddPeriodic(TimeSpan.FromMinutes(30)); backgroundWorker.WorkStore.AddPeriodic(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1)); var notificationBackgroundWorker = provider.GetRequiredService(); @@ -55,7 +55,7 @@ namespace AsbCloudInfrastructure System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes. DbContext count is:{AsbCloudDbContext.ReferenceCount}"); return Task.CompletedTask; }; - var work = new Work("Memory monitoring", workAction); + var work = Work.CreateByDelegate("Memory monitoring", workAction); return work; } diff --git a/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs b/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs index 96556845..b118e179 100644 --- a/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs +++ b/AsbCloudWebApi.Tests/Middlware/UserConnectionsLimitMiddlwareTest.cs @@ -1,4 +1,6 @@ -using AsbCloudApp.Data.SAUB; +using AsbCloudApp.Data; +using AsbCloudApp.Data.SAUB; +using AsbCloudApp.Requests; using AsbCloudApp.Services; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -35,12 +37,22 @@ namespace AsbCloudWebApi.Tests.Middlware public class TelemetryDataSaubService : ITelemetryDataSaubService { - public async Task?> GetAsync(int idWell, DateTime dateBegin = default, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default) + public async Task> GetAsync(int idWell, DateTime dateBegin = default, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default) { await Task.Delay(1000, token); return Enumerable.Empty(); } + public Task> GetAsync(int idWell, TelemetryDataRequest request, CancellationToken token) + { + throw new NotImplementedException(); + } + + public Task GetRangeAsync(int idWell, DateTimeOffset start, DateTimeOffset end, CancellationToken token) + { + throw new NotImplementedException(); + } + public Task> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token) => throw new NotImplementedException(); public Task GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token) diff --git a/AsbCloudWebApi/Controllers/BackgroundWork.cs b/AsbCloudWebApi/Controllers/BackgroundWork.cs new file mode 100644 index 00000000..316d796e --- /dev/null +++ b/AsbCloudWebApi/Controllers/BackgroundWork.cs @@ -0,0 +1,34 @@ +using AsbCloudApp.Data; +using AsbCloudInfrastructure.Background; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Linq; + +namespace AsbCloudWebApi.Controllers +{ + [Route("api/[controller]")] + [Authorize] + [ApiController] + public class BackgroundWork : ControllerBase + { + private readonly BackgroundWorker backgroundWorker; + + public BackgroundWork(BackgroundWorker backgroundWorker) + { + this.backgroundWorker = backgroundWorker; + } + + [HttpGet] + //[ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] + public IActionResult GetAll() + { + var result = new { + CurrentWork = (BackgroundWorkDto?)backgroundWorker.CurrentWork, + RunOnceQueue = backgroundWorker.WorkStore.RunOnceQueue.Select(work => (BackgroundWorkDto)work), + Periodics = backgroundWorker.WorkStore.Periodics.Select(work => (BackgroundWorkDto)work.Work), + Felled = backgroundWorker.WorkStore.Felled.Select(work => (BackgroundWorkDto)work), + }; + return Ok(result); + } + } +} diff --git a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs index 143055e5..962e5e0f 100644 --- a/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs +++ b/AsbCloudWebApi/SignalR/Services/SignalRNotificationTransportService.cs @@ -33,7 +33,7 @@ public class SignalRNotificationTransportService : INotificationTransportService return Task.CompletedTask; var workAction = MakeSignalRSendWorkAction(notifications); - var work = new Work(workId, workAction); + var work = Work.CreateByDelegate(workId, workAction); backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work); return Task.CompletedTask; diff --git a/ConsoleApp1/DebugWellOperationsStatService.cs b/ConsoleApp1/DebugWellOperationsStatService.cs deleted file mode 100644 index acb6452c..00000000 --- a/ConsoleApp1/DebugWellOperationsStatService.cs +++ /dev/null @@ -1,40 +0,0 @@ -using AsbCloudApp.Data; -using System; -using System.Collections.Generic; - -namespace ConsoleApp1 -{ - public static class DebugWellOperationsStatService - { - public static void Main(/*string[] args*/) - { - //var options = new DbContextOptionsBuilder() - // .UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True") - // .Options; - //using var db = new AsbCloudDbContext(options); - //var cacheDb = new CacheDb(); - //var telemetryService = new TelemetryService(db, new TelemetryTracker(cacheDb), cacheDb); - //var wellService = new WellService(db, telemetryService, cacheDb); - //var wellOptsStat = new OperationsStatService(db, cacheDb, wellService); - //var tvd = wellOptsStat.GetTvdAsync(1, default).Result; - //Print(tvd); - } - - private static void Print(IEnumerable> tvd) - { - Console.WriteLine("|\tplan\t|\tfact\t|\tprog\t|"); - Console.WriteLine("|:-------------:|:-------------:|:-------------:|"); - foreach (var item in tvd) - Print(item); - } - - private static void Print(PlanFactPredictBase item) - { - static string GetText(WellOperationDto item) - => (item is null) - ? " --------- " - : $"{item.IdCategory} d:{item.DepthStart} "; - Console.WriteLine($"|\t{GetText(item.Plan)}\t|\t{GetText(item.Fact)}\t|\t{GetText(item.Predict)}\t|"); - } - } -} From 1f79a8f5f71c813bb01a4efdbfa27e53b42059b0 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Tue, 10 Oct 2023 11:24:04 +0500 Subject: [PATCH 05/13] Fix WorkLimitingParameterCalc sql query --- .../WorkLimitingParameterCalc.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) rename AsbCloudInfrastructure/{Services => Background}/WorkLimitingParameterCalc.cs (92%) diff --git a/AsbCloudInfrastructure/Services/WorkLimitingParameterCalc.cs b/AsbCloudInfrastructure/Background/WorkLimitingParameterCalc.cs similarity index 92% rename from AsbCloudInfrastructure/Services/WorkLimitingParameterCalc.cs rename to AsbCloudInfrastructure/Background/WorkLimitingParameterCalc.cs index 3ab159e0..76749e07 100644 --- a/AsbCloudInfrastructure/Services/WorkLimitingParameterCalc.cs +++ b/AsbCloudInfrastructure/Background/WorkLimitingParameterCalc.cs @@ -7,14 +7,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; -using AsbCloudInfrastructure.Background; using Microsoft.Extensions.DependencyInjection; -namespace AsbCloudInfrastructure.Services; +namespace AsbCloudInfrastructure.Background; public class WorkLimitingParameterCalc : Work { - public WorkLimitingParameterCalc() + public WorkLimitingParameterCalc() : base("Limiting parameter calc") { Timeout = TimeSpan.FromMinutes(30); @@ -51,7 +50,7 @@ public class WorkLimitingParameterCalc : Work var i = 0d; foreach (var item in telemetryLastDetectedDates) { - onProgressCallback($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++/count); + onProgressCallback($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count); var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); if (newLimitingParameters?.Any() == true) { @@ -63,7 +62,7 @@ public class WorkLimitingParameterCalc : Work private static async Task> GetLimitingParameterAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) { - var query = + var query = $"select " + $"limiting_parameters.date, limiting_parameters.id_feed_regulator, limiting_parameters.well_depth " + $"from ( " + @@ -72,9 +71,9 @@ public class WorkLimitingParameterCalc : Work $"lag(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lag, " + $"lead(id_feed_regulator, 1) over (order by date) as id_feed_regulator_lead " + $"from t_telemetry_data_saub " + - $"where id_feed_regulator is not null " + - $"and id_telemetry = {idTelemetry}" + - $"and date >= '{begin:u}'" + + $"where id_feed_regulator is not null " + + $"and id_telemetry = {idTelemetry} " + + $"and date >= '{begin:u}' " + $"order by date) as limiting_parameters " + $"where id_feed_regulator_lag is null " + $"or (id_feed_regulator != id_feed_regulator_lag and id_feed_regulator_lead != id_feed_regulator_lag) " + @@ -102,14 +101,15 @@ public class WorkLimitingParameterCalc : Work if (limitingLast.IdFeedRegulator != idLimiting || limitingLast.DepthStart < wellDepth) { - limitingParameters.Add(new LimitingParameter { + limitingParameters.Add(new LimitingParameter + { IdTelemetry = idTelemetry, IdFeedRegulator = limitingLast.IdFeedRegulator, DateStart = limitingLast.DateStart, DateEnd = date, DepthStart = limitingLast.DepthStart, DepthEnd = wellDepth - }); + }); limitingLast = new LimitingParameter { From d7aea0f5833f2ba770cc59e505c50b6dfbc59b96 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Tue, 10 Oct 2023 13:45:30 +0500 Subject: [PATCH 06/13] WorkWellInfoUpdate inceace Timeout to 30 min --- AsbCloudInfrastructure/Services/WellInfoService.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/AsbCloudInfrastructure/Services/WellInfoService.cs b/AsbCloudInfrastructure/Services/WellInfoService.cs index 7e4cd8ce..0632bafc 100644 --- a/AsbCloudInfrastructure/Services/WellInfoService.cs +++ b/AsbCloudInfrastructure/Services/WellInfoService.cs @@ -27,7 +27,7 @@ public class WellInfoService public WorkWellInfoUpdate() : base("Well statistics update") { - Timeout = TimeSpan.FromMinutes(20); + Timeout = TimeSpan.FromMinutes(30); } protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) @@ -40,8 +40,9 @@ public class WellInfoService var messageHub = services.GetRequiredService>(); var wells = await wellService.GetAllAsync(token); + var activeWells = wells.Where(well => well.IdState == 1); - var wellsIds = wells.Select(w => w.Id); + var wellsIds = activeWells.Select(w => w.Id); var processMapRequests = wellsIds.Select(id => new ProcessMapRequest { IdWell = id }); var processMaps = await processMapRepository.GetProcessMapAsync(processMapRequests, token); @@ -58,9 +59,9 @@ public class WellInfoService var subsystemStat = await subsystemOperationTimeService .GetStatByActiveWells(wellsIds, token); - var count = wells.Count(); + var count = activeWells.Count(); var i = 0d; - WellMapInfo = wells.Select(well => { + WellMapInfo = activeWells.Select(well => { var wellMapInfo = well.Adapt(); wellMapInfo.IdState = well.IdState; onProgressCallback($"Start updating info by well({well.Id}): {well.Caption}", i++ / count); From c5ee0eeb393909e012f77b5d195f84b81345152b Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Tue, 10 Oct 2023 13:45:48 +0500 Subject: [PATCH 07/13] Split BackgroundWorkController --- ...undWork.cs => BackgroundWorkController.cs} | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) rename AsbCloudWebApi/Controllers/{BackgroundWork.cs => BackgroundWorkController.cs} (63%) diff --git a/AsbCloudWebApi/Controllers/BackgroundWork.cs b/AsbCloudWebApi/Controllers/BackgroundWorkController.cs similarity index 63% rename from AsbCloudWebApi/Controllers/BackgroundWork.cs rename to AsbCloudWebApi/Controllers/BackgroundWorkController.cs index 316d796e..65e7171b 100644 --- a/AsbCloudWebApi/Controllers/BackgroundWork.cs +++ b/AsbCloudWebApi/Controllers/BackgroundWorkController.cs @@ -9,17 +9,16 @@ namespace AsbCloudWebApi.Controllers [Route("api/[controller]")] [Authorize] [ApiController] - public class BackgroundWork : ControllerBase + public class BackgroundWorkController : ControllerBase { private readonly BackgroundWorker backgroundWorker; - public BackgroundWork(BackgroundWorker backgroundWorker) + public BackgroundWorkController(BackgroundWorker backgroundWorker) { this.backgroundWorker = backgroundWorker; } [HttpGet] - //[ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] public IActionResult GetAll() { var result = new { @@ -30,5 +29,19 @@ namespace AsbCloudWebApi.Controllers }; return Ok(result); } + + [HttpGet("Current")] + public IActionResult GetCurrent() + { + var result = (BackgroundWorkDto?)backgroundWorker.CurrentWork; + return Ok(result); + } + + [HttpGet("Failed")] + public IActionResult GetFelled() + { + var result = backgroundWorker.WorkStore.Felled.Select(work => (BackgroundWorkDto)work); + return Ok(result); + } } } From 95238b9e3e4a274c3d5dcb08c0228dd84476fee9 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Tue, 10 Oct 2023 13:49:07 +0500 Subject: [PATCH 08/13] SubsystemOperationTimeController Remove validation --- .../SubsystemOperationTimeController.cs | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs b/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs index 39d60ea9..efa60638 100644 --- a/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs +++ b/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs @@ -1,11 +1,9 @@ using AsbCloudApp.Data; using AsbCloudApp.Data.Subsystems; -using AsbCloudApp.Exceptions; using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudApp.Services.Subsystems; using AsbCloudDb.Model; -using AsbCloudInfrastructure; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System; @@ -56,7 +54,6 @@ namespace AsbCloudWebApi.Controllers.Subsystems { if (!await UserHasAccesToWellAsync(request.IdWell, token)) return Forbid(); - await CustomValidate(request, token); var subsystemResult = await subsystemOperationTimeService.GetStatAsync(request, token); return Ok(subsystemResult); } @@ -133,7 +130,6 @@ namespace AsbCloudWebApi.Controllers.Subsystems { if (!await UserHasAccesToWellAsync(request.IdWell, token)) return Forbid(); - await CustomValidate(request, token); var result = await subsystemOperationTimeService.GetOperationTimeAsync(request, token); return Ok(result); @@ -155,7 +151,6 @@ namespace AsbCloudWebApi.Controllers.Subsystems { if (!await UserHasAccesToWellAsync(request.IdWell, token)) return Forbid(); - await CustomValidate(request, token); var result = await subsystemOperationTimeService.DeleteAsync(request, token); return Ok(result); } @@ -180,24 +175,5 @@ namespace AsbCloudWebApi.Controllers.Subsystems return true; return false; } - - /// - /// Валидирует запрос и бросает исключение ArgumentInvalidException - /// - /// - /// - /// - /// - private async Task CustomValidate(SubsystemOperationTimeRequest request, CancellationToken token) - { - var well = await wellService.GetOrDefaultAsync(request.IdWell, token); - if (well is not null && request.LtDate.HasValue) - { - var ltDate = request.LtDate.Value; - var utcDateRequest = ltDate.ToUtcDateTimeOffset(well.Timezone.Hours); - if (utcDateRequest.AddHours(2) > DateTime.UtcNow) - throw new ArgumentInvalidException(nameof(request.LtDate), "Запрашиваемый диапазон должен заканчиваться за 2 часа до текущего времени"); - } - } } } From 0e3a0d90cc4dc75e3f1a599fb131570063ffaa4b Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Tue, 10 Oct 2023 15:06:58 +0500 Subject: [PATCH 09/13] optimize WorkWellInfoUpdate --- AsbCloudInfrastructure/Services/WellInfoService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AsbCloudInfrastructure/Services/WellInfoService.cs b/AsbCloudInfrastructure/Services/WellInfoService.cs index 0632bafc..bbaba879 100644 --- a/AsbCloudInfrastructure/Services/WellInfoService.cs +++ b/AsbCloudInfrastructure/Services/WellInfoService.cs @@ -59,6 +59,8 @@ public class WellInfoService var subsystemStat = await subsystemOperationTimeService .GetStatByActiveWells(wellsIds, token); + subsystemStat = subsystemStat.ToArray(); + var count = activeWells.Count(); var i = 0d; WellMapInfo = activeWells.Select(well => { From 4e58841619798adfa1a0f43a7c127cf74f28f250 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Thu, 12 Oct 2023 11:48:55 +0500 Subject: [PATCH 10/13] Add MockController to mock http-400 --- .../Controllers/BackgroundWorkController.cs | 7 +++- AsbCloudWebApi/Controllers/Mock.cs | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 AsbCloudWebApi/Controllers/Mock.cs diff --git a/AsbCloudWebApi/Controllers/BackgroundWorkController.cs b/AsbCloudWebApi/Controllers/BackgroundWorkController.cs index 65e7171b..a0bf6560 100644 --- a/AsbCloudWebApi/Controllers/BackgroundWorkController.cs +++ b/AsbCloudWebApi/Controllers/BackgroundWorkController.cs @@ -33,8 +33,11 @@ namespace AsbCloudWebApi.Controllers [HttpGet("Current")] public IActionResult GetCurrent() { - var result = (BackgroundWorkDto?)backgroundWorker.CurrentWork; - return Ok(result); + var work = backgroundWorker.CurrentWork; + if (work == null) + return NoContent(); + + return Ok(work); } [HttpGet("Failed")] diff --git a/AsbCloudWebApi/Controllers/Mock.cs b/AsbCloudWebApi/Controllers/Mock.cs new file mode 100644 index 00000000..910b1428 --- /dev/null +++ b/AsbCloudWebApi/Controllers/Mock.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace AsbCloudWebApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class MockController : ControllerBase + { + /// + /// получить статистику + /// + [HttpGet("400")] + [ProducesResponseType(typeof(ValidationProblemDetails), (int)System.Net.HttpStatusCode.BadRequest)] + public IActionResult Get([FromQuery, Required]IDictionary args) + { + var errors = new Dictionary(); + + foreach (var arg in args) + { + var countOfErrors = ((arg.Key + arg.Value).Length % 3) + 1; + var errorsText = Enumerable.Range(0, countOfErrors) + .Select(i => $"{arg.Value} не соответствует критериям проверки № {i}"); + + errors.Add(arg.Key, errorsText.ToArray()); + } + + if (errors.Any()) + { + var problem = new ValidationProblemDetails(errors); + return BadRequest(problem); + } + else + { + var problem = new ValidationProblemDetails { Detail = "at least one argument must be provided" }; + return BadRequest(problem); + } + } + } +} From fd5deaa5a2747cf2e4d6e3d6038601438d2c8547 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Thu, 12 Oct 2023 12:13:52 +0500 Subject: [PATCH 11/13] Add Mock controller --- .../{Mock.cs => MockController.cs} | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) rename AsbCloudWebApi/Controllers/{Mock.cs => MockController.cs} (60%) diff --git a/AsbCloudWebApi/Controllers/Mock.cs b/AsbCloudWebApi/Controllers/MockController.cs similarity index 60% rename from AsbCloudWebApi/Controllers/Mock.cs rename to AsbCloudWebApi/Controllers/MockController.cs index 910b1428..0c95725e 100644 --- a/AsbCloudWebApi/Controllers/Mock.cs +++ b/AsbCloudWebApi/Controllers/MockController.cs @@ -5,16 +5,19 @@ using System.Linq; namespace AsbCloudWebApi.Controllers { + /// + /// Имитирует разные типы ответа сервера + /// [Route("api/[controller]")] [ApiController] public class MockController : ControllerBase { /// - /// получить статистику + /// имитирует http-400 /// [HttpGet("400")] [ProducesResponseType(typeof(ValidationProblemDetails), (int)System.Net.HttpStatusCode.BadRequest)] - public IActionResult Get([FromQuery, Required]IDictionary args) + public IActionResult Get400([FromQuery, Required]IDictionary args) { var errors = new Dictionary(); @@ -38,5 +41,32 @@ namespace AsbCloudWebApi.Controllers return BadRequest(problem); } } + + /// + /// имитирует http-403 + /// + [HttpGet("403")] + public IActionResult Get403() + { + return Forbid(); + } + + /// + /// имитирует http-401 + /// + [HttpGet("401")] + public IActionResult Get401() + { + return Unauthorized(); + } + + /// + /// имитирует http-500 + /// + [HttpGet("500")] + public IActionResult Get500() + { + throw new System.Exception("Это тестовое исключение"); + } } } From d99d1a27a12bea36a65593851d403b923b02f178 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Thu, 12 Oct 2023 12:45:19 +0500 Subject: [PATCH 12/13] =?UTF-8?q?Work=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5?= =?UTF-8?q?=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=B2=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BE=D0=B1=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AsbCloudInfrastructure/Background/Work.cs | 29 ++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/AsbCloudInfrastructure/Background/Work.cs b/AsbCloudInfrastructure/Background/Work.cs index 9372343b..eba7df66 100644 --- a/AsbCloudInfrastructure/Background/Work.cs +++ b/AsbCloudInfrastructure/Background/Work.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Data; using System; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -74,7 +75,8 @@ public abstract class Work : BackgroundWorkDto } catch (Exception exception) { - SetLastError(exception.Message); + var message = FormatExceptionMessage(exception); + SetLastError(message); if (OnErrorAsync is not null) { var task = Task.Run( @@ -86,6 +88,31 @@ public abstract class Work : BackgroundWorkDto return false; } + private static string FormatExceptionMessage(Exception exception) + { + var firstException = FirstException(exception); + var message = new StringBuilder(); + if (firstException != exception) + { + message.Append("top exception:"); + message.AppendLine(exception.Message); + + message.Append("inner exception:"); + message.AppendLine(firstException.Message); + } + else + message.AppendLine(firstException.Message); + message.AppendLine(exception.StackTrace); + return message.ToString(); + } + + private static Exception FirstException(Exception exception) + { + if (exception.InnerException is not null) + return FirstException(exception.InnerException); + return exception; + } + /// /// делегат фоновой работы /// From 6c722e5478dc05433586f0046cba20e23353dbd7 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Thu, 12 Oct 2023 17:54:05 +0500 Subject: [PATCH 13/13] =?UTF-8?q?=D1=83=D0=B2=D0=B5=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B8=D0=BB=20timeout=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D0=B0=20=D0=91=D0=94=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=84=D0=BE=D0=BD=D0=BE=D0=B2=D1=8B=D1=85=20=D0=B7=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D1=87.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Background/WorkLimitingParameterCalc.cs | 1 + .../Subsystems/WorkSubsystemOperationTimeCalc.cs | 13 +++++++------ AsbCloudInfrastructure/Startup.cs | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/AsbCloudInfrastructure/Background/WorkLimitingParameterCalc.cs b/AsbCloudInfrastructure/Background/WorkLimitingParameterCalc.cs index 76749e07..c0d6eb48 100644 --- a/AsbCloudInfrastructure/Background/WorkLimitingParameterCalc.cs +++ b/AsbCloudInfrastructure/Background/WorkLimitingParameterCalc.cs @@ -22,6 +22,7 @@ public class WorkLimitingParameterCalc : Work protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) { using var db = services.GetRequiredService(); + db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); var lastDetectedDates = await db.LimitingParameter .GroupBy(o => o.IdTelemetry) .Select(g => new diff --git a/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs b/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs index bda4bbe4..5b9d00f8 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/WorkSubsystemOperationTimeCalc.cs @@ -15,7 +15,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.Subsystems; -public class WorkSubsystemOperationTimeCalc: Work +public class WorkSubsystemOperationTimeCalc : Work { private const int idSubsytemTorqueMaster = 65537; private const int idSubsytemSpinMaster = 65536; @@ -23,7 +23,7 @@ public class WorkSubsystemOperationTimeCalc: Work private const int idSubsystemAPDSlide = 12; private const int idSubsytemMse = 2; - public WorkSubsystemOperationTimeCalc() + public WorkSubsystemOperationTimeCalc() : base("Subsystem operation time calc") { Timeout = TimeSpan.FromMinutes(20); @@ -32,7 +32,7 @@ public class WorkSubsystemOperationTimeCalc: Work protected override async Task Action(string id, IServiceProvider services, Action onProgressCallback, CancellationToken token) { using var db = services.GetRequiredService(); - + db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); var lastDetectedDates = await db.SubsystemOperationTimes .GroupBy(o => o.IdTelemetry) .Select(g => new @@ -61,7 +61,7 @@ public class WorkSubsystemOperationTimeCalc: Work var i = 0d; foreach (var item in telemetryLastDetectedDates) { - onProgressCallback($"Start hanling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count); + onProgressCallback($"Start handling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count); var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); if (newOperationsSaub?.Any() == true) { @@ -114,7 +114,7 @@ public class WorkSubsystemOperationTimeCalc: Work $" lag(mode,1) over (order by date) as mode_lag, " + $" lead(mode,1) over (order by date) as mode_lead " + $" from t_telemetry_data_saub " + - $" where id_telemetry = {idTelemetry} and well_depth is not null and well_depth > 0" + + $" where id_telemetry = {idTelemetry} and well_depth is not null and well_depth > 0 " + $" order by date ) as tt " + $"where (tt.mode_lag is null or (tt.mode != tt.mode_lag and tt.mode_lead != tt.mode_lag)) and tt.date >= '{begin:u}' " + $"order by tt.date;"; @@ -278,7 +278,8 @@ public class WorkSubsystemOperationTimeCalc: Work .Where(d => d.WellDepth != null) .Where(d => d.WellDepth > 0) .GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10)) - .Select(g => new { + .Select(g => new + { DateMin = g.Min(d => d.DateTime), DepthMin = g.Min(d => d.WellDepth) ?? 0, }) diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index d00de488..9aff9ca9 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -1,7 +1,6 @@ using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Services.DetectOperations; -using AsbCloudInfrastructure.Services.Subsystems; using AsbCloudInfrastructure.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -12,6 +11,7 @@ using System.Threading; using AsbCloudInfrastructure.Background; using AsbCloudApp.Data.SAUB; using AsbCloudInfrastructure.Services.SAUB; +using AsbCloudInfrastructure.Services.Subsystems; namespace AsbCloudInfrastructure { @@ -23,7 +23,7 @@ namespace AsbCloudInfrastructure var provider = scope.ServiceProvider; var context = provider.GetRequiredService(); - context.Database.SetCommandTimeout(TimeSpan.FromSeconds(2 * 60)); + context.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); context.Database.Migrate(); // TODO: Сделать инициализацию кеша телеметрии более явной.