Files
natsdotnet/src/NATS.Server/Monitoring/MonitorServer.cs

153 lines
5.7 KiB
C#

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace NATS.Server.Monitoring;
/// <summary>
/// HTTP monitoring server providing /healthz, /varz, and other monitoring endpoints.
/// Corresponds to Go server/monitor.go HTTP server setup.
/// </summary>
public sealed class MonitorServer : IAsyncDisposable
{
private readonly WebApplication _app;
private readonly ILogger<MonitorServer> _logger;
private readonly VarzHandler _varzHandler;
private readonly ConnzHandler _connzHandler;
private readonly SubszHandler _subszHandler;
private readonly JszHandler _jszHandler;
private readonly RoutezHandler _routezHandler;
private readonly GatewayzHandler _gatewayzHandler;
private readonly LeafzHandler _leafzHandler;
private readonly AccountzHandler _accountzHandler;
private readonly PprofHandler _pprofHandler;
public MonitorServer(NatsServer server, NatsOptions options, ServerStats stats, ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<MonitorServer>();
var builder = WebApplication.CreateSlimBuilder();
builder.WebHost.UseUrls($"http://{options.MonitorHost}:{options.MonitorPort}");
builder.Logging.ClearProviders();
_app = builder.Build();
var basePath = options.MonitorBasePath ?? "";
_varzHandler = new VarzHandler(server, options);
_connzHandler = new ConnzHandler(server);
_subszHandler = new SubszHandler(server);
_jszHandler = new JszHandler(server, options);
_routezHandler = new RoutezHandler(server);
_gatewayzHandler = new GatewayzHandler(server);
_leafzHandler = new LeafzHandler(server);
_accountzHandler = new AccountzHandler(server);
_pprofHandler = new PprofHandler();
_app.MapGet(basePath + "/", () =>
{
stats.HttpReqStats.AddOrUpdate("/", 1, (_, v) => v + 1);
return Results.Ok(new
{
endpoints = new[]
{
"/varz", "/connz", "/healthz", "/routez",
"/gatewayz", "/leafz", "/subz", "/accountz", "/jsz",
},
});
});
_app.MapGet(basePath + "/healthz", () =>
{
stats.HttpReqStats.AddOrUpdate("/healthz", 1, (_, v) => v + 1);
return Results.Ok("ok");
});
_app.MapGet(basePath + "/varz", async (HttpContext ctx) =>
{
stats.HttpReqStats.AddOrUpdate("/varz", 1, (_, v) => v + 1);
return Results.Ok(await _varzHandler.HandleVarzAsync(ctx.RequestAborted));
});
_app.MapGet(basePath + "/connz", (HttpContext ctx) =>
{
stats.HttpReqStats.AddOrUpdate("/connz", 1, (_, v) => v + 1);
return Results.Ok(_connzHandler.HandleConnz(ctx));
});
_app.MapGet(basePath + "/routez", () =>
{
stats.HttpReqStats.AddOrUpdate("/routez", 1, (_, v) => v + 1);
return Results.Ok(_routezHandler.Build());
});
_app.MapGet(basePath + "/gatewayz", () =>
{
stats.HttpReqStats.AddOrUpdate("/gatewayz", 1, (_, v) => v + 1);
return Results.Ok(_gatewayzHandler.Build());
});
_app.MapGet(basePath + "/leafz", () =>
{
stats.HttpReqStats.AddOrUpdate("/leafz", 1, (_, v) => v + 1);
return Results.Ok(_leafzHandler.Build());
});
_app.MapGet(basePath + "/subz", (HttpContext ctx) =>
{
stats.HttpReqStats.AddOrUpdate("/subz", 1, (_, v) => v + 1);
return Results.Ok(_subszHandler.HandleSubsz(ctx));
});
_app.MapGet(basePath + "/subscriptionsz", (HttpContext ctx) =>
{
stats.HttpReqStats.AddOrUpdate("/subscriptionsz", 1, (_, v) => v + 1);
return Results.Ok(_subszHandler.HandleSubsz(ctx));
});
_app.MapGet(basePath + "/accountz", () =>
{
stats.HttpReqStats.AddOrUpdate("/accountz", 1, (_, v) => v + 1);
return Results.Ok(_accountzHandler.Build());
});
_app.MapGet(basePath + "/accstatz", () =>
{
stats.HttpReqStats.AddOrUpdate("/accstatz", 1, (_, v) => v + 1);
return Results.Ok(_accountzHandler.BuildStats());
});
_app.MapGet(basePath + "/jsz", () =>
{
stats.HttpReqStats.AddOrUpdate("/jsz", 1, (_, v) => v + 1);
return Results.Ok(_jszHandler.Build());
});
if (options.ProfPort > 0)
{
_app.MapGet(basePath + "/debug/pprof", () =>
{
stats.HttpReqStats.AddOrUpdate("/debug/pprof", 1, (_, v) => v + 1);
return Results.Text(_pprofHandler.Index(), "text/plain");
});
_app.MapGet(basePath + "/debug/pprof/profile", (HttpContext ctx) =>
{
stats.HttpReqStats.AddOrUpdate("/debug/pprof/profile", 1, (_, v) => v + 1);
var seconds = 30;
if (ctx.Request.Query.TryGetValue("seconds", out var values)
&& int.TryParse(values.ToString(), out var parsed))
{
seconds = parsed;
}
return Results.File(_pprofHandler.CaptureCpuProfile(seconds), "application/json");
});
}
}
public async Task StartAsync(CancellationToken ct)
{
await _app.StartAsync(ct);
_logger.LogInformation("Monitoring listening on {Urls}", string.Join(", ", _app.Urls));
}
public async ValueTask DisposeAsync()
{
await _app.StopAsync();
await _app.DisposeAsync();
_varzHandler.Dispose();
}
}