Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bind system load route to correct permission #1394

Merged
merged 2 commits into from
Mar 15, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 98 additions & 92 deletions src/main/java/sirius/web/health/SystemController.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ public class SystemController extends BasicController {
/**
* Simply responds with OK for <tt>/system/ok</tt>
* <p>
* This can be used to monitoring tools the check the system health.
* This can be used by monitoring tools to check the system's health.
*
* @param ctx the request being handled
* @param webContext the request being handled
*/
@Routed("/system/ok")
@PublicService(apiName = "health", format = Format.RAW)
Expand All @@ -105,16 +105,16 @@ public class SystemController extends BasicController {
@ApiResponse(responseCode = "200",
description = "Successful response",
content = @Content(mediaType = "text/plain", examples = @ExampleObject("OK")))
public void ok(WebContext ctx) {
ctx.respondWith().direct(HttpResponseStatus.OK, "OK");
public void ok(WebContext webContext) {
webContext.respondWith().direct(HttpResponseStatus.OK, "OK");
}

/**
* Determines if there is currently an ALARM present or not for: <tt>/system/monitor</tt>
* <p>
* Reports OK or ERROR, if a cluster alarm is present.
*
* @param ctx the request being handled
* @param webContext the request being handled
*/
@Routed("/system/monitor")
@PublicService(apiName = "health", format = Format.RAW)
Expand All @@ -126,20 +126,20 @@ public void ok(WebContext ctx) {
description = "Successful response",
content = @Content(mediaType = "text/plain", examples = @ExampleObject("OK")))
@ApiResponse(responseCode = "417",
description = "Failing metrics",
content = @Content(mediaType = "text/plain", examples = @ExampleObject("""
ERROR

Failing Metrics on this node:
sirius_node_state 0.0
""")))
public void monitorNode(WebContext ctx) {
description = "Failing metrics",
content = @Content(mediaType = "text/plain", examples = @ExampleObject("""
ERROR
Failing Metrics on this node:
sirius_node_state 0.0
""")))
public void monitorNode(WebContext webContext) {
if (!cluster.isAlarmPresent() || cluster.getNodeState() != MetricState.RED) {
ctx.respondWith().direct(HttpResponseStatus.OK, "OK");
webContext.respondWith().direct(HttpResponseStatus.OK, "OK");
return;
}

try (PrintWriter writer = createSimpleErrorResponse(ctx)) {
try (PrintWriter writer = createSimpleErrorResponse(webContext)) {
writer.println("ERROR");
writer.println();
writer.println("Failing Metrics on this node:");
Expand All @@ -153,32 +153,32 @@ public void monitorNode(WebContext ctx) {
}
}

private PrintWriter createSimpleErrorResponse(WebContext ctx) {
OutputStream os = ctx.respondWith().outputStream(HttpResponseStatus.EXPECTATION_FAILED, null);
return new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
private PrintWriter createSimpleErrorResponse(WebContext webContext) {
OutputStream output = webContext.respondWith().outputStream(HttpResponseStatus.EXPECTATION_FAILED, null);
return new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
}

/**
* Sends the value for the requested metric for <tt>/system/metric/[name]</tt>
*
* @param ctx the request being handled
* @param key the name of the metric to fetch
* @param webContext the request being handled
* @param key the name of the metric to fetch
*/
@Routed("/system/metric/:1")
public void metric(WebContext ctx, String key) {
for (Metric m : metrics.getMetrics()) {
if (Strings.areEqual(key, m.getCode())) {
ctx.respondWith().direct(HttpResponseStatus.OK, NLS.toMachineString(m.getValue()));
public void metric(WebContext webContext, String key) {
for (Metric metric : metrics.getMetrics()) {
if (Strings.areEqual(key, metric.getCode())) {
webContext.respondWith().direct(HttpResponseStatus.OK, NLS.toMachineString(metric.getValue()));
return;
}
}
ctx.respondWith().direct(HttpResponseStatus.OK, NLS.toMachineString(0d));
webContext.respondWith().direct(HttpResponseStatus.OK, NLS.toMachineString(0d));
}

/**
* Sends all known metrics in a format understood by <b>prometheus.io</b>.
*
* @param ctx the request being handled
* @param webContext the request being handled
*/
@Routed("/system/metrics")
@PublicService(apiName = "health", format = Format.RAW)
Expand All @@ -196,27 +196,27 @@ public void metric(WebContext ctx, String key) {
sirius_http_open_connections 7.0
""")))
@ApiResponse(responseCode = "403",
description = "Invalid authentication",
content = @Content(mediaType = "text/plain"))
public void metrics(WebContext ctx) {
if (blockPublicAccess && ctx.getHeaderValue(WebServer.HEADER_X_FORWARDED_FOR).isFilled()) {
ctx.respondWith().error(HttpResponseStatus.FORBIDDEN);
description = "Invalid authentication",
content = @Content(mediaType = "text/plain"))
public void metrics(WebContext webContext) {
if (blockPublicAccess && webContext.getHeaderValue(WebServer.HEADER_X_FORWARDED_FOR).isFilled()) {
webContext.respondWith().error(HttpResponseStatus.FORBIDDEN);
return;
}

try (PrintWriter out = new PrintWriter(new OutputStreamWriter(ctx.respondWith()
.outputStream(HttpResponseStatus.OK,
"text/plain; version=0.0.4"),
StandardCharsets.UTF_8))) {
outputNodeStateAsMetric(out);
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(webContext.respondWith()
.outputStream(HttpResponseStatus.OK,
"text/plain; version=0.0.4"),
StandardCharsets.UTF_8))) {
outputNodeStateAsMetric(writer);

for (Metric m : metrics.getMetrics()) {
outputMetric(out, m);
for (Metric metric : metrics.getMetrics()) {
outputMetric(writer, metric);
}

for (LoadInfoProvider provider : loadInfoProviders) {
for (LoadInfo info : provider.collectLoadInfos()) {
outputMetric(out, transformLoadIntoToMetric(provider, info));
outputMetric(writer, transformLoadIntoToMetric(provider, info));
}
}
}
Expand All @@ -233,48 +233,48 @@ private Metric transformLoadIntoToMetric(LoadInfoProvider provider, LoadInfo inf
/**
* Reports the node state as metric (0=OK, 1=WARN, 2=ERROR).
*
* @param out the output stream to write the metric to
* @param writer the output stream to write the metric to
*/
@SuppressWarnings("squid:S2184")
@Explain("We're only doing calculations with simple operations and small numbers.")
private void outputNodeStateAsMetric(PrintWriter out) {
outputMetric(out,
private void outputNodeStateAsMetric(PrintWriter writer) {
outputMetric(writer,
new Metric("node_state",
"Node State",
cluster.getNodeState().ordinal() - 1,
cluster.getNodeState(),
null));
}

private void outputMetric(PrintWriter out, Metric m) {
String effectiveCode = metricLabelPrefix + m.getCode().toLowerCase().replaceAll("[^a-z0-9]", "_");
out.print("# HELP ");
out.print(effectiveCode);
out.print(" ");
if (Strings.isFilled(m.getUnit())) {
out.print(m.getLabel());
out.print(" (");
out.print(m.getUnit());
out.println(")");
private void outputMetric(PrintWriter writer, Metric metric) {
String effectiveCode = metricLabelPrefix + metric.getCode().toLowerCase().replaceAll("[^a-z0-9]", "_");
writer.print("# HELP ");
writer.print(effectiveCode);
writer.print(" ");
if (Strings.isFilled(metric.getUnit())) {
writer.print(metric.getLabel());
writer.print(" (");
writer.print(metric.getUnit());
writer.println(")");
} else {
out.println(m.getLabel());
writer.println(metric.getLabel());
}

out.print("# TYPE ");
out.print(effectiveCode);
out.println(" gauge");
out.print(effectiveCode);
out.print(" ");
out.println(NLS.toMachineString(m.getValue()));
writer.print("# TYPE ");
writer.print(effectiveCode);
writer.println(" gauge");
writer.print(effectiveCode);
writer.print(" ");
writer.println(NLS.toMachineString(metric.getValue()));
}

/**
* Can be used to forcefully create an error. (A HandledException in this case.)
*
* @param ctx the current request
* @param webContext the current request
*/
@Routed("/system/fail")
public void fail(WebContext ctx) {
public void fail(WebContext webContext) {
throw Exceptions.createHandled().withSystemErrorMessage("Forced Exception").handle();
}

Expand All @@ -295,79 +295,85 @@ public void info(WebContext webContext) {
* <p>
* Clears all session data available for the current request.
*
* @param ctx the current request
* @param webContext the current request
*/
@Routed("/system/reset")
public void reset(WebContext ctx) {
ctx.clearSession();
ctx.respondWith().direct(HttpResponseStatus.OK, "Session has been cleared...");
public void reset(WebContext webContext) {
webContext.clearSession();
webContext.respondWith().direct(HttpResponseStatus.OK, "Session has been cleared...");
}

/**
* Reports the system and cluster state.
*
* @param ctx the current request
* @param webContext the current request
*/
@Routed("/system/state")
@Permission(PERMISSION_SYSTEM_STATE)
public void state(WebContext ctx) {
ctx.respondWith()
.template("/templates/system/state.html.pasta",
cluster,
metrics,
ctx.get("all").asBoolean(false),
NLS.convertDuration(Duration.ofMillis(Sirius.getUptimeInMilliseconds()), true, false));
public void state(WebContext webContext) {
webContext.respondWith()
.template("/templates/system/state.html.pasta",
cluster,
metrics,
webContext.get("all").asBoolean(false),
NLS.convertDuration(Duration.ofMillis(Sirius.getUptimeInMilliseconds()), true, false));
}

/**
* Reports the system load.
*
* @param ctx the current request
* @param webContext the current request
*/
@Routed("/system/load")
@Permission(PERMISSION_SYSTEM_STATE)
public void load(WebContext ctx) {
ctx.respondWith()
.template("/templates/system/load.html.pasta",
loadInfoProviders.getParts()
.stream()
.sorted(Comparator.comparing(LoadInfoProvider::getLabel))
.toList(),
ctx.get("all").asBoolean(false));
@Permission(PERMISSION_SYSTEM_LOAD)
public void load(WebContext webContext) {
webContext.respondWith()
.template("/templates/system/load.html.pasta",
loadInfoProviders.getParts()
.stream()
.sorted(Comparator.comparing(LoadInfoProvider::getLabel))
.toList(),
webContext.get("all").asBoolean(false));
}

/**
* Provides a list of recorded micro timings.
*
* @param ctx the current request
* @param webContext the current request
*/
@Routed("/system/timing")
@Permission(PERMISSION_SYSTEM_TIMING)
public void timing(WebContext ctx) {
if (ctx.hasParameter("enable")) {
public void timing(WebContext webContext) {
if (webContext.hasParameter("enable")) {
Microtiming.setEnabled(true);
}
if (ctx.hasParameter("disable")) {
if (webContext.hasParameter("disable")) {
Microtiming.setEnabled(false);
}

String periodSinceReset =
NLS.convertDuration(Duration.ofMillis(System.currentTimeMillis() - Microtiming.getLastReset()), true, false);
NLS.convertDuration(Duration.ofMillis(System.currentTimeMillis() - Microtiming.getLastReset()),
true,
false);

Page<String> page = new Page<>();
page.bindToRequest(ctx);
page.bindToRequest(webContext);

List<Tuple<String, Collection<Tuple<String, String>>>> timings = computeTimingInfos(page);

ctx.respondWith()
.template("/templates/system/timing.html.pasta", Microtiming.isEnabled(), timings, page, periodSinceReset);
webContext.respondWith()
.template("/templates/system/timing.html.pasta",
Microtiming.isEnabled(),
timings,
page,
periodSinceReset);
}

private List<Tuple<String, Collection<Tuple<String, String>>>> computeTimingInfos(Page<String> page) {
MultiMap<String, Tuple<String, String>> timingMap = MultiMap.createOrdered();
String query = Strings.isFilled(page.getQuery()) ? page.getQuery().toLowerCase() : null;
List<Microtiming.Timing> timings = new ArrayList<>(Microtiming.getTimings());
timings.sort(Comparator.comparingDouble(t -> t.getAvg().getCount() * t.getAvg().getAvg() * -1d));
timings.sort(Comparator.comparingDouble(timing -> timing.getAvg().getCount() * timing.getAvg().getAvg() * -1d));
for (Microtiming.Timing timing : timings) {
if (matchesQuery(query, timing)) {
timingMap.put(timing.getCategory(),
Expand Down