diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/STS4LanguageClient.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/STS4LanguageClient.java index c561e3111..8d9ffb020 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/STS4LanguageClient.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/STS4LanguageClient.java @@ -45,6 +45,12 @@ public interface STS4LanguageClient extends LanguageClient { @JsonNotification("sts/liveprocess/updated") void liveProcessDataUpdated(LiveProcessSummary processKey); + + @JsonNotification("sts/liveprocess/memory/metrics/updated") + void liveProcessMemoryMetricsDataUpdated(LiveProcessSummary processKey); + + @JsonNotification("sts/liveprocess/gcpauses/metrics/updated") + void liveProcessGcPausesMetricsDataUpdated(LiveProcessSummary processKey); @JsonNotification("sts/highlight") void highlight(HighlightParams highlights); diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java index 12788293e..b9fa8edf8 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java @@ -414,6 +414,15 @@ public class LanguageServerHarness { @Override public void liveProcessDataUpdated(LiveProcessSummary process) { } + + @Override + public void liveProcessMemoryMetricsDataUpdated(LiveProcessSummary processKey) { + } + + @Override + public void liveProcessGcPausesMetricsDataUpdated(LiveProcessSummary processKey) { + } + }); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/ActuatorConnection.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/ActuatorConnection.java index ab464a1ea..adadc3212 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/ActuatorConnection.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/ActuatorConnection.java @@ -31,4 +31,8 @@ public interface ActuatorConnection { String getMetrics(String metric, Map tags) throws IOException; Map getStartup() throws IOException; + +// String getGcPausesMetrics() throws IOException; + +// String getMemoryMetrics(String metricName) throws IOException; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/AvailableTags.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/AvailableTags.java new file mode 100644 index 000000000..9fb959c10 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/AvailableTags.java @@ -0,0 +1,26 @@ +package org.springframework.ide.vscode.boot.java.livehover.v2; + +import java.util.Arrays; + +/** + * @author Udayani V + */ +public class AvailableTags { + + private String tag; + private String[] values; + + public String getTag() { + return tag; + } + + public String[] getValues() { + return values; + } + + @Override + public String toString() { + return "AvailableTags [tag=" + tag + ", values=" + Arrays.toString(values) + "]"; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/LiveMemoryMetricsModel.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/LiveMemoryMetricsModel.java new file mode 100644 index 000000000..853778979 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/LiveMemoryMetricsModel.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2017, 2018 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.livehover.v2; + +/** + * @author Udayani V + */ +public class LiveMemoryMetricsModel { + + private String name; + private String description; + private Measurements[] measurements; + private String baseUnit; + private AvailableTags[] availableTags; + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Measurements[] getMeasurements() { + return measurements; + } + + public String getBaseUnit() { + return baseUnit; + } + + public AvailableTags[] getAvailableTags() { + return availableTags; + } + + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/Measurements.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/Measurements.java new file mode 100644 index 000000000..e46f5ca1a --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/Measurements.java @@ -0,0 +1,22 @@ +package org.springframework.ide.vscode.boot.java.livehover.v2; + +public class Measurements { + + private String statistic; + private Long value; + + public String getStatistic() { + return statistic; + } + + + public Long getValue() { + return value; + } + + @Override + public String toString() { + return "Measurements [statistic=" + statistic + ", value=" + value + "]"; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessCommandHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessCommandHandler.java index 72e4c5010..58460b26b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessCommandHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessCommandHandler.java @@ -41,6 +41,8 @@ public class SpringProcessCommandHandler { private static final String COMMAND_DISCONNECT = "sts/livedata/disconnect"; private static final String COMMAND_GET = "sts/livedata/get"; private static final String COMMAND_LIST_CONNECTED = "sts/livedata/listConnected"; + private static final String COMMAND_GET_METRICS = "sts/livedata/get/metrics"; + private static final String COMMAND_GET_REFRESH_METRICS = "sts/livedata/refresh/metrics"; private final SpringProcessConnectorService connectorService; private final SpringProcessConnectorLocal localProcessConnector; @@ -77,6 +79,16 @@ public class SpringProcessCommandHandler { }); log.info("Registered command handler: {}",COMMAND_GET); + server.onCommand(COMMAND_GET_METRICS, (params) -> { + return handleLiveMetricsProcessRequest(params); + }); + log.info("Registered command handler: {}",COMMAND_GET_METRICS); + + server.onCommand(COMMAND_GET_REFRESH_METRICS, (params) -> { + return refreshMetrics(params); + }); + log.info("Registered command handler: {}",COMMAND_GET_METRICS); + server.onCommand(COMMAND_LIST_CONNECTED, (params) -> { List result = new ArrayList<>(); for (SpringProcessConnector process : connectorService.getConnectedProcesses()) { @@ -136,8 +148,20 @@ public class SpringProcessCommandHandler { private CompletableFuture refresh(ExecuteCommandParams params) { String processKey = getProcessKey(params); + String endpoint = getArgumentByKey(params, "endpoint"); if (processKey != null) { - connectorService.refreshProcess(processKey); + connectorService.refreshProcess(processKey, endpoint, ""); + } + + return CompletableFuture.completedFuture(null); + } + + private CompletableFuture refreshMetrics(ExecuteCommandParams params) { + String processKey = getProcessKey(params); + String endpoint = getArgumentByKey(params, "endpoint"); + String metricName = getArgumentByKey(params, "metricName"); + if (processKey != null) { + connectorService.refreshProcess(processKey, endpoint, metricName); } return CompletableFuture.completedFuture(null); @@ -266,5 +290,25 @@ public class SpringProcessCommandHandler { return CompletableFuture.completedFuture(null); } + + private CompletableFuture handleLiveMetricsProcessRequest(ExecuteCommandParams params) { + String processKey = getProcessKey(params); + String metricName = getArgumentByKey(params, "metricName"); + if (processKey != null) { + switch(metricName) { + case "gcPauses": { + SpringProcessGcPausesMetricsLiveData data = connectorService.getGcPausesMetricsLiveData(processKey); + return CompletableFuture.completedFuture(data.getGcPausesMetrics()); + } + case "memory": { + SpringProcessMemoryMetricsLiveData data = connectorService.getMemoryMetricsLiveData(processKey); + return CompletableFuture.completedFuture(data.getMemoryMetrics()); + } + default: {} + } + } + + return CompletableFuture.completedFuture(null); + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnector.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnector.java index a2685815e..534621726 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnector.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnector.java @@ -27,4 +27,6 @@ public interface SpringProcessConnector { String getProjectName(); String getProcessId(); String getProcessName(); + SpringProcessGcPausesMetricsLiveData refreshGcPausesMetrics(SpringProcessLiveData current, String metricName) throws Exception; + SpringProcessMemoryMetricsLiveData refreshMemoryMetrics(SpringProcessLiveData current, String metricName) throws Exception; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverHttp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverHttp.java index 46a0168fb..8080fb52d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverHttp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverHttp.java @@ -102,4 +102,18 @@ public class SpringProcessConnectorOverHttp implements SpringProcessConnector { public String toString() { return "SpringProcessConnectorOverHttp [actuatorURL=" + actuatorUrl + "]"; } + + @Override + public SpringProcessGcPausesMetricsLiveData refreshGcPausesMetrics(SpringProcessLiveData current, String metricName) + throws Exception { + return null; + } + + @Override + public SpringProcessMemoryMetricsLiveData refreshMemoryMetrics(SpringProcessLiveData current, String metricName) + throws Exception { + return null; + } + + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverJMX.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverJMX.java index 76cc6a588..7f075668c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverJMX.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverJMX.java @@ -147,6 +147,52 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector { throw new Exception("no live data received, lets try again"); } + + @Override + public SpringProcessMemoryMetricsLiveData refreshMemoryMetrics(SpringProcessLiveData currentData, String metricName) throws Exception { + log.info("try to open JMX connection to: " + jmxURL); + + if (jmxConnection != null) { + try { + SpringProcessLiveDataExtractorOverJMX springJMXConnector = new SpringProcessLiveDataExtractorOverJMX(); + + log.info("retrieve live data from: " + jmxURL); + SpringProcessMemoryMetricsLiveData liveData = springJMXConnector.retrieveLiveMemoryMetricsData(getProcessType(), jmxConnection, processID, processName, currentData, metricName); + + if (liveData != null && liveData.getMemoryMetrics() != null && liveData.getMemoryMetrics().length > 0) { + return liveData; + } + } + catch (Exception e) { + log.error("exception while connecting to jmx: " + jmxURL, e); + } + } + + throw new Exception("no live data received, lets try again"); + } + + @Override + public SpringProcessGcPausesMetricsLiveData refreshGcPausesMetrics(SpringProcessLiveData currentData, String metricName) throws Exception { + log.info("try to open JMX connection to: " + jmxURL); + + if (jmxConnection != null) { + try { + SpringProcessLiveDataExtractorOverJMX springJMXConnector = new SpringProcessLiveDataExtractorOverJMX(); + + log.info("retrieve live data from: " + jmxURL); + SpringProcessGcPausesMetricsLiveData liveData = springJMXConnector.retrieveLiveGcPausesMetricsData(getProcessType(), jmxConnection, processID, processName, currentData, metricName); + + if (liveData != null && liveData.getGcPausesMetrics() != null && liveData.getGcPausesMetrics().length > 0) { + return liveData; + } + } + catch (Exception e) { + log.error("exception while connecting to jmx: " + jmxURL, e); + } + } + + throw new Exception("no live data received, lets try again"); + } @Override public void disconnect() throws Exception { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorService.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorService.java index 3d6f57a68..6b1b59fe9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorService.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorService.java @@ -99,7 +99,7 @@ public class SpringProcessConnectorService { } } - public void refreshProcess(String processKey) { + public void refreshProcess(String processKey, String endpoint, String metricName) { log.info("refresh process: " + processKey); SpringProcessConnector connector = this.connectors.get(processKey); @@ -109,13 +109,21 @@ public class SpringProcessConnectorService { progressTask.progressBegin("Refresh", null); - scheduleRefresh(progressTask, processKey, connector, 0, TimeUnit.SECONDS, 0); + scheduleRefresh(progressTask, processKey, connector, 0, TimeUnit.SECONDS, 0, endpoint, metricName); } } public SpringProcessLiveData getLiveData(String processKey) { return this.liveDataProvider.getCurrent(processKey); } + + public SpringProcessMemoryMetricsLiveData getMemoryMetricsLiveData(String processKey) { + return this.liveDataProvider.getMemoryMetrics(processKey); + } + + public SpringProcessGcPausesMetricsLiveData getGcPausesMetricsLiveData(String processKey) { + return this.liveDataProvider.getGcPausesMetrics(processKey); + } public void disconnectProcess(String processKey) { log.info("disconnect from process: " + processKey); @@ -162,7 +170,9 @@ public class SpringProcessConnectorService { connector.connect(); progressTask.progressDone(); - refreshProcess(processKey); + refreshProcess(processKey, "", ""); + refreshProcess(processKey, "metrics", "memory"); + refreshProcess(processKey, "metrics", "gcPauses"); } catch (Exception e) { log.info("problem occured during process connect", e); @@ -210,7 +220,7 @@ public class SpringProcessConnectorService { }, delay, unit); } - private void scheduleRefresh(ProgressTask progressTask, String processKey, SpringProcessConnector connector, long delay, TimeUnit unit, int retryNo) { + private void scheduleRefresh(ProgressTask progressTask, String processKey, SpringProcessConnector connector, long delay, TimeUnit unit, int retryNo, String endpoint, String metricName) { String progressMessage = "Refreshing data from Spring process: " + processKey + " - retry no: " + retryNo; log.info(progressMessage); @@ -219,14 +229,38 @@ public class SpringProcessConnectorService { try { progressTask.progressEvent(progressMessage); - SpringProcessLiveData newLiveData = connector.refresh(this.liveDataProvider.getCurrent(processKey)); - - if (newLiveData != null) { - if (!this.liveDataProvider.add(processKey, newLiveData)) { - this.liveDataProvider.update(processKey, newLiveData); + if(endpoint.equals("metrics") && metricName.equals("memory")) { + SpringProcessMemoryMetricsLiveData newMetricsLiveData = connector.refreshMemoryMetrics(this.liveDataProvider.getCurrent(processKey), metricName); + + if (newMetricsLiveData != null) { + if (!this.liveDataProvider.addMemoryMetrics(processKey, newMetricsLiveData)) { + this.liveDataProvider.updateMemoryMetrics(processKey, newMetricsLiveData); + } + + this.connectedSuccess.put(processKey, true); } - this.connectedSuccess.put(processKey, true); + } else if(endpoint.equals("metrics") && metricName.equals("gcPauses")) { + SpringProcessGcPausesMetricsLiveData newMetricsLiveData = connector.refreshGcPausesMetrics(this.liveDataProvider.getCurrent(processKey), metricName); + + if (newMetricsLiveData != null) { + if (!this.liveDataProvider.addGcPausesMetrics(processKey, newMetricsLiveData)) { + this.liveDataProvider.updateGcPausesMetrics(processKey, newMetricsLiveData); + } + + this.connectedSuccess.put(processKey, true); + } + + } else { + SpringProcessLiveData newLiveData = connector.refresh(this.liveDataProvider.getCurrent(processKey)); + + if (newLiveData != null) { + if (!this.liveDataProvider.add(processKey, newLiveData)) { + this.liveDataProvider.update(processKey, newLiveData); + } + + this.connectedSuccess.put(processKey, true); + } } progressTask.progressDone(); } @@ -236,7 +270,7 @@ public class SpringProcessConnectorService { if (retryNo < maxRetryCount && isKnownProcessKey(processKey)) { scheduleRefresh(progressTask, processKey, connector, retryDelayInSeconds, TimeUnit.SECONDS, - retryNo + 1); + retryNo + 1, endpoint, metricName); } else { progressTask.progressDone(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessGcPausesMetricsLiveData.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessGcPausesMetricsLiveData.java new file mode 100644 index 000000000..59d414c28 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessGcPausesMetricsLiveData.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2019, 2020 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.livehover.v2; + +/** + * @author V Udayani + */ +public class SpringProcessGcPausesMetricsLiveData { + + private final ProcessType processType; + private final String processName; + private final String processID; + + private final LiveMemoryMetricsModel[] gcPausesMetrics; + + public SpringProcessGcPausesMetricsLiveData(ProcessType processType, String processName, String processID, LiveMemoryMetricsModel[] gcPausesMetrics) { + super(); + this.processType = processType; + this.processName = processName; + this.processID = processID; + this.gcPausesMetrics = gcPausesMetrics; + + } + + public ProcessType getProcessType() { + return processType; + } + + public String getProcessName() { + return this.processName; + } + + public String getProcessID() { + return this.processID; + } + + public LiveMemoryMetricsModel[] getGcPausesMetrics() { + return this.gcPausesMetrics; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataExtractorOverHttp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataExtractorOverHttp.java index f0042e8e5..9c16d30f3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataExtractorOverHttp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataExtractorOverHttp.java @@ -76,6 +76,8 @@ public class SpringProcessLiveDataExtractorOverHttp { LiveBeansModel beans = getBeans(connection); LiveMetricsModel metrics = getMetrics(connection); StartupMetricsModel startup = getStartupMetrics(connection, currentData == null ? null : currentData.getStartupMetrics()); +// LiveMemoryMetricsModel[] memoryMetrics = getMemoryMetrics(connection); +// LiveMemoryMetricsModel gcPausesMetrics = getGcPausesMetrics(connection); if (contextPath == null) { contextPath = getContextPath(environment); @@ -99,7 +101,10 @@ public class SpringProcessLiveDataExtractorOverHttp { conditionals, properties, metrics, - startup); + startup + ); +// memoryMetrics, +// gcPausesMetrics); } catch (Exception e) { log.error("error reading live data from: " + processID + " - " + processName, e); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataExtractorOverJMX.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataExtractorOverJMX.java index 29f329c75..bd393f2da 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataExtractorOverJMX.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataExtractorOverJMX.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -36,6 +37,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.util.StringUtil; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -95,6 +97,8 @@ public class SpringProcessLiveDataExtractorOverJMX { LiveBeansModel beans = getBeans(connection, domain); LiveMetricsModel metrics = getMetrics(connection, domain); StartupMetricsModel startup = getStartupMetrics(connection, domain, currentData == null ? null : currentData.getStartupMetrics()); +// LiveMemoryMetricsModel[] memoryMetrics = getLiveMemoryMetrics(connection, domain); +// LiveMemoryMetricsModel gcPausesMetrics = getGcPausesMetrics(connection, domain); if (contextPath == null) { contextPath = getContextPath(connection, domain, environment); @@ -118,7 +122,10 @@ public class SpringProcessLiveDataExtractorOverJMX { conditionals, properties, metrics, - startup); + startup +// memoryMetrics, +// gcPausesMetrics + ); } catch (Exception e) { log.error("error reading live data from: " + processID + " - " + processName, e); @@ -127,6 +134,108 @@ public class SpringProcessLiveDataExtractorOverJMX { return null; } + /** + * @param processType + * @param processID if null, will be determined searching existing mbeans for that information (for remote processes via platform beans runtime name) + * @param processName if null, will be determined searching existing mbeans for that information (for remote processes inferring the java command from the system properties) + * @param currentData currently stored live data + * @param metricName + */ + public SpringProcessMemoryMetricsLiveData retrieveLiveMemoryMetricsData(ProcessType processType, JMXConnector jmxConnector, String processID, String processName, + SpringProcessLiveData currentData, String metricName) { + + List memoryTags = Arrays.asList( "jvm.memory.used", "jvm.memory.committed", "jvm.memory.max"); + + try { + MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); + List memoryMetricsList = new ArrayList<>(); + String domain = getDomainForActuator(connection); + + if (processID == null) { + processID = getProcessID(connection); + } + + if (processName == null) { + Properties systemProperties = getSystemProperties(connection); + if (systemProperties != null) { + String javaCommand = getJavaCommand(systemProperties); + processName = getProcessName(javaCommand); + } + } + +// if(metricName.equals("memory")) { + for(String metric : memoryTags) { + LiveMemoryMetricsModel metrics = getLiveMetrics(connection, domain, metric); + if(metrics != null) { + memoryMetricsList.add(metrics); + } + } + +// } + + LiveMemoryMetricsModel[] res = (LiveMemoryMetricsModel[]) memoryMetricsList.toArray(new LiveMemoryMetricsModel[memoryMetricsList.size()]); + return new SpringProcessMemoryMetricsLiveData( + processType, + processName, + processID, + res + ); + } + catch (Exception e) { + log.error("error reading live metrics data from: " + processID + " - " + processName, e); + } + + return null; + } + + /** + * @param processType + * @param processID if null, will be determined searching existing mbeans for that information (for remote processes via platform beans runtime name) + * @param processName if null, will be determined searching existing mbeans for that information (for remote processes inferring the java command from the system properties) + * @param currentData currently stored live data + * @param metricName + */ + public SpringProcessGcPausesMetricsLiveData retrieveLiveGcPausesMetricsData(ProcessType processType, JMXConnector jmxConnector, String processID, String processName, + SpringProcessLiveData currentData, String metricName) { + + try { + MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); + List memoryMetricsList = new ArrayList<>(); + String domain = getDomainForActuator(connection); + + if (processID == null) { + processID = getProcessID(connection); + } + + if (processName == null) { + Properties systemProperties = getSystemProperties(connection); + if (systemProperties != null) { + String javaCommand = getJavaCommand(systemProperties); + processName = getProcessName(javaCommand); + } + } + + LiveMemoryMetricsModel metrics = getLiveMetrics(connection, domain, "jvm.gc.pause"); + if(metrics != null) { + memoryMetricsList.add(getLiveMetrics(connection, domain, "jvm.gc.pause")); + } + + + LiveMemoryMetricsModel[] res = (LiveMemoryMetricsModel[]) memoryMetricsList.toArray(new LiveMemoryMetricsModel[memoryMetricsList.size()]); + return new SpringProcessGcPausesMetricsLiveData( + processType, + processName, + processID, + res + ); + } + catch (Exception e) { + log.error("error reading live metrics data from: " + processID + " - " + processName, e); + } + + return null; + } + private LiveMetricsModel getMetrics(MBeanServerConnection connection, String domain) { return new LiveMetricsModel() { @@ -257,6 +366,31 @@ public class SpringProcessLiveDataExtractorOverJMX { } return LiveBeansModel.builder().build(); } + + + public LiveMemoryMetricsModel getLiveMetrics(MBeanServerConnection connection, String domain, String metricName) { + + List tags = new ArrayList<>(); + Object[] params1 = new Object[] {metricName, tags}; + String[] signature = new String[] {String.class.getName(), List.class.getName()}; + + try { + Object metricsData = getActuatorDataFromOperation(connection, + getObjectName(domain, "type=Endpoint,name=Metrics"), + "metric", + params1, + signature); + if (metricsData instanceof String) { + return gson.fromJson((String)metricsData, LiveMemoryMetricsModel.class); + } else if(metricsData != null){ + ObjectMapper mapper = new ObjectMapper(); + return mapper.convertValue(metricsData, LiveMemoryMetricsModel.class); + } + } catch (Exception e) { + log.error("", e); + } + return null; + } protected Object getBeansFromNonBootMBean(MBeanServerConnection connection) throws Exception { Set nonBootSpringLiveMBeans = getNonBootSpringLiveMBeans(connection); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataProvider.java index 48f439846..e92453aff 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveDataProvider.java @@ -27,12 +27,16 @@ import org.springframework.ide.vscode.commons.util.Assert; public class SpringProcessLiveDataProvider { private final ConcurrentMap liveData; + private final ConcurrentMap memoryMetricsLiveData; + private final ConcurrentMap gcPausesMetricsLiveData; private final List listeners; private final SimpleLanguageServer server; public SpringProcessLiveDataProvider(SimpleLanguageServer server) { this.server = server; this.liveData = new ConcurrentHashMap<>(); + this.memoryMetricsLiveData = new ConcurrentHashMap<>(); + this.gcPausesMetricsLiveData = new ConcurrentHashMap<>(); this.listeners = new CopyOnWriteArrayList<>(); } @@ -54,6 +58,22 @@ public class SpringProcessLiveDataProvider { return oldData == null; } + public boolean addMemoryMetrics(String processKey, SpringProcessMemoryMetricsLiveData liveData) { + SpringProcessMemoryMetricsLiveData oldData = this.memoryMetricsLiveData.putIfAbsent(processKey, liveData); + if (oldData == null) { + getClient().liveProcessMemoryMetricsDataUpdated(createProcessSummaryForMetrics(processKey, liveData.getProcessType().jsonName(),liveData.getProcessName(),liveData.getProcessID())); + } + return oldData == null; + } + + public boolean addGcPausesMetrics(String processKey, SpringProcessGcPausesMetricsLiveData liveData) { + SpringProcessGcPausesMetricsLiveData oldData = this.gcPausesMetricsLiveData.putIfAbsent(processKey, liveData); + if (oldData == null) { + getClient().liveProcessGcPausesMetricsDataUpdated(createProcessSummaryForMetrics(processKey, liveData.getProcessType().jsonName(),liveData.getProcessName(),liveData.getProcessID())); + } + return oldData == null; + } + private STS4LanguageClient getClient() { STS4LanguageClient client = server.getClient(); Assert.isLegal(client!=null, "Client is null. Language server not yet initialized?"); @@ -74,6 +94,16 @@ public class SpringProcessLiveDataProvider { getClient().liveProcessDataUpdated(createProcessSummary(processKey, liveData)); } + public void updateMemoryMetrics(String processKey, SpringProcessMemoryMetricsLiveData liveData) { + this.memoryMetricsLiveData.put(processKey, liveData); + getClient().liveProcessMemoryMetricsDataUpdated(createProcessSummaryForMetrics(processKey, liveData.getProcessType().jsonName(),liveData.getProcessName(),liveData.getProcessID())); + } + + public void updateGcPausesMetrics(String processKey, SpringProcessGcPausesMetricsLiveData liveData) { + this.gcPausesMetricsLiveData.put(processKey, liveData); + getClient().liveProcessGcPausesMetricsDataUpdated(createProcessSummaryForMetrics(processKey, liveData.getProcessType().jsonName(),liveData.getProcessName(),liveData.getProcessID())); + } + public void addLiveDataChangeListener(SpringProcessLiveDataChangeListener listener) { this.listeners.add(listener); } @@ -93,6 +123,14 @@ public class SpringProcessLiveDataProvider { public SpringProcessLiveData getCurrent(String processKey) { return this.liveData.get(processKey); } + + public SpringProcessMemoryMetricsLiveData getMemoryMetrics(String processKey) { + return this.memoryMetricsLiveData.get(processKey); + } + + public SpringProcessGcPausesMetricsLiveData getGcPausesMetrics(String processKey) { + return this.gcPausesMetricsLiveData.get(processKey); + } public static LiveProcessSummary createProcessSummary(String processKey, SpringProcessLiveData liveData) { LiveProcessSummary p = new LiveProcessSummary(); @@ -102,5 +140,15 @@ public class SpringProcessLiveDataProvider { p.setPid(liveData.getProcessID()); return p; } + + public static LiveProcessSummary createProcessSummaryForMetrics(String processKey, String processType, String processName, + String processID) { + LiveProcessSummary p = new LiveProcessSummary(); + p.setType(processType); + p.setProcessKey(processKey); + p.setProcessName(processName); + p.setPid(processID); + return p; + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessMemoryMetricsLiveData.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessMemoryMetricsLiveData.java new file mode 100644 index 000000000..b566154cd --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessMemoryMetricsLiveData.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2019, 2020 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.livehover.v2; + +/** + * @author V Udayani + */ +public class SpringProcessMemoryMetricsLiveData { + + private final ProcessType processType; + private final String processName; + private final String processID; + + private final LiveMemoryMetricsModel[] memoryMetrics; + + public SpringProcessMemoryMetricsLiveData(ProcessType processType, String processName, String processID, LiveMemoryMetricsModel[] memoryMetrics) { + super(); + this.processType = processType; + this.processName = processName; + this.processID = processID; + this.memoryMetrics = memoryMetrics; + + } + + public ProcessType getProcessType() { + return processType; + } + + public String getProcessName() { + return this.processName; + } + + public String getProcessID() { + return this.processID; + } + + public LiveMemoryMetricsModel[] getMemoryMetrics() { + return this.memoryMetrics; + } + +} diff --git a/vscode-extensions/vscode-spring-boot/lib/api.d.ts b/vscode-extensions/vscode-spring-boot/lib/api.d.ts index 646c142c4..acbfce999 100644 --- a/vscode-extensions/vscode-spring-boot/lib/api.d.ts +++ b/vscode-extensions/vscode-spring-boot/lib/api.d.ts @@ -20,10 +20,35 @@ export interface ExtensionAPI { */ readonly onDidLiveProcessUpdate: Event + /** + * An event which fires on live process gcpauses metrics data change. Payload is processKey. + */ + readonly onDidLiveProcessGcPausesMetricsUpdate: Event + + /** + * An event which fires on live process memory metrics data change. Payload is processKey. + */ + readonly onDidLiveProcessMemoryMetricsUpdate: Event + /** * A command to get live process data. */ - readonly getLiveProcessData: (query: SimpleQuery | BeansQuery) => Promise + readonly getLiveProcessData: (query: SimpleQuery | BeansQuery ) => Promise + + /** + * A command to refresh live process data. + */ + readonly refreshLiveProcessData: (query: SimpleQuery | BeansQuery) => Promise; + + /** + * A command to get live process metrics data. + */ + readonly getLiveProcessMetricsData: (query: MetricsQuery) => Promise; + + /** + * A command to refresh live process metrics data. + */ + readonly refreshLiveProcessMetricsData: (query: MetricsQuery) => Promise; /** * A command to list all currently connected processes. @@ -52,3 +77,10 @@ interface BeansQuery extends LiveProcessDataQuery { beanName?: string; dependingOn?: string; } + +interface MetricsQuery extends LiveProcessDataQuery { + endpoint: "metrics"; + metricName: string; + tag?: { key: string; value: string }; + +} diff --git a/vscode-extensions/vscode-spring-boot/lib/apiManager.ts b/vscode-extensions/vscode-spring-boot/lib/apiManager.ts index 9c8cdc9fd..377b57e9a 100644 --- a/vscode-extensions/vscode-spring-boot/lib/apiManager.ts +++ b/vscode-extensions/vscode-spring-boot/lib/apiManager.ts @@ -1,39 +1,68 @@ import { commands, Uri } from "vscode"; import { Emitter, LanguageClient } from "vscode-languageclient/node"; import { ExtensionAPI } from "./api"; -import { LiveProcess, LiveProcessConnectedNotification, LiveProcessDisconnectedNotification, LiveProcessUpdatedNotification } from "./notification"; +import { LiveProcess, LiveProcessConnectedNotification, LiveProcessDisconnectedNotification, LiveProcessUpdatedNotification, LiveProcessGcPausesMetricsUpdatedNotification, LiveProcessMemoryMetricsUpdatedNotification } from "./notification"; export class ApiManager { public api: ExtensionAPI; private onDidLiveProcessConnectEmitter: Emitter = new Emitter(); private onDidLiveProcessDisconnectEmitter: Emitter = new Emitter(); private onDidLiveProcessUpdateEmitter: Emitter = new Emitter(); + private onDidLiveProcessGcPausesMetricsUpdateEmitter: Emitter = new Emitter(); + private onDidLiveProcessMemoryMetricsUpdateEmitter: Emitter = new Emitter(); public constructor(client: LanguageClient) { const onDidLiveProcessConnect = this.onDidLiveProcessConnectEmitter.event; const onDidLiveProcessDisconnect = this.onDidLiveProcessDisconnectEmitter.event; const onDidLiveProcessUpdate = this.onDidLiveProcessUpdateEmitter.event; + const onDidLiveProcessGcPausesMetricsUpdate = this.onDidLiveProcessGcPausesMetricsUpdateEmitter.event; + const onDidLiveProcessMemoryMetricsUpdate = this.onDidLiveProcessMemoryMetricsUpdateEmitter.event; const COMMAND_LIVEDATA_GET = "sts/livedata/get"; const getLiveProcessData = async (query) => { return await commands.executeCommand(COMMAND_LIVEDATA_GET, query); } + const COMMAND_LIVEDATA_REFRESH = "sts/livedata/refresh"; + const refreshLiveProcessData = async (query) => { + return await commands.executeCommand(COMMAND_LIVEDATA_REFRESH, query); + } + const COMMAND_LIVEDATA_LIST_CONNECTED = "sts/livedata/listConnected" const listConnectedProcesses = async () : Promise => { return await commands.executeCommand(COMMAND_LIVEDATA_LIST_CONNECTED); } + const COMMAND_LIVEDATA_GET_METRICS = "sts/livedata/get/metrics" + const getLiveProcessMetricsData = async (query) : Promise => { + console.log(query); + console.log("in get live metrics function"); + return await commands.executeCommand(COMMAND_LIVEDATA_GET_METRICS, query); + } + + const COMMAND_LIVEDATA_REFRESH_METRICS = "sts/livedata/refresh/metrics"; + const refreshLiveProcessMetricsData = async (query) => { + console.log("in get live metrics refresh function"); + return await commands.executeCommand(COMMAND_LIVEDATA_REFRESH_METRICS, query); + } + client.onNotification(LiveProcessConnectedNotification.type, (process: LiveProcess) => this.onDidLiveProcessConnectEmitter.fire(process)); client.onNotification(LiveProcessDisconnectedNotification.type, (process: LiveProcess) => this.onDidLiveProcessDisconnectEmitter.fire(process)); client.onNotification(LiveProcessUpdatedNotification.type, (process: LiveProcess) => this.onDidLiveProcessUpdateEmitter.fire(process)); + client.onNotification(LiveProcessGcPausesMetricsUpdatedNotification.type, (process: LiveProcess) => this.onDidLiveProcessGcPausesMetricsUpdateEmitter.fire(process)); + client.onNotification(LiveProcessMemoryMetricsUpdatedNotification.type, (process: LiveProcess) => this.onDidLiveProcessMemoryMetricsUpdateEmitter.fire(process)); this.api = { client, onDidLiveProcessConnect, onDidLiveProcessDisconnect, onDidLiveProcessUpdate, + onDidLiveProcessMemoryMetricsUpdate, + onDidLiveProcessGcPausesMetricsUpdate, getLiveProcessData, + refreshLiveProcessData, + getLiveProcessMetricsData, + refreshLiveProcessMetricsData, listConnectedProcesses, }; } diff --git a/vscode-extensions/vscode-spring-boot/lib/notification.ts b/vscode-extensions/vscode-spring-boot/lib/notification.ts index 07f9268f9..b006e73f2 100644 --- a/vscode-extensions/vscode-spring-boot/lib/notification.ts +++ b/vscode-extensions/vscode-spring-boot/lib/notification.ts @@ -28,4 +28,12 @@ export namespace LiveProcessDisconnectedNotification { export namespace LiveProcessUpdatedNotification { export const type = new NotificationType('sts/liveprocess/updated'); +} + +export namespace LiveProcessGcPausesMetricsUpdatedNotification { + export const type = new NotificationType('sts/liveprocess/gcpauses/metrics/updated'); +} + +export namespace LiveProcessMemoryMetricsUpdatedNotification { + export const type = new NotificationType('sts/liveprocess/memory/metrics/updated'); } \ No newline at end of file