This is an automated email from the ASF dual-hosted git repository.

DomGarguilo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/main by this push:
     new a273a41e7d Add per-server metrics page (#6429)
a273a41e7d is described below

commit a273a41e7d917ce7ef42358ac951b87411bcbc28
Author: Dom G. <[email protected]>
AuthorDate: Tue Jun 23 11:09:21 2026 -0400

    Add per-server metrics page (#6429)
    
    * Add per-server metrics page
    
    * Require server identity in monitor detail route
---
 .../apache/accumulo/monitor/next/Endpoints.java    |  31 +++
 .../accumulo/monitor/next/SystemInformation.java   |  14 +-
 .../monitor/next/views/TableDataFactory.java       |  60 ++++-
 .../org/apache/accumulo/monitor/view/WebViews.java |  32 +++
 .../accumulo/monitor/resources/js/compactions.js   |   2 +-
 .../accumulo/monitor/resources/js/functions.js     |  32 +++
 .../apache/accumulo/monitor/resources/js/server.js | 291 +++------------------
 .../monitor/resources/js/server_process_common.js  |  11 +-
 .../apache/accumulo/monitor/templates/server.ftl   | 103 ++------
 9 files changed, 235 insertions(+), 341 deletions(-)

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 68d4d5627d..c72a55cc04 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
@@ -50,6 +50,7 @@ import org.apache.accumulo.core.Constants;
 import org.apache.accumulo.core.client.admin.TabletInformation;
 import org.apache.accumulo.core.client.admin.servers.ServerId;
 import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.process.thrift.MetricResponse;
 import org.apache.accumulo.core.util.compaction.RunningCompactionInfo;
 import org.apache.accumulo.monitor.Monitor;
 import org.apache.accumulo.monitor.next.InformationFetcher.InstanceSummary;
@@ -185,6 +186,36 @@ public class Endpoints {
     return view;
   }
 
+  @GET
+  @Path("servers/detail/{type}/{resourceGroup}/{server}")
+  @Produces(MediaType.APPLICATION_JSON)
+  @Description("Returns a UI-ready metric table for one server process")
+  public TableData getServerDetail(@PathParam("type") ServerId.Type type,
+      @PathParam("resourceGroup") String resourceGroup, @PathParam("server") 
String server) {
+    if (type == null) {
+      throw new BadRequestException("A 'type' parameter is required");
+    }
+    if (resourceGroup == null || resourceGroup.isBlank()) {
+      throw new BadRequestException("A 'resourceGroup' parameter is required");
+    }
+    if (server == null || server.isBlank()) {
+      throw new BadRequestException("A 'server' parameter is required");
+    }
+
+    MetricResponse response;
+    try {
+      response = monitor.getInformationFetcher().getSummaryForEndpoint()
+          .getServerMetricResponse(type, resourceGroup, server);
+    } catch (IllegalArgumentException e) {
+      throw new BadRequestException("Invalid server metrics parameters", e);
+    }
+    if (response == null) {
+      throw new NotFoundException("Server " + type.name() + " " + server + " 
in resource group "
+          + resourceGroup + " not found");
+    }
+    return TableDataFactory.forServer(response);
+  }
+
   @GET
   @Path("compactions/running")
   @Produces(MediaType.APPLICATION_JSON)
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 d786fdcf96..70dfe828cf 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
@@ -889,7 +889,9 @@ public class SystemInformation {
 
     if (!qm.isEmpty()) {
       // Create a ServersView object from the MetricResponse for each queue
-      return TableDataFactory.forColumns(qm.keySet(), qm, cols);
+      TableData tableData = TableDataFactory.forColumns(qm.keySet(), qm, cols);
+      tableData.data().forEach(row -> 
row.put(TableDataFactory.LINK_RG_COL_KEY, ""));
+      return tableData;
     }
     return TableDataFactory.forColumns(Set.of(), Map.of(), cols);
   }
@@ -1639,6 +1641,16 @@ public class SystemInformation {
     return null;
   }
 
+  public MetricResponse getServerMetricResponse(ServerId.Type type, String 
resourceGroup,
+      String serverAddress) {
+    HostAndPort address = HostAndPort.fromString(serverAddress);
+    if (!address.hasPort()) {
+      throw new IllegalArgumentException("Server address must be host:port");
+    }
+    return allMetrics.getIfPresent(new ServerId(type, 
ResourceGroupId.of(resourceGroup),
+        address.getHost(), address.getPort()));
+  }
+
   public static Number getMetricValue(FMetric metric) {
     if (metric.ivalue() != 0) {
       return metric.ivalue();
diff --git 
a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/TableDataFactory.java
 
b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/TableDataFactory.java
index a16cb13063..859bca36cf 100644
--- 
a/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/TableDataFactory.java
+++ 
b/server/monitor/src/main/java/org/apache/accumulo/monitor/next/views/TableDataFactory.java
@@ -39,6 +39,7 @@ import org.apache.accumulo.core.metrics.flatbuffers.FMetric;
 import org.apache.accumulo.core.metrics.flatbuffers.FTag;
 import org.apache.accumulo.core.process.thrift.MetricResponse;
 import org.apache.accumulo.core.util.threads.ThreadPoolNames;
+import org.apache.accumulo.monitor.next.SystemInformation;
 import org.apache.accumulo.monitor.next.views.TableData.Column;
 import org.apache.accumulo.server.metrics.MetricResponseWrapper;
 
@@ -53,14 +54,30 @@ public class TableDataFactory {
 
   public static final String RG_COL_KEY = "resourceGroup";
   public static final String ADDR_COL_KEY = "serverAddress";
+  public static final String TYPE_COL_KEY = "serverType";
+  public static final String LINK_RG_COL_KEY = "serverLinkResourceGroup";
   public static final String TIME_COL_KEY = "lastContact";
+  public static final String METRIC_NAME_COL_KEY = "metricName";
+  public static final String METRIC_VALUE_COL_KEY = "metricValue";
+  public static final String METRIC_TYPE_COL_KEY = "metricType";
+  public static final String METRIC_TAGS_COL_KEY = "metricTags";
+  public static final String METRIC_DESCRIPTION_COL_KEY = "metricDescription";
+  public static final String METRIC_SECTION_COL_KEY = "metricSection";
 
   public static final Column LAST_CONTACT_COLUMN = new Column(TIME_COL_KEY, 
"Last Contact",
       "Time since the server last responded to the monitor", "duration");
   public static final Column RG_COLUMN =
       new Column(RG_COL_KEY, "Resource Group", "Resource Group", "");
   public static final Column ADDR_COLUMN =
-      new Column(ADDR_COL_KEY, "Server Address", "Server address", "");
+      new Column(ADDR_COL_KEY, "Server Address", "Server address", 
"server-address");
+
+  private static final List<Column> SERVER_DETAIL_COLUMNS =
+      List.of(new Column(METRIC_NAME_COL_KEY, "Metric", "Metric name", ""),
+          new Column(METRIC_VALUE_COL_KEY, "Value", "Metric value", ""),
+          new Column(METRIC_TYPE_COL_KEY, "Type", "Metric type", ""),
+          new Column(METRIC_TAGS_COL_KEY, "Tags", "Metric tags", ""),
+          new Column(METRIC_SECTION_COL_KEY, "Section", "Metric documentation 
section", ""),
+          new Column(METRIC_DESCRIPTION_COL_KEY, "Description", "Metric 
description", ""));
 
   /**
    * Server-process table identifiers accepted by /rest-v2/servers/view. These 
enum names are used
@@ -146,6 +163,8 @@ public class TableDataFactory {
     List<Map<String,Object>> data = new ArrayList<>();
     serverMetricRows.forEach(serverMetricRow -> {
       Map<String,Object> row = new LinkedHashMap<>();
+      row.put(TYPE_COL_KEY, serverMetricRow.server().getType().name());
+      row.put(LINK_RG_COL_KEY, 
serverMetricRow.server().getResourceGroup().canonical());
       for (ColumnFactory colf : requestedColumns) {
         row.put(colf.getColumn().key(), 
colf.getRowData(serverMetricRow.server(),
             serverMetricRow.response(), serverMetricRow.metrics()));
@@ -157,6 +176,45 @@ public class TableDataFactory {
 
   }
 
+  public static TableData forServer(final MetricResponse metricResponse) {
+    if (!hasMetricData(metricResponse)) {
+      return new TableData(SERVER_DETAIL_COLUMNS, List.of());
+    }
+
+    List<Map<String,Object>> data = new ArrayList<>();
+    FMetric metric = new FMetric();
+    for (var binary : metricResponse.getMetrics()) {
+      metric = FMetric.getRootAsFMetric(binary, metric);
+      Map<String,Object> row = new LinkedHashMap<>();
+      row.put(METRIC_NAME_COL_KEY, metric.name());
+      row.put(METRIC_VALUE_COL_KEY, SystemInformation.getMetricValue(metric));
+      row.put(METRIC_TYPE_COL_KEY, metric.type());
+      row.put(METRIC_TAGS_COL_KEY, formatTags(metric));
+
+      try {
+        Metric metricDefinition = Metric.fromName(metric.name());
+        row.put(METRIC_SECTION_COL_KEY, 
metricDefinition.getDocSection().getSectionTitle());
+        row.put(METRIC_DESCRIPTION_COL_KEY, metricDefinition.getDescription());
+      } catch (IllegalArgumentException e) {
+        row.put(METRIC_SECTION_COL_KEY, "");
+        row.put(METRIC_DESCRIPTION_COL_KEY, "");
+      }
+      data.add(row);
+    }
+
+    return new TableData(SERVER_DETAIL_COLUMNS, data);
+  }
+
+  private static String formatTags(FMetric metric) {
+    List<String> tags = new ArrayList<>();
+    FTag tag = new FTag();
+    for (int i = 0; i < metric.tagsLength(); i++) {
+      tag = metric.tags(tag, i);
+      tags.add(tag.key() + "=" + tag.value());
+    }
+    return String.join(", ", tags);
+  }
+
   public static TableData forTable(final TableName table, final Set<ServerId> 
servers,
       final Map<ServerId,MetricResponse> allMetrics) {
 
diff --git 
a/server/monitor/src/main/java/org/apache/accumulo/monitor/view/WebViews.java 
b/server/monitor/src/main/java/org/apache/accumulo/monitor/view/WebViews.java
index 55e1e90efa..2dafa85d67 100644
--- 
a/server/monitor/src/main/java/org/apache/accumulo/monitor/view/WebViews.java
+++ 
b/server/monitor/src/main/java/org/apache/accumulo/monitor/view/WebViews.java
@@ -77,6 +77,10 @@ public class WebViews {
    */
   private static final String TSERVER_PARAM_KEY = "s";
 
+  private static final String SERVER_TYPE_PARAM_KEY = "type";
+
+  private static final String RESOURCE_GROUP_PARAM_KEY = "resourceGroup";
+
   private static final Logger log = LoggerFactory.getLogger(WebViews.class);
 
   @Inject
@@ -195,9 +199,12 @@ public class WebViews {
     Map<String,Object> model = getModel();
     model.put("title", "Tablet Server Status");
     if (server != null && !server.isBlank()) {
+      model.put("title", "Server Metrics");
       model.put("template", "server.ftl");
       model.put("js", "server.js");
       model.put("server", server);
+      model.put("serverType", "TABLET_SERVER");
+      model.put("resourceGroup", "");
       return model;
     }
     model.put("template", "tservers.ftl");
@@ -205,6 +212,31 @@ public class WebViews {
     return model;
   }
 
+  /**
+   * Returns the server metrics template
+   *
+   * @param serverType Accumulo server process type
+   * @param resourceGroup server resource group
+   * @param server server address
+   * @return server metrics model
+   */
+  @GET
+  @Path("server")
+  @Template(name = "/default.ftl")
+  public Map<String,Object> 
getServerMetrics(@QueryParam(SERVER_TYPE_PARAM_KEY) String serverType,
+      @QueryParam(RESOURCE_GROUP_PARAM_KEY) String resourceGroup,
+      @QueryParam(TSERVER_PARAM_KEY) @Pattern(regexp = HOSTNAME_PORT_REGEX) 
String server) {
+
+    Map<String,Object> model = getModel();
+    model.put("title", "Server Metrics");
+    model.put("template", "server.ftl");
+    model.put("js", "server.js");
+    model.put("server", server);
+    model.put("serverType", serverType);
+    model.put("resourceGroup", resourceGroup == null ? "" : resourceGroup);
+    return model;
+  }
+
   /**
    * Returns the scan servers template
    *
diff --git 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactions.js
 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactions.js
index 37ec085371..62d1a45bcf 100644
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactions.js
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/compactions.js
@@ -51,7 +51,7 @@ $(function () {
         "type": "html",
         "render": function (data, type, row, meta) {
           if (type === 'display') {
-            data = '<a class="link-body-emphasis" href="tservers?s=' + 
row.server + '">' + row.server + '</a>';
+            data = renderServerMetricsLink('COMPACTOR', '', row.server);
           }
           return data;
         }
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 597144a211..bbe619a5c0 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
@@ -44,6 +44,7 @@ const ALERT_COUNTS = 'alertCounts';
 const RECOVERY = 'recovery';
 const INSTANCE_OVERVIEW = 'instanceOverview';
 const SCANS = 'scans';
+const SERVER_METRICS = 'serverMetrics';
 const LAST_UPDATE = 'lastUpdate';
 
 var STATUS_REQUEST = null;
@@ -311,6 +312,31 @@ function sanitize(url) {
   return url.split('+').join('%2B');
 }
 
+function escapeHtml(value) {
+  if (value === null || value === undefined) {
+    return '';
+  }
+  return $('<div>').text(value).html();
+}
+
+function serverMetricsHref(serverType, resourceGroup, serverAddress) {
+  var href = 'server?type=' + encodeURIComponent(serverType) +
+    '&s=' + encodeURIComponent(serverAddress);
+  if (resourceGroup !== null && resourceGroup !== undefined && resourceGroup 
!== '') {
+    href += '&resourceGroup=' + encodeURIComponent(resourceGroup);
+  }
+  return href;
+}
+
+function renderServerMetricsLink(serverType, resourceGroup, serverAddress) {
+  if (!serverType || !resourceGroup || !serverAddress) {
+    return escapeHtml(serverAddress);
+  }
+  return '<a class="link-body-emphasis" href="' +
+    serverMetricsHref(serverType, resourceGroup, serverAddress) + '">' +
+    escapeHtml(serverAddress) + '</a>';
+}
+
 /**
  * Creates a string with the value to sort and the value to display
  * Options are 0 = firstcell left, 1 = right, 2 = center, 3 = left
@@ -606,6 +632,12 @@ function getServerProcessView(table, storageKey) {
   return getJSONForTable(url, storageKey);
 }
 
+function getServerMetrics(serverType, resourceGroup, serverAddress) {
+  var url = REST_V2_PREFIX + '/servers/detail/' + 
encodeURIComponent(serverType) + '/' +
+    encodeURIComponent(resourceGroup) + '/' + 
encodeURIComponent(serverAddress);
+  return getJSONForTable(url, SERVER_METRICS);
+}
+
 function getCompactorsView() {
   return getServerProcessView('COMPACTORS', COMPACTOR_SERVER_PROCESS_VIEW);
 }
diff --git 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server.js
 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server.js
index f995a2fc85..2d17d13712 100644
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server.js
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/server.js
@@ -18,264 +18,45 @@
  */
 "use strict";
 
-var detailTable, historyTable, currentTable, resultsTable;
-
-/**
- * Makes the REST calls, generates the tables with the new information
- */
-function refreshServer() {
-  ajaxReloadTable(detailTable);
-  ajaxReloadTable(historyTable);
-  ajaxReloadTable(currentTable);
-  ajaxReloadTable(resultsTable);
+const htmlServerMetricsTable = '#serverMetrics';
+const htmlServerMetricsBanner = '#serverMetricsStatusBanner';
+const htmlServerMetricsBannerMessage = '#server-metrics-banner-message';
+
+function refreshServerMetrics() {
+  if (!serverMetricsType || !serverMetricsResourceGroup || 
!serverMetricsAddress) {
+    sessionStorage[SERVER_METRICS] = JSON.stringify({
+      data: [],
+      columns: []
+    });
+    refreshTable(htmlServerMetricsTable, SERVER_METRICS);
+    $(htmlServerMetricsBannerMessage).text('ERROR: missing server metrics 
parameters.');
+    $(htmlServerMetricsBanner).show();
+    return;
+  }
+
+  getServerMetrics(serverMetricsType, serverMetricsResourceGroup, 
serverMetricsAddress).then(
+    function () {
+      refreshTable(htmlServerMetricsTable, SERVER_METRICS);
+      $(htmlServerMetricsBanner).hide();
+    }).fail(function () {
+    sessionStorage[SERVER_METRICS] = JSON.stringify({
+      data: [],
+      columns: []
+    });
+    refreshTable(htmlServerMetricsTable, SERVER_METRICS);
+    $(htmlServerMetricsBannerMessage).text('ERROR: unable to retrieve server 
metrics.');
+    $(htmlServerMetricsBanner).show();
+  });
 }
 
-/**
- * Used to redraw the page
- */
 function refresh() {
-  refreshServer();
+  refreshServerMetrics();
 }
 
-/**
- * Initializes all of the DataTables for the given hostname
- * 
- * @param {String} serv the tserver hostname
- */
-function initServerTables(serv) {
-
-  const url = contextPath + 'rest/tservers/' + serv;
-  console.debug('REST url used to fetch data for server.js DataTables: ' + 
url);
-
-  // Create a table for details on the current server
-  detailTable = $('#tServerDetail').DataTable({
-    "ajax": {
-      "url": url,
-      "dataSrc": function (data) {
-        // the data needs to be in an array to work with DataTables
-        var arr = [];
-        if (data.details === undefined) {
-          console.warn('the value of "details" is undefined');
-        } else {
-          arr = [data.details];
-        }
-
-        return arr;
-      }
-    },
-    "stateSave": true,
-    "searching": false,
-    "paging": false,
-    "info": false,
-    "columnDefs": [{
-      "targets": "big-num",
-      "render": function (data, type) {
-        if (type === 'display') {
-          data = bigNumberForQuantity(data);
-        }
-        return data;
-      }
-    }],
-    "columns": [{
-        "data": "hostedTablets"
-      },
-      {
-        "data": "entries"
-      },
-      {
-        "data": "minors"
-      }
-    ]
+$(function () {
+  sessionStorage[SERVER_METRICS] = JSON.stringify({
+    data: [],
+    columns: []
   });
-
-  // Create a table for all time tablet operations
-  historyTable = $('#opHistoryDetails').DataTable({
-    "ajax": {
-      "url": url,
-      "dataSrc": "allTimeTabletResults"
-    },
-    "stateSave": true,
-    "searching": false,
-    "paging": false,
-    "info": false,
-    "columnDefs": [{
-        "targets": "big-num",
-        "render": function (data, type) {
-          if (type === 'display') {
-            data = bigNumberForQuantity(data);
-          }
-          return data;
-        }
-      },
-      {
-        "targets": "duration",
-        "render": function (data, type) {
-          if (type === 'display') {
-            if (data === null) {
-              data = '&mdash;';
-            } else {
-              data = timeDuration(data * 1000.0);
-            }
-          }
-          return data;
-        }
-      }
-    ],
-    "columns": [{
-        "data": "operation"
-      },
-      {
-        "data": "success"
-      },
-      {
-        "data": "failure"
-      },
-      {
-        "data": "avgQueueTime"
-      },
-      {
-        "data": "queueStdDev"
-      },
-      {
-        "data": "avgTime"
-      },
-      {
-        "data": "stdDev"
-      },
-      {
-        "data": "timeSpent" // placeholder for percent column, replaced below
-      }
-    ],
-    // calculate and fill percent column each time table is drawn
-    "drawCallback": function () {
-      var totalTime = 0;
-      var api = this.api();
-
-      // calculate total duration of all tablet operations
-      api.rows().every(function () {
-        totalTime += this.data().timeSpent;
-      });
-
-      const percentColumnIndex = 7;
-      api.rows().every(function (rowIdx) {
-        // calculate the percentage of time taken for each row (each tablet op)
-        var currentPercent = (this.data().timeSpent / totalTime) * 100;
-        currentPercent = Math.round(currentPercent);
-        if (isNaN(currentPercent)) {
-          currentPercent = 0;
-        }
-        // insert the percentage bar into the current row and percent column
-        var newData = `<div class="progress"><div class="progress-bar" 
role="progressbar" style="min-width: 2em; 
width:${currentPercent}%;">${currentPercent}%</div></div>`
-        api.cell(rowIdx, percentColumnIndex).data(newData);
-      });
-    }
-  });
-
-  // Create a table for tablet operations on the current server
-  currentTable = $('#currentTabletOps').DataTable({
-    "ajax": {
-      "url": url,
-      "dataSrc": function (data) {
-        // the data needs to be in an array to work with DataTables
-        var arr = [];
-        if (data.currentTabletOperationResults === undefined) {
-          console.warn('the value of "currentTabletOperationResults" is 
undefined');
-        } else {
-          arr = [data.currentTabletOperationResults];
-        }
-
-        return arr;
-      }
-    },
-    "stateSave": true,
-    "searching": false,
-    "paging": false,
-    "info": false,
-    "columnDefs": [{
-      "targets": "duration",
-      "render": function (data, type) {
-        if (type === 'display') {
-          data = timeDuration(data * 1000.0);
-        }
-        return data;
-      }
-    }],
-    "columns": [{
-        "data": "currentMinorAvg"
-      },
-      {
-        "data": "currentMinorStdDev"
-      }
-    ]
-  });
-
-  // Create a table for detailed tablet operations
-  resultsTable = $('#perTabletResults').DataTable({
-    "ajax": {
-      "url": url,
-      "dataSrc": "currentOperations"
-    },
-    "stateSave": true,
-    "dom": 't<"align-left"l>p',
-    "columnDefs": [{
-        "targets": "big-num",
-        "render": function (data, type) {
-          if (type === 'display') {
-            data = bigNumberForQuantity(data);
-          }
-          return data;
-        }
-      },
-      {
-        "targets": "duration",
-        "render": function (data, type) {
-          if (type === 'display') {
-            data = timeDuration(data);
-          }
-          return data;
-        }
-      }
-    ],
-    "columns": [{
-        "data": "name",
-        "type": "html",
-        "render": function (data, type, row) {
-          if (type === 'display') {
-            data = `<a class="link-body-emphasis" 
href="tables/${row.tableID}">${data}</a>`;
-          }
-          return data;
-        }
-      },
-      {
-        "data": "tablet",
-        "type": "html",
-        "render": function (data, type) {
-          if (type === 'display') {
-            data = `<code>${data}</code>`;
-          }
-          return data;
-        }
-      },
-      {
-        "data": "entries"
-      },
-      {
-        "data": "ingest"
-      },
-      {
-        "data": "query"
-      },
-      {
-        "data": "minorAvg"
-      },
-      {
-        "data": "minorStdDev"
-      },
-      {
-        "data": "minorAvgES"
-      }
-    ]
-  });
-
-  refreshServer();
-}
+  refreshServerMetrics();
+});
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 bdb9361145..2952dfed5d 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
@@ -257,7 +257,7 @@ function createDataTable(table, storageKey) {
       "text": '<i class="bi bi-gear"></i>',
       "titleAttr": 'Columns'
     }],
-    "dom": '<"row"<"col-sm-12 col-md-4"l><"col-sm-12 col-md-6"f><"col-sm-12 
col-md-2"B>>' +
+    "dom": '<"row"<"col-sm-12 col-md-4"l><"col-sm-12 col-md-6 
text-md-end"f><"col-sm-12 col-md-2 text-md-end"B>>' +
       '<"row dt-row"<"col-sm-12"rt>>' +
       '<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
     "stateSave": true,
@@ -368,6 +368,15 @@ function createDataTable(table, storageKey) {
       {
         "targets": "memory-state",
         "render": renderMemoryState
+      },
+      {
+        "targets": "server-address",
+        "render": function (data, type, row) {
+          if (type === 'display') {
+            return renderServerMetricsLink(row.serverType, 
row.serverLinkResourceGroup, data);
+          }
+          return data;
+        }
       }
     ],
     "columns": getDataTableCols(storageKey)
diff --git 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/server.ftl
 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/server.ftl
index b412b564b3..8204af61ba 100644
--- 
a/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/server.ftl
+++ 
b/server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/server.ftl
@@ -18,86 +18,25 @@
     under the License.
 
 -->
-      <script>
-        $(function () {
-          // initialize DataTables
-          initServerTables('${server}');
-        });
-      </script>
-      <div class="row">
-        <div class="col-xs-12">
-          <h3>${title}</h3>
-        </div>
-      </div>
-      <div class="row">
-        <div class="col-xs-12">
-          <table id="tServerDetail" class="table caption-top table-bordered 
table-striped table-condensed">
-            <caption><span class="table-caption">${server}</span></caption>
-            <thead>
-              <tr>
-                <th class="big-num">Hosted&nbsp;Tablets&nbsp;</th>
-                <th class="big-num">Entries&nbsp;</th>
-                <th class="big-num">Minor&nbsp;Compacting&nbsp;</th>
-              </tr>
-            </thead>
-            <tbody></tbody>
-          </table>
-        </div>
-      </div>
-      <br />
-      <div class="row">
-        <div class="col-xs-12">
-          <table id="opHistoryDetails" class="table caption-top table-bordered 
table-striped table-condensed">
-            <caption><span 
class="table-caption">All-Time&nbsp;Tablet&nbsp;Operation&nbsp;Results</span></caption>
-            <thead>
-              <tr>
-                <th>Operation&nbsp;</th>
-                <th class="big-num">Success&nbsp;</th>
-                <th class="big-num">Failure&nbsp;</th>
-                <th class="duration">Average<br />Queue&nbsp;Time&nbsp;</th>
-                <th class="duration">Std.&nbsp;Dev.<br 
/>Queue&nbsp;Time&nbsp;</th>
-                <th class="duration">Average<br />Time&nbsp;</th>
-                <th class="duration">Std.&nbsp;Dev.<br />Time&nbsp;</th>
-                <th>Percentage&nbsp;Time&nbsp;Spent&nbsp;</th>
-              </tr>
-            </thead>
-            <tbody></tbody>
-          </table>
-        </div>
-      </div>
-      <br />
-      <div class="row">
-        <div class="col-xs-12">
-          <table id="currentTabletOps" class="table caption-top table-bordered 
table-striped table-condensed">
-            <caption><span 
class="table-caption">Current&nbsp;Tablet&nbsp;Operation&nbsp;Results</span></caption>
-            <thead>
-              <tr>
-                <th class="duration">Minor&nbsp;Average&nbsp;</th>
-                <th class="duration">Minor&nbsp;Std&nbsp;Dev&nbsp;</th>
-              </tr>
-            </thead>
-            <tbody></tbody>
-          </table>
-        </div>
-      </div>
-      <br />
-      <div class="row">
-        <div class="col-xs-12">
-          <table id="perTabletResults" class="table caption-top table-bordered 
table-striped table-condensed">
-            <caption><span class="table-caption">Detailed Tablet 
Operations</span></caption>
-            <thead>
-              <tr>
-                <th>Table&nbsp;</th>
-                <th title="Run 'getsplits -v' in the Accumulo Shell to 
associate the encoded tablets with their actual splits.">Tablet&nbsp;</th>
-                <th class="big-num">Entries&nbsp;</th>
-                <th class="big-num">Ingest&nbsp;</th>
-                <th class="big-num">Query&nbsp;</th>
-                <th class="duration">Minor&nbsp;Avg&nbsp;</th>
-                <th class="duration">Minor&nbsp;Std&nbsp;Dev&nbsp;</th>
-                <th class="big-num">Minor&nbsp;Avg&nbsp;e/s&nbsp;</th>
-              </tr>
-            </thead>
-            <tbody></tbody>
-          </table>
-        </div>
+    <script>
+      var serverMetricsType = '${(serverType!)?js_string}';
+      var serverMetricsResourceGroup = '${(resourceGroup!)?js_string}';
+      var serverMetricsAddress = '${(server!)?js_string}';
+    </script>
+    <div id="serverMetricsStatusBanner" style="display: none;">
+      <div id="server-metrics-banner-message" class="alert alert-danger" 
role="alert"></div>
+    </div>
+    <div class="row">
+      <div class="col-xs-12">
+        <span class="table-caption">Server Metrics</span>
+        <br />
+        <span class="table-subcaption">
+          ${(serverType!)?html} ${(server!)?html}<#if 
resourceGroup?has_content> (${resourceGroup?html})</#if>
+        </span>
+        <br />
+        <br />
+        <table id="serverMetrics" class="table caption-top table-bordered 
table-striped table-condensed">
+          <#include "table_loading.ftl" >
+        </table>
       </div>
+    </div>

Reply via email to