-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathExecutionTimeSampler.cs
More file actions
109 lines (89 loc) · 3.51 KB
/
ExecutionTimeSampler.cs
File metadata and controls
109 lines (89 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace SharpHoundCommonLib;
/// <summary>
/// Holds a rolling sample of execution times on a function, providing and logging data aggregates.
/// </summary>
public class ExecutionTimeSampler : IDisposable {
private readonly ILogger _log;
private readonly int _sampleCount;
private readonly int _logFrequency;
private int _samplesSinceLastLog;
private ConcurrentQueue<double> _samples;
public int Count => _samples.Count;
public ExecutionTimeSampler(ILogger log, int sampleCount, int logFrequency) {
_log = log;
_sampleCount = sampleCount;
_logFrequency = logFrequency;
_samplesSinceLastLog = 0;
_samples = new ConcurrentQueue<double>();
}
public void ClearSamples() {
Log(flush: true);
_samples = new ConcurrentQueue<double>();
}
public double StandardDeviation() {
double average = _samples.Average();
double sumOfSquaresOfDifferences = _samples.Select(val => (val - average) * (val - average)).Sum();
double stddiv = Math.Sqrt(sumOfSquaresOfDifferences / _samples.Count);
return stddiv;
}
public double Average() => _samples.Average();
public async Task<T> SampleExecutionTime<T>(Func<Task<T>> func, Action<double> latencyObservation = null) {
var stopwatch = Stopwatch.StartNew();
var result = await func.Invoke();
stopwatch.Stop();
latencyObservation?.Invoke(stopwatch.ElapsedMilliseconds);
AddTimeSample(stopwatch.Elapsed);
return result;
}
public async Task SampleExecutionTime(Func<Task> func, Action<double> latencyObservation = null) {
var stopwatch = Stopwatch.StartNew();
await func.Invoke();
stopwatch.Stop();
latencyObservation?.Invoke(stopwatch.ElapsedMilliseconds);
AddTimeSample(stopwatch.Elapsed);
}
public T SampleExecutionTime<T>(Func<T> func, Action<double> latencyObservation = null) {
var stopwatch = Stopwatch.StartNew();
var result = func.Invoke();
stopwatch.Stop();
latencyObservation?.Invoke(stopwatch.ElapsedMilliseconds);
AddTimeSample(stopwatch.Elapsed);
return result;
}
public void SampleExecutionTime(Action func, Action<double> latencyObservation = null) {
var stopwatch = Stopwatch.StartNew();
func.Invoke();
stopwatch.Stop();
latencyObservation?.Invoke(stopwatch.ElapsedMilliseconds);
AddTimeSample(stopwatch.Elapsed);
}
public void Dispose() {
Log(flush: true);
}
private void AddTimeSample(TimeSpan timeSpan) {
while (_samples.Count >= _sampleCount) {
_samples.TryDequeue(out _);
}
_samples.Enqueue(timeSpan.TotalMilliseconds);
Interlocked.Increment(ref _samplesSinceLastLog);
Log();
}
private void Log(bool flush = false) {
if ((flush || _samplesSinceLastLog >= _logFrequency) && _samples.Count > 0) {
try {
_log.LogInformation("Execution time Average: {Average}ms, StdDiv: {StandardDeviation}ms", _samples.Average(), StandardDeviation());
}
catch (Exception ex) {
_log.LogWarning("Failed to calculate execution time statistics: {Error}", ex.Message);
}
Interlocked.Exchange(ref _samplesSinceLastLog, 0);
}
}
}