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-11 Adds Health Check Alerts to Monitoring Console #4390

Merged
merged 34 commits into from
Jan 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a4c25b9
APPSERV-11 adds basic alert system
jbee Dec 9, 2019
ec9a089
APPSERV-11 adds some health checks as metric (incomplete)
jbee Dec 10, 2019
31ba4c4
APPSERV-11 adds JS alert tables; updates most health checks to be mon…
jbee Dec 13, 2019
4581c85
APPSERV-11 collect health check metrics and watches
jbee Dec 16, 2019
5744a5e
APPSERV-11 adds health check preset page (and a fix for CP watch)
jbee Dec 16, 2019
1767ac1
APPSERV-11 adds missing data status text for health page, fixes filte…
jbee Dec 16, 2019
412e0bb
APPSERV-11 fixed NPE when removing watch without alerts; adds try-cat…
jbee Dec 16, 2019
e8fd1b6
APPSERV-11 adds alert ack to UI and web API
jbee Dec 16, 2019
348ea41
APPSERV-11 MP health check liveliness metric and watch as single perc…
jbee Dec 17, 2019
0ccad82
APPSERV-11 only draw decoration lines once
jbee Dec 17, 2019
e263249
APPSERV-11 adds gradient backgrounds for charts
jbee Dec 17, 2019
eb4f096
APPSERV-11 adds line chart backgrounds coloured by watch thresholds
jbee Jan 2, 2020
9864a8c
APPSERV-11 coloring line chart backs from watch thresholds and legend…
jbee Jan 3, 2020
0ad37ad
APPSERV-11 better watch indicator on axis
jbee Jan 3, 2020
9ad9607
APPSERV-11 line colors avoid colliding with indicator colors
jbee Jan 3, 2020
b873ca3
APPSERV-11 adds alerts settings; fixes alert isStopped()
jbee Jan 3, 2020
cb644fb
APPSERV-11 adds alert filter settings
jbee Jan 4, 2020
5497c1f
APPSERV-11 adds monitoring page and watch for collection duration
jbee Jan 4, 2020
7ac9e2e
APPSERV-11 adds lines for alert levels in line graphs
jbee Jan 4, 2020
e51bbdd
APPSERV-11 fixed ping execution via plain HTTP connection to avoid er…
jbee Jan 4, 2020
d3c725b
APPSERV-11 close URL connection
jbee Jan 4, 2020
20a64c0
APPSERV-11 decoration lines via background areas instead of data series
jbee Jan 4, 2020
f2ed1fa
APPSERV-11 coloring of 'series' now is per widget series
jbee Jan 4, 2020
e868eb8
APPSERV-11 removes merged JS file from sources by generating it in ta…
jbee Jan 5, 2020
90a027f
APPSERV-11 adds JVM page
jbee Jan 5, 2020
51b1b37
APPSERV-11 adds seperate rowspan for widgets
jbee Jan 5, 2020
f5cee4a
APPSERV-11 updates copyright header and adds javadoc
jbee Jan 6, 2020
3f9a6e3
APPSERV-11 adds more tests, some renames and javadoc
jbee Jan 6, 2020
537c605
APPSERV-11 fixed alert table filtering options for level
jbee Jan 6, 2020
3304dc0
Merge branch 'master' into APPSERV-11-health-check-alerts
jbee Jan 7, 2020
3718531
Merge branch 'master' into APPSERV-11-health-check-alerts
jbee Jan 8, 2020
2ce3ecf
APPSERV-11 reverts changes to JdbcResourcesUtil.java
jbee Jan 8, 2020
ef7ef56
APPSERV-11 APPSERV-14 fixed NPE when health check options are not ini…
jbee Jan 8, 2020
554e6ba
APPSERV-11 APPSERV-14 fixed NPE when health check options are not ini…
jbee Jan 8, 2020
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
@@ -0,0 +1,285 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 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
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://github.com/payara/Payara/blob/master/LICENSE.txt
* See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* The Payara Foundation designates this particular file as subject to the "Classpath"
* exception as provided by the Payara Foundation in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package fish.payara.monitoring.alert;

import static java.time.Instant.ofEpochMilli;
import static java.time.LocalDateTime.ofInstant;
import static java.time.ZoneId.systemDefault;

import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;

import fish.payara.monitoring.model.Series;
import fish.payara.monitoring.model.SeriesDataset;

/**
* An {@linkplain Alert} is raised when a watched series matches the {@link Circumstance}s for {@link Level#RED} or
* {@link Level#AMBER} and last until the same {@link Series} and instance transitions to {@link Level#GREEN} or
* {@link Level#WHITE}.
*
* @see Watch
*
* @author Jan Bernitt
*/
public final class Alert implements Iterable<Alert.Frame> {

public enum Level {
/**
* Critical level
*/
RED,
/**
* Elevated level to warn about
*/
AMBER,
/**
* Within expected range
*/
GREEN,
/**
* Not classified
*/
WHITE;

public boolean isLessSevereThan(Level other) {
return ordinal() > other.ordinal();
}
}

public static final class Frame implements Iterable<SeriesDataset> {
public final Level level;
public final SeriesDataset cause;
public final long start;
private final List<SeriesDataset> captured;
long end;

public Frame(Level level, SeriesDataset cause, List<SeriesDataset> captured) {
this.level = level;
this.cause = cause;
this.captured = captured;
this.start = System.currentTimeMillis();
}

@Override
public Iterator<SeriesDataset> iterator() {
return captured.iterator();
}

public long getEnd() {
return end;
}
}

private static final AtomicInteger NEXT_SERIAL = new AtomicInteger();
/**
* The change count tracks state changes of all {@link Alert} instances.
*/
private static final AtomicInteger CHANGE_COUNT = new AtomicInteger();

public static int getChangeCount() {
return CHANGE_COUNT.get();
}

public final int serial;
public final Watch initiator;
private final List<Frame> frames = new CopyOnWriteArrayList<>();
/**
* The current state of the alert.
*/
private Level level = Level.WHITE;
private boolean acknowledged;

public Alert(Watch initiator) {
this.initiator = initiator;
this.serial = NEXT_SERIAL.incrementAndGet();
}

@Override
public Iterator<Frame> iterator() {
return frames.iterator();
}

public Alert addTransition(Level to, SeriesDataset cause, List<SeriesDataset> captured) {
assertRedOrAmberLevel(to);
if (!isStopped()) {
if (!frames.isEmpty()) {
Frame recent = getEndFrame();
assertSameSeriesAndInstance(cause, recent.cause);
recent.end = System.currentTimeMillis();
acknowledged = acknowledged && to.isLessSevereThan(recent.level);
} else {
assertMatchesWachtedSeries(cause);
acknowledged = false;
}
frames.add(new Frame(to, cause, captured));
CHANGE_COUNT.incrementAndGet();
level = to;
}
return this;
}

public boolean isStarted() {
return !frames.isEmpty();
}

public boolean isAcknowledged() {
return acknowledged;
}

public void acknowledge() {
if (!isAcknowledged()) {
acknowledged = true;
CHANGE_COUNT.incrementAndGet();
}
}

public boolean isStopped() {
return level.isLessSevereThan(Level.AMBER) && !frames.isEmpty();
}

public void stop(Level to) {
if (!isStopped()) {
assertGreenOrWhiteLevel(to);
this.level = to;
getEndFrame().end = System.currentTimeMillis();
CHANGE_COUNT.incrementAndGet();
}
}

public Level getLevel() {
return level;
}

public long getStartTime() {
return frames.isEmpty() ? -1L : frames.get(0).start;
}

public long getEndTime() {
return frames.isEmpty() ? -1L : getEndFrame().end;
}

public Series getSeries() {
return getEndFrame().cause.getSeries();
}

public String getInstance() {
return getEndFrame().cause.getInstance();
}

public Frame getEndFrame() {
return frames.get(frames.size() - 1);
}

@Override
public int hashCode() {
return serial;
}

@Override
public boolean equals(Object obj) {
return obj instanceof Alert && equalTo((Alert) obj);
}

public boolean equalTo(Alert other) {
return serial == other.serial;
}

@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append('(').append(serial).append(") ").append(initiator.name);
long startTime = getStartTime();
str.append('[');
if (startTime >= 0) {
str.append(formatTime(startTime));
}
str.append('-');
long endTime = getEndTime();
if (endTime >= 0) {
str.append(formatTime(endTime));
}
if (isAcknowledged()) {
str.append(" ACK");
}
str.append(']');
str.append(' ');
for (int i = 0; i < frames.size(); i++) {
if (i > 0) {
str.append(" => ");
}
str.append(frames.get(i).level);
}
return str.toString();
}

private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ISO_TIME;

private static String formatTime(long epochMillis) {
return TIME_FORMATTER.format(ofInstant(ofEpochMilli(epochMillis), systemDefault()));
}

private static void assertRedOrAmberLevel(Level to) {
if (to != Level.RED && to != Level.AMBER) {
throw new IllegalArgumentException("Alerts only transtion between RED and AMBER levels but got: " + to);
}
}

private void assertMatchesWachtedSeries(SeriesDataset cause) {
if (!initiator.watched.series.matches(cause.getSeries())) {
throw new IllegalArgumentException("Cause did not match with watched series: " + cause.getSeries());
}
}

private static void assertSameSeriesAndInstance(SeriesDataset a, SeriesDataset b) {
if (!b.getSeries().equalTo(a.getSeries()) || !b.getInstance().equals(a.getInstance())) {
throw new IllegalArgumentException(
"All transitions for an alert must refer to same cause series and instance but got: " + a);
}
}

private static void assertGreenOrWhiteLevel(Level to) {
if (to != Level.GREEN && to != Level.WHITE) {
throw new IllegalArgumentException("Alerts only end on GREEN or WHITE levels but got: " + to);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 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
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://github.com/payara/Payara/blob/master/LICENSE.txt
* See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* The Payara Foundation designates this particular file as subject to the "Classpath"
* exception as provided by the Payara Foundation in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package fish.payara.monitoring.alert;

import java.util.Collection;
import java.util.function.Predicate;

import org.jvnet.hk2.annotations.Contract;

import fish.payara.monitoring.model.Series;

/**
* The {@link AlertService} manages and evaluates {@link Watch}s that cause {@link Alert}s.
*
* @author Jan Bernitt
*/
@Contract
public interface AlertService {

/*
* Alerts
*/

class AlertStatistics {
/**
* Can be used by (asynchronous) consumers to determine if they have seen the most recent state of alerts. If
* the change count is still the same they have processed already there is nothing new to process.
*/
public int changeCount;
public int unacknowledgedRedAlerts;
public int acknowledgedRedAlerts;
public int unacknowledgedAmberAlerts;
public int acknowledgedAmberAlerts;
public int watches;
}

AlertStatistics getAlertStatistics();

Collection<Alert> alertsMatching(Predicate<Alert> filter);

default Alert alertBySerial(int serial) {
Collection<Alert> matches = alertsMatching(alert -> alert.serial == serial);
return matches.isEmpty() ? null : matches.iterator().next();
}

default Collection<Alert> alertsFor(Series series) {
return alertsMatching(alert -> alert.getSeries().equalTo(series));
}

default Collection<Alert> alerts() {
return alertsMatching(alert -> true);
}

/*
* Watches
*/

/**
* 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);

/**
* @return All watches registered for evaluation.
*/
Collection<Watch> watches();

/**
* @param series a simple or pattern {@link Series}, not null
* @return All watches matching the given {@link Series}. {@link Series#ANY} will match all watches similar to
* {@link #watches()}.
*/
Collection<Watch> wachtesFor(Series series);
}
Loading