From 01ebe3219d4f95945fb8e685b800a711d440ab93 Mon Sep 17 00:00:00 2001 From: Cinka Date: Fri, 27 Jun 2025 21:31:38 +0300 Subject: [PATCH] - add: Metrics --- Nebula.Launcher/Nebula.Launcher.csproj | 4 + .../ServerCompoundEntryModelView.cs | 16 +++- Nebula.Runner/App.cs | 1 + Nebula.Runner/Nebula.Runner.csproj | 1 + Nebula.Runner/Services/HarmonyService.cs | 55 +++++++++++ Nebula.Runner/Services/ReflectionService.cs | 59 ++++++++++++ .../Services/RunnerService.cs | 92 ++++++++++++++++++- Nebula.Shared/FileApis/FtpFileApi.cs | 74 --------------- Nebula.Shared/Nebula.Shared.csproj | 1 - Nebula.sln.DotSettings.user | 11 +++ 10 files changed, 233 insertions(+), 81 deletions(-) create mode 100644 Nebula.Runner/Services/HarmonyService.cs create mode 100644 Nebula.Runner/Services/ReflectionService.cs rename {Nebula.Shared => Nebula.Runner}/Services/RunnerService.cs (50%) delete mode 100644 Nebula.Shared/FileApis/FtpFileApi.cs diff --git a/Nebula.Launcher/Nebula.Launcher.csproj b/Nebula.Launcher/Nebula.Launcher.csproj index f6e067d..d96641b 100644 --- a/Nebula.Launcher/Nebula.Launcher.csproj +++ b/Nebula.Launcher/Nebula.Launcher.csproj @@ -54,6 +54,8 @@ + + @@ -61,6 +63,8 @@ + + diff --git a/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs index c15c938..73aeb8f 100644 --- a/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs +++ b/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs @@ -27,6 +27,8 @@ public sealed partial class ServerCompoundEntryViewModel : [ObservableProperty] private bool _loading = true; private string? _name; + private RobustUrl? _url; + private ServerFilter? _currentFilter; public string? Name { @@ -58,14 +60,16 @@ public sealed partial class ServerCompoundEntryViewModel : { Task.Run(async () => { + _url = url; try { Message = "Loading server entry..."; - var status = await RestService.GetAsync(url.StatusUri, cancellationToken); + var status = await RestService.GetAsync(_url.StatusUri, cancellationToken); - CurrentEntry = ServiceProvider.GetService()!.WithData(url,name, status); + CurrentEntry = ServiceProvider.GetService()!.WithData(_url,name, status); CurrentEntry.IsFavorite = IsFavorite; CurrentEntry.Loading = false; + CurrentEntry.ProcessFilter(_currentFilter); Loading = false; } catch (Exception e) @@ -79,12 +83,20 @@ public sealed partial class ServerCompoundEntryViewModel : public void ToggleFavorites() { + if (CurrentEntry is null && _url is not null) + { + IsFavorite = !IsFavorite; + if(IsFavorite) FavoriteServerListProvider.AddFavorite(_url); + else FavoriteServerListProvider.RemoveFavorite(_url); + } + CurrentEntry?.ToggleFavorites(); } public void ProcessFilter(ServerFilter? serverFilter) { + _currentFilter = serverFilter; if(CurrentEntry is IFilterConsumer filterConsumer) filterConsumer.ProcessFilter(serverFilter); } diff --git a/Nebula.Runner/App.cs b/Nebula.Runner/App.cs index 45bfaba..73221ad 100644 --- a/Nebula.Runner/App.cs +++ b/Nebula.Runner/App.cs @@ -1,3 +1,4 @@ +using Nebula.Runner.Services; using Nebula.Shared; using Nebula.Shared.Models; using Nebula.Shared.Services; diff --git a/Nebula.Runner/Nebula.Runner.csproj b/Nebula.Runner/Nebula.Runner.csproj index 66664cc..321412a 100644 --- a/Nebula.Runner/Nebula.Runner.csproj +++ b/Nebula.Runner/Nebula.Runner.csproj @@ -12,6 +12,7 @@ + diff --git a/Nebula.Runner/Services/HarmonyService.cs b/Nebula.Runner/Services/HarmonyService.cs new file mode 100644 index 0000000..b769567 --- /dev/null +++ b/Nebula.Runner/Services/HarmonyService.cs @@ -0,0 +1,55 @@ +using System.Data; +using HarmonyLib; +using Nebula.Shared; + +namespace Nebula.Runner.Services; + +[ServiceRegister] +public class HarmonyService(ReflectionService reflectionService) +{ + private HarmonyInstance? _instance; + + public HarmonyInstance Instance + { + get + { + if (_instance is null) + CreateInstance(); + return _instance!; + } + } + + public void CreateInstance() + { + if (_instance is not null) + throw new Exception(); + + _instance = new HarmonyInstance(); + UnShittyWizard(); + } + + /// + /// Я помню пенис большой,Я помню пенис большой, Я помню пенис большой, я помню.... + /// + private void UnShittyWizard() + { + var method = reflectionService.GetType("Robust.Client.GameController").TypeInitializer; + _instance!.Harmony.Patch(method, new HarmonyMethod(Prefix)); + } + + static bool Prefix() + { + // Returning false skips the execution of the original static constructor + return false; + } +} + +public class HarmonyInstance +{ + public readonly Harmony Harmony; + + internal HarmonyInstance() + { + Harmony = new Harmony("ru.cinka.patch"); + } +} \ No newline at end of file diff --git a/Nebula.Runner/Services/ReflectionService.cs b/Nebula.Runner/Services/ReflectionService.cs new file mode 100644 index 0000000..d1432e2 --- /dev/null +++ b/Nebula.Runner/Services/ReflectionService.cs @@ -0,0 +1,59 @@ +using System.Reflection; +using Nebula.Shared; +using Nebula.Shared.FileApis; +using Nebula.Shared.Services; + +namespace Nebula.Runner.Services; + +[ServiceRegister] +public class ReflectionService(AssemblyService assemblyService) +{ + private Dictionary _typeCache = new(); + + public void RegisterAssembly(Assembly robustAssembly) + { + _typeCache.Add(robustAssembly.GetName().Name!, robustAssembly); + } + + public void RegisterRobustAssemblies(AssemblyApi engine) + { + RegisterAssembly(GetRobustAssembly("Robust.Shared", engine)); + RegisterAssembly(GetRobustAssembly("Robust.Client", engine)); + } + + private Assembly GetRobustAssembly(string assemblyName, AssemblyApi engine) + { + if(!assemblyService.TryOpenAssembly(assemblyName, engine, out var assembly)) + throw new Exception($"Unable to locate {assemblyName}.dll in engine build!"); + return assembly; + } + + public Type? GetTypeImp(string name) + { + foreach (var (prefix,assembly) in _typeCache) + { + string appendedName = prefix + name; + var theType = assembly.GetType(appendedName); + if (theType != null) + { + return theType; + } + } + + return null; + } + + public Type GetType(string name) + { + var prefix = ExtrackPrefix(name); + return !_typeCache.TryGetValue(prefix, out var assembly) + ? GetTypeImp(name)! + : assembly.GetType(name)!; + } + + private string ExtrackPrefix(string path) + { + var sp = path.Split("."); + return sp[0] + "." + sp[1]; + } +} \ No newline at end of file diff --git a/Nebula.Shared/Services/RunnerService.cs b/Nebula.Runner/Services/RunnerService.cs similarity index 50% rename from Nebula.Shared/Services/RunnerService.cs rename to Nebula.Runner/Services/RunnerService.cs index 628bd2e..9151bb8 100644 --- a/Nebula.Shared/Services/RunnerService.cs +++ b/Nebula.Runner/Services/RunnerService.cs @@ -1,8 +1,14 @@ -using Nebula.Shared.Models; +using System.Globalization; +using System.Reflection; +using System.Reflection.Emit; +using HarmonyLib; +using Nebula.Shared; +using Nebula.Shared.Models; +using Nebula.Shared.Services; using Nebula.Shared.Services.Logging; using Robust.LoaderApi; -namespace Nebula.Shared.Services; +namespace Nebula.Runner.Services; [ServiceRegister] public sealed class RunnerService( @@ -10,9 +16,12 @@ public sealed class RunnerService( DebugService debugService, ConfigurationService varService, EngineService engineService, - AssemblyService assemblyService) + AssemblyService assemblyService, + ReflectionService reflectionService, + HarmonyService harmonyService) { private ILogger _logger = debugService.GetLogger("RunnerService"); + private bool MetricEnabled = false; //TODO: ADD METRIC THINKS LATER public async Task Run(string[] runArgs, RobustBuildInfo buildInfo, IRedialApi redialApi, ILoadingHandler loadingHandler, @@ -52,11 +61,86 @@ public sealed class RunnerService( if (!assemblyService.TryOpenAssembly(varService.GetConfigValue(CurrentConVar.RobustAssemblyName)!, engine, out var clientAssembly)) throw new Exception("Unable to locate Robust.Client.dll in engine build!"); - + if (!assemblyService.TryGetLoader(clientAssembly, out var loader)) return; + + if(!assemblyService.TryOpenAssembly("Prometheus.NetStandard", engine, out var prometheusAssembly)) + return; + + reflectionService.RegisterRobustAssemblies(engine); + harmonyService.CreateInstance(); + + IDisposable? metricServer = null; + if (MetricEnabled) + { + MetricsEnabledPatcher.ApplyPatch(reflectionService, harmonyService); + metricServer = RunHelper.RunMetric(prometheusAssembly); + } + + await Task.Run(() => loader.Main(args), cancellationToken); + + metricServer?.Dispose(); + } +} + +public static class MetricsEnabledPatcher +{ + public static void ApplyPatch(ReflectionService reflectionService, HarmonyService harmonyService) + { + var harmony = harmonyService.Instance.Harmony; + + // Get the target method: the getter of MetricsEnabled + var targetType = reflectionService.GetType("Robust.Shared.GameObjects.EntitySystemManager"); + var targetMethod = targetType.GetProperty("MetricsEnabled").GetGetMethod(); + + // Get MethodInfo for the prefix + var prefix = typeof(MetricsEnabledPatcher).GetMethod(nameof(MetricsEnabledGetterPrefix), + BindingFlags.Static | BindingFlags.NonPublic); + + // Create HarmonyMethod + var prefixMethod = new HarmonyMethod(prefix); + + // Patch it! + harmony.Patch(targetMethod, prefix: prefixMethod); + } + + // This prefix will override the getter and force return true + private static bool MetricsEnabledGetterPrefix(ref bool __result) + { + __result = true; + return false; // Skip original method + } +} + +public static class RunHelper +{ + public static IDisposable RunMetric(Assembly prometheusAssembly) + { + var metricServerType = prometheusAssembly.GetType("Prometheus.MetricServer"); + var collectorRegistryType = prometheusAssembly.GetType("Prometheus.CollectorRegistry"); + + var ctor = metricServerType!.GetConstructor(new Type[] + { + typeof(string), + typeof(int), + typeof(string), + collectorRegistryType!, + typeof(bool) + }); + + var hostname = "localhost"; + var port = 51235; + var url = "metrics/"; + object? registry = null; + var useHttps = false; + + var metricServerInstance = ctor!.Invoke(new object[] { hostname, port, url, registry!, useHttps }); + metricServerType.GetMethod("Start")!.Invoke(metricServerInstance, BindingFlags.Default, null, null, CultureInfo.CurrentCulture); + + return (IDisposable)metricServerInstance; } } diff --git a/Nebula.Shared/FileApis/FtpFileApi.cs b/Nebula.Shared/FileApis/FtpFileApi.cs deleted file mode 100644 index 43d7103..0000000 --- a/Nebula.Shared/FileApis/FtpFileApi.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Net; -using FluentFTP; -using Nebula.Shared.FileApis.Interfaces; - -namespace Nebula.Shared.FileApis; - -public class FtpFileApi : IWriteFileApi, IDisposable -{ - private readonly string _ftpHost; - private readonly string _username; - private readonly string _password; - - private readonly FtpClient Client; - - public FtpFileApi(string ftpHost, string username, string password) - { - _ftpHost = ftpHost; - _username = username; - _password = password; - Client = CreateClient(); - Client.AutoConnect(); - } - - public bool Save(string path, Stream input) - { - try - { - var result = Client.UploadStream(input, path, FtpRemoteExists.Overwrite, true); - return result == FtpStatus.Success; - } - catch - { - return false; - } - } - - public bool Remove(string path) - { - try - { - Client.DeleteFile(path); - return true; - } - catch - { - return false; - } - } - - public bool Has(string path) - { - try - { - return Client.FileExists(path); - } - catch - { - return false; - } - } - - private FtpClient CreateClient() - { - var client = new FtpClient(_ftpHost, _username, _password); - client.Config.EncryptionMode = FtpEncryptionMode.None; - client.Config.ValidateAnyCertificate = true; - return client; - } - - public void Dispose() - { - Client.Dispose(); - } -} \ No newline at end of file diff --git a/Nebula.Shared/Nebula.Shared.csproj b/Nebula.Shared/Nebula.Shared.csproj index 51c89f5..4a244a7 100644 --- a/Nebula.Shared/Nebula.Shared.csproj +++ b/Nebula.Shared/Nebula.Shared.csproj @@ -11,7 +11,6 @@ Utility.runtime.json - diff --git a/Nebula.sln.DotSettings.user b/Nebula.sln.DotSettings.user index c725730..d5dc88b 100644 --- a/Nebula.sln.DotSettings.user +++ b/Nebula.sln.DotSettings.user @@ -1,5 +1,6 @@  ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -20,6 +21,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -27,22 +29,29 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -51,4 +60,6 @@ <AssemblyExplorer> <Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.2\ref\net9.0\System.Net.Http.dll" /> <Assembly Path="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.2\System.Net.Http.dll" /> + <Assembly Path="C:\Users\Cinka\.nuget\packages\prometheus-net\0.0.2\lib\net40\prometheus-net.dll" /> + <Assembly Path="C:\Users\Cinka\.nuget\packages\microsoft.extensions.objectpool\7.0.0\lib\net7.0\Microsoft.Extensions.ObjectPool.dll" /> </AssemblyExplorer> \ No newline at end of file