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

APPSERV-47 Adds Custom Watches to Monitoring Console #4463

Merged
merged 15 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from 14 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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved.
* Copyright (c) 2019-2020 Payara Foundation and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
Expand Down Expand Up @@ -42,8 +42,11 @@

import static org.glassfish.config.support.CommandTarget.DAS;

import java.beans.PropertyVetoException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.logging.Logger;

import javax.inject.Inject;

Expand All @@ -53,6 +56,8 @@
import org.glassfish.api.admin.AdminCommandContext;
import org.glassfish.api.admin.CommandRunner;
import org.glassfish.api.admin.ExecuteOn;
import org.glassfish.api.admin.RestEndpoint;
import org.glassfish.api.admin.RestEndpoints;
import org.glassfish.api.admin.RuntimeType;
import org.glassfish.config.support.TargetType;
import org.glassfish.deployment.autodeploy.AutoDeployer;
Expand All @@ -61,22 +66,50 @@
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.ServiceLocator;
import org.jvnet.hk2.annotations.Service;
import org.jvnet.hk2.config.ConfigSupport;
import org.jvnet.hk2.config.SingleConfigCode;
import org.jvnet.hk2.config.TransactionFailure;

import com.sun.enterprise.config.serverbeans.Domain;
import com.sun.enterprise.util.SystemPropertyConstants;

import fish.payara.monitoring.configuration.MonitoringConsoleConfiguration;

@Service(name = "set-monitoring-console-configuration")
@PerLookup
@ExecuteOn({RuntimeType.DAS})
@TargetType({DAS})
@RestEndpoints({
@RestEndpoint(configBean = Domain.class,
opType = RestEndpoint.OpType.POST,
path = "set-monitoring-console-configuration",
description = "Set Monitoring Console Configuration")
})
public class SetMonitoringConsoleConfigurationCommand implements AdminCommand {

private static final Logger LOGGER = Logger.getLogger(SetMonitoringConsoleConfigurationCommand.class.getName());

private static final String MONITORING_CONSOLE_APP_NAME = "__monitoringconsole";
private final static String GLASSFISH_LIB_INSTALL_APPLICATIONS = "glassfish/lib/install/applications";

@Param(optional = true)
private Boolean enabled;

@Param(optional = true, alias = "disable-watch")
private String disableWatch;

@Param(optional = true, alias = "enable-watch")
private String enableWatch;

@Param(optional = true, alias = "add-watch-name")
private String addWatchName;

@Param(optional = true, alias = "add-watch-json")
private String addWatchJson;

@Param(optional = true, alias = "remove-watch")
private String removeWatch;

Copy link
Contributor Author

@jbee jbee Jan 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NB. I opted for a parameter for each action. This could also be done by adding a general parameter watch for the name and data for the JSON and another parameter action or so. But I found this easier to use. On the other hand these are not really intended to be used manually. They mainly exist so that the service can run them to update the config. Manual use is possible but will take a restart to take effect.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they're not for manual use you can prepend them with an underscore (or two underscores, can't remember) to hide them as options on the command line (but not from REST).

@Inject
protected CommandRunner commandRunner;

Expand All @@ -88,13 +121,69 @@ public class SetMonitoringConsoleConfigurationCommand implements AdminCommand {

@Override
public void execute(AdminCommandContext context) {
if (enabled != null) {
MonitoringConsoleConfiguration config = domain.getExtensionByType(MonitoringConsoleConfiguration.class);
if (config == null) {
context.getActionReport().failure(LOGGER, "Monitoring Console configuration does not exist.");
return;
}
if (enabled != null && Boolean.parseBoolean(config.getEnabled()) != enabled.booleanValue()) {
if (enabled.booleanValue()) {
deployMonitoringConsole(context.getActionReport());
} else {
undeployMonitoringConsole(context.getActionReport());
}
}
try {
ConfigSupport.apply(new SingleConfigCode<MonitoringConsoleConfiguration>(){
@Override
public Object run(MonitoringConsoleConfiguration configProxy) throws PropertyVetoException, TransactionFailure {
if (enabled != null) {
configProxy.setEnabled(enabled.toString());
}
if (isDefined(disableWatch) ) {
List<String> disabledWatchNames = configProxy.getDisabledWatchNames();
if (!disabledWatchNames.contains(disableWatch)) {
disabledWatchNames.add(disableWatch);
}
}
if (isDefined(enableWatch)) {
configProxy.getDisabledWatchNames().remove(enableWatch);
}
if (isDefined(addWatchName) && isDefined(addWatchJson)) {
List<String> customWatchNames = configProxy.getCustomWatchNames();
List<String> customWatchValues = configProxy.getCustomWatchValues();
int index = customWatchNames.indexOf(addWatchName);
if (index >= 0) {
customWatchNames.remove(index);
if (index < customWatchValues.size()) {
customWatchValues.remove(index);
}
}
customWatchNames.add(addWatchName);
customWatchValues.add(addWatchJson);
}
if (isDefined(removeWatch)) {
List<String> customWatchNames = configProxy.getCustomWatchNames();
int index = customWatchNames.indexOf(removeWatch);
if (index >= 0) {
customWatchNames.remove(index);
List<String> customWatchValues = configProxy.getCustomWatchValues();
if (index < customWatchValues.size()) {
customWatchValues.remove(index);
}
}
configProxy.getDisabledWatchNames().remove(removeWatch);
}
return null;
}
}, config);
} catch (TransactionFailure ex) {
context.getActionReport().failure(LOGGER, "Failed to update Monitoring Console configuration", ex);
}
}

static boolean isDefined(String value) {
return value != null && !value.isEmpty();
}

private void undeployMonitoringConsole(ActionReport report) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ public enum Level {
public boolean isLessSevereThan(Level other) {
return ordinal() > other.ordinal();
}

public static fish.payara.monitoring.alert.Alert.Level parse(String level) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the full canonical name necessary here? I can't see a conflicting import, and neither does my IDE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope. good catch.

return valueOf(level.toUpperCase());
}
}

public static final class Frame implements Iterable<SeriesDataset> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,28 @@ default Collection<Alert> alerts() {
* Watches
*/

/**
* @param name name of the {@link Watch} to look up
* @return the {@link Watch} with the give name or {@code null} if no such watch exist.
*/
Watch watchByName(String name);

/**
* Adds a watch to the evaluation loop. To remove the watch just use {@link Watch#stop()}.
*
* @param watch new watch to add to evaluation loop
*/
void addWatch(Watch watch);

/**
* Removes the given watch. Alerts triggered by this watch will end now.
*
* @param watch the watch to end, not null
*/
void removeWatch(Watch watch);

boolean toggleWatch(String name, boolean disabled);

/**
* @return All watches registered for evaluation.
*/
Expand All @@ -111,4 +126,5 @@ default Collection<Alert> alerts() {
* {@link #watches()}.
*/
Collection<Watch> wachtesFor(Series series);

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
*/
package fish.payara.monitoring.alert;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonValue;

import fish.payara.monitoring.alert.Alert.Level;
import fish.payara.monitoring.model.SeriesLookup;
import fish.payara.monitoring.model.Metric;
Expand Down Expand Up @@ -137,4 +141,30 @@ public String toString() {
}
return str.toString();
}

public JsonValue toJSON() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this class already exists, but a description of what it's used for wouldn't go amiss - circumstance isn't an intuitive name to me in the context of alerts (in comparison to something like event).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added javadoc.

if (isUnspecified()) {
return JsonValue.NULL;
}
return Json.createObjectBuilder()
.add("level", level.name().toLowerCase())
.add("start", start.toJSON())
.add("stop", stop.toJSON())
.add("suppress", suppress.toJSON())
.add("suppressing", suppressing == null ? JsonValue.NULL : suppressing.toJSON())
.build();
}

public static Circumstance fromJson(JsonValue value) {
if (value == JsonValue.NULL || value == null) {
return Circumstance.UNSPECIFIED;
}
JsonObject obj = value.asJsonObject();
return new Circumstance(
Level.parse(obj.getString("level")),
Condition.fromJSON(obj.get("start")),
Condition.fromJSON(obj.get("stop")),
Metric.fromJSON(obj.get("suppressing")),
Condition.fromJSON(obj.get("suppress")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,14 @@
import static java.lang.Math.abs;
import static java.lang.Math.min;

import java.util.NoSuchElementException;
import java.util.Objects;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonValue;

import fish.payara.monitoring.model.SeriesDataset;

/**
Expand Down Expand Up @@ -74,6 +80,15 @@ public enum Operator {
public String toString() {
return symbol;
}

public static Operator parse(String symbol) {
for (Operator op : values()) {
if (op.symbol.equals(symbol) ) {
return op;
}
}
throw new NoSuchElementException("Operator with symbol does not exist: " + symbol);
}
}

public final Operator comparison;
Expand All @@ -88,7 +103,7 @@ public Condition(Operator comparison, long threshold) {
public Condition(Operator comparison, long threshold, Number forLast, boolean onAverage) {
this.comparison = comparison;
this.threshold = threshold;
this.forLast = forLast;
this.forLast = !onAverage && forLast instanceof Integer && forLast.intValue() == 1 ? null : forLast;
this.onAverage = onAverage && forLast != null && forLast.longValue() > 0L;
}

Expand Down Expand Up @@ -248,16 +263,15 @@ public String toString() {
StringBuilder str = new StringBuilder();
boolean any = isForLastPresent() && forLast.intValue() == 0;
boolean anyN = isForLastPresent() && forLast.intValue() < 0;
if (any || anyN) {
str.append("any 1 ");
}
str.append("value ").append(comparison.toString()).append(' ').append(threshold);
if (isForLastPresent() && !any) {
if (isForLastPresent()) {
if (onAverage) {
str.append(" for average of last ");
} else if (anyN) {
str.append(" in last ");
}else {
} else if (any) {
str.append(" in sample");
} else {
str.append(" for last ");
}
}
Expand All @@ -270,4 +284,39 @@ public String toString() {
return str.toString();
}

public JsonValue toJSON() {
if (isNone()) {
return JsonValue.NULL;
}
JsonObjectBuilder builder = Json.createObjectBuilder()
.add("comparison", comparison.symbol)
.add("threshold", threshold)
.add("onAverage", onAverage);
if (isForLastMillis()) {
builder.add("forMillis", forLast.longValue());
}
if (isForLastTimes()) {
builder.add("forTimes", forLast.intValue());
}
return builder.build();
}

public static Condition fromJSON(JsonValue value) {
if (value == null || value == JsonValue.NULL) {
return Condition.NONE;
}
JsonObject obj = value.asJsonObject();
Number forLast = null;
if (obj.containsKey("forMillis")) {
forLast = obj.getJsonNumber("forMillis").longValue();
}
if (obj.containsKey("forTimes")) {
forLast = obj.getInt("forTimes");
}
return new Condition(
Operator.parse(obj.getString("comparison", ">")),
obj.getJsonNumber("threshold").longValue(),
forLast,
obj.getBoolean("onAverage", false));
}
}
Loading