Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding NProg tracker class #138

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 225 additions & 0 deletions OrderCloud.Catalyst/Core/NProg.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace OrderCloud.Catalyst
{
public class Tracker
{
private long _startTime = DateTime.UtcNow.Ticks;
private long _endTime;
private int _total;
private int _started;
private int _succeeded;
private int _failed;

private readonly List<ProgressAction> _actions = new List<ProgressAction>();
private readonly List<Task> _tasks = new List<Task>();
private readonly List<TimerAction> _timers = new List<TimerAction>();

public Tracker() { }
public Tracker(int itemCount) => _total = itemCount;

public void Start()
{
_startTime = DateTime.UtcNow.Ticks;
_timers.ForEach(t => t.Start(this));
}

public void Stop()
{
_endTime = DateTime.UtcNow.Ticks;
_timers.ForEach(t => t.Stop());
}

public void Every(Trigger trigger, Action<Progress> action) => _actions.Add(new ProgressAction { Trigger = trigger, Action = action, Recurring = true });
public void On(Trigger trigger, Action<Progress> action) => _actions.Add(new ProgressAction { Trigger = trigger, Action = action, Recurring = false });
public void Every(TimeSpan interval, Action<Progress> action) => _timers.Add(new TimerAction { Interval = interval, Action = action });
public void OnComplete(Action<Progress> action) => On(_total.ItemsDone(), action);
public void Now(Action<Progress> action) => action(GetProgress());

public void Every(Trigger trigger, Func<Progress, Task> action) => _actions.Add(new ProgressAction { Trigger = trigger, AsyncAction = action, Recurring = true });
public void On(Trigger trigger, Func<Progress, Task> action) => _actions.Add(new ProgressAction { Trigger = trigger, AsyncAction = action, Recurring = false });
public void Every(TimeSpan interval, Func<Progress, Task> action) => _timers.Add(new TimerAction { Interval = interval, AsyncAction = action });
public void OnComplete(Func<Progress, Task> action) => On(_total.ItemsDone(), action);
public void Now(Func<Progress, Task> action) => action(GetProgress());

private readonly object _lock = new object();

public void ItemStarted() => ProcessTriggers(ref _started);
public void ItemSucceeded() => ProcessTriggers(ref _succeeded);
public void ItemFailed() => ProcessTriggers(ref _failed);

public void ItemsDiscovered(int count)
{
Interlocked.Add(ref _total, count);
}

/// <summary>
/// Returns a completion task that should be awaited if any async actions were triggered.
/// Async actions are NOT awaited inline.
/// </summary>
public Task CompleteAsync() => Task.WhenAll(_tasks);

public Progress GetProgress() => new Progress(_startTime, _endTime, _total, _started, _succeeded, _failed);

private void ProcessTriggers(ref int fieldToIncrement)
{
var fired = new List<ProgressAction>();
Progress prog;

lock (_lock)
{
fieldToIncrement++;
prog = GetProgress();
fired = _actions.Where(act => act.Trigger.IsFired(prog)).ToList();

foreach (var act in fired)
{
if (!act.Recurring)
_actions.Remove(act);
}

_tasks.RemoveAll(t => t.IsCompleted);
}

fired.ForEach(act => act.Invoke(prog, _tasks));
}

private abstract class ActionBase
{
public Action<Progress> Action { get; set; }
public Func<Progress, Task> AsyncAction { get; set; }

public void Invoke(Progress prog, IList<Task> tasks)
{
var task = AsyncAction?.Invoke(prog);
Action?.Invoke(prog);
if (task?.IsCompleted == false)
tasks.Add(task);
}
}

private class ProgressAction : ActionBase
{
public Trigger Trigger { get; set; }
public bool Recurring { get; set; }
}

private class TimerAction : ActionBase
{
private Timer _timer;

public TimeSpan Interval { get; set; }

public void Start(Tracker tracker)
{
_timer = new Timer(_ => Invoke(tracker.GetProgress(), tracker._tasks), null, Interval, Interval);
}

public void Stop()
{
_timer.Dispose();
}
}
}

public class Trigger
{
private readonly int _targetNumber;
private readonly Func<Progress, int> _getCurrentNumber;
private int _nextNumber;

public Trigger(int targetNumber, Func<Progress, int> getCurrentNumber)
{
_nextNumber = _targetNumber = targetNumber;
_getCurrentNumber = getCurrentNumber;
}

public bool IsFired(Progress prog)
{
if (_getCurrentNumber(prog) >= _nextNumber)
{
_nextNumber += _targetNumber;
return true;
}
return false;
}
}

public static class TriggerBuilderExtensions
{
public static Trigger ItemsStarted(this int i) => new Trigger(i, p => p.ItemsStarted);
public static Trigger ItemsDone(this int i) => new Trigger(i, p => p.ItemsDone);
public static Trigger ItemsSucceeded(this int i) => new Trigger(i, p => p.ItemsSucceeded);
public static Trigger ItemsFailed(this int i) => new Trigger(i, p => p.ItemsFailed);

public static Trigger PercentStarted(this int i) => new Trigger(i, p => p.PercentStarted);
public static Trigger PercentDone(this int i) => new Trigger(i, p => p.PercentDone);
public static Trigger PercentSucceeded(this int i) => new Trigger(i, p => p.PercentSucceeded);
public static Trigger PercentFailed(this int i) => new Trigger(i, p => p.PercentFailed);

public static TimeSpan Seconds(this int i) => TimeSpan.FromSeconds(i);
public static TimeSpan Minutes(this int i) => TimeSpan.FromMinutes(i);
public static TimeSpan Hours(this int i) => TimeSpan.FromHours(i);
}

public class Progress
{
private readonly long _startTime;
private readonly long _endTime;

public int TotalItems { get; }
public int ItemsStarted { get; }
public int ItemsSucceeded { get; }
public int ItemsFailed { get; }

public int ItemsDone => ItemsSucceeded + ItemsFailed;
public int ItemsInProgress => ItemsStarted - ItemsDone;
public int ItemsRemaining => TotalItems - ItemsStarted;

public int PercentStarted => SafeDivide(100 * ItemsStarted, TotalItems);
public int PercentDone => SafeDivide(100 * ItemsDone, TotalItems);
public int PercentSucceeded => SafeDivide(100 * ItemsSucceeded, TotalItems);
public int PercentFailed => SafeDivide(100 * ItemsFailed, TotalItems);
public int PercentInProgress => SafeDivide(100 * ItemsInProgress, TotalItems);
public int PercentRemaining => SafeDivide(100 * ItemsRemaining, TotalItems);

public double PercentStartedExact => SafeDivideExact(100 * ItemsStarted, TotalItems);
public double PercentDoneExact => SafeDivideExact(100 * ItemsDone, TotalItems);
public double PercentSucceededExact => SafeDivideExact(100 * ItemsSucceeded, TotalItems);
public double PercentFailedExact => SafeDivideExact(100 * ItemsFailed, TotalItems);
public double PercentInProgressExact => SafeDivideExact(100 * ItemsInProgress, TotalItems);
public double PercentRemainingExact => SafeDivideExact(100 * ItemsRemaining, TotalItems);

private long ElapsedTicks => (_endTime == 0 ? DateTime.UtcNow.Ticks : _endTime) - _startTime;

public TimeSpan ElapsedTime => TimeSpan.FromTicks(ElapsedTicks);
public int ElapsedSeconds => (int)ElapsedTime.TotalSeconds;
public int ElapsedMinutes => (int)ElapsedTime.TotalMinutes;
public int ElapsedHours => (int)ElapsedTime.TotalHours;

public TimeSpan EstTotalTime => TimeSpan.FromTicks(SafeDivide(ElapsedTicks * TotalItems, ItemsDone));
public TimeSpan EstTimeRemaining => EstTotalTime - ElapsedTime;
public DateTime EstEndTimeUtc => DateTime.UtcNow + EstTimeRemaining;
public DateTime EstEndTimeLocal => DateTime.Now + EstTimeRemaining;

public bool IsDone => ItemsDone == TotalItems;

public Progress(long startTime, long endTime, int total, int started, int succeeded, int failed)
{
_startTime = startTime;
_endTime = endTime;
TotalItems = total;
ItemsStarted = started;
ItemsSucceeded = succeeded;
ItemsFailed = failed;
}

private int SafeDivide(int x, int y) => y == 0 ? 0 : x / y;
private long SafeDivide(long x, long y) => y == 0 ? 0 : x / y;
private double SafeDivideExact(long x, long y) => y == 0 ? 0 : (double)x / y;
}
}
Loading