Skip to content

Commit 0fadee1

Browse files
rudraptpsinghRudra Pratap SIngh
andauthored
Fix WindowsPerformanceCounter OOM on ARM64 >64 LP systems (#661)
* Add WMI counter provider and circuit breaker for >64 LP systems Add WindowsWmiPerformanceCounter that uses wmic.exe subprocess to collect CPU performance counters via Win32_PerfFormattedData_Counters_ProcessorInformation. This WMI class supports multi-processor groups, unlike the legacy .NET PerformanceCounter API which fails on systems with >64 logical processors. Add CounterProvider parameter (Default|WMI) to WindowsPerformanceCounterMonitor. Profile authors can explicitly choose the counter collection method: - Default: legacy .NET PerformanceCounter API (existing behavior) - WMI: wmic.exe subprocess (for bare metal >64 LP systems) Add circuit breaker to WindowsPerformanceCounter: after 5 consecutive Capture() failures, sets IsDisabled=true and subsequent calls return immediately. This prevents the error flood (7M-32M events) that caused OOM on Cobalt 200 machines regardless of which provider is used. Changes: - WindowsWmiPerformanceCounter (new): extends WindowsPerformanceCounter, overrides TryGetCounterValue with wmic subprocess, includes bidirectional counter name mapping for 19 known Processor counters - WindowsPerformanceCounter: circuit breaker (IsDisabled, MaxConsecutiveFailures=5, ConsecutiveFailures, LastError, ResetDisabledState) - WindowsPerformanceCounterMonitor: CounterProvider parameter, LoadWmiCounters method, skip disabled counters in capture loop - Unit tests: 7 new tests (circuit breaker, WMI mappings, constructor) * Add WindowsWmiPerformanceCounterMonitor using CimSession for >64 LP systems Separate WMI counter implementation into its own monitor class per Bryan's review feedback. Add CounterProvider parameter (Default|WMI) to the base WindowsPerformanceCounterMonitor that auto-detects >64 LP systems and switches to WMI transparently. Profiles use CounterProvider=Default for automatic selection, or CounterProvider=WMI to force WMI. Changes: - WindowsPerformanceCounterMonitor: Add CounterProvider parameter with auto-detection (Default auto-selects WMI when ProcessorCount > 64). Add UseWmiProvider property and CounterProviderName for telemetry. LoadCounters() dispatches to WMI path when UseWmiProvider is true. - WindowsWmiPerformanceCounterMonitor (NEW): Separate monitor class. Extracts WMI loading into internal static LoadWmiCounters() shared by both the subclass and the base class dispatch path. - WindowsWmiPerformanceCounter: Complete rewrite from deprecated wmic.exe to in-process CimSession (Microsoft.Management.Infrastructure). Add per-category result cache (800ms TTL) reducing WMI queries from 1188 to 7 per capture cycle. Support 9 categories: Processor, Memory, PhysicalDisk, System, IPv4, Hyper-V LP/Root VP/VP. Add PascalCase-to- counter-name mapping for generic WMI property translation. - Add Microsoft.Management.Infrastructure 3.0.0 NuGet package. - Update unit tests for new category mappings and PascalCase fallback. Profile usage: CounterProvider=Default -> auto-selects WMI on >64 LP systems CounterProvider=WMI -> always uses WMI regardless of LP count Tested on C4142A1157C0413 (ARM64, 132 LPs): 1188 counters loaded across 7 categories, 3 snapshots per 2.5min with cached queries (exit code 0). --------- Co-authored-by: Rudra Pratap SIngh <rudrasingh@microsoft.com>
1 parent 4b55e79 commit 0fadee1

10 files changed

Lines changed: 827 additions & 3 deletions

File tree

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
3434
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta1.21308.1" />
3535
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.9" />
36+
<PackageVersion Include="Microsoft.Management.Infrastructure" Version="3.0.0" />
3637
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.9" />
3738
<PackageVersion Include="System.IO.Abstractions" Version="22.0.14" />
3839
<PackageVersion Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />

src/VirtualClient/VirtualClient.Core.UnitTests/WindowsPerformanceCounterTests.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,94 @@ public void WindowsPerformanceCounterReturnsTheExpectedSnapshotWhenARawStrategyI
124124
Assert.AreEqual((double)captures.Last(), snapshot.Value);
125125
}
126126

127+
[Test]
128+
public void WindowsPerformanceCounterDisablesAfterMaxConsecutiveFailures()
129+
{
130+
this.performanceCounter.OnGetCounterValue = () => throw new InvalidOperationException("Instance '7' does not exist in the specified Category.");
131+
132+
for (int i = 0; i < WindowsPerformanceCounter.MaxConsecutiveFailures; i++)
133+
{
134+
Assert.Throws<InvalidOperationException>(() => this.performanceCounter.Capture());
135+
}
136+
137+
Assert.IsTrue(this.performanceCounter.IsDisabled);
138+
Assert.AreEqual(WindowsPerformanceCounter.MaxConsecutiveFailures, this.performanceCounter.ConsecutiveFailures);
139+
}
140+
141+
[Test]
142+
public void WindowsPerformanceCounterSkipsCaptureWhenDisabled()
143+
{
144+
this.performanceCounter.OnGetCounterValue = () => throw new InvalidOperationException("test");
145+
146+
for (int i = 0; i < WindowsPerformanceCounter.MaxConsecutiveFailures; i++)
147+
{
148+
Assert.Throws<InvalidOperationException>(() => this.performanceCounter.Capture());
149+
}
150+
151+
Assert.IsTrue(this.performanceCounter.IsDisabled);
152+
153+
// Subsequent calls should return immediately without throwing
154+
Assert.DoesNotThrow(() => this.performanceCounter.Capture());
155+
}
156+
157+
[Test]
158+
public void WindowsPerformanceCounterResetsFailureCountOnSuccess()
159+
{
160+
int callCount = 0;
161+
this.performanceCounter.OnGetCounterValue = () =>
162+
{
163+
callCount++;
164+
if (callCount <= 3)
165+
{
166+
throw new InvalidOperationException("transient");
167+
}
168+
169+
return 42.0f;
170+
};
171+
172+
for (int i = 0; i < 3; i++)
173+
{
174+
Assert.Throws<InvalidOperationException>(() => this.performanceCounter.Capture());
175+
}
176+
177+
Assert.IsFalse(this.performanceCounter.IsDisabled);
178+
Assert.AreEqual(3, this.performanceCounter.ConsecutiveFailures);
179+
180+
this.performanceCounter.Capture();
181+
Assert.IsFalse(this.performanceCounter.IsDisabled);
182+
Assert.AreEqual(0, this.performanceCounter.ConsecutiveFailures);
183+
Assert.IsNull(this.performanceCounter.LastError);
184+
}
185+
186+
[Test]
187+
public void WindowsPerformanceCounterResetDisabledStateAllowsRetry()
188+
{
189+
this.performanceCounter.OnGetCounterValue = () => throw new InvalidOperationException("broken");
190+
191+
for (int i = 0; i < WindowsPerformanceCounter.MaxConsecutiveFailures; i++)
192+
{
193+
Assert.Throws<InvalidOperationException>(() => this.performanceCounter.Capture());
194+
}
195+
196+
Assert.IsTrue(this.performanceCounter.IsDisabled);
197+
198+
this.performanceCounter.ResetDisabledState();
199+
Assert.IsFalse(this.performanceCounter.IsDisabled);
200+
Assert.AreEqual(0, this.performanceCounter.ConsecutiveFailures);
201+
202+
Assert.Throws<InvalidOperationException>(() => this.performanceCounter.Capture());
203+
}
204+
205+
[Test]
206+
public void WindowsPerformanceCounterStoresLastError()
207+
{
208+
var expectedException = new InvalidOperationException("Instance '42' does not exist");
209+
this.performanceCounter.OnGetCounterValue = () => throw expectedException;
210+
211+
Assert.Throws<InvalidOperationException>(() => this.performanceCounter.Capture());
212+
Assert.AreEqual(expectedException, this.performanceCounter.LastError);
213+
}
214+
127215
private class TestWindowsPerformanceCounter : WindowsPerformanceCounter
128216
{
129217
public TestWindowsPerformanceCounter(string category, string name, string instance, CaptureStrategy captureStrategy)
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace VirtualClient
5+
{
6+
using System.Collections.Generic;
7+
using NUnit.Framework;
8+
using VirtualClient.Contracts;
9+
10+
[TestFixture]
11+
[Category("Unit")]
12+
public class WindowsWmiPerformanceCounterTests
13+
{
14+
[Test]
15+
public void WindowsWmiPerformanceCounterForwardMappingsAreCorrect()
16+
{
17+
Assert.AreEqual("PercentProcessorTime", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% Processor Time"));
18+
Assert.AreEqual("PercentUserTime", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% User Time"));
19+
Assert.AreEqual("PercentPrivilegedTime", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% Privileged Time"));
20+
Assert.AreEqual("PercentIdleTime", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% Idle Time"));
21+
Assert.AreEqual("PercentInterruptTime", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% Interrupt Time"));
22+
Assert.AreEqual("PercentDPCTime", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% DPC Time"));
23+
Assert.AreEqual("PercentC1Time", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% C1 Time"));
24+
Assert.AreEqual("PercentC2Time", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% C2 Time"));
25+
Assert.AreEqual("PercentC3Time", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% C3 Time"));
26+
Assert.AreEqual("InterruptsPersec", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("Interrupts/sec"));
27+
Assert.AreEqual("DPCsQueuedPersec", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("DPCs Queued/sec"));
28+
Assert.AreEqual("DPCRate", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("DPC Rate"));
29+
Assert.AreEqual("C1TransitionsPersec", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("C1 Transitions/sec"));
30+
Assert.AreEqual("C2TransitionsPersec", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("C2 Transitions/sec"));
31+
Assert.AreEqual("C3TransitionsPersec", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("C3 Transitions/sec"));
32+
Assert.AreEqual("PercentProcessorPerformance", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% Processor Performance"));
33+
Assert.AreEqual("PercentProcessorUtility", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% Processor Utility"));
34+
Assert.AreEqual("ProcessorFrequency", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("Processor Frequency"));
35+
Assert.AreEqual("PercentofMaximumFrequency", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("% of Maximum Frequency"));
36+
37+
// Default fallback — strips spaces, replaces % and /
38+
Assert.AreEqual("SomeCustomCounter", WindowsWmiPerformanceCounter.MapCounterNameToWmiProperty("Some Custom Counter"));
39+
}
40+
41+
[Test]
42+
public void WindowsWmiPerformanceCounterReverseMappingsAreCorrect()
43+
{
44+
// All known reverse mappings
45+
Assert.AreEqual("% Processor Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentProcessorTime"));
46+
Assert.AreEqual("% User Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentUserTime"));
47+
Assert.AreEqual("% Privileged Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentPrivilegedTime"));
48+
Assert.AreEqual("% Idle Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentIdleTime"));
49+
Assert.AreEqual("% Interrupt Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentInterruptTime"));
50+
Assert.AreEqual("% DPC Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentDPCTime"));
51+
Assert.AreEqual("% C1 Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentC1Time"));
52+
Assert.AreEqual("% C2 Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentC2Time"));
53+
Assert.AreEqual("% C3 Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentC3Time"));
54+
Assert.AreEqual("Interrupts/sec", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("InterruptsPersec"));
55+
Assert.AreEqual("DPCs Queued/sec", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("DPCsQueuedPersec"));
56+
Assert.AreEqual("DPC Rate", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("DPCRate"));
57+
Assert.AreEqual("C1 Transitions/sec", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("C1TransitionsPersec"));
58+
Assert.AreEqual("C2 Transitions/sec", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("C2TransitionsPersec"));
59+
Assert.AreEqual("C3 Transitions/sec", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("C3TransitionsPersec"));
60+
Assert.AreEqual("% Processor Performance", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentProcessorPerformance"));
61+
Assert.AreEqual("% Processor Utility", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentProcessorUtility"));
62+
Assert.AreEqual("Processor Frequency", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("ProcessorFrequency"));
63+
Assert.AreEqual("% of Maximum Frequency", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentofMaximumFrequency"));
64+
}
65+
66+
[Test]
67+
public void WindowsWmiPerformanceCounterPascalCaseFallbackMappingsAreCorrect()
68+
{
69+
// PascalCase → space-separated (non-Processor categories like Memory, System)
70+
Assert.AreEqual("Available Bytes", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("AvailableBytes"));
71+
Assert.AreEqual("Cache Bytes", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("CacheBytes"));
72+
Assert.AreEqual("Committed Bytes", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("CommittedBytes"));
73+
Assert.AreEqual("Context Switches/sec", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("ContextSwitchesPersec"));
74+
Assert.AreEqual("Page Faults/sec", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PageFaultsPersec"));
75+
Assert.AreEqual("Page Reads/sec", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PageReadsPersec"));
76+
Assert.AreEqual("Processes", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("Processes"));
77+
Assert.AreEqual("Threads", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("Threads"));
78+
79+
// Percent prefix handling
80+
Assert.AreEqual("% Guest Run Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentGuestRunTime"));
81+
Assert.AreEqual("% Disk Time", WindowsWmiPerformanceCounter.MapWmiPropertyToCounterName("PercentDiskTime"));
82+
}
83+
84+
[Test]
85+
public void WindowsWmiPerformanceCounterGetWmiClassNameReturnsCorrectMappings()
86+
{
87+
// Processor
88+
Assert.AreEqual("Win32_PerfFormattedData_Counters_ProcessorInformation", WindowsWmiPerformanceCounter.GetWmiClassName("Processor"));
89+
Assert.AreEqual("Win32_PerfFormattedData_Counters_ProcessorInformation", WindowsWmiPerformanceCounter.GetWmiClassName("Processor Information"));
90+
91+
// Memory, PhysicalDisk, System, IPv4
92+
Assert.AreEqual("Win32_PerfFormattedData_PerfOS_Memory", WindowsWmiPerformanceCounter.GetWmiClassName("Memory"));
93+
Assert.AreEqual("Win32_PerfFormattedData_PerfDisk_PhysicalDisk", WindowsWmiPerformanceCounter.GetWmiClassName("PhysicalDisk"));
94+
Assert.AreEqual("Win32_PerfFormattedData_PerfOS_System", WindowsWmiPerformanceCounter.GetWmiClassName("System"));
95+
Assert.AreEqual("Win32_PerfFormattedData_Tcpip_IPv4", WindowsWmiPerformanceCounter.GetWmiClassName("IPv4"));
96+
97+
// Hyper-V
98+
Assert.AreEqual("Win32_PerfFormattedData_HvStats_HyperVHypervisorLogicalProcessor", WindowsWmiPerformanceCounter.GetWmiClassName("Hyper-V Hypervisor Logical Processor"));
99+
Assert.AreEqual("Win32_PerfFormattedData_HvStats_HyperVHypervisorRootVirtualProcessor", WindowsWmiPerformanceCounter.GetWmiClassName("Hyper-V Hypervisor Root Virtual Processor"));
100+
Assert.AreEqual("Win32_PerfFormattedData_HvStats_HyperVHypervisorVirtualProcessor", WindowsWmiPerformanceCounter.GetWmiClassName("Hyper-V Hypervisor Virtual Processor"));
101+
102+
// Unsupported
103+
Assert.IsNull(WindowsWmiPerformanceCounter.GetWmiClassName("NonExistentCategory"));
104+
Assert.IsNull(WindowsWmiPerformanceCounter.GetWmiClassName(null));
105+
}
106+
107+
[Test]
108+
public void WindowsWmiPerformanceCounterGetWmiClassNameIsCaseInsensitive()
109+
{
110+
Assert.AreEqual("Win32_PerfFormattedData_PerfOS_Memory", WindowsWmiPerformanceCounter.GetWmiClassName("memory"));
111+
Assert.AreEqual("Win32_PerfFormattedData_PerfOS_Memory", WindowsWmiPerformanceCounter.GetWmiClassName("MEMORY"));
112+
Assert.AreEqual("Win32_PerfFormattedData_Counters_ProcessorInformation", WindowsWmiPerformanceCounter.GetWmiClassName("processor"));
113+
}
114+
115+
[Test]
116+
public void WindowsWmiPerformanceCounterConstructorSetsPropertiesCorrectly()
117+
{
118+
using (var counter = new WindowsWmiPerformanceCounter("Processor", "% Processor Time", "_Total", CaptureStrategy.Average))
119+
{
120+
Assert.AreEqual("Processor", counter.Category);
121+
Assert.AreEqual("% Processor Time", counter.Name);
122+
Assert.AreEqual("_Total", counter.InstanceName);
123+
Assert.AreEqual(CaptureStrategy.Average, counter.Strategy);
124+
Assert.AreEqual(@"\Processor(_Total)\% Processor Time", counter.MetricName);
125+
Assert.AreEqual(MetricRelativity.Undefined, counter.MetricRelativity);
126+
Assert.IsFalse(counter.IsDisabled);
127+
}
128+
}
129+
130+
[Test]
131+
public void WindowsWmiPerformanceCounterSnapshotWithNoCapturesReturnsNone()
132+
{
133+
using (var counter = new WindowsWmiPerformanceCounter("Processor", "% Processor Time", "_Total", CaptureStrategy.Average))
134+
{
135+
Metric noData = counter.Snapshot();
136+
Assert.AreEqual(Metric.None, noData);
137+
}
138+
}
139+
140+
[Test]
141+
public void WindowsWmiPerformanceCounterQueryAllInstancesReturnsEmptyForUnsupportedCategory()
142+
{
143+
Dictionary<string, Dictionary<string, float>> empty = WindowsWmiPerformanceCounter.QueryAllInstances("NonExistentCategory");
144+
Assert.IsEmpty(empty);
145+
}
146+
}
147+
}

src/VirtualClient/VirtualClient.Core/VirtualClient.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<PackageReference Include="Azure.Security.KeyVault.Keys" />
1616
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
1717
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" />
18+
<PackageReference Include="Microsoft.Management.Infrastructure" />
1819
<PackageReference Include="System.Diagnostics.PerformanceCounter" />
1920
</ItemGroup>
2021

src/VirtualClient/VirtualClient.Core/WindowsPerformanceCounter.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,19 @@ namespace VirtualClient
1818
/// </summary>
1919
public class WindowsPerformanceCounter : IPerformanceMetric, IDisposable
2020
{
21+
/// <summary>
22+
/// The maximum number of consecutive capture failures before the counter is disabled.
23+
/// </summary>
24+
public const int MaxConsecutiveFailures = 5;
25+
2126
private PerformanceCounter counter;
2227
private ConcurrentBag<float> counterValues;
2328
private SemaphoreSlim semaphore;
2429
private DateTime? captureStartTime;
2530
private DateTime nextCounterVerificationTime;
2631
private bool disposed;
32+
private int consecutiveFailures;
33+
private Exception lastError;
2734

2835
/// <summary>
2936
/// Intialize a new instance of the <see cref="WindowsPerformanceCounter"/> class.
@@ -156,6 +163,22 @@ public WindowsPerformanceCounter(string counterCategory, string counterName, str
156163
/// </summary>
157164
public CaptureStrategy Strategy { get; }
158165

166+
/// <summary>
167+
/// Gets a value indicating whether this counter has been disabled due to
168+
/// repeated consecutive capture failures.
169+
/// </summary>
170+
public bool IsDisabled { get; private set; }
171+
172+
/// <summary>
173+
/// Gets the number of consecutive capture failures.
174+
/// </summary>
175+
public int ConsecutiveFailures => this.consecutiveFailures;
176+
177+
/// <summary>
178+
/// Gets the last error that occurred during capture.
179+
/// </summary>
180+
public Exception LastError => this.lastError;
181+
159182
/// <summary>
160183
/// The set of counter values that have been captured during the current
161184
/// interval.
@@ -191,26 +214,54 @@ public static string GetCounterName(string category, string counterName, string
191214
/// <inheritdoc />
192215
public void Capture()
193216
{
217+
if (this.IsDisabled)
218+
{
219+
return;
220+
}
221+
194222
try
195223
{
196224
this.semaphore.Wait(CancellationToken.None);
197225

198226
if (this.TryGetCounterValue(out float? counterValue))
199227
{
200228
this.counterValues.Add(counterValue.Value);
229+
Interlocked.Exchange(ref this.consecutiveFailures, 0);
230+
this.lastError = null;
201231

202232
if (this.captureStartTime == null)
203233
{
204234
this.captureStartTime = DateTime.UtcNow;
205235
}
206236
}
207237
}
238+
catch (Exception exc)
239+
{
240+
this.lastError = exc;
241+
int failures = Interlocked.Increment(ref this.consecutiveFailures);
242+
if (failures >= WindowsPerformanceCounter.MaxConsecutiveFailures)
243+
{
244+
this.IsDisabled = true;
245+
}
246+
247+
throw;
248+
}
208249
finally
209250
{
210251
this.semaphore.Release();
211252
}
212253
}
213254

255+
/// <summary>
256+
/// Resets the disabled state so the counter can be retried.
257+
/// </summary>
258+
public void ResetDisabledState()
259+
{
260+
this.IsDisabled = false;
261+
Interlocked.Exchange(ref this.consecutiveFailures, 0);
262+
this.lastError = null;
263+
}
264+
214265
/// <summary>
215266
/// Disposes of resources used by the instance.
216267
/// </summary>

0 commit comments

Comments
 (0)