dlmarion commented on code in PR #4567:
URL: https://github.com/apache/accumulo/pull/4567#discussion_r1605246152


##########
server/base/src/main/java/org/apache/accumulo/server/util/serviceStatus/ServiceStatusReport.java:
##########
@@ -0,0 +1,168 @@
+/*
+ * 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.server.util.serviceStatus;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+
+/**
+ * Wrapper for JSON formatted report.
+ */
+public class ServiceStatusReport {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(ServiceStatusReport.class);
+
+  private static final Gson gson = new Gson();
+
+  private static final DateTimeFormatter rptTimeFmt =
+      DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+  private static final String I2 = "  ";
+  private static final String I4 = "    ";
+
+  private final String reportTime;
+  private final int zkReadErrors;
+  private final boolean noHosts;
+  private final Map<ReportKey,StatusSummary> summaryMap;
+
+  public ServiceStatusReport(final Map<ReportKey,StatusSummary> summaryMap, 
final boolean noHosts) {
+    reportTime = rptTimeFmt.format(ZonedDateTime.now(ZoneId.of("UTC")));
+    zkReadErrors = 
summaryMap.values().stream().map(StatusSummary::getErrorCount)
+        .reduce(Integer::sum).orElse(0);
+    this.noHosts = noHosts;
+    this.summaryMap = summaryMap;
+  }
+
+  public String getReportTime() {
+    return reportTime;
+  }
+
+  public Map<ReportKey,StatusSummary> getSummaryMap() {
+    return summaryMap;
+  }
+
+  public String toJson() {
+    return gson.toJson(this, ServiceStatusReport.class);
+  }
+
+  public String report(final StringBuilder sb) {
+    sb.append("Report time: 
").append(rptTimeFmt.format(ZonedDateTime.now(ZoneId.of("UTC"))))
+        .append("\n");
+    int zkErrors = 
summaryMap.values().stream().map(StatusSummary::getErrorCount)
+        .reduce(Integer::sum).orElse(0);
+    sb.append("ZooKeeper read errors: ").append(zkErrors).append("\n");
+
+    writeServiceStatus(sb, ReportKey.MANAGER, 
summaryMap.get(ReportKey.MANAGER), noHosts);
+    writeServiceStatus(sb, ReportKey.MONITOR, 
summaryMap.get(ReportKey.MONITOR), noHosts);
+    writeServiceStatus(sb, ReportKey.GC, summaryMap.get(ReportKey.GC), 
noHosts);
+    writeServiceStatus(sb, ReportKey.T_SERVER, 
summaryMap.get(ReportKey.T_SERVER), noHosts);
+    writeResourceGroups(sb, ReportKey.S_SERVER, 
summaryMap.get(ReportKey.S_SERVER), noHosts);
+    writeServiceStatus(sb, ReportKey.COORDINATOR, 
summaryMap.get(ReportKey.COORDINATOR), noHosts);
+    writeResourceGroups(sb, ReportKey.COMPACTOR, 
summaryMap.get(ReportKey.COMPACTOR), noHosts);
+
+    sb.append("\n");
+    LOG.trace("fmtStatus - with hosts: {}", summaryMap);
+    return sb.toString();
+  }
+
+  public void writeServiceStatus(final StringBuilder sb, final ReportKey 
displayNames,
+      final StatusSummary summary, boolean noHosts) {
+    if (summary == null) {
+      sb.append(displayNames).append(": unavailable").append("\n");
+      return;
+    }
+
+    writeCounts(sb, summary);
+
+    // skip host info if requested
+    if (noHosts) {
+      return;
+    }
+    if (summary.getServiceCount() > 0) {
+      var hosts = summary.getServiceNames();
+      hosts.forEach(h -> sb.append(I2).append(h).append("\n"));
+    }
+  }
+
+  private void writeCounts(StringBuilder sb, StatusSummary summary) {
+    sb.append(summary.getDisplayName()).append(": count: 
").append(summary.getServiceCount());
+    if (summary.getErrorCount() > 0) {
+      sb.append(", (ZooKeeper errors: 
").append(summary.getErrorCount()).append(")\n");
+    } else {
+      sb.append("\n");
+    }
+  }
+
+  private void writeResourceGroups(final StringBuilder sb, final ReportKey 
reportKey,
+      final StatusSummary summary, boolean noHosts) {
+    if (summary == null) {
+      sb.append(reportKey).append(": unavailable").append("\n");
+      return;
+    }
+
+    writeCounts(sb, summary);
+
+    // skip host info if requested
+    if (noHosts) {
+      return;
+    }
+    if (!summary.getResourceGroups().isEmpty()) {
+      sb.append(I2).append("resource groups:\n");
+      summary.getResourceGroups().forEach(g -> 
sb.append(I4).append(g).append("\n"));

Review Comment:
   Do we need to print the groups twice? I wonder if we could print something 
like:
   ```
     Group: DEFAULT
       localhost:1234
       localhost:1235
     Group: Group1
       localhost:1236
   ```



##########
server/base/src/main/java/org/apache/accumulo/server/util/ServiceStatusCmd.java:
##########
@@ -0,0 +1,336 @@
+/*
+ * 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.server.util;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.util.Collection;
+import java.util.Map;
+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.Constants;
+import org.apache.accumulo.core.fate.zookeeper.ZooReader;
+import org.apache.accumulo.core.util.Pair;
+import org.apache.accumulo.core.util.ServerServices;
+import org.apache.accumulo.server.ServerContext;
+import org.apache.accumulo.server.util.serviceStatus.ServiceStatusReport;
+import org.apache.accumulo.server.util.serviceStatus.StatusSummary;
+import org.apache.zookeeper.KeeperException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.google.common.annotations.VisibleForTesting;
+
+public class ServiceStatusCmd {
+  private static final Logger LOG = 
LoggerFactory.getLogger(ServiceStatusCmd.class);
+
+  public ServiceStatusCmd() {}
+
+  /**
+   * ServerServices writes lock data as SERVICE=host. This strips the SERVICE= 
from the string.
+   *
+   * @return a sort set of host names.
+   */
+  private static Set<String> stripServiceName(Set<String> hostnames) {
+    return hostnames.stream().map(h -> {
+      if (h.contains(ServerServices.SEPARATOR_CHAR)) {
+        return h.substring(h.indexOf(ServerServices.SEPARATOR_CHAR) + 1);
+      }
+      return h;
+    }).collect(Collectors.toCollection(TreeSet::new));
+  }
+
+  /**
+   * Read the service statuses from ZooKeeper, build the status report and 
then output the report to
+   * stdout.
+   */
+  public void execute(final ServerContext context, final Opts opts) {
+
+    ZooReader zooReader = context.getZooReader();
+
+    final String zooRoot = context.getZooKeeperRoot();
+    LOG.trace("zooRoot: {}", zooRoot);
+
+    final Map<ServiceStatusReport.ReportKey,StatusSummary> services = new 
TreeMap<>();
+
+    services.put(ServiceStatusReport.ReportKey.MANAGER, 
getManagerStatus(zooReader, zooRoot));
+    services.put(ServiceStatusReport.ReportKey.MONITOR, 
getMonitorStatus(zooReader, zooRoot));
+    services.put(ServiceStatusReport.ReportKey.T_SERVER, 
getTServerStatus(zooReader, zooRoot));
+    services.put(ServiceStatusReport.ReportKey.S_SERVER, 
getScanServerStatus(zooReader, zooRoot));
+    services.put(ServiceStatusReport.ReportKey.COORDINATOR,
+        getCoordinatorStatus(zooReader, zooRoot));
+    services.put(ServiceStatusReport.ReportKey.COMPACTOR, 
getCompactorStatus(zooReader, zooRoot));
+    services.put(ServiceStatusReport.ReportKey.GC, getGcStatus(zooReader, 
zooRoot));
+
+    ServiceStatusReport report = new ServiceStatusReport(services, 
opts.noHosts);
+
+    if (opts.json) {
+      System.out.println(report.toJson());
+    } else {
+      StringBuilder sb = new StringBuilder(8192);
+      report.report(sb);
+      System.out.println(sb);
+    }
+  }
+
+  /**
+   * The manager paths in ZooKeeper are: {@code 
/accumulo/[IID]/managers/lock/zlock#[NUM]} with the
+   * lock data providing host:port.
+   */
+  @VisibleForTesting
+  StatusSummary getManagerStatus(final ZooReader zooReader, String zRootPath) {
+    String lockPath = zRootPath + Constants.ZMANAGER_LOCK;
+    return getStatusSummary(ServiceStatusReport.ReportKey.MANAGER, zooReader, 
lockPath);
+  }
+
+  /**
+   * The monitor paths in ZooKeeper are: {@code 
/accumulo/[IID]/monitor/lock/zlock#[NUM]} with the
+   * lock data providing host:port.
+   */
+  @VisibleForTesting
+  StatusSummary getMonitorStatus(final ZooReader zooReader, String zRootPath) {
+    String lockPath = zRootPath + Constants.ZMONITOR_LOCK;
+    return getStatusSummary(ServiceStatusReport.ReportKey.MONITOR, zooReader, 
lockPath);
+  }
+
+  /**
+   * The tserver paths in ZooKeeper are: {@code 
/accumulo/[IID]/tservers/[host:port]/zlock#[NUM]}
+   * with the lock data providing TSERV_CLIENT=host:port.
+   */
+  @VisibleForTesting
+  StatusSummary getTServerStatus(final ZooReader zooReader, String zRootPath) {
+    String lockPath = zRootPath + Constants.ZTSERVERS;
+    return getServerHostStatus(zooReader, lockPath, 
ServiceStatusReport.ReportKey.T_SERVER);
+  }
+
+  /**
+   * The sserver paths in ZooKeeper are: {@code 
/accumulo/[IID]/sservers/[host:port]/zlock#[NUM]}
+   * with the lock data providing [UUID],[GROUP]
+   */
+  @VisibleForTesting
+  StatusSummary getScanServerStatus(final ZooReader zooReader, String 
zRootPath) {
+    String lockPath = zRootPath + Constants.ZSSERVERS;
+    return getServerHostStatus(zooReader, lockPath, 
ServiceStatusReport.ReportKey.S_SERVER);
+  }
+
+  /**
+   * handles paths for tservers and servers with the lock stored beneath the 
host: port like:
+   * {@code /accumulo/IID/[tservers | sservers]/HOST:PORT/[LOCK]}
+   */
+  private StatusSummary getServerHostStatus(final ZooReader zooReader, String 
basePath,
+      ServiceStatusReport.ReportKey displayNames) {
+    AtomicInteger errorSum = new AtomicInteger(0);
+
+    Set<String> hostNames = new TreeSet<>();
+    Set<String> groupNames = new TreeSet<>();
+
+    var nodeNames = readNodeNames(zooReader, basePath);
+
+    nodeNames.getSecond().forEach(name -> {
+      var lock = readNodeNames(zooReader, basePath + "/" + name);
+      lock.getSecond().forEach(l -> {
+        var r = readNodeData(zooReader, basePath + "/" + name + "/" + l);
+        int err = r.getFirst();
+        if (err > 0) {
+          errorSum.addAndGet(r.getFirst());
+        } else {
+          // process resource groups
+          var payload = r.getSecond();
+          String[] tokens = payload.split(",");
+          String groupSeparator = "";
+          if (tokens.length == 2) {
+            groupNames.add(tokens[1]);
+            groupSeparator = tokens[1] + ": ";
+          }

Review Comment:
   Is this parsing the lock content represented by the ServerServices object?  
If so, be aware that this changed in 3.1, ref: 
https://github.com/apache/accumulo/blob/main/server/tserver/src/main/java/org/apache/accumulo/tserver/TabletServer.java#L664



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to