diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/AgentSelfMetricsCollector.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/AgentSelfMetricsCollector.cs index d94e7361..e581b085 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/AgentSelfMetricsCollector.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/AgentSelfMetricsCollector.cs @@ -76,7 +76,11 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic protected override void Collect() { - var data = new Dictionary + // 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(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 + var pressureData = new Dictionary(capacity: 8, StringComparer.Ordinal) { { "pendingItemCount", pendingItemCount }, { "fileSizeBytes", spoolFileSizeBytes }, diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/PerformanceCollector.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/PerformanceCollector.cs index 43f6a877..1584883e 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/PerformanceCollector.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/PerformanceCollector.cs @@ -88,7 +88,10 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Periodic protected override void Collect() { - var data = new Dictionary(); + // 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(capacity: 12, StringComparer.Ordinal); // CPU usage try diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Telemetry/Events/BackPressureEventObserver.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Telemetry/Events/BackPressureEventObserver.cs index 6c0e2bc5..3feb94f1 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Telemetry/Events/BackPressureEventObserver.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Telemetry/Events/BackPressureEventObserver.cs @@ -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(StringComparer.Ordinal) + Data = new Dictionary(capacity: 4, StringComparer.Ordinal) { ["origin"] = origin, ["channelCapacity"] = channelCapacity, diff --git a/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt index 109144d2..fc2f3d0a 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt @@ -1 +1 @@ -653 +659 diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index b151ec96..80a61005 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -1286 +1288 diff --git a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionAuditTrailBuilder.cs b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionAuditTrailBuilder.cs index 40692557..1431e4a2 100644 --- a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionAuditTrailBuilder.cs +++ b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionAuditTrailBuilder.cs @@ -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(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(capacity: 16, StringComparer.Ordinal) { ["decisionSource"] = DecisionSource, ["trigger"] = trigger, @@ -159,7 +162,7 @@ namespace AutopilotMonitor.DecisionCore.Engine private static Dictionary BuildScenarioBlock(DecisionState s) { - var block = new Dictionary(StringComparer.Ordinal); + var block = new Dictionary(capacity: 6, StringComparer.Ordinal); if (s.ScenarioProfile != null) { block["mode"] = s.ScenarioProfile.Mode.ToString(); @@ -185,7 +188,7 @@ namespace AutopilotMonitor.DecisionCore.Engine /// private static Dictionary FlattenClassifierInputs(object snapshot) { - var result = new Dictionary(StringComparer.Ordinal); + var result = new Dictionary(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 ToDictionary() => - new Dictionary(StringComparer.Ordinal) + new Dictionary(capacity: 5, StringComparer.Ordinal) { ["id"] = Id, ["level"] = Level, diff --git a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionStateSignalCensus.cs b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionStateSignalCensus.cs index 7b7a12cb..4ada6737 100644 --- a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionStateSignalCensus.cs +++ b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DecisionStateSignalCensus.cs @@ -40,8 +40,8 @@ namespace AutopilotMonitor.DecisionCore.Engine if (state == null) throw new ArgumentNullException(nameof(state)); var seen = new List(8); - var timestamps = new Dictionary(StringComparer.Ordinal); - var evidence = new Dictionary(StringComparer.Ordinal); + var timestamps = new Dictionary(capacity: 8, StringComparer.Ordinal); + var evidence = new Dictionary(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(StringComparer.Ordinal) + evidence["aadUserJoinObserved"] = new Dictionary(capacity: 2, StringComparer.Ordinal) { ["ordinal"] = obs.AadUserJoinWithUserObserved.SourceSignalOrdinal, ["withUser"] = obs.AadUserJoinWithUserObserved.Value, @@ -111,7 +111,7 @@ namespace AutopilotMonitor.DecisionCore.Engine private static Dictionary TimestampedEvidence(SignalFact? fact) { - var dict = new Dictionary(StringComparer.Ordinal) + var dict = new Dictionary(capacity: 2, StringComparer.Ordinal) { ["ordinal"] = fact!.SourceSignalOrdinal, }; @@ -127,7 +127,7 @@ namespace AutopilotMonitor.DecisionCore.Engine } private static Dictionary OrdinalEvidence(long ordinal) => - new Dictionary(StringComparer.Ordinal) + new Dictionary(capacity: 1, StringComparer.Ordinal) { ["ordinal"] = ordinal, };