HDDS-7045. Election info is out of date in Recon (#3677)

diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/recon/ReconConfigKeys.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/recon/ReconConfigKeys.java
index 90ac003..e5122f8 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/recon/ReconConfigKeys.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/recon/ReconConfigKeys.java
@@ -52,7 +52,7 @@
       "0.0.0.0";
   public static final int OZONE_RECON_DATANODE_PORT_DEFAULT = 9891;
   // Prometheus HTTP endpoint including port
-  // ex: http://localhost:9090
+  // ex: http://prometheus:9090
   public static final String OZONE_RECON_PROMETHEUS_HTTP_ENDPOINT =
       "ozone.recon.prometheus.http.endpoint";
   /**
diff --git a/hadoop-hdds/docs/content/concept/Recon.md b/hadoop-hdds/docs/content/concept/Recon.md
index 064127a..b5d4731 100644
--- a/hadoop-hdds/docs/content/concept/Recon.md
+++ b/hadoop-hdds/docs/content/concept/Recon.md
@@ -109,7 +109,7 @@
 and can display useful information in Recon UI in Datanodes and Pipelines pages.
 Recon also exposes a proxy endpoint ([/metrics]({{< ref "interface/ReconApi.md#metrics" >}}))
 to query Prometheus. This integration can be enabled by setting this configuration `ozone.recon.prometheus.http.endpoint` 
-to the Prometheus endpoint like `ozone.recon.prometheus.http.endpoint=localhost:9090`.
+to the Prometheus endpoint like `ozone.recon.prometheus.http.endpoint=http://prometheus:9090`.
 
 ## API Reference
 
diff --git a/hadoop-hdds/docs/content/concept/Recon.zh.md b/hadoop-hdds/docs/content/concept/Recon.zh.md
index 5c67351..3880cfd 100644
--- a/hadoop-hdds/docs/content/concept/Recon.zh.md
+++ b/hadoop-hdds/docs/content/concept/Recon.zh.md
@@ -70,7 +70,7 @@
 
 ## Recon 和 Prometheus
 
-Recon 可以与任何配置为收集指标的 Prometheus 实例集成,并且可以在数据节点和 Pipelines 页面的 Recon UI 中显示有用的信息。Recon 还公开了一个代理端点 ([/指标]({{< ref path="interface/ReconApi.zh.md#metrics" >}})) 来查询 Prometheus。可以通过将此配置`ozone.recon.prometheus.http.endpoint`设置为 Prometheus 端点如`ozone.recon.prometheus.http.endpoint=localhost:9090`来启用此集成。
+Recon 可以与任何配置为收集指标的 Prometheus 实例集成,并且可以在数据节点和 Pipelines 页面的 Recon UI 中显示有用的信息。Recon 还公开了一个代理端点 ([/指标]({{< ref path="interface/ReconApi.zh.md#metrics" >}})) 来查询 Prometheus。可以通过将此配置`ozone.recon.prometheus.http.endpoint`设置为 Prometheus 端点如`ozone.recon.prometheus.http.endpoint=http://prometheus:9090`来启用此集成。
 
 ## API 参考
 
diff --git a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/PipelineEndpoint.java b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/PipelineEndpoint.java
index 0595bbf..c5cc6d9 100644
--- a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/PipelineEndpoint.java
+++ b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/api/PipelineEndpoint.java
@@ -39,7 +39,6 @@
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 import java.util.TreeMap;
 import java.util.UUID;
 
@@ -114,12 +113,12 @@
         // is group-79DF51EE872D
         String[] splits = pipelineId.toString().split("-");
         String groupId = "group-" + splits[splits.length - 1].toUpperCase();
-        Optional<Long> leaderElectionCount = getMetricValue(
-            "ratis_leader_election_electionCount", groupId);
-        leaderElectionCount.ifPresent(pipelineBuilder::setLeaderElections);
-        Optional<Long> leaderElectionTime = getMetricValue(
-            "ratis_leader_election_lastLeaderElectionTime", groupId);
-        leaderElectionTime.ifPresent(pipelineBuilder::setLastLeaderElection);
+        Long leaderElectionCount = getElectionCountMetricValue(groupId);
+        pipelineBuilder.setLeaderElections(leaderElectionCount);
+        Long lastLeaderElectionElapsedTime
+                = getLastLeaderElectionElapsedTimeMetricValue(groupId,
+                pipeline.getLeaderId());
+        pipelineBuilder.setLastLeaderElection(lastLeaderElectionElapsedTime);
       }
 
       pipelinesList.add(pipelineBuilder.build());
@@ -130,25 +129,53 @@
     return Response.ok(pipelinesResponse).build();
   }
 
-  private Optional<Long> getMetricValue(String metricName, String groupId) {
+  private Long getElectionCountMetricValue(String groupId) {
+    Long electionCount = 0L;
     String metricsQuery = String.format(
-        "query=%s{group=\"%s\"}", metricName, groupId);
+            "query=ratis_leader_election_electionCount{group=\"%s\"}", groupId);
     try {
       List<Metric> metrics = metricsServiceProvider.getMetricsInstant(
-          metricsQuery);
+              metricsQuery);
       if (!metrics.isEmpty()) {
-        TreeMap<Double, Double> values = (TreeMap<Double, Double>)
-            metrics.get(0).getValues();
-        if (!values.isEmpty()) {
-          return Optional.of(values.firstEntry().getValue().longValue());
+        for (Metric m : metrics) {
+          TreeMap<Double, Double> values = (TreeMap<Double, Double>)
+                  m.getValues();
+          if (!values.isEmpty()) {
+            electionCount += values.firstEntry().getValue().longValue();
+          }
         }
       }
     } catch (Exception ex) {
       if (LOG.isErrorEnabled()) {
-        LOG.error(String.format("Unable to get metrics value for %s",
-            metricName), ex);
+        LOG.error("Unable to get metrics value for " +
+                "ratis_leader_election_electionCount", ex);
       }
     }
-    return Optional.empty();
+    return electionCount;
+  }
+
+  private Long getLastLeaderElectionElapsedTimeMetricValue(String groupId,
+                                                           UUID uuid) {
+    String metricsQuery = String.format(
+            "query=ratis_leader_election_lastLeaderElectionElapsedTime{group=" +
+                    "\"%s\",exported_instance=\"%s\"}", groupId,
+            uuid.toString());
+    try {
+      List<Metric> metrics = metricsServiceProvider.getMetricsInstant(
+              metricsQuery);
+      if (!metrics.isEmpty()) {
+        TreeMap<Double, Double> values = (TreeMap<Double, Double>)
+                metrics.get(0).getValues();
+        if (!values.isEmpty()) {
+          return values.firstEntry().getValue().longValue();
+        }
+      }
+    } catch (Exception ex) {
+      if (LOG.isErrorEnabled()) {
+        LOG.error("Unable to get metrics value for " +
+                "ratis_leader_election_lastLeaderElectionElapsedTime", ex);
+      }
+    }
+    return 0L;
   }
 }
diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx
index 6810cba..a71559d 100644
--- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx
+++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx
@@ -18,7 +18,7 @@
 
 import React from 'react';
 import axios from 'axios';
-import {Table, Tabs} from 'antd';
+import {Table, Tabs, Tooltip, Icon} from 'antd';
 import './pipelines.less';
 import {PaginationConfig} from 'antd/lib/pagination';
 import prettyMilliseconds from 'pretty-ms';
@@ -120,11 +120,17 @@
     sorter: (a: IPipelineResponse, b: IPipelineResponse) => a.leaderNode.localeCompare(b.leaderNode)
   },
   {
-    title: 'Last Leader Election',
+    title:
+  <span>
+    Last Leader Election&nbsp;
+    <Tooltip title='Elapsed time since the current leader got elected. Only available if any metrics service providers like Prometheus is configured.'>
+      <Icon type='info-circle'/>
+    </Tooltip>
+  </span>,
     dataIndex: 'lastLeaderElection',
     key: 'lastLeaderElection',
     render: (lastLeaderElection: number) => lastLeaderElection > 0 ?
-      moment(lastLeaderElection).format('ll LTS') : 'NA',
+      prettyMilliseconds(lastLeaderElection, {compact: true}) + " ago" : 'NA',
     sorter: (a: IPipelineResponse, b: IPipelineResponse) => a.lastLeaderElection - b.lastLeaderElection
   },
   {
@@ -135,10 +141,18 @@
     sorter: (a: IPipelineResponse, b: IPipelineResponse) => a.duration - b.duration
   },
   {
-    title: 'No. of Elections',
+    title:
+  <span>
+    No. of Elections&nbsp;
+    <Tooltip title='Number of elections in this pipeline. Only available if any metrics service providers like Prometheus is configured.'>
+      <Icon type='info-circle'/>
+    </Tooltip>
+  </span>,
     dataIndex: 'leaderElections',
     key: 'leaderElections',
     isSearchable: true,
+    render: (leaderElections: number) => leaderElections > 0 ?
+          leaderElections : 'NA',
     sorter: (a: IPipelineResponse, b: IPipelineResponse) => a.leaderElections - b.leaderElections
   }
 ];