Files
mapo-iob-man/IOB-MAN.Core/StatsCollector.cs
T
Samuele Locatelli 943ef13752 - update comments on statsCollector
- update yaml robocopy html
2025-11-28 10:07:38 +01:00

229 lines
8.0 KiB
C#

/// <summary>
/// StatsCollector provides real-time performance and operational statistics for the application.
///
/// This class collects and tracks execution metrics (e.g., duration, frequency, last call) for various functions.
/// It is used to generate detailed reports on application behavior, including startup time, uptime, and function performance.
///
/// Key Features:
/// - Tracks execution duration and frequency of functions (e.g., service checks, data scans).
/// - Calculates uptime and startup timestamp.
/// - Formats time durations in readable units (ns, ms, sec, min).
/// - Uses thread-safe concurrent dictionaries to ensure safe access in multi-threaded environments.
/// - Exposes a dictionary of statistics for use in reporting or logging.
/// </summary>
using System.Collections.Concurrent;
namespace IOB_MAN.Core
{
/// <summary>
/// Object responsible for collecting and reporting application-level statistics.
/// Used to monitor function execution times, frequency, and runtime behavior.
/// </summary>
public class StatsCollector
{
#region Public Properties
/// <summary>
/// Calculates and returns the current uptime of the application process.
///
/// Format:
/// - If over 1 day: "DD d HHh MMm"
/// - Otherwise: "HHh MMm SS s"
///
/// Example: "2d 3h 45m" or "1h 23m 15 s"
/// </summary>
public static string UptimeCurr
{
get
{
string answ = "ND";
var upT = DateTime.Now.Subtract(StartTime);
// Format based on duration
if (upT.TotalDays > 1)
{
answ = $"{upT.Days:00} d {upT.Hours:00}h {upT.Minutes:00}m";
}
else
{
answ = $"{upT.Hours:00}h {upT.Minutes:00}m {upT.Seconds:00} s";
}
return answ;
}
}
#endregion Public Properties
#region Public Methods
/// <summary>
/// Returns a dictionary containing all collected statistics for each function.
///
/// Each entry includes:
/// - The count of executions.
/// - The average execution duration (formatted).
/// - The timestamp of the last execution.
///
/// Example:
/// {
/// "ScanIOB": "15 x 2.345 sec",
/// "ScanIOBLast": "2025-04-05 10:30:22",
/// "Startup": "2025-04-05 09:00:00",
/// "Uptime": "2d 3h 45m"
/// }
/// </summary>
/// <returns>A dictionary of function-level statistics.</returns>
public static Dictionary<string, string> CurrentInfo()
{
Dictionary<string, string> result = new Dictionary<string, string>();
// Iterate over all tracked functions
foreach (var item in Counter)
{
string executionCount = $"{Counter[item.Key]} x {formElaps((Duration[item.Key]) / Counter[item.Key])}";
result.Add(item.Key, executionCount);
result.Add($"{item.Key}Last", $"{LastCall[item.Key]:yyyy-MM-dd HH:mm:ss}");
}
// Add startup and uptime metadata
result.Add("Startup", $"{StartTime:yyyy-MM-dd HH:mm:ss}");
result.Add("Uptime", UptimeCurr);
return result;
}
/// <summary>
/// Resets all internal statistics to their initial state.
///
/// Actions:
/// - Sets the new start time to current moment.
/// - Clears all counters, durations, and last-call timestamps.
///
/// Use case: Called during application restart, config reload, or debug reset.
/// </summary>
public static void ResetData()
{
StartTime = DateTime.Now;
Counter = new ConcurrentDictionary<string, long>();
Duration = new ConcurrentDictionary<string, TimeSpan>();
LastCall = new ConcurrentDictionary<string, DateTime>();
}
/// <summary>
/// Updates the execution statistics for a specific function.
///
/// Parameters:
/// - functionName: Name of the function (e.g., "DoScan", "CheckMemory").
/// - elaps: Duration of the function execution (e.g., 123.45 ms).
///
/// Behavior:
/// - Accumulates duration and execution count.
/// - Records the current timestamp as the last call.
///
/// Thread Safety:
/// - Uses concurrent dictionaries to safely handle updates from multiple threads.
/// </summary>
/// <param name="functionName">The name of the function being executed.</param>
/// <param name="elaps">The elapsed time of the function execution.</param>
public static void UpdateStat(string functionName, TimeSpan elaps)
{
// Accumulate execution duration
if (!Duration.ContainsKey(functionName))
{
Duration.TryAdd(functionName, elaps);
}
else
{
Duration[functionName] += elaps;
}
// Increment execution counter
if (!Counter.ContainsKey(functionName))
{
Counter.TryAdd(functionName, 1);
}
else
{
Counter[functionName]++;
}
// Record timestamp of last execution
DateTime adesso = DateTime.Now;
if (!LastCall.ContainsKey(functionName))
{
LastCall.TryAdd(functionName, adesso);
}
else
{
LastCall[functionName] = adesso;
}
}
#endregion Public Methods
#region Private Fields
/// <summary>
/// Thread-safe dictionary tracking how many times each function has been executed.
/// </summary>
private static ConcurrentDictionary<string, long> Counter = new ConcurrentDictionary<string, long>();
/// <summary>
/// Thread-safe dictionary storing the total duration (summed) of each function's execution.
/// </summary>
private static ConcurrentDictionary<string, TimeSpan> Duration = new ConcurrentDictionary<string, TimeSpan>();
/// <summary>
/// Thread-safe dictionary storing the timestamp of the last execution of each function.
/// </summary>
private static ConcurrentDictionary<string, DateTime> LastCall = new ConcurrentDictionary<string, DateTime>();
/// <summary>
/// Timestamp when the application started.
/// Used to calculate uptime and startup time.
/// </summary>
private static DateTime StartTime = DateTime.Now;
#endregion Private Fields
#region Private Methods
/// <summary>
/// Formats a time span into a human-readable string based on magnitude.
///
/// Format logic:
/// - Less than 1 ms → "X.XXX ns"
/// - 1 ms to 1 sec → "X.X ms"
/// - 1 sec to 300 sec → "X.X sec"
/// - Over 300 sec → "X.X min"
///
/// Example: 123.456 ms → "123.456 ms", 1.234 sec → "1.234 sec", 360 sec → "6.0 min"
/// </summary>
/// <param name="ts">The time span to format.</param>
/// <returns>Formatted time string.</returns>
private static string formElaps(TimeSpan ts)
{
string answ = "";
if (ts.Milliseconds < 1)
{
answ = $"{ts.TotalMilliseconds:N3} ns";
}
else if (ts.TotalSeconds < 1)
{
answ = $"{ts.TotalMilliseconds:N1} ms";
}
else if (ts.TotalSeconds < 300)
{
answ = $"{ts.TotalSeconds:N1} sec";
}
else
{
answ = $"{ts.TotalMinutes:N1} min";
}
return answ;
}
#endregion Private Methods
}
}