diff --git a/server/monitor/pom.xml b/server/monitor/pom.xml index 89a2597f9e8..089502e40c3 100644 --- a/server/monitor/pom.xml +++ b/server/monitor/pom.xml @@ -54,10 +54,6 @@ com.github.ben-manes.caffeine caffeine - - com.google.code.gson - gson - com.google.guava guava @@ -246,26 +242,6 @@ - - org.codehaus.mojo - exec-maven-plugin - - - metrics-javascript - - java - - prepare-package - - org.apache.accumulo.monitor.next.views.ColumnJsGen - compile - - ${project.build.directory}/classes/org/apache/accumulo/monitor/resources/js/columns.js - - - - - org.apache.maven.plugins maven-resources-plugin diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/Endpoints.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/Endpoints.java index a0b4971413d..f570d1ecbfa 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/Endpoints.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/Endpoints.java @@ -32,6 +32,7 @@ import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.GET; import jakarta.ws.rs.MatrixParam; import jakarta.ws.rs.NotFoundException; @@ -66,11 +67,6 @@ public class Endpoints { */ private static final String GROUP_PARAM_KEY = "group"; - /** - * A {@code String} constant representing the supplied server type in path parameter. - */ - private static final String SERVER_TYPE_KEY = "serverType"; - /** * A {@code String} constant representing the supplied tableId in path parameter. */ @@ -254,13 +250,15 @@ public Map getScanServerAllMetricSummary() { @GET @Path("servers/view") @Produces(MediaType.APPLICATION_JSON) - @Description("Returns a UI-ready view model for server processes. Add ';serverType=' to URL") - public ServersView getServerProcessView(@MatrixParam(SERVER_TYPE_KEY) ServerId.Type serverType) { + @Description("Returns a UI-ready table model for server process pages. Add ';table=' to URL") + public ServersView getServerProcessView(@MatrixParam("table") ServersView.ServerTable table) { + if (table == null) { + throw new BadRequestException("A 'table' parameter is required"); + } ServersView view = - monitor.getInformationFetcher().getSummaryForEndpoint().getServerProcessView(serverType); + monitor.getInformationFetcher().getSummaryForEndpoint().getServerProcessView(table); if (view == null) { - throw new NotFoundException( - "ServersView object for server type " + serverType.name() + " not found"); + throw new NotFoundException("ServersView object for table " + table.name() + " not found"); } return view; } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java index 3c5c4f44758..db9e077f9ed 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/SystemInformation.java @@ -413,8 +413,8 @@ public Stream stream() { private final Set configuredCompactionResourceGroups = ConcurrentHashMap.newKeySet(); private final AtomicLong timestamp = new AtomicLong(0); - private EnumMap> serverMetricsView = - new EnumMap<>(ServerId.Type.class); + private final EnumMap> serverMetricsView = + new EnumMap<>(ServersView.ServerTable.class); private DeploymentOverview deploymentOverview = new DeploymentOverview(0L, List.of()); private final int rgLongRunningCompactionSize; @@ -658,28 +658,28 @@ public void finish() { switch (type) { case COMPACTOR: compactors.values().forEach(servers::addAll); - serverMetricsView.put(type, memoize( - () -> new ServersView(servers, problemHostCount, allMetrics, timestamp.get()))); + cacheServerProcessView(ServersView.ServerTable.COMPACTORS, servers, problemHostCount); break; case GARBAGE_COLLECTOR: servers.add(gc.get()); - serverMetricsView.put(type, memoize( - () -> new ServersView(servers, problemHostCount, allMetrics, timestamp.get()))); + cacheServerProcessView(ServersView.ServerTable.GC_SUMMARY, servers, problemHostCount); + cacheServerProcessView(ServersView.ServerTable.GC_FILES, servers, problemHostCount); + cacheServerProcessView(ServersView.ServerTable.GC_WALS, servers, problemHostCount); break; case MANAGER: servers.addAll(managers); - serverMetricsView.put(type, memoize( - () -> new ServersView(servers, problemHostCount, allMetrics, timestamp.get()))); + cacheServerProcessView(ServersView.ServerTable.MANAGERS, servers, problemHostCount); + cacheServerProcessView(ServersView.ServerTable.MANAGER_FATE, servers, problemHostCount); + cacheServerProcessView(ServersView.ServerTable.MANAGER_COMPACTIONS, servers, + problemHostCount); break; case SCAN_SERVER: sservers.values().forEach(servers::addAll); - serverMetricsView.put(type, memoize( - () -> new ServersView(servers, problemHostCount, allMetrics, timestamp.get()))); + cacheServerProcessView(ServersView.ServerTable.SCAN_SERVERS, servers, problemHostCount); break; case TABLET_SERVER: tservers.values().forEach(servers::addAll); - serverMetricsView.put(type, memoize( - () -> new ServersView(servers, problemHostCount, allMetrics, timestamp.get()))); + cacheServerProcessView(ServersView.ServerTable.TABLET_SERVERS, servers, problemHostCount); break; case MONITOR: default: @@ -776,8 +776,17 @@ public long getTimestamp() { return this.timestamp.get(); } - public ServersView getServerProcessView(ServerId.Type type) { - Supplier view = this.serverMetricsView.get(type); + /** + * Cache a ServersView for the given table and set of servers. + */ + private void cacheServerProcessView(ServersView.ServerTable table, Set servers, + long problemHostCount) { + serverMetricsView.put(table, memoize(() -> new ServersView(servers, problemHostCount, + allMetrics, timestamp.get(), ServersView.columnsFor(table)))); + } + + public ServersView getServerProcessView(ServersView.ServerTable table) { + Supplier view = this.serverMetricsView.get(table); if (view != null) { return view.get(); } diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ColumnJsGen.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ColumnJsGen.java deleted file mode 100644 index b2593dc6b48..00000000000 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ColumnJsGen.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.accumulo.monitor.next.views; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.apache.accumulo.core.util.LazySingletons.GSON; -import static org.apache.accumulo.monitor.next.views.ServersView.ADDR_COL_NAME; -import static org.apache.accumulo.monitor.next.views.ServersView.RG_COL_NAME; -import static org.apache.accumulo.monitor.next.views.ServersView.TIME_COL_NAME; -import static org.apache.accumulo.monitor.next.views.ServersView.TYPE_COL_NAME; - -import java.io.IOException; -import java.io.PrintStream; -import java.util.Arrays; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.stream.Collectors; - -import org.apache.accumulo.core.metrics.Metric; -import org.apache.accumulo.core.metrics.Metric.MonitorCssClass; - -/** - * This class generates a map of metric name to column information for the Monitor - */ -public class ColumnJsGen { - - public record ColumnInformation(String header, String description, String classes) { - }; - - private static void printHeader(PrintStream out) { - final String hdr = """ - /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - - "use strict"; - - const COLUMN_MAP = new Map(["""; - out.println(hdr); - } - - private static void printMetrics(PrintStream out) { - - // Put in tree map to sort metrics by name in the output - Map output = new TreeMap<>(); - for (Metric m : Metric.values()) { - MonitorCssClass[] classes = m.getColumnClasses(); - String css = - Arrays.stream(classes).map(c -> c.getCssClass()).collect(Collectors.joining(" ")); - output.put(m.getName(), - new ColumnInformation(m.getColumnHeader(), m.getColumnDescription(), css)); - } - - // Add non-metric columns that are part of the ServersView - // object that is returned from the Monitor endpoint - output.put(ADDR_COL_NAME, new ColumnInformation(ADDR_COL_NAME, "Server Address", "firstcell")); - output.put(RG_COL_NAME, - new ColumnInformation(RG_COL_NAME, "Resource Group Name", "resource-group")); - output.put(TIME_COL_NAME, - new ColumnInformation(TIME_COL_NAME, "Last Contact Time", "duration")); - output.put(TYPE_COL_NAME, - new ColumnInformation(TYPE_COL_NAME, "Server Process Type", "server-type")); - - final Set keys = output.keySet(); - final int numKeys = keys.size(); - int counter = 0; - for (String key : keys) { - counter++; - out.println(" [\"%s\", %s]%s".formatted(key, GSON.get().toJson(output.get(key)), - counter == numKeys ? "" : ",")); - - } - } - - private static void printFooter(PrintStream out) { - final String footer = """ - ]); - """; - out.println(footer); - } - - public static void main(String args[]) throws IOException { - if (args.length != 1) { - throw new IllegalArgumentException( - "Usage: " + ColumnJsGen.class.getName() + " "); - } - try (var printStream = new PrintStream(args[0], UTF_8)) { - printHeader(printStream); - printMetrics(printStream); - printFooter(printStream); - } - } - -} diff --git a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ServersView.java b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ServersView.java index 6b476c4ad3f..4705ae33d8f 100644 --- a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ServersView.java +++ b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/ServersView.java @@ -19,16 +19,18 @@ package org.apache.accumulo.monitor.next.views; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import org.apache.accumulo.core.client.admin.servers.ServerId; +import org.apache.accumulo.core.metrics.Metric; +import org.apache.accumulo.core.metrics.Metric.MetricDocSection; import org.apache.accumulo.core.metrics.flatbuffers.FMetric; import org.apache.accumulo.core.metrics.flatbuffers.FTag; import org.apache.accumulo.core.process.thrift.MetricResponse; @@ -48,6 +50,11 @@ * data - an array of objects that can be used for the Data Table data definition * status - overall status information, counts, warnings, etc. * + * + * Each server-process table is identified by {@link ServerTable}. The table-specific metric methods + * define the metrics for each table, {@link #columnsFor(ServerTable)} converts those metrics to + * column definitions. The frontend uses the returned column definitions to build the table headers + * and DataTables column configuration. */ public class ServersView { @@ -59,54 +66,96 @@ public record Status(boolean hasServers, boolean hasProblemServers, boolean hasM String message) { } - public static final String TYPE_COL_NAME = "Server Type"; - public static final String RG_COL_NAME = "Resource Group"; - public static final String ADDR_COL_NAME = "Server Address"; - public static final String TIME_COL_NAME = "Last Contact"; + /** + * Definition of a column to be rendered in the UI + */ + public record Column(String key, String label, String description, String uiClass) { + } + + private record ServerMetricRow(ServerId server, MetricResponse response, + Map metrics) { + } + + /** + * Server-process table identifiers accepted by /rest-v2/servers/view. These enum names are used + * directly as the frontend table parameter values. + */ + public enum ServerTable { + COMPACTORS, + GC_SUMMARY, + GC_FILES, + GC_WALS, + MANAGERS, + MANAGER_FATE, + MANAGER_COMPACTIONS, + SCAN_SERVERS, + TABLET_SERVERS + } private static final String LEVEL_OK = "OK"; private static final String LEVEL_WARN = "WARN"; + public static final String RG_COL_KEY = "resourceGroup"; + public static final String ADDR_COL_KEY = "serverAddress"; + public static final String TIME_COL_KEY = "lastContact"; + + /** + * Common columns that are included in every ServersView table + */ + private static final List COMMON_COLUMNS = List.of( + new Column(TIME_COL_KEY, "Last Contact", + "Time since the server last responded to the monitor", "duration"), + new Column(RG_COL_KEY, "Resource Group", "Resource Group", ""), + new Column(ADDR_COL_KEY, "Server Address", "Server address", "")); + public final List> data = new ArrayList<>(); - public final Set columns = new TreeSet<>(); + public final List columns; public final Status status; public final long timestamp; public ServersView(final Set servers, final long problemServerCount, - final Cache allMetrics, final long timestamp) { + final Cache allMetrics, final long timestamp, + final List requestedColumns) { AtomicInteger serversMissingMetrics = new AtomicInteger(0); - servers.forEach(sid -> { - Map metrics = new TreeMap<>(); - - columns.add(TYPE_COL_NAME); - metrics.put(TYPE_COL_NAME, sid.getType().name()); - columns.add(RG_COL_NAME); - metrics.put(RG_COL_NAME, sid.getResourceGroup().canonical()); - columns.add(ADDR_COL_NAME); - metrics.put(ADDR_COL_NAME, sid.toHostPortString()); - - MetricResponse mr = allMetrics.getIfPresent(sid); - if (mr != null) { - // Don't use the timestamp for the last contact duration, - // use the current time. - columns.add(TIME_COL_NAME); - metrics.put(TIME_COL_NAME, System.currentTimeMillis() - mr.getTimestamp()); - - Map serverMetrics = metricValuesByName(mr); - for (Entry e : serverMetrics.entrySet()) { - columns.add(e.getKey()); - metrics.put(e.getKey(), e.getValue()); - } - data.add(metrics); - } else { + // Grab the current metrics for each server + List serverMetricRows = servers.stream().sorted().map(serverId -> { + MetricResponse metricResponse = allMetrics.getIfPresent(serverId); + boolean hasMetricData = hasMetricData(metricResponse); + Map serverMetrics = + hasMetricData ? metricValuesByName(metricResponse) : Map.of(); + + if (!hasMetricData) { serversMissingMetrics.incrementAndGet(); } + + return new ServerMetricRow(serverId, metricResponse, serverMetrics); + }).toList(); + + this.columns = requestedColumns; + + serverMetricRows.forEach(serverMetricRow -> { + Map row = new LinkedHashMap<>(); + for (Column col : columns) { + row.put(col.key(), valueForColumn(col.key(), serverMetricRow.server(), + serverMetricRow.response(), serverMetricRow.metrics())); + } + data.add(row); }); status = buildStatus(servers.size(), problemServerCount, serversMissingMetrics.get()); this.timestamp = timestamp; } + private static Object valueForColumn(String key, ServerId sid, MetricResponse mr, + Map serverMetrics) { + return switch (key) { + case TIME_COL_KEY -> mr == null ? null : System.currentTimeMillis() - mr.getTimestamp(); + case RG_COL_KEY -> sid.getResourceGroup().canonical(); + case ADDR_COL_KEY -> sid.toHostPortString(); + default -> serverMetrics.get(key); + }; + } + private static Status buildStatus(int serverCount, long problemServerCount, int serversMissingMetrics) { final boolean hasServers = serverCount > 0; @@ -131,6 +180,103 @@ private static Status buildStatus(int serverCount, long problemServerCount, problemServerCount, serversMissingMetrics, LEVEL_WARN, message); } + private static boolean hasMetricData(MetricResponse mr) { + return mr != null && mr.getMetrics() != null && !mr.getMetrics().isEmpty(); + } + + /** + * Builds the final ordered columns for a table. First adds the common columns, then adds the + * table specific metrics. + */ + public static List columnsFor(ServerTable table) { + List cols = new ArrayList<>(COMMON_COLUMNS); + cols.addAll(metricsForTable(table).stream().map(ServersView::metricColumn).toList()); + return cols; + } + + private static List metricsForTable(ServerTable table) { + return switch (table) { + case COMPACTORS -> compactorMetrics(); + case GC_SUMMARY -> gcSummaryMetrics(); + case GC_FILES -> gcFileMetrics(); + case GC_WALS -> gcWalMetrics(); + case MANAGERS -> managerMetrics(); + case MANAGER_FATE -> managerFateMetrics(); + case MANAGER_COMPACTIONS -> managerCompactionMetrics(); + case SCAN_SERVERS -> scanServerMetrics(); + case TABLET_SERVERS -> tabletServerMetrics(); + }; + } + + /** + * The following helper methods are where the metrics included in each table are defined as well + * as their order. + */ + private static List compactorMetrics() { + return metricList(MetricDocSection.GENERAL_SERVER, MetricDocSection.COMPACTION, + MetricDocSection.COMPACTOR); + } + + private static List gcSummaryMetrics() { + return metricList(MetricDocSection.GENERAL_SERVER); + } + + private static List gcFileMetrics() { + return Arrays.stream(Metric.values()).filter(metric -> { + String name = metric.getName(); + return metric.getDocSection() == MetricDocSection.GARBAGE_COLLECTION + && !name.startsWith("accumulo.gc.wal."); + }).toList(); + } + + private static List gcWalMetrics() { + return Arrays.stream(Metric.values()) + .filter(metric -> metric.getName().startsWith("accumulo.gc.wal.")) + .collect(Collectors.toList()); + } + + private static List managerMetrics() { + return metricList(MetricDocSection.GENERAL_SERVER, MetricDocSection.MANAGER); + } + + private static List managerFateMetrics() { + return metricList(MetricDocSection.FATE); + } + + private static List managerCompactionMetrics() { + return metricList(MetricDocSection.COMPACTION); + } + + private static List scanServerMetrics() { + return metricList(MetricDocSection.GENERAL_SERVER, MetricDocSection.SCAN_SERVER, + MetricDocSection.SCAN, MetricDocSection.BLOCK_CACHE); + } + + private static List tabletServerMetrics() { + return metricList(MetricDocSection.GENERAL_SERVER, MetricDocSection.TABLET_SERVER, + MetricDocSection.SCAN, MetricDocSection.COMPACTION, MetricDocSection.BLOCK_CACHE); + } + + /** + * @return all the metrics for the given sections + */ + private static List metricList(MetricDocSection... sections) { + Set requestedSections = Set.of(sections); + return Arrays.stream(Metric.values()) + .filter(metric -> requestedSections.contains(metric.getDocSection())) + .collect(Collectors.toList()); + } + + /** + * @return a Column definition converted from the given Metric + */ + private static Column metricColumn(Metric metric) { + String classes = Arrays.stream(metric.getColumnClasses()) + .map(Metric.MonitorCssClass::getCssClass).collect(Collectors.joining(" ")); + return new Column(metric.getName(), metric.getColumnHeader(), metric.getColumnDescription(), + classes); + } + public static Map metricValuesByName(MetricResponse response) { var values = new HashMap(); if (response == null || response.getMetrics() == null || response.getMetrics().isEmpty()) { @@ -147,19 +293,16 @@ public static Map metricValuesByName(MetricResponse response) { values.compute(metric.name(), (k, v) -> { if (v == null) { return val; + } else if (v instanceof Integer i) { + return i + val.intValue(); + } else if (v instanceof Long l) { + return l + val.longValue(); + } else if (v instanceof Double d) { + return d + val.doubleValue(); } else { - if (v instanceof Integer i) { - return i + val.intValue(); - } else if (v instanceof Long l) { - return l + val.longValue(); - } else if (v instanceof Double d) { - return d + val.doubleValue(); - } else { - throw new RuntimeException("Unexpected value type: " + val.getClass()); - } + throw new RuntimeException("Unexpected value type: " + val.getClass()); } }); - values.putIfAbsent(metric.name(), SystemInformation.getMetricValue(metric)); } } return values; diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactors.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactors.js index 11957b2e90b..907f7935a4e 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactors.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactors.js @@ -25,18 +25,19 @@ const htmlBanner = '#compactorsStatusBanner' const htmlBannerMessage = '#compactors-banner-message' const htmlTable = '#compactorsTable' -const visibleColumnFilter = (col) => col != "Server Type"; function refresh() { - refreshServerInformation(getCompactorsView, htmlTable, COMPACTOR_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); + refreshServerInformation(getCompactorsView, htmlTable, COMPACTOR_SERVER_PROCESS_VIEW, + htmlBanner, htmlBannerMessage); } $(function () { - sessionStorage[SCAN_SERVER_PROCESS_VIEW] = JSON.stringify({ + sessionStorage[COMPACTOR_SERVER_PROCESS_VIEW] = JSON.stringify({ data: [], columns: [], status: null }); - refreshServerInformation(getCompactorsView, htmlTable, COMPACTOR_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); + refreshServerInformation(getCompactorsView, htmlTable, COMPACTOR_SERVER_PROCESS_VIEW, + htmlBanner, htmlBannerMessage); }); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js index b8c0fbb34a3..9b418568491 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/functions.js @@ -30,8 +30,12 @@ const REST_V2_PREFIX = contextPath + 'rest-v2'; const MANAGER_GOAL_STATE_METRIC = 'accumulo.manager.goal.state'; const COMPACTOR_SERVER_PROCESS_VIEW = 'compactorsView'; -const GC_SERVER_PROCESS_VIEW = 'gcView'; +const GC_SERVER_PROCESS_VIEW = 'gcSummaryView'; +const GC_FILE_SERVER_PROCESS_VIEW = 'gcFileView'; +const GC_WAL_SERVER_PROCESS_VIEW = 'gcWalView'; const MANAGER_SERVER_PROCESS_VIEW = 'managerssView'; +const MANAGER_FATE_SERVER_PROCESS_VIEW = 'managersFateView'; +const MANAGER_COMPACTION_SERVER_PROCESS_VIEW = 'managersCompactionView'; const SCAN_SERVER_PROCESS_VIEW = 'sserversView'; const TABLET_SERVER_PROCESS_VIEW = 'tserversView'; @@ -677,44 +681,45 @@ function getDeployment() { return getJSONForTable(REST_V2_PREFIX + '/deployment', 'deployment'); } -/** - * REST GET call for /servers/view;serverType=COMPACTOR, - * stores it on a sessionStorage variable - */ +function getServerProcessView(table, storageKey) { + var url = REST_V2_PREFIX + '/servers/view;table=' + table; + return getJSONForTable(url, storageKey); +} + function getCompactorsView() { - return getJSONForTable(REST_V2_PREFIX + '/servers/view;serverType=COMPACTOR', COMPACTOR_SERVER_PROCESS_VIEW); + return getServerProcessView('COMPACTORS', COMPACTOR_SERVER_PROCESS_VIEW); } -/** - * REST GET call for /servers/view;serverType=GARBAGE_COLLECTOR, - * stores it on a sessionStorage variable - */ function getGcView() { - return getJSONForTable(REST_V2_PREFIX + '/servers/view;serverType=GARBAGE_COLLECTOR', GC_SERVER_PROCESS_VIEW); + return getServerProcessView('GC_SUMMARY', GC_SERVER_PROCESS_VIEW); +} + +function getGcFileView() { + return getServerProcessView('GC_FILES', GC_FILE_SERVER_PROCESS_VIEW); +} + +function getGcWalView() { + return getServerProcessView('GC_WALS', GC_WAL_SERVER_PROCESS_VIEW); } -/** - * REST GET call for /servers/view;serverType=MANAGER, - * stores it on a sessionStorage variable - */ function getManagersView() { - return getJSONForTable(REST_V2_PREFIX + '/servers/view;serverType=MANAGER', MANAGER_SERVER_PROCESS_VIEW); + return getServerProcessView('MANAGERS', MANAGER_SERVER_PROCESS_VIEW); +} + +function getManagersFateView() { + return getServerProcessView('MANAGER_FATE', MANAGER_FATE_SERVER_PROCESS_VIEW); +} + +function getManagersCompactionView() { + return getServerProcessView('MANAGER_COMPACTIONS', MANAGER_COMPACTION_SERVER_PROCESS_VIEW); } -/** - * REST GET call for /servers/view;serverType=SCAN_SERVER, - * stores it on a sessionStorage variable - */ function getSserversView() { - return getJSONForTable(REST_V2_PREFIX + '/servers/view;serverType=SCAN_SERVER', SCAN_SERVER_PROCESS_VIEW); + return getServerProcessView('SCAN_SERVERS', SCAN_SERVER_PROCESS_VIEW); } -/** - * REST GET call for /servers/view;serverType=TABLET_SERVER, - * stores it on a sessionStorage variable - */ function getTserversView() { - return getJSONForTable(REST_V2_PREFIX + '/servers/view;serverType=TABLET_SERVER', TABLET_SERVER_PROCESS_VIEW); + return getServerProcessView('TABLET_SERVERS', TABLET_SERVER_PROCESS_VIEW); } diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/gc.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/gc.js index 6c33007e80d..caf70d956f4 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/gc.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/gc.js @@ -18,27 +18,24 @@ */ /* JSLint global definitions */ /*global - $, GC_SERVER_PROCESS_VIEW, getGcView, refreshServerInformation + $, GC_SERVER_PROCESS_VIEW, GC_FILE_SERVER_PROCESS_VIEW, GC_WAL_SERVER_PROCESS_VIEW, + getGcView, getGcFileView, getGcWalView, refreshServerInformation */ "use strict"; const htmlBanner = '#gcStatusBanner' const htmlBannerMessage = '#gc-banner-message' const htmlTable = '#gc-server' -const visibleColumnFilter = (col) => col != "Server Type" && !col.startsWith("accumulo.gc."); - const fileHtmlTable = '#gc-file' -const fileVisibleColumnFilter = (col) => col == "Last Contact" || col == "Resource Group" || - col == "Server Address" || (col.startsWith("accumulo.gc.") && !col.startsWith("accumulo.gc.wal.")); - const walHtmlTable = '#gc-wal' -const walVisibleColumnFilter = (col) => col == "Last Contact" || col == "Resource Group" || - col == "Server Address" || col.startsWith("accumulo.gc.wal."); function refresh() { - refreshServerInformation(getGcView, htmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); - refreshServerInformation(getGcView, fileHtmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, fileVisibleColumnFilter); - refreshServerInformation(getGcView, walHtmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, walVisibleColumnFilter); + refreshServerInformation(getGcView, htmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, + htmlBannerMessage); + refreshServerInformation(getGcFileView, fileHtmlTable, GC_FILE_SERVER_PROCESS_VIEW, htmlBanner, + htmlBannerMessage); + refreshServerInformation(getGcWalView, walHtmlTable, GC_WAL_SERVER_PROCESS_VIEW, htmlBanner, + htmlBannerMessage); } $(function () { @@ -48,7 +45,21 @@ $(function () { status: null }); - refreshServerInformation(getGcView, htmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); - refreshServerInformation(getGcView, fileHtmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, fileVisibleColumnFilter); - refreshServerInformation(getGcView, walHtmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, walVisibleColumnFilter); + sessionStorage[GC_FILE_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null + }); + sessionStorage[GC_WAL_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null + }); + + refreshServerInformation(getGcView, htmlTable, GC_SERVER_PROCESS_VIEW, htmlBanner, + htmlBannerMessage); + refreshServerInformation(getGcFileView, fileHtmlTable, GC_FILE_SERVER_PROCESS_VIEW, htmlBanner, + htmlBannerMessage); + refreshServerInformation(getGcWalView, walHtmlTable, GC_WAL_SERVER_PROCESS_VIEW, htmlBanner, + htmlBannerMessage); }); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/manager.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/manager.js index 55d090f57f1..18c2cc0f066 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/manager.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/manager.js @@ -19,7 +19,9 @@ /* JSLint global definitions */ /*global $, sessionStorage, MANAGER_SERVER_PROCESS_VIEW, getManagersView, getStoredRows, getStoredStatus, - refreshTable, refreshBanner, showBannerError, getManagerGoalStateFromSession + MANAGER_FATE_SERVER_PROCESS_VIEW, MANAGER_COMPACTION_SERVER_PROCESS_VIEW, getManagersFateView, + getManagersCompactionView, refreshTable, refreshBanner, showBannerError, + getManagerGoalStateFromSession */ "use strict"; @@ -29,16 +31,8 @@ const htmlBannerMessage = '#manager-banner-message' const managerStateBanner = '#managerStateBanner' const managerStateBannerMessage = '#manager-state-message' const htmlTable = '#managers' -const visibleColumnFilter = (col) => col != "Server Type" && !col.startsWith("accumulo.compaction.") && - !col.startsWith("accumulo.fate."); - const fateHtmlTable = '#managers_fate' -const fateVisibleColumnFilter = (col) => col == "Last Contact" || col == "Resource Group" || - col == "Server Address" || col.startsWith("accumulo.fate."); - const compactionHtmlTable = '#managers_compactions' -const compactionVisibleColumnFilter = (col) => col == "Last Contact" || col == "Resource Group" || - col == "Server Address" || col.startsWith("accumulo.compaction."); function updateManagerGoalStateBanner() { const goalState = getManagerGoalStateFromSession(); @@ -72,10 +66,10 @@ function refreshManagerBanners() { } function refresh() { - getManagersView().then(function () { - refreshTable(htmlTable, MANAGER_SERVER_PROCESS_VIEW, visibleColumnFilter); - refreshTable(fateHtmlTable, MANAGER_SERVER_PROCESS_VIEW, fateVisibleColumnFilter); - refreshTable(compactionHtmlTable, MANAGER_SERVER_PROCESS_VIEW, compactionVisibleColumnFilter); + $.when(getManagersView(), getManagersFateView(), getManagersCompactionView()).then(function () { + refreshTable(htmlTable, MANAGER_SERVER_PROCESS_VIEW); + refreshTable(fateHtmlTable, MANAGER_FATE_SERVER_PROCESS_VIEW); + refreshTable(compactionHtmlTable, MANAGER_COMPACTION_SERVER_PROCESS_VIEW); refreshManagerBanners(); refreshBanner(htmlBanner, htmlBannerMessage, getStoredStatus(MANAGER_SERVER_PROCESS_VIEW)); }).fail(function () { @@ -84,13 +78,24 @@ function refresh() { columns: [], status: null }); - refreshTable(htmlTable, MANAGER_SERVER_PROCESS_VIEW, visibleColumnFilter); - refreshTable(fateHtmlTable, MANAGER_SERVER_PROCESS_VIEW, fateVisibleColumnFilter); - refreshTable(compactionHtmlTable, MANAGER_SERVER_PROCESS_VIEW, compactionVisibleColumnFilter); + sessionStorage[MANAGER_FATE_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null + }); + sessionStorage[MANAGER_COMPACTION_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null + }); + refreshTable(htmlTable, MANAGER_SERVER_PROCESS_VIEW); + refreshTable(fateHtmlTable, MANAGER_FATE_SERVER_PROCESS_VIEW); + refreshTable(compactionHtmlTable, MANAGER_COMPACTION_SERVER_PROCESS_VIEW); $(runningBanner).show(); $(htmlTable).hide(); $(fateHtmlTable).hide(); $(compactionHtmlTable).hide(); + $(managerStateBanner).hide(); showBannerError(htmlBanner, htmlBannerMessage); }); } @@ -101,6 +106,16 @@ $(function () { columns: [], status: null }); + sessionStorage[MANAGER_FATE_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null + }); + sessionStorage[MANAGER_COMPACTION_SERVER_PROCESS_VIEW] = JSON.stringify({ + data: [], + columns: [], + status: null + }); refresh(); }); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js index 68eb3c7a9b7..000f7a8a021 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server_process_common.js @@ -19,49 +19,53 @@ /* JSLint global definitions */ /*global $, sessionStorage, timeDuration, bigNumberForQuantity, bigNumberForSize, ajaxReloadTable, - renderActivityState, renderMemoryState, COLUMN_MAP + renderActivityState, renderMemoryState, dateFormat */ "use strict"; /** * This file contains methods used to display tables on the Monitor's - * pages for server processes. The REST Endpoint /rest-v2/servers/view;serverType= + * pages for server processes. The REST Endpoint /rest-v2/servers/view;table= * returns a data structure that has the following format: * * { * "data": [ * { - * "colA", "valueA", - * "colB", "valueB" + * "colA": "valueA", + * "colB": "valueB" * }, * { - * "colA", "valueA", - * "colB", "valueB" + * "colA": "valueA", + * "colB": "valueB" * } * ], * "columns": [ - * "colA", - * "colB" + * { + * "key": "colA", + * "label": "Column A", + * "description": "Description of Column A", + * "uiClass": "big-num" + * }, + * { + * "key": "colB", + * "label": "Column B", + * "description": "Description of Column B", + * "uiClass": "" + * } * ], * "status": { * }, * timestamp: long * } * - * The value for the 'columns' key is an array of strings, where each string is the name - * of a column. The value for the 'data' key is an array of objects. Each object represents - * a server and the fields in the object contain the fields specified in the 'columns' - * array and are in the same order. + * The value for the 'columns' key is an array of column definitions. The value for the + * 'data' key is an array of row objects keyed by the column 'key' values. * * The 'columns' array is used to dynamically create table header rows in the html and * the 'data' object is directly consumed by the DataTable where each object in the 'data' * is a row in the table and each field in the object is a column. - * - * Modify the exclusion list in AbstractServer to remove columns from being returned from - * the AbstractServer.getMetrics RPC call. Be aware that other pages in the Monitor may - * use this information, not just the server process pages. The entrypoint for using - * the methods in this file is the refreshServerInformation method. To influence which columns - * are displayed on the server process pages use the column filter in that method. + * + * The entrypoint for using the methods in this file is the refreshServerInformation method. */ var dataTableRefs = new Map(); @@ -95,7 +99,6 @@ function getStoredRows(storageKey) { if (!Array.isArray(view.data)) { return []; } - console.debug('table data: ' + JSON.stringify(view.data)); return view.data; } @@ -109,25 +112,20 @@ function getStoredStatus(storageKey) { /** * This function is called as part of the DataTable initialization. - * It retrieves the columns from the response and sets the columns - * visibility based on the supplied filter. Setting the visiblity - * to false will hide the column in the display even if the table - * header HTML column exists. This allows us to create the table - * header rows without supplying the filter. + * It retrieves the exact columns from the cached response and creates + * the DataTables column definitions from them. */ -function getDataTableCols(storageKey, visibleColumnFilter) { +function getDataTableCols(storageKey) { var dataTableColumns = []; var storedColumns = getStoredColumns(storageKey); - var visibleColumns = storedColumns.filter(visibleColumnFilter); $.each(storedColumns, function (index, col) { - var v = visibleColumns.includes(col); - var colName = col.replaceAll(".", "\\."); + var colName = col.key.replaceAll(".", "\\."); dataTableColumns.push({ data: colName, - visible: v + title: col.label, + className: col.uiClass || '' }); }); - console.debug('table columns: ' + JSON.stringify(dataTableColumns)); return dataTableColumns; } @@ -137,7 +135,7 @@ function getDataTableCols(storageKey, visibleColumnFilter) { * HTML elements from the columns, redefines the DataTable, * and executes an ajax method to load the data. */ -function refreshTable(table, storageKey, visibleColumnFilter) { +function refreshTable(table, storageKey) { // Destroy the DataTable var dataTableRef = dataTableRefs.get(table); @@ -156,27 +154,18 @@ function refreshTable(table, storageKey, visibleColumnFilter) { var storedColumns = getStoredColumns(storageKey); $.each(storedColumns, function (index, col) { - console.debug('Adding table header row for column: ' + JSON.stringify(col)); - if (COLUMN_MAP.has(col)) { - var mapping = COLUMN_MAP.get(col); - var th = $(document.createElement("th")); - th.addClass(mapping.classes); - th.text(mapping.header); - th.attr("title", mapping.description); - theadRow.append(th); - } else { - var th = $(document.createElement("th")); - th.text(col); - th.attr("title", "Unmapped column"); - theadRow.append(th); - } + var th = $(document.createElement("th")); + th.addClass(col.uiClass || ''); + th.text(col.label); + th.attr("title", col.description || col.label); + theadRow.append(th); }); thead.append(theadRow); htmlTableElement.append(thead); htmlTableElement.append($(document.createElement('tbody'))); // Create the DataTable - dataTableRef = createDataTable(table, storageKey, visibleColumnFilter); + dataTableRef = createDataTable(table, storageKey); ajaxReloadTable(dataTableRef); dataTableRefs.set(table, dataTableRef); } @@ -214,11 +203,10 @@ function showBannerError(banner, bannerMsg) { * storageKey - the session storage key for the data returned from the REST API * banner - reference to the HTML table that displays a banner * bannerMsg - reference to the HTML object that is the banner - * visibleColumnFilter - filter to apply to columns to determine which are displayed */ -function refreshServerInformation(callback, table, storageKey, banner, bannerMsg, visibleColumnFilter) { +function refreshServerInformation(callback, table, storageKey, banner, bannerMsg) { callback().then(function () { - refreshTable(table, storageKey, visibleColumnFilter); + refreshTable(table, storageKey); refreshBanner(banner, bannerMsg, getStoredStatus(storageKey)); }).fail(function () { sessionStorage[storageKey] = JSON.stringify({ @@ -226,7 +214,7 @@ function refreshServerInformation(callback, table, storageKey, banner, bannerMsg columns: [], status: null }); - refreshTable(table, storageKey, visibleColumnFilter); + refreshTable(table, storageKey); showBannerError(banner, bannerMsg); }); } @@ -237,7 +225,7 @@ function refreshServerInformation(callback, table, storageKey, banner, bannerMsg * certain columns should be rendered based on the columns * css class. */ -function createDataTable(table, storageKey, visibleColumnFilter) { +function createDataTable(table, storageKey) { var dataTableRef = $(table).DataTable({ "autoWidth": false, "ajax": function (data, callback) { @@ -279,7 +267,8 @@ function createDataTable(table, storageKey, visibleColumnFilter) { "targets": "start-date", "render": function (data, type, row) { if (type === 'display') { - if (data === 0) data = 'Waiting'; + if (data === null || data === undefined) data = '—'; + else if (data === 0) data = 'Waiting'; else if (data > 0) data = dateFormat(data); else data = 'Error'; } @@ -290,7 +279,7 @@ function createDataTable(table, storageKey, visibleColumnFilter) { "targets": "end-date", "render": function (data, type, row) { if (type === 'display') { - if (data === 0) data = '—'; + if (data === null || data === undefined || data === 0) data = '—'; else if (data > 0) data = dateFormat(data); else data = 'Error'; } @@ -301,6 +290,9 @@ function createDataTable(table, storageKey, visibleColumnFilter) { "targets": "duration", "render": function (data, type) { if (type === 'display') { + if (data === null || data === undefined) { + return '—'; + } data = timeDuration(data); } return data; @@ -310,6 +302,9 @@ function createDataTable(table, storageKey, visibleColumnFilter) { "targets": "percent", "render": function (data, type) { if (type === 'display') { + if (data === null || data === undefined) { + return '—'; + } data = Math.round(data * 100) + '%'; } return data; @@ -324,7 +319,7 @@ function createDataTable(table, storageKey, visibleColumnFilter) { "render": renderMemoryState } ], - "columns": getDataTableCols(storageKey, visibleColumnFilter) + "columns": getDataTableCols(storageKey) }); return dataTableRef; } diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/sservers.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/sservers.js index eba0de1c13c..7491874e6fd 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/sservers.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/sservers.js @@ -25,10 +25,10 @@ const htmlBanner = '#sserversStatusBanner' const htmlBannerMessage = '#sservers-banner-message' const htmlTable = '#sservers' -const visibleColumnFilter = (col) => col != "Server Type"; function refresh() { - refreshServerInformation(getSserversView, htmlTable, SCAN_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); + refreshServerInformation(getSserversView, htmlTable, SCAN_SERVER_PROCESS_VIEW, htmlBanner, + htmlBannerMessage); } $(function () { @@ -38,5 +38,6 @@ $(function () { status: null }); - refreshServerInformation(getSserversView, htmlTable, SCAN_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); + refreshServerInformation(getSserversView, htmlTable, SCAN_SERVER_PROCESS_VIEW, htmlBanner, + htmlBannerMessage); }); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/tservers.js b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/tservers.js index bfb45dab690..d659a97f3c2 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/tservers.js +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/tservers.js @@ -26,8 +26,6 @@ const htmlBanner = '#tserversStatusBanner' const htmlBannerMessage = '#tservers-banner-message' const htmlTable = '#tservers' -const visibleColumnFilter = (col) => col != "Server Type"; - var tserversTable; var recoveryList = []; @@ -97,7 +95,8 @@ function refreshTServersBanner() { function refresh() { refreshRecoveryList(); refreshTServersBanner(); - refreshServerInformation(getTserversView, htmlTable, TABLET_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); + refreshServerInformation(getTserversView, htmlTable, TABLET_SERVER_PROCESS_VIEW, htmlBanner, + htmlBannerMessage); } $(function () { @@ -110,5 +109,6 @@ $(function () { status: null }); - refreshServerInformation(getTserversView, htmlTable, TABLET_SERVER_PROCESS_VIEW, htmlBanner, htmlBannerMessage, visibleColumnFilter); + refreshServerInformation(getTserversView, htmlTable, TABLET_SERVER_PROCESS_VIEW, htmlBanner, + htmlBannerMessage); }); diff --git a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl index 1f26bfdc496..454294dedba 100644 --- a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl +++ b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/default.ftl @@ -47,7 +47,6 @@ -