mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
perf(agent/v2): pre-size hot-path event dictionaries
Skip the default 0->3->7->17 Dictionary resize cascade on hot-path event/audit-trail allocations by setting initial capacity + Ordinal comparer. ~3k Gen0 allocs saved per session for an agent that runs continuously on endpoints. Behaviour unchanged, 1295 tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+6
-2
@@ -76,7 +76,11 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic
|
||||
|
||||
protected override void Collect()
|
||||
{
|
||||
var data = new Dictionary<string, object>
|
||||
// Full path writes 18 keys: agent_version + process metrics (5: cpu, ws, private,
|
||||
// threads, handles) + spool stats (4) + network delta (8: requests, failures,
|
||||
// bytes_up/down, avg_latency, total_up/down/requests). cap=18 → HashHelpers.GetPrime(18)=23
|
||||
// buckets → no resize on the 18th key (cap=16 would land at 17 buckets and resize).
|
||||
var data = new Dictionary<string, object>(capacity: 18, StringComparer.Ordinal)
|
||||
{
|
||||
{ "agent_version", _agentVersion }
|
||||
};
|
||||
@@ -193,7 +197,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic
|
||||
|| spoolFileSizeBytes > PressureFileSizeBytesThreshold)
|
||||
&& Interlocked.CompareExchange(ref _pressureEmitted, 1, 0) == 0)
|
||||
{
|
||||
var pressureData = new Dictionary<string, object>
|
||||
var pressureData = new Dictionary<string, object>(capacity: 8, StringComparer.Ordinal)
|
||||
{
|
||||
{ "pendingItemCount", pendingItemCount },
|
||||
{ "fileSizeBytes", spoolFileSizeBytes },
|
||||
|
||||
+4
-1
@@ -88,7 +88,10 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic
|
||||
|
||||
protected override void Collect()
|
||||
{
|
||||
var data = new Dictionary<string, object>();
|
||||
// Full path writes up to 12 keys (cpu + 3 memory + disk_queue + 2 disk + 5 network).
|
||||
// cap=12 → HashHelpers.GetPrime(12)=17 buckets → no resize on the last network key
|
||||
// (cap=8 would land at 11 buckets and resize on the 12th).
|
||||
var data = new Dictionary<string, object>(capacity: 12, StringComparer.Ordinal);
|
||||
|
||||
// CPU usage
|
||||
try
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Telemetry.Events
|
||||
Message = $"Signal ingress back-pressure on origin '{origin}' (queue {queueLength}/{channelCapacity}, blocked {blockDuration.TotalMilliseconds:F0}ms)",
|
||||
Timestamp = _clock.UtcNow,
|
||||
ImmediateUpload = false,
|
||||
Data = new Dictionary<string, object>(StringComparer.Ordinal)
|
||||
Data = new Dictionary<string, object>(capacity: 4, StringComparer.Ordinal)
|
||||
{
|
||||
["origin"] = origin,
|
||||
["channelCapacity"] = channelCapacity,
|
||||
|
||||
@@ -1 +1 @@
|
||||
653
|
||||
659
|
||||
|
||||
@@ -1 +1 @@
|
||||
1286
|
||||
1288
|
||||
|
||||
@@ -69,7 +69,10 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
if (postState == null) throw new ArgumentNullException(nameof(postState));
|
||||
if (string.IsNullOrEmpty(trigger)) throw new ArgumentException("Trigger is mandatory.", nameof(trigger));
|
||||
|
||||
var data = new Dictionary<string, object>(StringComparer.Ordinal)
|
||||
// Capacity sized for 8 mandatory anchors + up to ~8 optional fields (classifier,
|
||||
// classifierInputs, reason, helloOutcome, helloPolicyEnabled, imePatternMatched,
|
||||
// signalOrdinal). Avoids the default 0→3→7→17 resize cascade on every transition.
|
||||
var data = new Dictionary<string, object>(capacity: 16, StringComparer.Ordinal)
|
||||
{
|
||||
["decisionSource"] = DecisionSource,
|
||||
["trigger"] = trigger,
|
||||
@@ -159,7 +162,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
|
||||
private static Dictionary<string, object> BuildScenarioBlock(DecisionState s)
|
||||
{
|
||||
var block = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
var block = new Dictionary<string, object>(capacity: 6, StringComparer.Ordinal);
|
||||
if (s.ScenarioProfile != null)
|
||||
{
|
||||
block["mode"] = s.ScenarioProfile.Mode.ToString();
|
||||
@@ -185,7 +188,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
/// </summary>
|
||||
private static Dictionary<string, object> FlattenClassifierInputs(object snapshot)
|
||||
{
|
||||
var result = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
var result = new Dictionary<string, object>(capacity: 8, StringComparer.Ordinal);
|
||||
foreach (var prop in snapshot.GetType().GetProperties())
|
||||
{
|
||||
if (!prop.CanRead) continue;
|
||||
@@ -242,7 +245,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
public string InputHash { get; }
|
||||
|
||||
public Dictionary<string, object> ToDictionary() =>
|
||||
new Dictionary<string, object>(StringComparer.Ordinal)
|
||||
new Dictionary<string, object>(capacity: 5, StringComparer.Ordinal)
|
||||
{
|
||||
["id"] = Id,
|
||||
["level"] = Level,
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
if (state == null) throw new ArgumentNullException(nameof(state));
|
||||
|
||||
var seen = new List<string>(8);
|
||||
var timestamps = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
var evidence = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
var timestamps = new Dictionary<string, string>(capacity: 8, StringComparer.Ordinal);
|
||||
var evidence = new Dictionary<string, object>(capacity: 8, StringComparer.Ordinal);
|
||||
|
||||
// Order matches the historical DecisionAuditTrailBuilder.BuildSignalCensus
|
||||
// ordering so existing snapshot-style assertions remain stable.
|
||||
@@ -99,7 +99,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
seen.Add(obs.AadUserJoinWithUserObserved.Value
|
||||
? "aad_user_joined_with_user"
|
||||
: "aad_user_joined_device_only");
|
||||
evidence["aadUserJoinObserved"] = new Dictionary<string, object>(StringComparer.Ordinal)
|
||||
evidence["aadUserJoinObserved"] = new Dictionary<string, object>(capacity: 2, StringComparer.Ordinal)
|
||||
{
|
||||
["ordinal"] = obs.AadUserJoinWithUserObserved.SourceSignalOrdinal,
|
||||
["withUser"] = obs.AadUserJoinWithUserObserved.Value,
|
||||
@@ -111,7 +111,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
|
||||
private static Dictionary<string, object> TimestampedEvidence<T>(SignalFact<T>? fact)
|
||||
{
|
||||
var dict = new Dictionary<string, object>(StringComparer.Ordinal)
|
||||
var dict = new Dictionary<string, object>(capacity: 2, StringComparer.Ordinal)
|
||||
{
|
||||
["ordinal"] = fact!.SourceSignalOrdinal,
|
||||
};
|
||||
@@ -127,7 +127,7 @@ namespace AutopilotMonitor.DecisionCore.Engine
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> OrdinalEvidence(long ordinal) =>
|
||||
new Dictionary<string, object>(StringComparer.Ordinal)
|
||||
new Dictionary<string, object>(capacity: 1, StringComparer.Ordinal)
|
||||
{
|
||||
["ordinal"] = ordinal,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user