From 77f18e469034912f9b545118ca8ee2037cce03c6 Mon Sep 17 00:00:00 2001 From: Michael Cuomo Date: Wed, 31 Dec 2025 16:51:13 -0500 Subject: [PATCH 1/3] wip: register metrics and dispose --- src/Client/Flags.cs | 1 + src/Options.cs | 3 +++ src/Sharphound.cs | 39 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Client/Flags.cs b/src/Client/Flags.cs index 48e935a..0f04ccc 100644 --- a/src/Client/Flags.cs +++ b/src/Client/Flags.cs @@ -28,5 +28,6 @@ public class Flags public bool RecurseDomains { get; set; } public bool DoLocalAdminSessionEnum { get; set; } public bool ParititonLdapQueries { get; set; } + public bool Metrics { get; set; } } } \ No newline at end of file diff --git a/src/Options.cs b/src/Options.cs index e560b02..bdc84e2 100644 --- a/src/Options.cs +++ b/src/Options.cs @@ -142,6 +142,9 @@ public class Options [Option(HelpText = "Split the main ldap query into smaller chunks to attempt to reduce server load")] public bool PartitionLdapQueries { get; set; } + + [Option(HelpText = "Output metrics that were captured from SharpHound")] + public bool Metrics { get; set; } //Loop Options [Option('l', "Loop", HelpText = "Loop computer collection")] diff --git a/src/Sharphound.cs b/src/Sharphound.cs index 87e009b..2d9b750 100644 --- a/src/Sharphound.cs +++ b/src/Sharphound.cs @@ -25,6 +25,9 @@ using Sharphound.Client; using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.Models; +using SharpHoundCommonLib.Services; +using SharpHoundCommonLib.Static; namespace Sharphound { @@ -36,6 +39,8 @@ namespace Sharphound #region Console Entrypoint public class Program { + private static MetricsFlushTimer _flushTimer; + public static async Task Main(string[] args) { var logger = new BasicLogger((int)LogLevel.Information); logger.LogInformation("This version of SharpHound is compatible with the 5.0.0 Release of BloodHound"); @@ -88,7 +93,8 @@ await options.WithParsedAsync(async options => SearchForest = options.SearchForest, RecurseDomains = options.RecurseDomains, DoLocalAdminSessionEnum = options.DoLocalAdminSessionEnum, - ParititonLdapQueries = options.PartitionLdapQueries + ParititonLdapQueries = options.PartitionLdapQueries, + Metrics = options.Metrics, }; var ldapOptions = new LdapConfig @@ -149,10 +155,40 @@ await options.WithParsedAsync(async options => } } + if (flags.Metrics) { + var metricsRegistry = new MetricRegistry(); + metricsRegistry.RegisterDefaultMetrics(); + metricsRegistry.Seal(); + + var fileSinkOptions = new FileMetricSinkOptions { + FlushInterval = TimeSpan.FromSeconds(5), + }; + + var metricsWriter = new MetricWriter(); + + var fileSink = new FileMetricSink( + metricsRegistry.Definitions, + filePath: "metrics.log", + metricsWriter, + fileSinkOptions); + + var metricsRouter = new MetricRouter( + metricsRegistry.Definitions, + [fileSink]); + + Metrics.Factory = new MetricFactory(metricsRouter); + + _flushTimer = new MetricsFlushTimer( + flush: metricsRouter.Flush, + interval: fileSinkOptions.FlushInterval); + + } + await StartCollection(options, logger, resolved, flags, ldapOptions); }); } catch (Exception ex) { logger.LogError($"Error running SharpHound: {ex.Message}\n{ex.StackTrace}"); + _flushTimer.Dispose(); } } @@ -214,6 +250,7 @@ private static async Task StartCollection(Options options, BasicLogger logger, C context = await links.AwaitLoopCompletion(context); context = links.SaveCacheFile(context); links.Finish(context); + _flushTimer.Dispose(); } // Accessor function for the PS1 to work, do not change or remove From b67d776f3fff5f7d319df6dc202697c0c0f235df Mon Sep 17 00:00:00 2001 From: Michael Cuomo Date: Mon, 16 Mar 2026 13:00:35 -0400 Subject: [PATCH 2/3] chore: add default label cache --- src/Sharphound.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sharphound.cs b/src/Sharphound.cs index 2d9b750..089a7fe 100644 --- a/src/Sharphound.cs +++ b/src/Sharphound.cs @@ -174,7 +174,8 @@ await options.WithParsedAsync(async options => var metricsRouter = new MetricRouter( metricsRegistry.Definitions, - [fileSink]); + [fileSink], + new DefaultLabelValuesCache()); Metrics.Factory = new MetricFactory(metricsRouter); From 2d0ee0fe375851fe9c5427d56937c5cf3b0ee669 Mon Sep 17 00:00:00 2001 From: Michael Cuomo Date: Mon, 16 Mar 2026 13:51:19 -0400 Subject: [PATCH 3/3] chore: coderabbbit suggested changes --- src/Sharphound.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Sharphound.cs b/src/Sharphound.cs index 089a7fe..1229650 100644 --- a/src/Sharphound.cs +++ b/src/Sharphound.cs @@ -166,9 +166,12 @@ await options.WithParsedAsync(async options => var metricsWriter = new MetricWriter(); + var metricsFileName = string.IsNullOrEmpty(options.OutputPrefix) ? "metrics.log" : $"{options.OutputPrefix}_metrics.log"; + var metricsFilePath = System.IO.Path.Combine(options.OutputDirectory, metricsFileName); + var fileSink = new FileMetricSink( metricsRegistry.Definitions, - filePath: "metrics.log", + filePath: metricsFilePath, metricsWriter, fileSinkOptions); @@ -189,7 +192,7 @@ await options.WithParsedAsync(async options => }); } catch (Exception ex) { logger.LogError($"Error running SharpHound: {ex.Message}\n{ex.StackTrace}"); - _flushTimer.Dispose(); + _flushTimer?.Dispose(); } } @@ -251,7 +254,7 @@ private static async Task StartCollection(Options options, BasicLogger logger, C context = await links.AwaitLoopCompletion(context); context = links.SaveCacheFile(context); links.Finish(context); - _flushTimer.Dispose(); + _flushTimer?.Dispose(); } // Accessor function for the PS1 to work, do not change or remove