You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by ap...@apache.org on 2022/02/16 02:40:37 UTC

[hbase] branch branch-2 updated: HBASE-26730 Extend hbase shell 'status' command to support an option 'tasks' (#4094)

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

apurtell pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2 by this push:
     new 3e84e50  HBASE-26730 Extend hbase shell 'status' command to support an option 'tasks' (#4094)
3e84e50 is described below

commit 3e84e50107d90cedd7429177f47a1c5c51b4b7fb
Author: Andrew Purtell <ap...@apache.org>
AuthorDate: Tue Feb 15 18:38:25 2022 -0800

    HBASE-26730 Extend hbase shell 'status' command to support an option 'tasks' (#4094)
    
    Signed-off-by: Geoffrey Jacoby <gj...@apache.org>
    Signed-off-by: Viraj Jasani <vj...@apache.org>
---
 .../org/apache/hadoop/hbase/ClusterMetrics.java    |  10 ++
 .../apache/hadoop/hbase/ClusterMetricsBuilder.java |  29 ++++-
 .../org/apache/hadoop/hbase/ClusterStatus.java     |   5 +
 .../java/org/apache/hadoop/hbase/ServerLoad.java   |   5 +
 .../org/apache/hadoop/hbase/ServerMetrics.java     |   7 ++
 .../apache/hadoop/hbase/ServerMetricsBuilder.java  |  37 ++++--
 .../java/org/apache/hadoop/hbase/ServerTask.java   |  64 +++++++++++
 .../org/apache/hadoop/hbase/ServerTaskBuilder.java | 126 +++++++++++++++++++++
 .../hadoop/hbase/shaded/protobuf/ProtobufUtil.java |  22 ++++
 .../src/main/protobuf/ClusterStatus.proto          |  22 ++++
 .../org/apache/hadoop/hbase/master/HMaster.java    |  46 +++++++-
 .../hadoop/hbase/regionserver/HRegionServer.java   |  12 +-
 .../hadoop/hbase/TestClientClusterMetrics.java     |  59 +++++++++-
 .../hbase/master/TestRegionsRecoveryChore.java     |  11 ++
 hbase-shell/src/main/ruby/hbase/admin.rb           |  38 ++++++-
 hbase-shell/src/main/ruby/shell/commands/status.rb |   5 +-
 16 files changed, 479 insertions(+), 19 deletions(-)

diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterMetrics.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterMetrics.java
index 497ab93..29679e6 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterMetrics.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterMetrics.java
@@ -162,6 +162,12 @@ public interface ClusterMetrics {
   Map<TableName, RegionStatesCount> getTableRegionStatesCount();
 
   /**
+   * Provide the list of master tasks
+   */
+  @Nullable
+  List<ServerTask> getMasterTasks();
+
+  /**
    * Kinds of ClusterMetrics
    */
   enum Option {
@@ -213,5 +219,9 @@ public interface ClusterMetrics {
      * metrics about table to no of regions status count
      */
     TABLE_TO_REGIONS_COUNT,
+    /**
+     * metrics about monitored tasks
+     */
+    TASKS,
   }
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterMetricsBuilder.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterMetricsBuilder.java
index 493fe71..011f93f 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterMetricsBuilder.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterMetricsBuilder.java
@@ -83,6 +83,10 @@ public final class ClusterMetricsBuilder {
     if (metrics.getMasterName() != null) {
       builder.setMaster(ProtobufUtil.toServerName((metrics.getMasterName())));
     }
+    if (metrics.getMasterTasks() != null) {
+      builder.addAllMasterTasks(metrics.getMasterTasks().stream()
+        .map(t -> ProtobufUtil.toServerTask(t)).collect(Collectors.toList()));
+    }
     if (metrics.getBalancerOn() != null) {
       builder.setBalancerOn(metrics.getBalancerOn());
     }
@@ -122,7 +126,9 @@ public final class ClusterMetricsBuilder {
           proto.getTableRegionStatesCountList().stream()
           .collect(Collectors.toMap(
             e -> ProtobufUtil.toTableName(e.getTableName()),
-            e -> ProtobufUtil.toTableRegionStatesCount(e.getRegionStatesCount()))));
+            e -> ProtobufUtil.toTableRegionStatesCount(e.getRegionStatesCount()))))
+        .setMasterTasks(proto.getMasterTasksList().stream()
+          .map(t -> ProtobufUtil.getServerTask(t)).collect(Collectors.toList()));
     if (proto.hasClusterId()) {
       builder.setClusterId(ClusterId.convert(proto.getClusterId()).toString());
     }
@@ -164,6 +170,7 @@ public final class ClusterMetricsBuilder {
       case SERVERS_NAME: return ClusterMetrics.Option.SERVERS_NAME;
       case MASTER_INFO_PORT: return ClusterMetrics.Option.MASTER_INFO_PORT;
       case TABLE_TO_REGIONS_COUNT: return ClusterMetrics.Option.TABLE_TO_REGIONS_COUNT;
+      case TASKS: return ClusterMetrics.Option.TASKS;
       // should not reach here
       default: throw new IllegalArgumentException("Invalid option: " + option);
     }
@@ -188,6 +195,7 @@ public final class ClusterMetricsBuilder {
       case SERVERS_NAME: return Option.SERVERS_NAME;
       case MASTER_INFO_PORT: return ClusterStatusProtos.Option.MASTER_INFO_PORT;
       case TABLE_TO_REGIONS_COUNT: return ClusterStatusProtos.Option.TABLE_TO_REGIONS_COUNT;
+      case TASKS: return ClusterStatusProtos.Option.TASKS;
       // should not reach here
       default: throw new IllegalArgumentException("Invalid option: " + option);
     }
@@ -231,6 +239,8 @@ public final class ClusterMetricsBuilder {
   private int masterInfoPort;
   private List<ServerName> serversName = Collections.emptyList();
   private Map<TableName, RegionStatesCount> tableRegionStatesCount = Collections.emptyMap();
+  @Nullable
+  private List<ServerTask> masterTasks;
 
   private ClusterMetricsBuilder() {
   }
@@ -280,6 +290,10 @@ public final class ClusterMetricsBuilder {
     this.serversName = serversName;
     return this;
   }
+  public ClusterMetricsBuilder setMasterTasks(List<ServerTask> masterTasks) {
+    this.masterTasks = masterTasks;
+    return this;
+  }
 
   public ClusterMetricsBuilder setTableRegionStatesCount(
       Map<TableName, RegionStatesCount> tableRegionStatesCount) {
@@ -300,7 +314,8 @@ public final class ClusterMetricsBuilder {
         balancerOn,
         masterInfoPort,
         serversName,
-        tableRegionStatesCount
+        tableRegionStatesCount,
+        masterTasks
     );
   }
   private static class ClusterMetricsImpl implements ClusterMetrics {
@@ -320,6 +335,7 @@ public final class ClusterMetricsBuilder {
     private final int masterInfoPort;
     private final List<ServerName> serversName;
     private final Map<TableName, RegionStatesCount> tableRegionStatesCount;
+    private final List<ServerTask> masterTasks;
 
     ClusterMetricsImpl(String hbaseVersion, List<ServerName> deadServerNames,
         Map<ServerName, ServerMetrics> liveServerMetrics,
@@ -331,7 +347,8 @@ public final class ClusterMetricsBuilder {
         Boolean balancerOn,
         int masterInfoPort,
         List<ServerName> serversName,
-        Map<TableName, RegionStatesCount> tableRegionStatesCount) {
+        Map<TableName, RegionStatesCount> tableRegionStatesCount,
+        List<ServerTask> masterTasks) {
       this.hbaseVersion = hbaseVersion;
       this.deadServerNames = Preconditions.checkNotNull(deadServerNames);
       this.liveServerMetrics = Preconditions.checkNotNull(liveServerMetrics);
@@ -344,6 +361,7 @@ public final class ClusterMetricsBuilder {
       this.masterInfoPort = masterInfoPort;
       this.serversName = serversName;
       this.tableRegionStatesCount = Preconditions.checkNotNull(tableRegionStatesCount);
+      this.masterTasks = masterTasks;
     }
 
     @Override
@@ -407,6 +425,11 @@ public final class ClusterMetricsBuilder {
     }
 
     @Override
+    public List<ServerTask> getMasterTasks() {
+      return masterTasks;
+    }
+
+    @Override
     public String toString() {
       StringBuilder sb = new StringBuilder(1024);
       sb.append("Master: " + getMasterName());
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterStatus.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterStatus.java
index 6fdb588..6a51db0 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterStatus.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ClusterStatus.java
@@ -357,6 +357,11 @@ public class ClusterStatus implements ClusterMetrics {
   }
 
   @Override
+  public List<ServerTask> getMasterTasks() {
+    return metrics.getMasterTasks();
+  }
+
+  @Override
   public String toString() {
     StringBuilder sb = new StringBuilder(1024);
     sb.append("Master: " + metrics.getMasterName());
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerLoad.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerLoad.java
index b22d6c4..7b8f713 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerLoad.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerLoad.java
@@ -431,6 +431,11 @@ public class ServerLoad implements ServerMetrics {
     return metrics.getLastReportTimestamp();
   }
 
+  @Override
+  public List<ServerTask> getTasks() {
+    return metrics.getTasks();
+  }
+
   /**
    * Originally, this method factored in the effect of requests going to the
    * server as well. However, this does not interact very well with the current
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerMetrics.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerMetrics.java
index 21fad92..893534c 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerMetrics.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerMetrics.java
@@ -114,4 +114,11 @@ public interface ServerMetrics {
    */
   long getLastReportTimestamp();
 
+  /**
+   * Called directly from clients such as the hbase shell
+   * @return the active monitored tasks
+   */
+  @Nullable
+  List<ServerTask> getTasks();
+
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerMetricsBuilder.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerMetricsBuilder.java
index d935272..89cee9f 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerMetricsBuilder.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerMetricsBuilder.java
@@ -85,6 +85,8 @@ public final class ServerMetricsBuilder {
       .setReplicationLoadSink(serverLoadPB.hasReplLoadSink()
         ? ProtobufUtil.toReplicationLoadSink(serverLoadPB.getReplLoadSink())
         : null)
+      .setTasks(serverLoadPB.getTasksList().stream()
+          .map(ProtobufUtil::getServerTask).collect(Collectors.toList()))
       .setReportTimestamp(serverLoadPB.getReportEndTime())
       .setLastReportTimestamp(serverLoadPB.getReportStartTime()).setVersionNumber(versionNumber)
       .setVersion(version).build();
@@ -103,19 +105,24 @@ public final class ServerMetricsBuilder {
         .setInfoServerPort(metrics.getInfoServerPort())
         .setMaxHeapMB((int) metrics.getMaxHeapSize().get(Size.Unit.MEGABYTE))
         .setUsedHeapMB((int) metrics.getUsedHeapSize().get(Size.Unit.MEGABYTE))
-        .addAllCoprocessors(toCoprocessor(metrics.getCoprocessorNames())).addAllRegionLoads(
+        .addAllCoprocessors(toCoprocessor(metrics.getCoprocessorNames()))
+        .addAllRegionLoads(
             metrics.getRegionMetrics().values().stream().map(RegionMetricsBuilder::toRegionLoad)
-                .collect(Collectors.toList())).addAllUserLoads(
+                .collect(Collectors.toList()))
+        .addAllUserLoads(
             metrics.getUserMetrics().values().stream().map(UserMetricsBuilder::toUserMetrics)
-                .collect(Collectors.toList())).addAllReplLoadSource(
+                .collect(Collectors.toList()))
+        .addAllReplLoadSource(
             metrics.getReplicationLoadSourceList().stream()
                 .map(ProtobufUtil::toReplicationLoadSource).collect(Collectors.toList()))
+        .addAllTasks(
+            metrics.getTasks().stream().map(ProtobufUtil::toServerTask)
+                .collect(Collectors.toList()))
         .setReportStartTime(metrics.getLastReportTimestamp())
         .setReportEndTime(metrics.getReportTimestamp());
     if (metrics.getReplicationLoadSink() != null) {
       builder.setReplLoadSink(ProtobufUtil.toReplicationLoadSink(metrics.getReplicationLoadSink()));
     }
-
     return builder.build();
   }
 
@@ -139,6 +146,8 @@ public final class ServerMetricsBuilder {
   private final Set<String> coprocessorNames = new TreeSet<>();
   private long reportTimestamp = EnvironmentEdgeManager.currentTime();
   private long lastReportTimestamp = 0;
+  private final List<ServerTask> tasks = new ArrayList<>();
+
   private ServerMetricsBuilder(ServerName serverName) {
     this.serverName = serverName;
   }
@@ -213,6 +222,11 @@ public final class ServerMetricsBuilder {
     return this;
   }
 
+  public ServerMetricsBuilder setTasks(List<ServerTask> tasks) {
+    this.tasks.addAll(tasks);
+    return this;
+  }
+
   public ServerMetrics build() {
     return new ServerMetricsImpl(
         serverName,
@@ -229,7 +243,8 @@ public final class ServerMetricsBuilder {
         coprocessorNames,
         reportTimestamp,
         lastReportTimestamp,
-        userMetrics);
+        userMetrics,
+        tasks);
   }
 
   private static class ServerMetricsImpl implements ServerMetrics {
@@ -249,12 +264,14 @@ public final class ServerMetricsBuilder {
     private final long reportTimestamp;
     private final long lastReportTimestamp;
     private final Map<byte[], UserMetrics> userMetrics;
+    private final List<ServerTask> tasks;
 
     ServerMetricsImpl(ServerName serverName, int versionNumber, String version,
         long requestCountPerSecond, long requestCount, Size usedHeapSize, Size maxHeapSize,
         int infoServerPort, List<ReplicationLoadSource> sources, ReplicationLoadSink sink,
-        Map<byte[], RegionMetrics> regionStatus, Set<String> coprocessorNames, long reportTimestamp,
-        long lastReportTimestamp, Map<byte[], UserMetrics> userMetrics) {
+        Map<byte[], RegionMetrics> regionStatus, Set<String> coprocessorNames,
+        long reportTimestamp, long lastReportTimestamp, Map<byte[], UserMetrics> userMetrics,
+        List<ServerTask> tasks) {
       this.serverName = Preconditions.checkNotNull(serverName);
       this.versionNumber = versionNumber;
       this.version = version;
@@ -270,6 +287,7 @@ public final class ServerMetricsBuilder {
       this.coprocessorNames =Preconditions.checkNotNull(coprocessorNames);
       this.reportTimestamp = reportTimestamp;
       this.lastReportTimestamp = lastReportTimestamp;
+      this.tasks = tasks;
     }
 
     @Override
@@ -357,6 +375,11 @@ public final class ServerMetricsBuilder {
     }
 
     @Override
+    public List<ServerTask> getTasks() {
+      return tasks;
+    }
+
+    @Override
     public String toString() {
       int storeCount = 0;
       int storeFileCount = 0;
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerTask.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerTask.java
new file mode 100644
index 0000000..e791093
--- /dev/null
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerTask.java
@@ -0,0 +1,64 @@
+/*
+ * 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
+ *
+ * http://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.hadoop.hbase;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/** Information about active monitored server tasks */
+@InterfaceAudience.Public
+public interface ServerTask {
+
+  /** Task state */
+  enum State {
+    RUNNING,
+    WAITING,
+    COMPLETE,
+    ABORTED;
+  }
+
+  /**
+   * Get the task's description.
+   * @return the task's description, typically a name
+   */
+  String getDescription();
+
+  /**
+   * Get the current status of the task.
+   * @return the task's current status
+   */
+  String getStatus();
+
+  /**
+   * Get the current state of the task.
+   * @return the task's current state
+   */
+  State getState();
+
+  /**
+   * Get the task start time.
+   * @return the time when the task started, or 0 if it has not started yet
+   */
+  long getStartTime();
+
+  /**
+   * Get the task completion time.
+   * @return the time when the task completed, or 0 if it has not completed yet
+   */
+  long getCompletionTime();
+
+}
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerTaskBuilder.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerTaskBuilder.java
new file mode 100644
index 0000000..d493737
--- /dev/null
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerTaskBuilder.java
@@ -0,0 +1,126 @@
+/*
+ * 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
+ *
+ * http://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.hadoop.hbase;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+/** Builder for information about active monitored server tasks */
+@InterfaceAudience.Private
+public final class ServerTaskBuilder {
+
+  public static ServerTaskBuilder newBuilder() {
+    return new ServerTaskBuilder();
+  }
+
+  private String description = "";
+  private String status = "";
+  private ServerTask.State state = ServerTask.State.RUNNING;
+  private long startTime;
+  private long completionTime;
+
+  private ServerTaskBuilder() { }
+
+  private static final class ServerTaskImpl implements ServerTask {
+
+    private final String description;
+    private final String status;
+    private final ServerTask.State state;
+    private final long startTime;
+    private final long completionTime;
+
+    private ServerTaskImpl(final String description, final String status,
+        final ServerTask.State state, final long startTime, final long completionTime) {
+      this.description = description;
+      this.status = status;
+      this.state = state;
+      this.startTime = startTime;
+      this.completionTime = completionTime;
+    }
+
+    @Override
+    public String getDescription() {
+      return description;
+    }
+
+    @Override
+    public String getStatus() {
+      return status;
+    }
+
+    @Override
+    public State getState() {
+      return state;
+    }
+
+    @Override
+    public long getStartTime() {
+      return startTime;
+    }
+
+    @Override
+    public long getCompletionTime() {
+      return completionTime;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder(512);
+      sb.append(getDescription());
+      sb.append(": status=");
+      sb.append(getStatus());
+      sb.append(", state=");
+      sb.append(getState());
+      sb.append(", startTime=");
+      sb.append(getStartTime());
+      sb.append(", completionTime=");
+      sb.append(getCompletionTime());
+      return sb.toString();
+    }
+
+  }
+
+  public ServerTaskBuilder setDescription(final String description) {
+    this.description = description;
+    return this;
+  }
+
+  public ServerTaskBuilder setStatus(final String status) {
+    this.status = status;
+    return this;
+  }
+
+  public ServerTaskBuilder setState(final ServerTask.State state) {
+    this.state = state;
+    return this;
+  }
+
+  public ServerTaskBuilder setStartTime(final long startTime) {
+    this.startTime = startTime;
+    return this;
+  }
+
+  public ServerTaskBuilder setCompletionTime(final long completionTime) {
+    this.completionTime = completionTime;
+    return this;
+  }
+
+  public ServerTask build() {
+    return new ServerTaskImpl(description, status, state, startTime, completionTime);
+  }
+
+}
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java
index 36b106a..523b224 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java
@@ -66,6 +66,8 @@ import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.ServerTask;
+import org.apache.hadoop.hbase.ServerTaskBuilder;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.Append;
 import org.apache.hadoop.hbase.client.BalanceResponse;
@@ -3792,4 +3794,24 @@ public final class ProtobufUtil {
       .build();
   }
 
+  public static ServerTask getServerTask(ClusterStatusProtos.ServerTask task) {
+    return ServerTaskBuilder.newBuilder()
+      .setDescription(task.getDescription())
+      .setStatus(task.getStatus())
+      .setState(ServerTask.State.valueOf(task.getState().name()))
+      .setStartTime(task.getStartTime())
+      .setCompletionTime(task.getCompletionTime())
+      .build();
+  }
+
+  public static ClusterStatusProtos.ServerTask toServerTask(ServerTask task) {
+    return ClusterStatusProtos.ServerTask.newBuilder()
+      .setDescription(task.getDescription())
+      .setStatus(task.getStatus())
+      .setState(ClusterStatusProtos.ServerTask.State.valueOf(task.getState().name()))
+      .setStartTime(task.getStartTime())
+      .setCompletionTime(task.getCompletionTime())
+      .build();
+  }
+
 }
diff --git a/hbase-protocol-shaded/src/main/protobuf/ClusterStatus.proto b/hbase-protocol-shaded/src/main/protobuf/ClusterStatus.proto
index acff682..4957d7c 100644
--- a/hbase-protocol-shaded/src/main/protobuf/ClusterStatus.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/ClusterStatus.proto
@@ -229,6 +229,21 @@ message ReplicationLoadSource {
   optional uint64 oPsShipped = 12;
 }
 
+message ServerTask {
+  required string description = 1;
+  required string status = 2;
+  required State state = 3;
+  optional uint64 startTime = 4;
+  optional uint64 completionTime = 5;
+
+  enum State {
+    RUNNING = 0;
+    WAITING = 1;
+    COMPLETE = 2;
+    ABORTED = 3;
+  }
+}
+
 message ServerLoad {
   /** Number of requests since last report. */
   optional uint64 number_of_requests = 1;
@@ -285,6 +300,11 @@ message ServerLoad {
    * The metrics for each user on this region server
    */
   repeated UserLoad userLoads = 12;
+
+  /**
+   * The active monitored tasks
+   */
+  repeated ServerTask tasks = 15; /* 15 here to stay in sync with master branch */
 }
 
 message LiveServerInfo {
@@ -318,6 +338,7 @@ message ClusterStatus {
   optional int32 master_info_port = 10 [default = -1];
   repeated ServerName servers_name = 11;
   repeated TableRegionStatesCount table_region_states_count = 12;
+  repeated ServerTask master_tasks = 13;
 }
 
 enum Option {
@@ -333,4 +354,5 @@ enum Option {
   MASTER_INFO_PORT = 9;
   SERVERS_NAME = 10;
   TABLE_TO_REGIONS_COUNT = 11;
+  TASKS = 12;
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
index 7820c2a..a91fe01 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
@@ -79,6 +79,8 @@ import org.apache.hadoop.hbase.RegionMetrics;
 import org.apache.hadoop.hbase.ReplicationPeerNotFoundException;
 import org.apache.hadoop.hbase.ServerMetrics;
 import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.ServerTask;
+import org.apache.hadoop.hbase.ServerTaskBuilder;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.TableNotDisabledException;
 import org.apache.hadoop.hbase.TableNotFoundException;
@@ -2712,16 +2714,39 @@ public class HMaster extends HRegionServer implements MasterServices {
       options = EnumSet.allOf(Option.class);
     }
 
+    // TASKS and/or LIVE_SERVERS will populate this map, which will be given to the builder if
+    // not null after option processing completes.
+    Map<ServerName, ServerMetrics> serverMetricsMap = null;
+
     for (Option opt : options) {
       switch (opt) {
         case HBASE_VERSION: builder.setHBaseVersion(VersionInfo.getVersion()); break;
         case CLUSTER_ID: builder.setClusterId(getClusterId()); break;
         case MASTER: builder.setMasterName(getServerName()); break;
         case BACKUP_MASTERS: builder.setBackerMasterNames(getBackupMasters()); break;
+        case TASKS: {
+          // Master tasks
+          builder.setMasterTasks(TaskMonitor.get().getTasks().stream()
+            .map(task -> ServerTaskBuilder.newBuilder()
+              .setDescription(task.getDescription())
+              .setStatus(task.getStatus())
+              .setState(ServerTask.State.valueOf(task.getState().name()))
+              .setStartTime(task.getStartTime())
+              .setCompletionTime(task.getCompletionTimestamp())
+              .build())
+            .collect(Collectors.toList()));
+          // TASKS is also synonymous with LIVE_SERVERS for now because task information for
+          // regionservers is carried in ServerLoad.
+          // Add entries to serverMetricsMap for all live servers, if we haven't already done so
+          if (serverMetricsMap == null) {
+            serverMetricsMap = getOnlineServers();
+          }
+          break;
+        }
         case LIVE_SERVERS: {
-          if (serverManager != null) {
-            builder.setLiveServerMetrics(serverManager.getOnlineServers().entrySet().stream()
-              .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())));
+          // Add entries to serverMetricsMap for all live servers, if we haven't already done so
+          if (serverMetricsMap == null) {
+            serverMetricsMap = getOnlineServers();
           }
           break;
         }
@@ -2783,9 +2808,24 @@ public class HMaster extends HRegionServer implements MasterServices {
         }
       }
     }
+
+    if (serverMetricsMap != null) {
+      builder.setLiveServerMetrics(serverMetricsMap);
+    }
+
     return builder.build();
   }
 
+  private Map<ServerName, ServerMetrics> getOnlineServers() {
+    if (serverManager != null) {
+      final Map<ServerName, ServerMetrics> map = new HashMap<>();
+      serverManager.getOnlineServers().entrySet()
+        .forEach(e -> map.put(e.getKey(), e.getValue()));
+      return map;
+    }
+    return null;
+  }
+
   /**
    * @return cluster status
    */
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
index b7374f9..4293670 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
@@ -127,6 +127,7 @@ import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.LoadBalancer;
 import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer;
 import org.apache.hadoop.hbase.mob.MobFileCache;
+import org.apache.hadoop.hbase.monitoring.TaskMonitor;
 import org.apache.hadoop.hbase.namequeues.NamedQueueRecorder;
 import org.apache.hadoop.hbase.namequeues.SlowLogTableOpsChore;
 import org.apache.hadoop.hbase.net.Address;
@@ -1461,10 +1462,10 @@ public class HRegionServer extends Thread implements
         serverLoad.addUserLoads(createUserLoad(entry.getKey(), entry.getValue()));
       }
     }
+
     // for the replicationLoad purpose. Only need to get from one executorService
     // either source or sink will get the same info
     ReplicationSourceService rsources = getReplicationSourceService();
-
     if (rsources != null) {
       // always refresh first to get the latest value
       ReplicationLoad rLoad = rsources.refreshAndGetReplicationLoad();
@@ -1478,6 +1479,15 @@ public class HRegionServer extends Thread implements
       }
     }
 
+    TaskMonitor.get().getTasks().forEach(task ->
+      serverLoad.addTasks(ClusterStatusProtos.ServerTask.newBuilder()
+        .setDescription(task.getDescription())
+        .setStatus(task.getStatus() != null ? task.getStatus() : "")
+        .setState(ClusterStatusProtos.ServerTask.State.valueOf(task.getState().name()))
+        .setStartTime(task.getStartTime())
+        .setCompletionTime(task.getCompletionTimestamp())
+        .build()));
+
     return serverLoad.build();
   }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/TestClientClusterMetrics.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestClientClusterMetrics.java
index c62424e..a8e5280 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/TestClientClusterMetrics.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestClientClusterMetrics.java
@@ -48,12 +48,14 @@ import org.apache.hadoop.hbase.coprocessor.MasterObserver;
 import org.apache.hadoop.hbase.coprocessor.ObserverContext;
 import org.apache.hadoop.hbase.filter.FilterAllFilter;
 import org.apache.hadoop.hbase.master.HMaster;
+import org.apache.hadoop.hbase.monitoring.TaskMonitor;
 import org.apache.hadoop.hbase.regionserver.HRegionServer;
 import org.apache.hadoop.hbase.regionserver.MetricsUserAggregateFactory;
 import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.security.UserProvider;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
 import org.apache.hadoop.hbase.util.JVMClusterUtil.MasterThread;
 import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
 import org.junit.AfterClass;
@@ -79,6 +81,17 @@ public class TestClientClusterMetrics {
   private static final TableName TABLE_NAME = TableName.valueOf("test");
   private static final byte[] CF = Bytes.toBytes("cf");
 
+  // We need to promote the visibility of tryRegionServerReport for this test
+  public static class MyRegionServer extends MiniHBaseCluster.MiniHBaseClusterRegionServer {
+    public MyRegionServer(Configuration conf) throws IOException, InterruptedException {
+      super(conf);
+    }
+    @Override
+    public void tryRegionServerReport(long reportStartTime, long reportEndTime)
+        throws IOException {
+      super.tryRegionServerReport(reportStartTime, reportEndTime);
+    }
+  }
 
   @BeforeClass
   public static void setUpBeforeClass() throws Exception {
@@ -86,6 +99,7 @@ public class TestClientClusterMetrics {
     conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, MyObserver.class.getName());
     UTIL = new HBaseTestingUtility(conf);
     StartMiniClusterOption option = StartMiniClusterOption.builder()
+        .rsClass(TestClientClusterMetrics.MyRegionServer.class)
         .numMasters(MASTERS).numRegionServers(SLAVES).numDataNodes(SLAVES).build();
     UTIL.startMiniCluster(option);
     CLUSTER = UTIL.getHBaseCluster();
@@ -250,7 +264,8 @@ public class TestClientClusterMetrics {
     Assert.assertEquals(MASTERS - 1, metrics.getBackupMasterNames().size());
   }
 
-  @Test public void testUserMetrics() throws Exception {
+  @Test
+  public void testUserMetrics() throws Exception {
     Configuration conf = UTIL.getConfiguration();
     // If metrics for users is not enabled, this test doesn't  make sense.
     if (!conf.getBoolean(MetricsUserAggregateFactory.METRIC_USER_ENABLED_CONF,
@@ -338,6 +353,48 @@ public class TestClientClusterMetrics {
     UTIL.deleteTable(TABLE_NAME);
   }
 
+  @Test
+  public void testServerTasks() throws Exception {
+    // TaskMonitor is a singleton per VM, so will be shared among all minicluster "servers",
+    // so we only need to look at the first live server's results to find it.
+    final String testTaskName = "TEST TASK";
+    TaskMonitor.get().createStatus(testTaskName).setStatus("Testing 1... 2... 3...");
+    // Of course, first we must trigger regionserver reports.
+    final long now = EnvironmentEdgeManager.currentTime();
+    final long last = now - 1000; // fake a period, or someone might div by zero
+    for (RegionServerThread rs: CLUSTER.getRegionServerThreads()) {
+      ((MyRegionServer)rs.getRegionServer()).tryRegionServerReport(last, now);
+    }
+    // Get status now
+    ClusterMetrics clusterMetrics = ADMIN.getClusterMetrics(EnumSet.of(Option.TASKS));
+    // The test task will be in the master metrics list
+    boolean found = false;
+    for (ServerTask task: clusterMetrics.getMasterTasks()) {
+      if (testTaskName.equals(task.getDescription())) {
+        // Found it
+        found = true;
+        break;
+      }
+    }
+    Assert.assertTrue("Expected task not found in master task list", found);
+    // Get the tasks information (carried in server metrics)
+    found = false;
+    for (ServerMetrics serverMetrics: clusterMetrics.getLiveServerMetrics().values()) {
+      if (serverMetrics.getTasks() != null) {
+        for (ServerTask task: serverMetrics.getTasks()) {
+          if (testTaskName.equals(task.getDescription())) {
+            // Found it
+            found = true;
+            break;
+          }
+        }
+      }
+    }
+    // We will fall through here if getClusterMetrics(TASKS) did not correctly process the
+    // task list.
+    Assert.assertTrue("Expected task not found in server load", found);
+  }
+
   private RegionMetrics getMetaMetrics() throws IOException {
     for (ServerMetrics serverMetrics : ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
         .getLiveServerMetrics().values()) {
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRegionsRecoveryChore.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRegionsRecoveryChore.java
index f5306a4..d97bec9 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRegionsRecoveryChore.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRegionsRecoveryChore.java
@@ -33,6 +33,7 @@ import org.apache.hadoop.hbase.HBaseTestingUtility;
 import org.apache.hadoop.hbase.RegionMetrics;
 import org.apache.hadoop.hbase.ServerMetrics;
 import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.ServerTask;
 import org.apache.hadoop.hbase.Size;
 import org.apache.hadoop.hbase.Stoppable;
 import org.apache.hadoop.hbase.TableName;
@@ -293,6 +294,11 @@ public class TestRegionsRecoveryChore {
         return null;
       }
 
+      @Override
+      public List<ServerTask> getMasterTasks() {
+        return null;
+      }
+
     };
     return clusterMetrics;
   }
@@ -377,6 +383,11 @@ public class TestRegionsRecoveryChore {
         return 0;
       }
 
+      @Override
+      public List<ServerTask> getTasks() {
+        return null;
+      }
+
     };
     return serverMetrics;
   }
diff --git a/hbase-shell/src/main/ruby/hbase/admin.rb b/hbase-shell/src/main/ruby/hbase/admin.rb
index 412793e..25f0799 100644
--- a/hbase-shell/src/main/ruby/hbase/admin.rb
+++ b/hbase-shell/src/main/ruby/hbase/admin.rb
@@ -850,12 +850,16 @@ module Hbase
           puts(format('    %s', v))
         end
         master = status.getMaster
-        puts(format('active master:  %s:%d %d', master.getHostname, master.getPort, master.getStartcode))
+        unless master.nil?
+          puts(format('active master:  %s:%d %d', master.getHostname, master.getPort, master.getStartcode))
+          for task in status.getMasterTasks
+            puts(format('    %s', task.toString))
+          end
+        end
         puts(format('%d backup masters', status.getBackupMastersSize))
         for server in status.getBackupMasters
           puts(format('    %s:%d %d', server.getHostname, server.getPort, server.getStartcode))
         end
-
         master_coprocs = java.util.Arrays.toString(@admin.getMasterCoprocessors)
         unless master_coprocs.nil?
           puts(format('master coprocessors: %s', master_coprocs))
@@ -868,6 +872,9 @@ module Hbase
             puts(format('        %s', region.getNameAsString.dump))
             puts(format('            %s', region.toString))
           end
+          for task in status.getLoad(server).getTasks
+            puts(format('        %s', task.toString))
+          end
         end
         puts(format('%d dead servers', status.getDeadServersSize))
         for server in status.getDeadServerNames
@@ -906,6 +913,33 @@ module Hbase
             puts(format('%<sink>s', sink: r_sink_string))
           end
         end
+      elsif format == 'tasks'
+        master = status.getMaster
+        unless master.nil?
+          puts(format('active master:  %s:%d %d', master.getHostname, master.getPort, master.getStartcode))
+          printed = false
+          for task in status.getMasterTasks
+            next unless task.getState.name == 'RUNNING'
+            puts(format('    %s', task.toString))
+            printed = true
+          end
+          if !printed
+            puts('    no active tasks')
+          end
+        end
+        puts(format('%d live servers', status.getServersSize))
+        for server in status.getServers
+          puts(format('    %s:%d %d', server.getHostname, server.getPort, server.getStartcode))
+          printed = false
+          for task in status.getLoad(server).getTasks
+            next unless task.getState.name == 'RUNNING'
+            puts(format('        %s', task.toString))
+            printed = true
+          end
+          if !printed
+            puts('        no active tasks')
+          end
+        end
       elsif format == 'simple'
         load = 0
         regions = 0
diff --git a/hbase-shell/src/main/ruby/shell/commands/status.rb b/hbase-shell/src/main/ruby/shell/commands/status.rb
index af71f6c..eac49a9 100644
--- a/hbase-shell/src/main/ruby/shell/commands/status.rb
+++ b/hbase-shell/src/main/ruby/shell/commands/status.rb
@@ -22,13 +22,14 @@ module Shell
     class Status < Command
       def help
         <<-EOF
-Show cluster status. Can be 'summary', 'simple', 'detailed', or 'replication'. The
+Show cluster status. Can be 'summary', 'simple', 'detailed', 'tasks', or 'replication'. The
 default is 'summary'. Examples:
 
   hbase> status
-  hbase> status 'simple'
   hbase> status 'summary'
+  hbase> status 'simple'
   hbase> status 'detailed'
+  hbase> status 'tasks'
   hbase> status 'replication'
   hbase> status 'replication', 'source'
   hbase> status 'replication', 'sink'