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; + } +}