Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
54138e5
wip: stub MetricProcessor.cs
mykeelium Oct 1, 2025
addb5a8
wip: cleanup MetricProcessor and Writer
mykeelium Oct 2, 2025
8e1256c
chore: alter histogram value type
mykeelium Dec 29, 2025
dbe3475
fix: create Dictionary Directly
mykeelium Dec 29, 2025
9f8c679
Merge branch 'v4' into poc/metrics
mykeelium Dec 29, 2025
129c35c
wip: create platform for defining and observing metrics
mykeelium Dec 30, 2025
43d4aa7
wip: adding some ldap metrics to LdapUtils and LdapConnectionPool
mykeelium Dec 31, 2025
5f21f63
feat: add FileMetricSink, MetricsFlushTimer, and MetricWriter. Update…
mykeelium Dec 31, 2025
def2895
feat: refine metric logic
mykeelium Jan 5, 2026
3928eac
test: fix AdaptiveTimeout LatencyObservation, Add Tests
mykeelium Jan 5, 2026
454af11
test: fix AdaptiveTimeout LatencyObservation, Add MetricDefinitionTes…
mykeelium Jan 5, 2026
eaaa040
test: add FileMetricSinkTests
mykeelium Jan 6, 2026
1b2dc0a
chore: add notes for IsExternalInit.cs
mykeelium Jan 6, 2026
4b04027
tests: Add MetricAggregatorTests.cs, MetricRegistryTests.cs, and Metr…
mykeelium Jan 6, 2026
6bba590
tests: adjusting to relax ranges on AdaptiveTimeoutTests
mykeelium Jan 6, 2026
76719a8
chore: coderabbit suggestions
mykeelium Jan 6, 2026
ef03459
chore: more coderabbit suggestions
mykeelium Jan 6, 2026
3333ef6
chore: more coderabbit suggestions
mykeelium Jan 6, 2026
f6df3ca
feat: update generics for aggregator to send values
mykeelium Feb 25, 2026
35b3425
Merge branch 'v4' into poc/metrics
mykeelium Feb 25, 2026
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
20 changes: 12 additions & 8 deletions src/CommonLib/AdaptiveTimeout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,15 @@ public void ClearSamples() {
/// <typeparam name="T"></typeparam>
/// <param name="func"></param>
/// <param name="parentToken"></param>
/// <param name="latencyObservation">A method that is used to observe the latency of the request.</param>
/// <returns>Returns a Fail result if a task runs longer than its budgeted time.</returns>
public async Task<Result<T>> ExecuteWithTimeout<T>(Func<CancellationToken, T> func, CancellationToken parentToken = default) {
public async Task<Result<T>> ExecuteWithTimeout<T>(Func<CancellationToken, T> func, CancellationToken parentToken = default, Action<double> latencyObservation = null) {
DateTime startTime = default;
var result = await Timeout.ExecuteWithTimeout(GetAdaptiveTimeout(), (timeoutToken) =>
_sampler.SampleExecutionTime(() => {
startTime = DateTime.Now; // for ordinal tracking; see use in TimeSpikeSafetyValve
return func(timeoutToken);
}), parentToken);
}, latencyObservation), parentToken);
TimeSpikeSafetyValve(result.IsSuccess, startTime);
return result;
}
Expand All @@ -84,14 +85,15 @@ public async Task<Result<T>> ExecuteWithTimeout<T>(Func<CancellationToken, T> fu
/// </summary>
/// <param name="func"></param>
/// <param name="parentToken"></param>
/// <param name="latencyObservation">A method that is used to observe the latency of the request.</param>
/// <returns>Returns a Fail result if a task runs longer than its budgeted time.</returns>
public async Task<Result> ExecuteWithTimeout(Action<CancellationToken> func, CancellationToken parentToken = default) {
public async Task<Result> ExecuteWithTimeout(Action<CancellationToken> func, CancellationToken parentToken = default, Action<double> latencyObservation = null) {
DateTime startTime = default;
var result = await Timeout.ExecuteWithTimeout(GetAdaptiveTimeout(), (timeoutToken) =>
_sampler.SampleExecutionTime(() => {
startTime = DateTime.Now; // for ordinal tracking; see use in TimeSpikeSafetyValve
func(timeoutToken);
}), parentToken);
}, latencyObservation), parentToken);
TimeSpikeSafetyValve(result.IsSuccess, startTime);
return result;
}
Expand All @@ -107,14 +109,15 @@ public async Task<Result> ExecuteWithTimeout(Action<CancellationToken> func, Can
/// <typeparam name="T"></typeparam>
/// <param name="func"></param>
/// <param name="parentToken"></param>
/// <param name="latencyObservation">A method that is used to observe the latency of the request.</param>
/// <returns>Returns a Fail result if a task runs longer than its budgeted time.</returns>
public async Task<Result<T>> ExecuteWithTimeout<T>(Func<CancellationToken, Task<T>> func, CancellationToken parentToken = default) {
public async Task<Result<T>> ExecuteWithTimeout<T>(Func<CancellationToken, Task<T>> func, CancellationToken parentToken = default, Action<double> latencyObservation = null) {
DateTime startTime = default;
var result = await Timeout.ExecuteWithTimeout(GetAdaptiveTimeout(), (timeoutToken) =>
_sampler.SampleExecutionTime(() => {
startTime = DateTime.Now; // for ordinal tracking; see use in TimeSpikeSafetyValve
return func(timeoutToken);
}), parentToken);
}, latencyObservation), parentToken);
TimeSpikeSafetyValve(result.IsSuccess, startTime);
return result;
}
Expand All @@ -129,14 +132,15 @@ public async Task<Result<T>> ExecuteWithTimeout<T>(Func<CancellationToken, Task<
/// </summary>
/// <param name="func"></param>
/// <param name="parentToken"></param>
/// <param name="latencyObservation">A method that is used to observe the latency of the request.</param>
/// <returns>Returns a Fail result if a task runs longer than its budgeted time.</returns>
public async Task<Result> ExecuteWithTimeout(Func<CancellationToken, Task> func, CancellationToken parentToken = default) {
public async Task<Result> ExecuteWithTimeout(Func<CancellationToken, Task> func, CancellationToken parentToken = default, Action<double> latencyObservation = null) {
DateTime startTime = default;
var result = await Timeout.ExecuteWithTimeout(GetAdaptiveTimeout(), (timeoutToken) =>
_sampler.SampleExecutionTime(() => {
startTime = DateTime.Now; // for ordinal tracking; see use in TimeSpikeSafetyValve
return func(timeoutToken);
}), parentToken);
}, latencyObservation), parentToken);
TimeSpikeSafetyValve(result.IsSuccess, startTime);
return result;
}
Expand Down
12 changes: 8 additions & 4 deletions src/CommonLib/ExecutionTimeSampler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,35 +43,39 @@ public double StandardDeviation() {

public double Average() => _samples.Average();

public async Task<T> SampleExecutionTime<T>(Func<Task<T>> func) {
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) {
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) {
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) {
public void SampleExecutionTime(Action func, Action<double> latencyObservation = null) {
var stopwatch = Stopwatch.StartNew();
func.Invoke();
stopwatch.Stop();
latencyObservation?.Invoke(stopwatch.ElapsedMilliseconds);
AddTimeSample(stopwatch.Elapsed);
}

Expand Down
5 changes: 5 additions & 0 deletions src/CommonLib/Interfaces/ILabelValuesCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace SharpHoundCommonLib.Interfaces;

public interface ILabelValuesCache {
string[] Intern(string[] values);
}
5 changes: 5 additions & 0 deletions src/CommonLib/Interfaces/IMetricFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace SharpHoundCommonLib.Interfaces;

public interface IMetricFactory {
IMetricRouter CreateMetricRouter();
}
9 changes: 9 additions & 0 deletions src/CommonLib/Interfaces/IMetricRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Collections.Generic;
using SharpHoundCommonLib.Models;

namespace SharpHoundCommonLib.Interfaces;

public interface IMetricRegistry {
bool TryRegister(MetricDefinition definition, out int definitionId);
IReadOnlyList<MetricDefinition> Definitions { get; }
}
8 changes: 8 additions & 0 deletions src/CommonLib/Interfaces/IMetricRouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using SharpHoundCommonLib.Models;

namespace SharpHoundCommonLib.Interfaces;

public interface IMetricRouter {
void Observe(int definitionId, double value, LabelValues labelValues);
void Flush();
}
8 changes: 8 additions & 0 deletions src/CommonLib/Interfaces/IMetricSink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using SharpHoundCommonLib.Models;

namespace SharpHoundCommonLib.Interfaces;

public interface IMetricSink {
void Observe(in MetricObservation.DoubleMetricObservation observation);
void Flush();
}
17 changes: 17 additions & 0 deletions src/CommonLib/Interfaces/IMetricWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Text;
using SharpHoundCommonLib.Models;
using SharpHoundCommonLib.Services;

namespace SharpHoundCommonLib.Interfaces;

public interface IMetricWriter {
void StringBuilderAppendMetric(
StringBuilder builder,
MetricDefinition definition,
LabelValues labelValues,
MetricAggregator aggregator,
DateTimeOffset timestamp,
string timestampOutputString = "yyyy-MM-dd HH:mm:ss.fff"
);
}
Loading
Loading