Finalize metrics for Hovers and CodeLenses. Add tests

This commit is contained in:
BoykoAlex
2020-11-13 14:36:32 -05:00
parent 0992fffdf2
commit d6323cf6fb
4 changed files with 160 additions and 58 deletions

View File

@@ -556,6 +556,26 @@ public class Editor {
assertContains(snippet, hoverString(hover));
}
public void assertLiveCodeLensContains(String codeLensOver, int occurrence, String snippet) throws Exception {
int cmPosition = getHoverPosition(codeLensOver, occurrence);
for (CodeLens cm : getLiveDataCodeLenses()) {
if (cmPosition >= doc.toOffset(cm.getRange().getStart()) && cmPosition <= doc.toOffset(cm.getRange().getEnd())) {
if (cm.getData() instanceof String) {
if (((String) cm.getData()).contains(snippet)) {
return;
}
} else {
fail("Live Data CodeLens data field is not a string");
}
}
}
fail("Cannot find '" + snippet + "' in Live Data Code Lenses");
}
public void assertLiveCodeLensContains(String codeLensOver, String snippet) throws Exception {
assertLiveCodeLensContains(codeLensOver, 1, snippet);
}
public String hoverString(Hover hover) {
StringBuilder buf = new StringBuilder();
boolean first = true;
@@ -619,6 +639,16 @@ public class Editor {
Hover hover = harness.getHover(doc, doc.toPosition(hoverPosition));
assertEquals(expectedHover.trim(), hoverString(hover).trim());
}
public List<? extends CodeLens> getCodeLenses() throws Exception {
return harness.getCodeLenses(doc);
}
public List<? extends CodeLens> getLiveDataCodeLenses() throws Exception {
HighlightParams highlights = this.highlightsFuture.get(HIGHLIGHTS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
return highlights != null ? highlights.getCodeLenses() : ImmutableList.of();
}
public void assertNoHover(String hoverOver, int occurence) throws Exception {
int hoverPosition = getHoverPosition(hoverOver,occurence);

View File

@@ -13,8 +13,6 @@ package org.springframework.ide.vscode.boot.java.livehover.v2;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -38,7 +36,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.util.StringUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -129,13 +126,12 @@ public class SpringProcessLiveDataExtractorOverJMX {
return new LiveMetricsModel() {
@Override
public RequestMappingMetrics getRequestMappingMetrics(String[] uris, String[] requestMethods) {
public RequestMappingMetrics getRequestMappingMetrics(String[] paths, String[] requestMethods) {
try {
List<Object> tags = new ArrayList<>();
if (uris.length == 0) {
if (paths.length == 0) {
return null;
}
String[] paths = getPaths(uris);
tags.add("uri:" + String.join(",", paths));
if (requestMethods.length > 0) {
tags.add("method:" + String.join(",", requestMethods));
@@ -161,22 +157,6 @@ public class SpringProcessLiveDataExtractorOverJMX {
return null;
}
private String[] getPaths(String[] uris) {
Builder<String> builder = ImmutableList.builder();
if (uris != null) {
for (String val : uris) {
try {
URI uri = new URI(val);
builder.add(uri.getPath());
} catch (URISyntaxException e) {
log.error("", e);
}
}
}
return builder.build().toArray(new String[0]);
}
};
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2019 Pivotal, Inc.
* Copyright (c) 2017, 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
@@ -140,10 +140,18 @@ public class RequestMappingHoverProvider implements HoverProvider {
int remaining = 0;
for (Tuple2<LiveRequestMapping, SpringProcessLiveData> dataEntry : data) {
for (String url : getUrls(dataEntry)) {
for (Tuple2<String, String> urlWithPath : getUrlsWithPath(dataEntry)) {
if (lenses.size() <= CODE_LENS_LIMIT) {
Set<String> requestMethods = dataEntry.getT1().getRequestMethods();
RequestMappingMetrics metrics = dataEntry.getT2().getLiveMterics().getRequestMappingMetrics(new String[] { url }, requestMethods.toArray(new String[requestMethods.size()]));
SpringProcessLiveData liveData = dataEntry.getT2();
LiveRequestMapping requestMapping = dataEntry.getT1();
String url = urlWithPath.getT1();
String path = urlWithPath.getT2();
Set<String> requestMethods = requestMapping.getRequestMethods();
RequestMappingMetrics metrics = liveData.getLiveMterics() == null ? null
: liveData.getLiveMterics().getRequestMappingMetrics(new String[] { path },
requestMethods.toArray(new String[requestMethods.size()]));
CodeLens codeLens = createCodeLensForRequestMapping(range, url, metrics);
lenses.add(codeLens);
} else {
@@ -232,8 +240,8 @@ public class RequestMappingHoverProvider implements HoverProvider {
return false;
}
private List<String> getUrls(Tuple2<LiveRequestMapping, SpringProcessLiveData> mappingMethod) {
List<String> urls = new ArrayList<>();
private List<Tuple2<String, String>> getUrlsWithPath(Tuple2<LiveRequestMapping, SpringProcessLiveData> mappingMethod) {
List<Tuple2<String, String>> urls = new ArrayList<>();
SpringProcessLiveData liveData = mappingMethod.getT2();
String contextPath = liveData.getContextPath();
@@ -241,7 +249,8 @@ public class RequestMappingHoverProvider implements HoverProvider {
String port = liveData.getPort();
String host = liveData.getHost();
String[] paths = mappingMethod.getT1().getSplitPath();
LiveRequestMapping requestMapping = mappingMethod.getT1();
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...
@@ -251,7 +260,7 @@ public class RequestMappingHoverProvider implements HoverProvider {
}
for (String path : paths) {
String url = UrlUtil.createUrl(urlScheme, host, port, path, contextPath);
urls.add(url);
urls.add(Tuples.of(url, path));
}
return urls;
}
@@ -286,10 +295,12 @@ public class RequestMappingHoverProvider implements HoverProvider {
Renderable urlRenderables = Renderables.concat(renderableUrls);
Set<String> requestMethods = requestMapping.getRequestMethods();
RequestMappingMetrics metrics = liveData.getLiveMterics().getRequestMappingMetrics(requestMapping.getSplitPath(), requestMethods.toArray(new String[requestMethods.size()]));
RequestMappingMetrics metrics = liveData.getLiveMterics() == null ? null
: liveData.getLiveMterics().getRequestMappingMetrics(requestMapping.getSplitPath(),
requestMethods.toArray(new String[requestMethods.size()]));
if (metrics != null) {
Renderable metricsRenderable = Renderables.concat(
Renderables.bold("Count: " + metrics.getCallsCount() + " | Total Time: " + metrics.getTotalTime() + " | Max Time: " + metrics.getMaxTime()),
Renderables.bold(createHoverMetricsContent(metrics)),
Renderables.text("\n\n"));
urlRenderables = Renderables.concat(urlRenderables, Renderables.text("\n\n"), metricsRenderable);
}
@@ -313,6 +324,42 @@ public class RequestMappingHoverProvider implements HoverProvider {
// being added between the content itself
return new Hover(ImmutableList.of(Either.forLeft(contentVal.toString())));
}
private String createHoverMetricsContent(RequestMappingMetrics metrics) {
char timeUnitShort = metrics.getTimeUnit().name().toLowerCase().charAt(0);
StringBuilder metricsContent = new StringBuilder();
metricsContent.append("Count: ");
metricsContent.append(metrics.getCallsCount());
metricsContent.append(" | Total Time: ");
metricsContent.append(metrics.getTotalTime());
metricsContent.append(timeUnitShort);
metricsContent.append(" | Max Time: ");
metricsContent.append(metrics.getMaxTime());
metricsContent.append(timeUnitShort);
return metricsContent.toString();
}
private String createCodeLensMetricsContent(RequestMappingMetrics metrics) {
char timeUnitShort = metrics.getTimeUnit().name().toLowerCase().charAt(0);
StringBuilder metricsContent = new StringBuilder();
metricsContent.append("Count=");
metricsContent.append(metrics.getCallsCount());
metricsContent.append(' ');
metricsContent.append("Total=");
metricsContent.append(String.format("%.2f", metrics.getTotalTime()));
metricsContent.append(timeUnitShort);
metricsContent.append(' ');
metricsContent.append("Max=");
metricsContent.append(String.format("%.2f", metrics.getMaxTime()));
metricsContent.append(timeUnitShort);
metricsContent.append(')');
return metricsContent.toString();
}
private CodeLens createCodeLensForRequestMapping(Range range, String content, RequestMappingMetrics metrics) {
CodeLens codeLens = new CodeLens();
@@ -322,24 +369,11 @@ public class RequestMappingHoverProvider implements HoverProvider {
if (StringUtil.hasText(content)) {
if (metrics != null) {
char timeUnitShort = metrics.getTimeUnit().name().charAt(0);
StringBuilder codeLensContent = new StringBuilder(content);
codeLensContent.append(' ');
codeLensContent.append('(');
codeLensContent.append("Count=");
codeLensContent.append(metrics.getCallsCount());
codeLensContent.append(' ');
codeLensContent.append("Total=");
codeLensContent.append(shorten(metrics.getTotalTime()));
codeLensContent.append(timeUnitShort);
codeLensContent.append(' ');
codeLensContent.append("Max=");
codeLensContent.append(shorten(metrics.getMaxTime()));
codeLensContent.append(timeUnitShort);
codeLensContent.append(createCodeLensMetricsContent(metrics));
codeLensContent.append(')');
content = codeLensContent.toString();
}
@@ -354,10 +388,6 @@ public class RequestMappingHoverProvider implements HoverProvider {
return codeLens;
}
private double shorten(double val) {
return val;
}
private CodeLens createCodeLensForRemaining(Range range, int remaining) {
CodeLens codeLens = new CodeLens();
codeLens.setRange(range);
@@ -373,5 +403,5 @@ public class RequestMappingHoverProvider implements HoverProvider {
return codeLens;
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2018 Pivotal, Inc.
* Copyright (c) 2017, 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
@@ -203,7 +203,7 @@ public class RequestMappingLiveHoverTest {
@Override
public double getTotalTime() {
return 3.0;
return "/hello".equals(paths[0]) ? 3.034567 : 5.43673;
}
@Override
@@ -213,12 +213,12 @@ public class RequestMappingLiveHoverTest {
@Override
public double getMaxTime() {
return 0.55;
return "/hello".equals(paths[0]) ? 0.5535632 : 0.7437624;
}
@Override
public long getCallsCount() {
return 25;
return "/hello".equals(paths[0]) ? 25 : 34;
}
};
}
@@ -231,10 +231,72 @@ public class RequestMappingLiveHoverTest {
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
editor.assertHighlights("@RequestMapping(\"/hello\")", "@RequestMapping(\"/goodbye\")");
editor.assertHoverContains("@RequestMapping(\"/hello\")", "[https://cfapps.io:999/hello](https://cfapps.io:999/hello) \n" +
"Process [PID=76543, name=`test-request-mapping-live-hover`]");
editor.assertHoverContains("@RequestMapping(\"/hello\")", "[https://cfapps.io:999/hello](https://cfapps.io:999/hello) \n");
editor.assertHoverContains("@RequestMapping(\"/hello\")", "Count: 25 | Total Time: 3.034567s | Max Time: 0.5535632s");
editor.assertHoverContains("@RequestMapping(\"/hello\")", "Process [PID=76543, name=`test-request-mapping-live-hover`]");
editor.assertHoverContains("@RequestMapping(\"/goodbye\")", "Count: 25 | Total Time: 3.0 | Max Time: 0.55");
editor.assertHoverContains("@RequestMapping(\"/goodbye\")", "Count: 34 | Total Time: 5.43673s | Max Time: 0.7437624s");
}
@Test
public void testLiveCodeLensMetricsSection() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = directory.toPath().resolve("src/main/java/example/RestApi.java").toUri()
.toString();
// Build a mock running boot app
SpringProcessLiveData liveData = new SpringProcessLiveDataBuilder()
.port("999")
.processID("76543")
.urlScheme("https")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.requestMappingsJson(
"{\"/webjars/**\":{\"bean\":\"resourceHandlerMapping\"},\"/**\":{\"bean\":\"resourceHandlerMapping\"},\"/**/favicon.ico\":{\"bean\":\"faviconHandlerMapping\"},\"{[/hello-world],methods=[GET]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public example.Greeting example.HelloWorldController.sayHello(java.lang.String)\"},\"{[/goodbye]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.goodbye()\"},\"{[/hello]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.hello()\"},\"{[/error]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)\"},\"{[/error],produces=[text/html]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)\"}}")
.liveMetrics(new LiveMetricsModel() {
@Override
public RequestMappingMetrics getRequestMappingMetrics(String[] paths, String[] requestMethods) {
return new RequestMappingMetrics() {
@Override
public double getTotalTime() {
return "/hello".equals(paths[0]) ? 3.034567 : 5.43673;
}
@Override
public TimeUnit getTimeUnit() {
return TimeUnit.SECONDS;
}
@Override
public double getMaxTime() {
return "/hello".equals(paths[0]) ? 0.5535632 : 0.7437624;
}
@Override
public long getCallsCount() {
return "/hello".equals(paths[0]) ? 25 : 34;
}
};
}
})
.build();
liveDataProvider.add("processkey", liveData);
harness.intialize(directory);
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
editor.assertHighlights("@RequestMapping(\"/hello\")", "@RequestMapping(\"/goodbye\")");
editor.assertLiveCodeLensContains("@RequestMapping(\"/hello\")", "Count=25 Total=3.03s Max=0.55s");
editor.assertLiveCodeLensContains("@RequestMapping(\"/goodbye\")", "Count=34 Total=5.44s Max=0.74s");
}