Improve speed stats, UI usability, and settings persistence

Enhanced speed counter with smoothing and elapsed time display.
Added folder open buttons to UI and repositioned speed label.
Added UpdateSubdirectories to settings for persistence.
This commit is contained in:
MaddoScientisto 2026-02-14 19:36:58 +01:00
commit 6ccbec890a
3 changed files with 103 additions and 28 deletions

View file

@ -849,6 +849,11 @@ namespace ImageCatalog_2
// Atomic counter for processed images — avoids expensive ConcurrentBag.Count enumerations
private int _processedAtomic = 0;
private System.Threading.Timer? _speedTimer;
// Stopwatch used to compute run-wide averages
private Stopwatch? _speedWatch;
// Recent diffs queue to smooth short-term fluctuations
private readonly Queue<int> _recentDiffs = new();
private int _recentWindowSize = 5; // average over last 5 samples (~5s)
private void Test(object parameter)
{
@ -907,6 +912,8 @@ namespace ImageCatalog_2
_processedAtomic = 0;
// Start speed timer (sample every second using lightweight atomic reads)
_speedWatch = Stopwatch.StartNew();
_recentDiffs.Clear();
_speedTimer = new System.Threading.Timer(UpdateSpeedCounter, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
var time = await _imageCreationService.CreaCatalogoParallel(
@ -915,9 +922,28 @@ namespace ImageCatalog_2
OnImageProcessed,
token);
SpeedCounter = time;
// Compute final averages and show only averages (do not show raw seconds)
var finalProcessed = System.Threading.Volatile.Read(ref _processedAtomic);
double overallAvg = 0.0;
double overallPerMin = 0.0;
if (_speedWatch is not null && _speedWatch.Elapsed.TotalSeconds > 0.0)
{
overallAvg = finalProcessed / _speedWatch.Elapsed.TotalSeconds;
overallPerMin = overallAvg * 60.0;
}
// Compute elapsed time as h m s and show final averages (no raw seconds parentheses)
var finalElapsed = _speedWatch?.Elapsed ?? TimeSpan.Zero;
int fh = (int)finalElapsed.TotalHours;
int fm = finalElapsed.Minutes;
int fs = finalElapsed.Seconds;
SpeedCounter = $"{fh}h {fm}m {fs}s{Environment.NewLine}media: {overallAvg:0.00} f/s{Environment.NewLine}media: {overallPerMin:0.00} f/m";
_speedTimer?.Dispose();
_speedTimer = null;
_speedWatch?.Stop();
_speedWatch = null;
}
catch (OperationCanceledException)
{
@ -942,12 +968,57 @@ namespace ImageCatalog_2
private void UpdateSpeedCounter(object? state)
{
_previousAmount = _currentAmount;
// Read the atomic counter without enumerating the ConcurrentBag
_currentAmount = System.Threading.Volatile.Read(ref _processedAtomic);
int diff = _currentAmount - _previousAmount;
// Report files per second (timer runs every 1s)
SpeedCounter = $"{diff} f/s";
try
{
_previousAmount = _currentAmount;
// Read the atomic counter without enumerating the ConcurrentBag
_currentAmount = System.Threading.Volatile.Read(ref _processedAtomic);
int diff = _currentAmount - _previousAmount;
// Protect against negative or spurious diffs
if (diff < 0) diff = 0;
// Maintain a small sliding window of recent diffs to smooth the display
lock (_recentDiffs)
{
_recentDiffs.Enqueue(diff);
if (_recentDiffs.Count > _recentWindowSize)
_recentDiffs.Dequeue();
}
double avgRecent;
lock (_recentDiffs)
{
avgRecent = _recentDiffs.Count == 0 ? 0.0 : _recentDiffs.Average();
}
// Compute overall average (since start) if we have a stopwatch
double overall = 0.0;
if (_speedWatch is not null && _speedWatch.Elapsed.TotalSeconds >= 1)
{
var elapsedSeconds = _speedWatch.Elapsed.TotalSeconds;
var total = System.Threading.Volatile.Read(ref _processedAtomic);
overall = elapsedSeconds > 0 ? total / elapsedSeconds : 0.0;
}
// Recent per-minute estimate
var recentPerMin = avgRecent * 60.0;
var overallPerMin = overall * 60.0;
// Build a two-line display plus elapsed time: first line shows f/s with overall media and elapsed time,
// second line shows recent photos per minute (media)
var elapsed = _speedWatch?.Elapsed ?? TimeSpan.Zero;
int hours = (int)elapsed.TotalHours;
int minutes = elapsed.Minutes;
int seconds = elapsed.Seconds;
var elapsedStr = $"{hours}h {minutes}m {seconds}s";
SpeedCounter = $"{avgRecent:0.00} f/s (media: {overall:0.00} f/s) - {elapsedStr}{Environment.NewLine}media: {recentPerMin:0.00} f/m";
}
catch
{
// Swallow unlikely errors from timing/queue operations but keep UI responsive
}
}
private void OnImageProcessed(object? sender, Tuple<string, int> args)

View file

@ -56,6 +56,8 @@ namespace ImageCatalog
Label8 = new Label();
Label7 = new Label();
GroupBox3 = new GroupBox();
btnOpenDestFolder = new Button();
btnOpenSourceFolder = new Button();
chkAggiornaSottodirectory = new CheckBox();
_Button3 = new Button();
_Button2 = new Button();
@ -181,8 +183,6 @@ namespace ImageCatalog
_btnCreaCatalogoAsync = new Button();
timer1 = new System.Windows.Forms.Timer(components);
dataModelBindingSource1 = new BindingSource(components);
btnOpenSourceFolder = new Button();
btnOpenDestFolder = new Button();
((System.ComponentModel.ISupportInitialize)bindingSource1).BeginInit();
((System.ComponentModel.ISupportInitialize)dataModelBindingSource).BeginInit();
TabControl1.SuspendLayout();
@ -245,7 +245,7 @@ namespace ImageCatalog
//
Label43.AutoSize = true;
Label43.DataBindings.Add(new Binding("Text", bindingSource1, "SpeedCounter", true));
Label43.Location = new Point(1073, 789);
Label43.Location = new Point(1074, 725);
Label43.Margin = new Padding(6, 0, 6, 0);
Label43.Name = "Label43";
Label43.Size = new Size(46, 30);
@ -418,6 +418,24 @@ namespace ImageCatalog
GroupBox3.TabStop = false;
GroupBox3.Text = "Directory";
//
// btnOpenDestFolder
//
btnOpenDestFolder.Location = new Point(939, 97);
btnOpenDestFolder.Margin = new Padding(6, 8, 6, 8);
btnOpenDestFolder.Name = "btnOpenDestFolder";
btnOpenDestFolder.Size = new Size(48, 35);
btnOpenDestFolder.TabIndex = 27;
btnOpenDestFolder.Text = "Apri";
//
// btnOpenSourceFolder
//
btnOpenSourceFolder.Location = new Point(939, 38);
btnOpenSourceFolder.Margin = new Padding(6, 8, 6, 8);
btnOpenSourceFolder.Name = "btnOpenSourceFolder";
btnOpenSourceFolder.Size = new Size(48, 35);
btnOpenSourceFolder.TabIndex = 26;
btnOpenSourceFolder.Text = "Apri";
//
// chkAggiornaSottodirectory
//
chkAggiornaSottodirectory.DataBindings.Add(new Binding("Checked", bindingSource1, "UpdateSubdirectories", true, DataSourceUpdateMode.OnPropertyChanged));
@ -1844,24 +1862,6 @@ namespace ImageCatalog
//
dataModelBindingSource1.DataSource = typeof(ImageCatalog_2.DataModel);
//
// btnOpenSourceFolder
//
btnOpenSourceFolder.Location = new Point(939, 38);
btnOpenSourceFolder.Margin = new Padding(6, 8, 6, 8);
btnOpenSourceFolder.Name = "btnOpenSourceFolder";
btnOpenSourceFolder.Size = new Size(48, 35);
btnOpenSourceFolder.TabIndex = 26;
btnOpenSourceFolder.Text = "Apri";
//
// btnOpenDestFolder
//
btnOpenDestFolder.Location = new Point(939, 97);
btnOpenDestFolder.Margin = new Padding(6, 8, 6, 8);
btnOpenDestFolder.Name = "btnOpenDestFolder";
btnOpenDestFolder.Size = new Size(48, 35);
btnOpenDestFolder.TabIndex = 27;
btnOpenDestFolder.Text = "Apri";
//
// MainForm
//
AutoScaleDimensions = new SizeF(12F, 30F);

View file

@ -177,6 +177,10 @@ namespace ImageCatalog_2.Models
[XmlElement("GeneraleRotazioneAutomatica")]
public bool AutomaticRotation { get; set; }
[JsonPropertyName("UpdateSubdirectories")]
[XmlElement("DirSottoDirectory")]
public bool UpdateSubdirectories { get; set; }
[JsonPropertyName("AddRaceTime")]
[XmlElement("TempoGara")]
public bool AddRaceTime { get; set; }