Initial POC

This commit is contained in:
BoykoAlex
2020-11-05 14:46:12 -05:00
parent f185db5882
commit ca72948de8
7 changed files with 159 additions and 4 deletions

View File

@@ -0,0 +1,7 @@
package org.springframework.ide.vscode.boot.java.livehover.v2;
public interface LiveMetricsModel {
RequestMappingMetrics getRequestMappingMetrics(LiveRequestMapping rm);
}

View File

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

View File

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

View File

@@ -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;

View File

@@ -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))

View File

@@ -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());
}
}

View File

@@ -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);
}
}