Files
runcat-dev--runcat365/RunCat365/InstancedPerformanceCounters.cs
2026-07-13 12:26:32 +08:00

131 lines
4.5 KiB
C#

// Copyright 2025 Takuto Nakamura
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Diagnostics;
namespace RunCat365
{
internal abstract class InstancedPerformanceCounters
{
private readonly Dictionary<string, PerformanceCounter> countersByInstance = [];
protected abstract string CategoryName { get; }
protected abstract string CounterName { get; }
protected virtual bool ShouldIncludeInstance(string instanceName) => true;
internal int Count => countersByInstance.Count;
protected bool TryInitialize()
{
try
{
_ = new PerformanceCounterCategory(CategoryName);
}
catch
{
return false;
}
RefreshInstances();
return Count != 0;
}
internal void RefreshInstances()
{
string[] currentInstanceNames;
try
{
var category = new PerformanceCounterCategory(CategoryName);
currentInstanceNames = category.GetInstanceNames()
.Where(ShouldIncludeInstance)
.ToArray();
}
catch (Exception exception) when (IsExpectedCounterException(exception))
{
Debug.WriteLine($"{GetType().Name}.RefreshInstances failed: {exception.Message}");
return;
}
var currentSet = new HashSet<string>(currentInstanceNames, StringComparer.Ordinal);
var staleInstances = countersByInstance.Keys
.Where(name => !currentSet.Contains(name))
.ToList();
foreach (var instanceName in staleInstances)
{
countersByInstance[instanceName].Close();
countersByInstance.Remove(instanceName);
}
foreach (var instanceName in currentInstanceNames)
{
if (countersByInstance.ContainsKey(instanceName)) continue;
PerformanceCounter? counter = null;
try
{
counter = new PerformanceCounter(CategoryName, CounterName, instanceName);
_ = counter.NextValue();
countersByInstance[instanceName] = counter;
counter = null;
}
catch (Exception exception) when (IsExpectedCounterException(exception))
{
Debug.WriteLine($"{GetType().Name}: failed to create counter for {instanceName}: {exception.Message}");
}
finally
{
counter?.Close();
}
}
}
internal List<float> ReadValues()
{
var values = new List<float>(countersByInstance.Count);
var deadInstances = new List<string>();
foreach (var pair in countersByInstance)
{
try
{
values.Add(pair.Value.NextValue());
}
catch (Exception exception) when (IsExpectedCounterException(exception))
{
Debug.WriteLine($"{GetType().Name}: counter {pair.Key} failed: {exception.Message}");
deadInstances.Add(pair.Key);
}
}
foreach (var instanceName in deadInstances)
{
countersByInstance[instanceName].Close();
countersByInstance.Remove(instanceName);
}
return values;
}
internal void Close()
{
foreach (var counter in countersByInstance.Values) counter.Close();
countersByInstance.Clear();
}
private static bool IsExpectedCounterException(Exception exception)
{
return exception is InvalidOperationException
or System.ComponentModel.Win32Exception
or UnauthorizedAccessException;
}
}
}