- add: Metrics

This commit is contained in:
2025-06-27 21:31:38 +03:00
parent 147915f684
commit 01ebe3219d
10 changed files with 233 additions and 81 deletions

View File

@@ -1,3 +1,4 @@
using Nebula.Runner.Services;
using Nebula.Shared;
using Nebula.Shared.Models;
using Nebula.Shared.Services;

View File

@@ -12,6 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Lib.Harmony" Version="2.3.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0"/>
<PackageReference Include="SharpZstd.Interop" Version="1.5.6"/>
</ItemGroup>

View File

@@ -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();
}
/// <summary>
/// Я помню пенис большой,Я помню пенис большой, Я помню пенис большой, я помню....
/// </summary>
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");
}
}

View File

@@ -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<string, Assembly> _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];
}
}

View File

@@ -0,0 +1,187 @@
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.Runner.Services;
[ServiceRegister]
public sealed class RunnerService(
ContentService contentService,
DebugService debugService,
ConfigurationService varService,
EngineService engineService,
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,
CancellationToken cancellationToken)
{
_logger.Log("Start Content!");
var engine = await engineService.EnsureEngine(buildInfo.BuildInfo.Build.EngineVersion);
if (engine is null)
throw new Exception("Engine version not found: " + buildInfo.BuildInfo.Build.EngineVersion);
var hashApi = await contentService.EnsureItems(buildInfo.RobustManifestInfo, loadingHandler, cancellationToken);
var extraMounts = new List<ApiMount>
{
new(hashApi, "/")
};
if (hashApi.TryOpen("manifest.yml", out var stream))
{
var modules = ContentManifestParser.ExtractModules(stream);
foreach (var moduleStr in modules)
{
var module =
await engineService.EnsureEngineModules(moduleStr, buildInfo.BuildInfo.Build.EngineVersion);
if (module is not null)
extraMounts.Add(new ApiMount(module, "/"));
}
await stream.DisposeAsync();
}
var args = new MainArgs(runArgs, engine, redialApi, extraMounts);
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;
}
}
public static class ContentManifestParser
{
public static List<string> ExtractModules(Stream manifestStream)
{
using var reader = new StreamReader(manifestStream);
return ExtractModules(reader.ReadToEnd());
}
public static List<string> ExtractModules(string manifestContent)
{
var modules = new List<string>();
var lines = manifestContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
bool inModulesSection = false;
foreach (var rawLine in lines)
{
var line = rawLine.Trim();
if (line.StartsWith("modules:"))
{
inModulesSection = true;
continue;
}
if (inModulesSection)
{
if (line.StartsWith("- "))
{
modules.Add(line.Substring(2).Trim());
}
else if (!line.StartsWith(" "))
{
break;
}
}
}
return modules;
}
}