From f76092952740a17027a90956fdac0c92a42734f0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 22 Mar 2024 22:19:13 +0100 Subject: [PATCH] Add IMeterFactory implementation to IoC This will be useful as we start using more System.Diagnostics.Metrics. --- RELEASE-NOTES.md | 1 + .../DataMetrics/MetricsManager.Factory.cs | 89 +++++++++++++++++++ Robust.Server/DataMetrics/MetricsManager.cs | 4 + Robust.Server/ServerIoC.cs | 2 + 4 files changed, 96 insertions(+) create mode 100644 Robust.Server/DataMetrics/MetricsManager.Factory.cs diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 8df162bf773..0784023d995 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -41,6 +41,7 @@ END TEMPLATE--> * Made a new `IMetricsManager` interface with an `UpdateMetrics` event that can be used to update Prometheus metrics whenever they are scraped. * Also added a `metrics.update_interval` CVar to go along with this, when metrics are scraped without usage of Prometheus directly. +* IoC now contains an `IMeterFactory` implementation that you can use to instantiate metric meters. ### Bugfixes diff --git a/Robust.Server/DataMetrics/MetricsManager.Factory.cs b/Robust.Server/DataMetrics/MetricsManager.Factory.cs new file mode 100644 index 00000000000..f9543db929f --- /dev/null +++ b/Robust.Server/DataMetrics/MetricsManager.Factory.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Linq; +using Robust.Shared.Utility; + +namespace Robust.Server.DataMetrics; + +internal sealed partial class MetricsManager : IMeterFactory +{ + private readonly Dictionary> _meterCache = new(); + private readonly object _meterCacheLock = new(); + + Meter IMeterFactory.Create(MeterOptions options) + { + if (options.Scope != null && options.Scope != this) + throw new InvalidOperationException("Cannot specify a custom scope when creating a meter"); + + lock (_meterCacheLock) + { + if (LockedFindCachedMeter(options) is { } cached) + return cached.Meter; + + var meter = new Meter(options.Name, options.Version, options.Tags, this); + var meterList = _meterCache.GetOrNew(options.Name); + meterList.Add(new CachedMeter(options.Version, TagsToDict(options.Tags), meter)); + return meter; + } + } + + private CachedMeter? LockedFindCachedMeter(MeterOptions options) + { + if (!_meterCache.TryGetValue(options.Name, out var metersList)) + return null; + + var tagsDict = TagsToDict(options.Tags); + + foreach (var cachedMeter in metersList) + { + if (cachedMeter.Version == options.Version && TagsMatch(tagsDict, cachedMeter.Tags)) + return cachedMeter; + } + + return null; + } + + private static bool TagsMatch(Dictionary a, Dictionary b) + { + if (a.Count != b.Count) + return false; + + foreach (var (key, valueA) in a) + { + if (!b.TryGetValue(key, out var valueB)) + return false; + + if (!Equals(valueA, valueB)) + return false; + } + + return true; + } + + private static Dictionary TagsToDict(IEnumerable>? tags) + { + return tags?.ToDictionary() ?? []; + } + + private void DisposeMeters() + { + lock (_meterCacheLock) + { + foreach (var meters in _meterCache.Values) + { + foreach (var meter in meters) + { + meter.Meter.Dispose(); + } + } + } + } + + private sealed class CachedMeter(string? version, Dictionary tags, Meter meter) + { + public readonly string? Version = version; + public readonly Dictionary Tags = tags; + public readonly Meter Meter = meter; + } +} diff --git a/Robust.Server/DataMetrics/MetricsManager.cs b/Robust.Server/DataMetrics/MetricsManager.cs index 2dbbb661bae..804ea02fb61 100644 --- a/Robust.Server/DataMetrics/MetricsManager.cs +++ b/Robust.Server/DataMetrics/MetricsManager.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.Metrics; using System.Diagnostics.Tracing; using System.Globalization; using System.Linq; @@ -26,6 +27,7 @@ namespace Robust.Server.DataMetrics; /// /// /// Metrics can be added through the types in System.Diagnostics.Metrics or Prometheus. +/// IoC contains an implementation of that can be used to instantiate meters. /// /// public interface IMetricsManager @@ -101,6 +103,8 @@ private async Task Stop() async void IDisposable.Dispose() { + DisposeMeters(); + await Stop(); _initialized = false; diff --git a/Robust.Server/ServerIoC.cs b/Robust.Server/ServerIoC.cs index 8b0710573f0..016e8271ee1 100644 --- a/Robust.Server/ServerIoC.cs +++ b/Robust.Server/ServerIoC.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.Metrics; using Robust.Server.Configuration; using Robust.Server.Console; using Robust.Server.DataMetrics; @@ -81,6 +82,7 @@ internal static void RegisterIoC(IDependencyCollection deps) deps.Register(); deps.Register(); deps.Register(); + deps.Register(); deps.Register(); deps.Register(); deps.Register();