Files
RobustToolbox/Robust.Server/DataMetrics/MetricsManager.MetricsServer.cs
Pieter-Jan Briers 0094040d68 Dependency update / fixes / skrungle bungle (#4825)
* Move to Central Package Management.

Allows us to store NuGet package versions all in one place. Yay!

* Update NuGet packages and fix code for changes.

Notable:

Changes to ILVerify.
Npgsql doesn't need hacks for inet anymore, now we need hacks to make the old code work with this new reality.
NUnit's analyzers are already complaining and I didn't even update it to 4.x yet.
TerraFX changed to GetLastSystemError so error handling had to be changed.
Buncha APIs have more NRT annotations.

* Remove dotnet-eng NuGet package source.

I genuinely don't know what this was for, and Central Package Management starts throwing warnings about it, so YEET.

* Fix double loading of assemblies due to ALC shenanigans.

Due to how the "sideloading" code for the ModLoader was set up, it would first try to load Microsoft.Extensions.Primitives from next to the content dll. But we already have that library in Robust!

Chaos ensues.

We now try to forcibly prioritize loading from the default ALC first to avoid this.

* Remove Robust.Physics project.

Never used.

* Remove erroneous NVorbis reference.

Should be VorbisPizza and otherwise wasn't used.

* Sandbox fixes

* Remove unused unit test package references.

Castle.Core and NUnit.ConsoleRunner.

* Update NUnit to 4.0.1

This requires replacing all the old assertion methods because they removed them 🥲

* Mute CA1416 (platform check) errors

TerraFX started annotating APIs with this and I can't be arsed to entertain this analyzer so out it goes.

* Fine ya cranky, no more CPM for Robust.Client.Injectors

* Changelog

* Oh so that's what dotnet-eng was used for. Yeah ok that makes sense.

* Central package management for remaining 2 robust projects

* Ok that was a bad idea let's just use NUnit 3 on the analyzer test project

* Oh right forgot to remove this one

* Update to a newer version of RemoteExecutor

* Disable RemoteExecutor test

https://github.com/dotnet/arcade/issues/8483 Yeah this package is not well maintained and clearly we can't rely on it.

* Fix immutable list serialization
2024-01-12 22:59:52 +01:00

182 lines
6.5 KiB
C#

using System;
using System.IO;
using System.IO.Compression;
using System.Threading;
using System.Threading.Tasks;
using SpaceWizards.HttpListener;
using Prometheus;
using Robust.Shared.Log;
namespace Robust.Server.DataMetrics;
internal sealed partial class MetricsManager
{
// prometheus-net's MetricServer uses HttpListener by default.
// Use our ManagedHttpListener instead because it's less problematic.
// Also allows us to implement gzip support.
private sealed class ManagedHttpListenerMetricsServer : MetricHandler
{
private readonly ISawmill _sawmill;
private readonly HttpListener _listener;
private readonly CollectorRegistry _registry;
public ManagedHttpListenerMetricsServer(ISawmill sawmill, string host, int port, string url = "metrics/",
CollectorRegistry? registry = null)
{
_sawmill = sawmill;
_listener = new HttpListener();
_listener.Prefixes.Add($"http://{host}:{port}/{url}");
_registry = registry ?? Metrics.DefaultRegistry;
}
protected override Task StartServer(CancellationToken cancel)
{
_listener.Start();
return Task.Run(() => ListenerThread(cancel), CancellationToken.None);
}
private async Task ListenerThread(CancellationToken cancel)
{
try
{
while (!cancel.IsCancellationRequested)
{
var getContextTask = _listener.GetContextAsync();
var ctx = await getContextTask.WaitAsync(cancel);
// Task.Run this so it gets run on another thread pool thread.
_ = Task.Run(async () =>
{
MetricsEvents.Log.RequestStart();
var resp = ctx.Response;
var req = ctx.Request;
try
{
MetricsEvents.Log.ScrapeStart();
var stream = resp.OutputStream;
// prometheus-net is a terrible library and have to do all this insanity,
// just to handle the ScrapeFailedException correctly.
// Ridiculous.
await _registry.CollectAndExportAsTextAsync(new WriteWrapStream(() =>
{
resp.ContentType = "text/plain; version=0.0.4; charset=utf-8";
resp.StatusCode = 200;
var acceptEncoding = req.Headers["Accept-Encoding"];
var gzip = acceptEncoding != null && acceptEncoding.Contains("gzip");
if (gzip)
{
stream = new GZipStream(stream, CompressionLevel.Fastest);
resp.Headers["Content-Encoding"] = "gzip";
}
return stream;
}), cancel);
await stream.DisposeAsync();
MetricsEvents.Log.ScrapeStop();
}
catch (ScrapeFailedException e)
{
resp.StatusCode = 503;
if (!string.IsNullOrWhiteSpace(e.Message))
{
await using var sw = new StreamWriter(resp.OutputStream);
await sw.WriteAsync(e.Message);
}
}
catch (OperationCanceledException)
{
// Nada.
}
catch (Exception e)
{
_sawmill.Log(LogLevel.Error, e, "Exception in metrics listener");
resp.StatusCode = 500;
}
finally
{
resp.Close();
MetricsEvents.Log.RequestStop();
}
}, CancellationToken.None);
}
}
finally
{
_listener.Stop();
_listener.Close();
}
}
private sealed class WriteWrapStream : Stream
{
private Stream? _stream;
private readonly Func<Stream> _streamFunc;
public WriteWrapStream(Func<Stream> streamFunc)
{
_streamFunc = streamFunc;
}
public override void Flush()
{
GetStream().Flush();
}
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count)
{
GetStream().Write(buffer, offset, count);
}
public override void Write(ReadOnlySpan<byte> buffer)
{
GetStream().Write(buffer);
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken = new CancellationToken())
{
return GetStream().WriteAsync(buffer, cancellationToken);
}
public override void WriteByte(byte value)
{
GetStream().WriteByte(value);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return GetStream().WriteAsync(buffer, offset, count, cancellationToken);
}
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
private Stream GetStream() => _stream ??= _streamFunc();
}
}
}