Initial POC
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
package org.springframework.ide.vscode.boot.java.livehover.v2;
|
||||
|
||||
public interface LiveMetricsModel {
|
||||
|
||||
RequestMappingMetrics getRequestMappingMetrics(LiveRequestMapping rm);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.springframework.ide.vscode.boot.java.livehover.v2;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public interface RequestMappingMetrics {
|
||||
|
||||
long getCallsCount();
|
||||
|
||||
double getTotalTime();
|
||||
|
||||
double getMaxTime();
|
||||
|
||||
TimeUnit getTimeUnit();
|
||||
|
||||
static RequestMappingMetrics parse(String metricsData) {
|
||||
final JSONObject obj = new JSONObject(metricsData);
|
||||
final TimeUnit timeUnit = TimeUnit.valueOf(obj.getString("baseUnit").toUpperCase());
|
||||
final JSONArray measurements = obj.getJSONArray("measurements");
|
||||
return new RequestMappingMetrics() {
|
||||
|
||||
@Override
|
||||
public double getTotalTime() {
|
||||
Double d = findStatistic(measurements, "TOTAL_TIME");
|
||||
return d == null ? 0 : d.doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxTime() {
|
||||
Double d = findStatistic(measurements, "MAX");
|
||||
return d == null ? 0 : d.doubleValue();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getCallsCount() {
|
||||
Double d = findStatistic(measurements, "COUNT");
|
||||
return d == null ? 0 : d.longValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeUnit getTimeUnit() {
|
||||
return timeUnit == null ? TimeUnit.SECONDS : timeUnit;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T findStatistic(JSONArray measurements, String statistic) {
|
||||
for (int i = 0; i < measurements.length(); i++) {
|
||||
JSONObject entry = measurements.getJSONObject(i);
|
||||
if (statistic.equals(entry.getString("statistic"))) {
|
||||
return (T) entry.get("value");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,10 +28,12 @@ public class SpringProcessLiveData {
|
||||
private final LiveRequestMapping[] requestMappings;
|
||||
private final LiveConditional[] conditionals;
|
||||
private final LiveProperties properties;
|
||||
private final LiveMetricsModel metrics;
|
||||
|
||||
public SpringProcessLiveData(String processName, String processID, String contextPath, String urlScheme,
|
||||
String port, String host, LiveBeansModel beansModel, String[] activeProfiles,
|
||||
LiveRequestMapping[] requestMappings, LiveConditional[] conditionals, LiveProperties properties) {
|
||||
LiveRequestMapping[] requestMappings, LiveConditional[] conditionals, LiveProperties properties,
|
||||
LiveMetricsModel metrics) {
|
||||
super();
|
||||
this.processName = processName;
|
||||
this.processID = processID;
|
||||
@@ -44,6 +46,7 @@ public class SpringProcessLiveData {
|
||||
this.requestMappings = requestMappings;
|
||||
this.conditionals = conditionals;
|
||||
this.properties = properties;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public String getProcessName() {
|
||||
@@ -89,5 +92,9 @@ public class SpringProcessLiveData {
|
||||
public LiveProperties getLiveProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
public LiveMetricsModel getLiveMterics() {
|
||||
return this.metrics;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ public class SpringProcessLiveDataExtractorOverJMX {
|
||||
LiveConditional[] conditionals = getConditionals(connection, domain, processID, processName);
|
||||
LiveRequestMapping[] requestMappings = getRequestMappings(connection, domain);
|
||||
LiveBeansModel beans = getBeans(connection, domain);
|
||||
LiveMetricsModel metrics = getMetrics(connection, domain);
|
||||
|
||||
if (contextPath == null) {
|
||||
contextPath = getContextPath(connection, domain, environment);
|
||||
@@ -110,7 +111,8 @@ public class SpringProcessLiveDataExtractorOverJMX {
|
||||
activeProfiles,
|
||||
requestMappings,
|
||||
conditionals,
|
||||
properties);
|
||||
properties,
|
||||
metrics);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("error reading live data from: " + processID + " - " + processName, e);
|
||||
@@ -119,6 +121,35 @@ public class SpringProcessLiveDataExtractorOverJMX {
|
||||
return null;
|
||||
}
|
||||
|
||||
private LiveMetricsModel getMetrics(MBeanServerConnection connection, String domain) {
|
||||
|
||||
return new LiveMetricsModel() {
|
||||
|
||||
@Override
|
||||
public RequestMappingMetrics getRequestMappingMetrics(LiveRequestMapping rm) {
|
||||
try {
|
||||
List<Object> tags = new ArrayList<>();
|
||||
if (rm.getSplitPath().length == 0) {
|
||||
return null;
|
||||
}
|
||||
tags.add("uri:" + rm.getSplitPath()[0]);
|
||||
if (!rm.getRequestMethods().isEmpty()) {
|
||||
tags.add("methods:" + String.join(",", rm.getRequestMethods()));
|
||||
}
|
||||
Object metricsData = getActuatorDataFromOperation(connection, getObjectName(domain, "type=Endpoint,name=Metrics"), "metric", "http.server.requests", tags);
|
||||
if (metricsData instanceof String) {
|
||||
return RequestMappingMetrics.parse((String) metricsData);
|
||||
} else {
|
||||
return RequestMappingMetrics.parse(gson.toJson(metricsData));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public String getProcessID(MBeanServerConnection connection) {
|
||||
try {
|
||||
RuntimeMXBean runtime = ManagementFactory.getPlatformMXBean(connection, RuntimeMXBean.class);
|
||||
@@ -379,6 +410,18 @@ public class SpringProcessLiveDataExtractorOverJMX {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object getActuatorDataFromOperation(MBeanServerConnection connection, ObjectName objectName, String operation, Object... parameters) throws Exception {
|
||||
if (objectName != null) {
|
||||
try {
|
||||
return connection.invoke(objectName, operation, parameters, null);
|
||||
}
|
||||
catch (InstanceNotFoundException|IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ObjectName getObjectName(String domain, String keyProperties) throws Exception {
|
||||
if (StringUtil.hasText(domain) && StringUtil.hasText(keyProperties)) {
|
||||
String fullName = domain + ":" + keyProperties;
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveRequestMapping;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.RequestMappingMetrics;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveData;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
@@ -256,11 +257,12 @@ public class RequestMappingHoverProvider implements HoverProvider {
|
||||
Tuple2<LiveRequestMapping, SpringProcessLiveData> mappingMethod = mappingMethods.get(i);
|
||||
|
||||
SpringProcessLiveData liveData = mappingMethod.getT2();
|
||||
LiveRequestMapping requestMapping = mappingMethod.getT1();
|
||||
String urlScheme = liveData.getUrlScheme();
|
||||
String port = liveData.getPort();
|
||||
String host = liveData.getHost();
|
||||
|
||||
String[] paths = mappingMethod.getT1().getSplitPath();
|
||||
String[] paths = requestMapping.getSplitPath();
|
||||
if (paths==null || paths.length==0) {
|
||||
//Technically, this means the path 'predicate' is unconstrained, meaning any path matches.
|
||||
//So this is not quite the same as the case where path=""... but...
|
||||
@@ -276,6 +278,13 @@ public class RequestMappingHoverProvider implements HoverProvider {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Renderable urlRenderables = Renderables.concat(renderableUrls);
|
||||
|
||||
RequestMappingMetrics metrics = liveData.getLiveMterics().getRequestMappingMetrics(requestMapping);
|
||||
if (metrics != null) {
|
||||
Renderable metricsRenderable = Renderables.bold("Count: " + metrics.getCallsCount() + " Total Time: " + metrics.getTotalTime());
|
||||
urlRenderables = Renderables.concat(urlRenderables, Renderables.text("\n\n"), metricsRenderable);
|
||||
}
|
||||
|
||||
Renderable processSection = Renderables.concat(
|
||||
urlRenderables,
|
||||
Renderables.mdBlob(LiveHoverUtils.niceAppName(liveData))
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.ide.vscode.boot.java.metrics.test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.RequestMappingMetrics;
|
||||
|
||||
public class RequestMappingMetricsTest {
|
||||
|
||||
@Test
|
||||
public void testParser1() throws Exception {
|
||||
RequestMappingMetrics mappingMetrics = RequestMappingMetrics.parse("{\"name\":\"http.server.requests\",\"description\":null,\"baseUnit\":\"seconds\",\"measurements\":[{\"statistic\":\"COUNT\",\"value\":1.0},{\"statistic\":\"TOTAL_TIME\",\"value\":0.03465965},{\"statistic\":\"MAX\",\"value\":0.47461985}],\"availableTags\":[{\"tag\":\"exception\",\"values\":[\"None\"]},{\"tag\":\"outcome\",\"values\":[\"SUCCESS\"]},{\"tag\":\"status\",\"values\":[\"200\"]}]}");
|
||||
assertEquals(TimeUnit.SECONDS, mappingMetrics.getTimeUnit());
|
||||
assertEquals(1, mappingMetrics.getCallsCount());
|
||||
assertEquals(0.47461985, mappingMetrics.getMaxTime());
|
||||
assertEquals(0.03465965, mappingMetrics.getTotalTime());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import org.springframework.ide.vscode.boot.java.livehover.v2.LiveBeansModel;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveConditional;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveConditionalParser;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveContextPathUtil;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveMetricsModel;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveProperties;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveRequestMapping;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveRequestMappingBoot1xRequestMapping;
|
||||
@@ -42,6 +43,7 @@ public class SpringProcessLiveDataBuilder {
|
||||
private LiveRequestMapping[] requestMappings;
|
||||
private LiveConditional[] conditionals;
|
||||
private LiveProperties properties;
|
||||
private LiveMetricsModel metrics;
|
||||
|
||||
public SpringProcessLiveDataBuilder processName(String processName) {
|
||||
this.processName = processName;
|
||||
@@ -122,8 +124,13 @@ public class SpringProcessLiveDataBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpringProcessLiveDataBuilder liveMetrics(LiveMetricsModel metrics) {
|
||||
this.metrics = metrics;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpringProcessLiveData build() {
|
||||
return new SpringProcessLiveData(processName, processID, contextPath, urlScheme, port, host, beansModel, activeProfiles, requestMappings, conditionals, properties);
|
||||
return new SpringProcessLiveData(processName, processID, contextPath, urlScheme, port, host, beansModel, activeProfiles, requestMappings, conditionals, properties, metrics);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user