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

roryqi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-uniffle.git


The following commit(s) were added to refs/heads/master by this push:
     new 2c670d5ae [#1924] feat(dashboard): Show Thread Dump, Conf and Metrics 
in DashBoard (#1927)
2c670d5ae is described below

commit 2c670d5ae615648d5dc7539cf27c00461f6b78dc
Author: kqhzz <kuangq...@gmail.com>
AuthorDate: Mon Jul 22 10:08:10 2024 +0800

    [#1924] feat(dashboard): Show Thread Dump, Conf and Metrics in DashBoard 
(#1927)
    
    ### What changes were proposed in this pull request?
    
    Add jetty_port in message ShuffleServerId.
    Modify dashboard, add some link in dashboard
    <img width="1086" alt="企业微信截图_ede78628-56ac-4b7e-86ab-84b224da7ce4" 
src="https://github.com/user-attachments/assets/a263871f-0a9a-4b1d-9ba6-0341708e0a2d";>
    <img width="1775" alt="企业微信截图_88b2c855-68e4-4054-94a3-d730f074a4b9" 
src="https://github.com/user-attachments/assets/480352d6-bf29-48bc-af73-a41fa1fd8248";>
    
    ### Why are the changes needed?
    
    Enhance dashboard capabilities
    
    Fix: #1924
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
---
 .../apache/uniffle/common/util/ThreadUtils.java    | 18 +++++
 .../org/apache/uniffle/common/web/JettyServer.java |  4 ++
 .../coordinator/CoordinatorGrpcService.java        |  3 +-
 .../org/apache/uniffle/coordinator/ServerNode.java | 40 +++++++++++
 .../web/resource/CoordinatorServerResource.java    | 21 ++++++
 .../dashboard/web/proxy/WebProxyServlet.java       | 14 ++--
 dashboard/src/main/webapp/src/api/api.js           | 78 ++++++++++++++++++++
 .../webapp/src/pages/CoordinatorServerPage.vue     | 35 ++++++++-
 .../webapp/src/pages/serverstatus/NodeListPage.vue | 54 +++++++++++++-
 dashboard/src/main/webapp/src/utils/http.js        |  7 +-
 .../client/impl/grpc/CoordinatorGrpcClient.java    |  7 +-
 .../client/request/RssSendHeartBeatRequest.java    |  9 ++-
 proto/src/main/proto/Rss.proto                     |  1 +
 .../apache/uniffle/server/RegisterHeartBeat.java   |  9 ++-
 .../org/apache/uniffle/server/ShuffleServer.java   | 11 ++-
 .../server/web/resource/ServerResource.java        | 82 ++++++++++++++++++++++
 .../apache/uniffle/server/web/vo/ServerConfVO.java | 44 ++++++++++++
 17 files changed, 415 insertions(+), 22 deletions(-)

diff --git 
a/common/src/main/java/org/apache/uniffle/common/util/ThreadUtils.java 
b/common/src/main/java/org/apache/uniffle/common/util/ThreadUtils.java
index eb2003995..d84e6dc23 100644
--- a/common/src/main/java/org/apache/uniffle/common/util/ThreadUtils.java
+++ b/common/src/main/java/org/apache/uniffle/common/util/ThreadUtils.java
@@ -17,6 +17,9 @@
 
 package org.apache.uniffle.common.util;
 
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -39,6 +42,7 @@ import org.slf4j.LoggerFactory;
 
 public class ThreadUtils {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(ThreadUtils.class);
+  private static final ThreadMXBean THREAD_BEAN = 
ManagementFactory.getThreadMXBean();
 
   /** Provide a general method to create a thread factory to make the code 
more standardized */
   public static ThreadFactory getThreadFactory(String factoryName) {
@@ -183,4 +187,18 @@ public class ThreadUtils {
       String taskMsg) {
     return executeTasks(executorService, items, task, timeoutMs, taskMsg, 
future -> null);
   }
+
+  public static synchronized void printThreadInfo(StringBuilder builder, 
String title) {
+    builder.append("Process Thread Dump: " + title + "\n");
+    builder.append(THREAD_BEAN.getThreadCount() + " active threads\n");
+    long[] threadIds = THREAD_BEAN.getAllThreadIds();
+    for (long id : threadIds) {
+      ThreadInfo info = THREAD_BEAN.getThreadInfo(id, Integer.MAX_VALUE);
+      if (info == null) {
+        // The thread is no longer active, ignore
+        continue;
+      }
+      builder.append(info + "\n");
+    }
+  }
 }
diff --git 
a/common/src/main/java/org/apache/uniffle/common/web/JettyServer.java 
b/common/src/main/java/org/apache/uniffle/common/web/JettyServer.java
index 87e52cbc6..0ace09414 100644
--- a/common/src/main/java/org/apache/uniffle/common/web/JettyServer.java
+++ b/common/src/main/java/org/apache/uniffle/common/web/JettyServer.java
@@ -175,4 +175,8 @@ public class JettyServer {
   public void stop() throws Exception {
     server.stop();
   }
+
+  public int getHttpPort() {
+    return httpPort;
+  }
 }
diff --git 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/CoordinatorGrpcService.java
 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/CoordinatorGrpcService.java
index ddca8a1e3..b3df63db5 100644
--- 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/CoordinatorGrpcService.java
+++ 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/CoordinatorGrpcService.java
@@ -433,6 +433,7 @@ public class CoordinatorGrpcService extends 
CoordinatorServerGrpc.CoordinatorSer
         Sets.newHashSet(request.getTagsList()),
         serverStatus,
         StorageInfoUtils.fromProto(request.getStorageInfoMap()),
-        request.getServerId().getNettyPort());
+        request.getServerId().getNettyPort(),
+        request.getServerId().getJettyPort());
   }
 }
diff --git 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/ServerNode.java 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/ServerNode.java
index ec33d262a..a9723e00b 100644
--- a/coordinator/src/main/java/org/apache/uniffle/coordinator/ServerNode.java
+++ b/coordinator/src/main/java/org/apache/uniffle/coordinator/ServerNode.java
@@ -42,6 +42,7 @@ public class ServerNode implements Comparable<ServerNode> {
   private ServerStatus status;
   private Map<String, StorageInfo> storageInfo;
   private int nettyPort = -1;
+  private int jettyPort = -1;
 
   public ServerNode(String id) {
     this(id, "", 0, 0, 0, 0, 0, Sets.newHashSet(), ServerStatus.EXCLUDED);
@@ -115,6 +116,7 @@ public class ServerNode implements Comparable<ServerNode> {
         tags,
         status,
         storageInfoMap,
+        -1,
         -1);
   }
 
@@ -130,6 +132,34 @@ public class ServerNode implements Comparable<ServerNode> {
       ServerStatus status,
       Map<String, StorageInfo> storageInfoMap,
       int nettyPort) {
+    this(
+        id,
+        ip,
+        grpcPort,
+        usedMemory,
+        preAllocatedMemory,
+        availableMemory,
+        eventNumInFlush,
+        tags,
+        status,
+        storageInfoMap,
+        nettyPort,
+        -1);
+  }
+
+  public ServerNode(
+      String id,
+      String ip,
+      int grpcPort,
+      long usedMemory,
+      long preAllocatedMemory,
+      long availableMemory,
+      int eventNumInFlush,
+      Set<String> tags,
+      ServerStatus status,
+      Map<String, StorageInfo> storageInfoMap,
+      int nettyPort,
+      int jettyPort) {
     this.id = id;
     this.ip = ip;
     this.grpcPort = grpcPort;
@@ -145,6 +175,9 @@ public class ServerNode implements Comparable<ServerNode> {
     if (nettyPort > 0) {
       this.nettyPort = nettyPort;
     }
+    if (jettyPort > 0) {
+      this.jettyPort = jettyPort;
+    }
   }
 
   public ShuffleServerId convertToGrpcProto() {
@@ -153,6 +186,7 @@ public class ServerNode implements Comparable<ServerNode> {
         .setIp(ip)
         .setPort(grpcPort)
         .setNettyPort(nettyPort)
+        .setJettyPort(jettyPort)
         .build();
   }
 
@@ -214,6 +248,8 @@ public class ServerNode implements Comparable<ServerNode> {
         + grpcPort
         + "], netty port["
         + nettyPort
+        + "], jettyPort["
+        + jettyPort
         + "], usedMemory["
         + usedMemory
         + "], preAllocatedMemory["
@@ -277,4 +313,8 @@ public class ServerNode implements Comparable<ServerNode> {
   public int getNettyPort() {
     return nettyPort;
   }
+
+  public int getJettyPort() {
+    return jettyPort;
+  }
 }
diff --git 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/CoordinatorServerResource.java
 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/CoordinatorServerResource.java
index e09d3dbdc..788c22461 100644
--- 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/CoordinatorServerResource.java
+++ 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/CoordinatorServerResource.java
@@ -31,7 +31,10 @@ import org.apache.hbase.thirdparty.javax.ws.rs.core.Context;
 import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
 
 import org.apache.uniffle.common.util.RssUtils;
+import org.apache.uniffle.common.util.ThreadUtils;
 import org.apache.uniffle.common.web.resource.BaseResource;
+import org.apache.uniffle.common.web.resource.MetricResource;
+import org.apache.uniffle.common.web.resource.PrometheusMetricResource;
 import org.apache.uniffle.common.web.resource.Response;
 import org.apache.uniffle.coordinator.CoordinatorConf;
 import org.apache.uniffle.coordinator.CoordinatorServer;
@@ -85,4 +88,22 @@ public class CoordinatorServerResource extends BaseResource {
     return (CoordinatorServer)
         
servletContext.getAttribute(CoordinatorServer.class.getCanonicalName());
   }
+
+  @Path("/metrics")
+  public Class<MetricResource> getMetricResource() {
+    return MetricResource.class;
+  }
+
+  @Path("/prometheus/metrics")
+  public Class<PrometheusMetricResource> getPrometheusMetricResource() {
+    return PrometheusMetricResource.class;
+  }
+
+  @GET
+  @Path("/stacks")
+  public String getCoordinatorStacks() {
+    StringBuilder builder = new StringBuilder();
+    ThreadUtils.printThreadInfo(builder, "");
+    return builder.toString();
+  }
 }
diff --git 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/proxy/WebProxyServlet.java
 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/proxy/WebProxyServlet.java
index 64fab2eaf..084478030 100644
--- 
a/dashboard/src/main/java/org/apache/uniffle/dashboard/web/proxy/WebProxyServlet.java
+++ 
b/dashboard/src/main/java/org/apache/uniffle/dashboard/web/proxy/WebProxyServlet.java
@@ -45,11 +45,15 @@ public class WebProxyServlet extends ProxyServlet {
     if (!validateDestination(clientRequest.getServerName(), 
clientRequest.getServerPort())) {
       return null;
     }
-    String targetAddress =
-        
coordinatorServerAddressesMap.get(clientRequest.getHeader("targetAddress"));
-    if (targetAddress == null) {
-      // Get random one from coordinatorServerAddressesMap
-      targetAddress = coordinatorServerAddressesMap.values().iterator().next();
+    String targetAddress;
+    if (clientRequest.getHeader("serverType").equals("coordinator")) {
+      targetAddress = 
coordinatorServerAddressesMap.get(clientRequest.getHeader("targetAddress"));
+      if (targetAddress == null) {
+        // Get random one from coordinatorServerAddressesMap
+        targetAddress = 
coordinatorServerAddressesMap.values().iterator().next();
+      }
+    } else {
+      targetAddress = clientRequest.getHeader("targetAddress");
     }
     StringBuilder target = new StringBuilder();
     if (targetAddress.endsWith("/")) {
diff --git a/dashboard/src/main/webapp/src/api/api.js 
b/dashboard/src/main/webapp/src/api/api.js
index 08277f0c9..b837658ce 100644
--- a/dashboard/src/main/webapp/src/api/api.js
+++ b/dashboard/src/main/webapp/src/api/api.js
@@ -26,6 +26,84 @@ export function getCoordinatorConf(params, headers) {
   return http.get('/coordinator/conf', params, headers, 0)
 }
 
+export async function getShuffleServerConf(address, params, headers) {
+  if (typeof headers === 'undefined') {
+    headers = {};
+  }
+  headers.targetAddress = address;
+  const response = await http.get('/shuffleServer/conf', params, headers, 0);
+  const newWindow = window.open('', '_blank');
+  let tableHTML = `
+    <style>
+      table {
+        width: 100%;
+      }
+      th, td {
+        padding: 0 20px;
+        text-align: left;
+      }
+    </style>
+    <table>
+      <tr>
+        <th>Key</th>
+        <th>Value</th>
+      </tr>
+  `;
+  for (const item of response.data.data) {
+    tableHTML += 
`<tr><td>${item.argumentKey}</td><td>${item.argumentValue}</td></tr>`;
+  }
+  tableHTML += '</table>';
+  newWindow.document.write(tableHTML);
+}
+
+export async function getCoordinatorMetrics(params, headers) {
+  const response = await http.get('/coordinator/metrics', params, headers, 0)
+  const newWindow = window.open('', '_blank');
+  newWindow.document.write('<pre>' + JSON.stringify(response.data, null, 2) + 
'</pre>');
+}
+
+export async function getShuffleServerMetrics(address, params, headers) {
+  if (typeof headers === 'undefined') {
+    headers = {}
+  }
+  headers.targetAddress = address
+  const response = await http.get('/shuffleServer/metrics', params, headers, 0)
+  const newWindow = window.open('', '_blank');
+  newWindow.document.write('<pre>' + JSON.stringify(response.data, null, 2) + 
'</pre>');
+}
+
+export async function getCoordinatorPrometheusMetrics(params, headers) {
+  const response = await http.get('/coordinator/prometheus/metrics/all', 
params, headers, 0)
+  const newWindow = window.open('', '_blank');
+  newWindow.document.write('<pre>' + response.data + '</pre>');
+}
+
+export async function getShuffleServerPrometheusMetrics(address, params, 
headers) {
+  if (typeof headers === 'undefined') {
+    headers = {}
+  }
+  headers.targetAddress = address
+  const response = await http.get('/shuffleServer/prometheus/metrics/all', 
params, headers, 0)
+  const newWindow = window.open('', '_blank');
+  newWindow.document.write('<pre>' + response.data + '</pre>');
+}
+
+export async function getCoordinatorStacks(params, headers) {
+  const response = await http.get('/coordinator/stacks', params, headers, 0)
+  const newWindow = window.open('', '_blank');
+  newWindow.document.write('<pre>' + response.data + '</pre>');
+}
+
+export async function getShuffleServerStacks(address, params, headers) {
+  if (typeof headers === 'undefined') {
+    headers = {}
+  }
+  headers.targetAddress = address
+  const response = await http.get('/shuffleServer/stacks', params, headers, 0)
+  const newWindow = window.open('', '_blank');
+  newWindow.document.write('<pre>' + response.data + '</pre>');
+}
+
 // Create an interface for the total number of nodes
 export function getShufflegetStatusTotal(params, headers) {
   return http.get('/server/nodes/summary', params, headers, 0)
diff --git a/dashboard/src/main/webapp/src/pages/CoordinatorServerPage.vue 
b/dashboard/src/main/webapp/src/pages/CoordinatorServerPage.vue
index a13be3bde..aac7cf317 100644
--- a/dashboard/src/main/webapp/src/pages/CoordinatorServerPage.vue
+++ b/dashboard/src/main/webapp/src/pages/CoordinatorServerPage.vue
@@ -74,19 +74,50 @@
           <el-table-column prop="argumentValue" label="Value" min-width="380" 
/>
         </el-table>
       </el-collapse-item>
+      <el-collapse-item title="Coordinator Metrics" name="3">
+        <el-link @click="getCoordinatorMetrics" target="_blank">
+          <el-icon :style="iconStyle">
+            <Link />
+          </el-icon>
+          metrics
+        </el-link>
+      </el-collapse-item>
+      <el-collapse-item title="Coordinator Prometheus Metrics" name="4">
+        <el-link @click="getCoordinatorPrometheusMetrics" target="_blank">
+          <el-icon :style="iconStyle">
+            <Link />
+          </el-icon>
+          prometheus metrics
+        </el-link>
+      </el-collapse-item>
+      <el-collapse-item title="Coordinator Stacks" name="5">
+        <el-link @click="getCoordinatorStacks" target="_blank">
+          <el-icon :style="iconStyle">
+            <Link />
+          </el-icon>
+          stacks
+        </el-link>
+      </el-collapse-item>
     </el-collapse>
   </div>
 </template>
 
 <script>
 import { ref, reactive, computed, onMounted } from 'vue'
-import { getCoordinatorConf, getCoordinatorServerInfo } from '@/api/api'
+import {
+  getCoordinatorConf,
+  getCoordinatorMetrics,
+  getCoordinatorPrometheusMetrics,
+  getCoordinatorServerInfo,
+  getCoordinatorStacks
+} from '@/api/api'
 import { useCurrentServerStore } from '@/store/useCurrentServerStore'
 
 export default {
+  methods: {getCoordinatorMetrics, getCoordinatorPrometheusMetrics, 
getCoordinatorStacks},
   setup() {
     const pageData = reactive({
-      activeNames: ['1', '2'],
+      activeNames: ['1', '2', '3', '4', '5'],
       tableData: [],
       serverInfo: {}
     })
diff --git a/dashboard/src/main/webapp/src/pages/serverstatus/NodeListPage.vue 
b/dashboard/src/main/webapp/src/pages/serverstatus/NodeListPage.vue
index 8c455f747..41c7a19c4 100644
--- a/dashboard/src/main/webapp/src/pages/serverstatus/NodeListPage.vue
+++ b/dashboard/src/main/webapp/src/pages/serverstatus/NodeListPage.vue
@@ -28,6 +28,7 @@
       <el-table-column prop="ip" label="IP" min-width="80" sortable />
       <el-table-column prop="grpcPort" label="Port" min-width="80" />
       <el-table-column prop="nettyPort" label="NettyPort" min-width="80" />
+      <el-table-column prop="jettyPort" label="JettyPort" min-width="80" />
       <el-table-column
         prop="usedMemory"
         label="UsedMem"
@@ -65,6 +66,46 @@
         :formatter="dateFormatter"
         sortable
       />
+      <el-table-column label="Conf">
+        <template v-slot="{ row }">
+          <el-link @click="getShuffleServerConf('http://' + row.ip + ':' + 
row.jettyPort)" target="_blank">
+            <el-icon :style="iconStyle">
+              <Link />
+            </el-icon>
+            conf
+          </el-link>
+        </template>
+      </el-table-column>
+      <el-table-column label="Metrics">
+        <template v-slot="{ row }">
+          <el-link @click="getShuffleServerMetrics('http://' + row.ip + ':' + 
row.jettyPort)" target="_blank">
+            <el-icon :style="iconStyle">
+              <Link />
+            </el-icon>
+            metrics
+          </el-link>
+        </template>
+      </el-table-column>
+      <el-table-column label="PrometheusMetrics" min-width="150">
+        <template v-slot="{ row }">
+          <el-link @click="getShuffleServerPrometheusMetrics('http://' + 
row.ip + ':' + row.jettyPort)" target="_blank">
+            <el-icon :style="iconStyle">
+              <Link />
+            </el-icon>
+            prometheus metrics
+          </el-link>
+        </template>
+      </el-table-column>
+      <el-table-column label="Stacks">
+        <template v-slot="{ row }">
+          <el-link @click="getShuffleServerStacks('http://' + row.ip + ':' + 
row.jettyPort)" target="_blank">
+            <el-icon :style="iconStyle">
+              <Link />
+            </el-icon>
+            stacks
+          </el-link>
+        </template>
+      </el-table-column>
       <el-table-column prop="tags" label="Tags" min-width="140" />
       <el-table-column v-if="isShowRemove" label="Operations">
         <template v-slot:default="scope">
@@ -88,10 +129,15 @@ import {
   getShuffleDecommissioningList,
   getShuffleLostList,
   getShuffleUnhealthyList,
-  deleteConfirmedLostServer
+  deleteConfirmedLostServer,
+  getShuffleServerConf,
+  getShuffleServerMetrics,
+  getShuffleServerPrometheusMetrics,
+  getShuffleServerStacks
 } from '@/api/api'
 
 export default {
+  methods: {getShuffleServerConf, getShuffleServerMetrics, 
getShuffleServerPrometheusMetrics, getShuffleServerStacks},
   setup() {
     const router = useRouter()
     const currentServerStore = useCurrentServerStore()
@@ -110,7 +156,8 @@ export default {
           tags: '',
           status: '',
           registrationTime: '',
-          timestamp: ''
+          timestamp: '',
+          jettyPort: 0
         }
       ]
     })
@@ -170,7 +217,8 @@ export default {
           tags: '',
           status: '',
           registrationTime: '',
-          timestamp: ''
+          timestamp: '',
+          jettyPort: 0
         }
       ]
       if (router.currentRoute.value.name === 'activeNodeList') {
diff --git a/dashboard/src/main/webapp/src/utils/http.js 
b/dashboard/src/main/webapp/src/utils/http.js
index d6afb4ed1..40efd1ed8 100644
--- a/dashboard/src/main/webapp/src/utils/http.js
+++ b/dashboard/src/main/webapp/src/utils/http.js
@@ -21,13 +21,14 @@ import { useCurrentServerStore } from 
'@/store/useCurrentServerStore'
 const http = {
   get(url, params, headers, fontBackFlag) {
     if (fontBackFlag === 0) {
-      // The system obtains the address of the Coordinator to be accessed from 
global variables.
-      const currentServerStore = useCurrentServerStore()
       if (headers) {
-        headers.targetAddress = currentServerStore.currentServer
+        headers.serverType = 'server'
       } else {
+        // The system obtains the address of the Coordinator to be accessed 
from global variables.
+        const currentServerStore = useCurrentServerStore()
         headers = {}
         headers.targetAddress = currentServerStore.currentServer
+        headers.serverType = 'coordinator'
       }
       return request.getBackEndAxiosInstance().get(url, { params, headers })
     } else {
diff --git 
a/internal-client/src/main/java/org/apache/uniffle/client/impl/grpc/CoordinatorGrpcClient.java
 
b/internal-client/src/main/java/org/apache/uniffle/client/impl/grpc/CoordinatorGrpcClient.java
index 484bb43d7..5900de6eb 100644
--- 
a/internal-client/src/main/java/org/apache/uniffle/client/impl/grpc/CoordinatorGrpcClient.java
+++ 
b/internal-client/src/main/java/org/apache/uniffle/client/impl/grpc/CoordinatorGrpcClient.java
@@ -124,13 +124,15 @@ public class CoordinatorGrpcClient extends GrpcClient 
implements CoordinatorClie
       Set<String> tags,
       ServerStatus serverStatus,
       Map<String, StorageInfo> storageInfo,
-      int nettyPort) {
+      int nettyPort,
+      int jettyPort) {
     ShuffleServerId serverId =
         ShuffleServerId.newBuilder()
             .setId(id)
             .setIp(ip)
             .setPort(port)
             .setNettyPort(nettyPort)
+            .setJettyPort(jettyPort)
             .build();
     ShuffleServerHeartBeatRequest request =
         ShuffleServerHeartBeatRequest.newBuilder()
@@ -216,7 +218,8 @@ public class CoordinatorGrpcClient extends GrpcClient 
implements CoordinatorClie
             request.getTags(),
             request.getServerStatus(),
             request.getStorageInfo(),
-            request.getNettyPort());
+            request.getNettyPort(),
+            request.getJettyPort());
 
     RssSendHeartBeatResponse response;
     RssProtos.StatusCode statusCode = rpcResponse.getStatus();
diff --git 
a/internal-client/src/main/java/org/apache/uniffle/client/request/RssSendHeartBeatRequest.java
 
b/internal-client/src/main/java/org/apache/uniffle/client/request/RssSendHeartBeatRequest.java
index 72d12642b..34d29d750 100644
--- 
a/internal-client/src/main/java/org/apache/uniffle/client/request/RssSendHeartBeatRequest.java
+++ 
b/internal-client/src/main/java/org/apache/uniffle/client/request/RssSendHeartBeatRequest.java
@@ -37,6 +37,7 @@ public class RssSendHeartBeatRequest {
   private final ServerStatus serverStatus;
   private final Map<String, StorageInfo> storageInfo;
   private final int nettyPort;
+  private final int jettyPort;
 
   public RssSendHeartBeatRequest(
       String shuffleServerId,
@@ -50,7 +51,8 @@ public class RssSendHeartBeatRequest {
       Set<String> tags,
       ServerStatus serverStatus,
       Map<String, StorageInfo> storageInfo,
-      int nettyPort) {
+      int nettyPort,
+      int jettyPort) {
     this.shuffleServerId = shuffleServerId;
     this.shuffleServerIp = shuffleServerIp;
     this.shuffleServerPort = shuffleServerPort;
@@ -63,6 +65,7 @@ public class RssSendHeartBeatRequest {
     this.serverStatus = serverStatus;
     this.storageInfo = storageInfo;
     this.nettyPort = nettyPort;
+    this.jettyPort = jettyPort;
   }
 
   public String getShuffleServerId() {
@@ -112,4 +115,8 @@ public class RssSendHeartBeatRequest {
   public int getNettyPort() {
     return nettyPort;
   }
+
+  public int getJettyPort() {
+    return jettyPort;
+  }
 }
diff --git a/proto/src/main/proto/Rss.proto b/proto/src/main/proto/Rss.proto
index 5ce36df7e..861d73a3f 100644
--- a/proto/src/main/proto/Rss.proto
+++ b/proto/src/main/proto/Rss.proto
@@ -287,6 +287,7 @@ message ShuffleServerId {
   string ip = 2;
   int32 port = 3;
   int32 netty_port = 4;
+  int32 jetty_port = 5;
 }
 
 message ShuffleServerResult {
diff --git 
a/server/src/main/java/org/apache/uniffle/server/RegisterHeartBeat.java 
b/server/src/main/java/org/apache/uniffle/server/RegisterHeartBeat.java
index 8181ddc74..a8c8b5d76 100644
--- a/server/src/main/java/org/apache/uniffle/server/RegisterHeartBeat.java
+++ b/server/src/main/java/org/apache/uniffle/server/RegisterHeartBeat.java
@@ -85,7 +85,8 @@ public class RegisterHeartBeat {
                 shuffleServer.getTags(),
                 shuffleServer.getServerStatus(),
                 shuffleServer.getStorageManager().getStorageInfo(),
-                shuffleServer.getNettyPort());
+                shuffleServer.getNettyPort(),
+                shuffleServer.getJettyPort());
           } catch (Exception e) {
             LOG.warn("Error happened when send heart beat to coordinator");
           }
@@ -106,7 +107,8 @@ public class RegisterHeartBeat {
       Set<String> tags,
       ServerStatus serverStatus,
       Map<String, StorageInfo> localStorageInfo,
-      int nettyPort) {
+      int nettyPort,
+      int jettyPort) {
     AtomicBoolean sendSuccessfully = new AtomicBoolean(false);
     // use `rss.server.heartbeat.interval` as the timeout option
     RssSendHeartBeatRequest request =
@@ -122,7 +124,8 @@ public class RegisterHeartBeat {
             tags,
             serverStatus,
             localStorageInfo,
-            nettyPort);
+            nettyPort,
+            jettyPort);
 
     ThreadUtils.executeTasks(
         heartBeatExecutorService,
diff --git a/server/src/main/java/org/apache/uniffle/server/ShuffleServer.java 
b/server/src/main/java/org/apache/uniffle/server/ShuffleServer.java
index 7340fe3b3..461fe2aab 100644
--- a/server/src/main/java/org/apache/uniffle/server/ShuffleServer.java
+++ b/server/src/main/java/org/apache/uniffle/server/ShuffleServer.java
@@ -237,7 +237,9 @@ public class ShuffleServer {
     jettyServer = new JettyServer(shuffleServerConf);
     registerMetrics();
     // register packages and instances for jersey
-    jettyServer.addResourcePackages("org.apache.uniffle.common.web.resource");
+    jettyServer.addResourcePackages(
+        "org.apache.uniffle.server.web.resource", 
"org.apache.uniffle.common.web.resource");
+    jettyServer.registerInstance(ShuffleServer.class, this);
     jettyServer.registerInstance(
         CollectorRegistry.class.getCanonicalName() + "#server",
         ShuffleServerMetrics.getCollectorRegistry());
@@ -532,6 +534,10 @@ public class ShuffleServer {
     return nettyPort;
   }
 
+  public int getJettyPort() {
+    return jettyServer.getHttpPort();
+  }
+
   public String getEncodedTags() {
     return StringUtils.join(tags, ",");
   }
@@ -550,6 +556,7 @@ public class ShuffleServer {
         shuffleServer.getTags(),
         shuffleServer.getServerStatus(),
         shuffleServer.getStorageManager().getStorageInfo(),
-        shuffleServer.getNettyPort());
+        shuffleServer.getNettyPort(),
+        shuffleServer.getJettyPort());
   }
 }
diff --git 
a/server/src/main/java/org/apache/uniffle/server/web/resource/ServerResource.java
 
b/server/src/main/java/org/apache/uniffle/server/web/resource/ServerResource.java
new file mode 100644
index 000000000..c84895c91
--- /dev/null
+++ 
b/server/src/main/java/org/apache/uniffle/server/web/resource/ServerResource.java
@@ -0,0 +1,82 @@
+/*
+ * 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.uniffle.server.web.resource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.ServletContext;
+
+import org.apache.hbase.thirdparty.javax.ws.rs.GET;
+import org.apache.hbase.thirdparty.javax.ws.rs.Path;
+import org.apache.hbase.thirdparty.javax.ws.rs.core.Context;
+
+import org.apache.uniffle.common.util.ThreadUtils;
+import org.apache.uniffle.common.web.resource.BaseResource;
+import org.apache.uniffle.common.web.resource.MetricResource;
+import org.apache.uniffle.common.web.resource.PrometheusMetricResource;
+import org.apache.uniffle.common.web.resource.Response;
+import org.apache.uniffle.server.ShuffleServer;
+import org.apache.uniffle.server.ShuffleServerConf;
+import org.apache.uniffle.server.web.vo.ServerConfVO;
+
+@Path("/api/shuffleServer")
+public class ServerResource extends BaseResource {
+  @Context protected ServletContext servletContext;
+
+  @GET
+  @Path("/conf")
+  public Response<List<ServerConfVO>> getShuffleServerConf() {
+    return execute(
+        () -> {
+          ShuffleServerConf serverConf = 
getShuffleServer().getShuffleServerConf();
+          Set<Map.Entry<String, Object>> allEntry = serverConf.getAll();
+          List<ServerConfVO> serverConfVOs = new ArrayList<>();
+          for (Map.Entry<String, Object> stringObjectEntry : allEntry) {
+            ServerConfVO result =
+                new ServerConfVO(
+                    stringObjectEntry.getKey(), 
String.valueOf(stringObjectEntry.getValue()));
+            serverConfVOs.add(result);
+          }
+          return serverConfVOs;
+        });
+  }
+
+  @Path("/metrics")
+  public Class<MetricResource> getMetricResource() {
+    return MetricResource.class;
+  }
+
+  @Path("/prometheus/metrics")
+  public Class<PrometheusMetricResource> getPrometheusMetricResource() {
+    return PrometheusMetricResource.class;
+  }
+
+  @GET
+  @Path("/stacks")
+  public String getShuffleServerStacks() {
+    StringBuilder builder = new StringBuilder();
+    ThreadUtils.printThreadInfo(builder, "");
+    return builder.toString();
+  }
+
+  private ShuffleServer getShuffleServer() {
+    return (ShuffleServer) 
servletContext.getAttribute(ShuffleServer.class.getCanonicalName());
+  }
+}
diff --git 
a/server/src/main/java/org/apache/uniffle/server/web/vo/ServerConfVO.java 
b/server/src/main/java/org/apache/uniffle/server/web/vo/ServerConfVO.java
new file mode 100644
index 000000000..0cf3ffe3f
--- /dev/null
+++ b/server/src/main/java/org/apache/uniffle/server/web/vo/ServerConfVO.java
@@ -0,0 +1,44 @@
+/*
+ * 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.uniffle.server.web.vo;
+
+public class ServerConfVO {
+  private String argumentKey;
+  private String argumentValue;
+
+  public ServerConfVO(String argumentKey, String argumentValue) {
+    this.argumentKey = argumentKey;
+    this.argumentValue = argumentValue;
+  }
+
+  public String getArgumentKey() {
+    return argumentKey;
+  }
+
+  public void setArgumentKey(String argumentKey) {
+    this.argumentKey = argumentKey;
+  }
+
+  public String getArgumentValue() {
+    return argumentValue;
+  }
+
+  public void setArgumentValue(String argumentValue) {
+    this.argumentValue = argumentValue;
+  }
+}

Reply via email to