diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml index b84a5f3b4c7d..b2357086f913 100644 --- a/java/java.lsp.server/nbproject/project.xml +++ b/java/java.lsp.server/nbproject/project.xml @@ -566,6 +566,14 @@ + + org.netbeans.modules.sampler + + + + 1.40 + + org.netbeans.modules.sendopts diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java index c582e825e321..1c58bf6bd5f8 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java @@ -32,16 +32,20 @@ import com.sun.source.util.TreePathScanner; import com.sun.source.util.Trees; import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; +import java.io.DataOutputStream; +import java.io.File; import java.io.FileNotFoundException; import java.net.URI; import java.net.URL; import java.time.Instant; import java.io.IOException; +import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URISyntaxException; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -63,6 +67,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.IntFunction; @@ -78,7 +83,6 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.swing.JEditorPane; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.BadLocationException; @@ -236,6 +240,7 @@ import org.netbeans.modules.java.lsp.server.URITranslator; import org.netbeans.modules.java.lsp.server.ui.AbstractJavaPlatformProviderOverride; import org.netbeans.modules.parsing.impl.SourceAccessor; +import org.netbeans.modules.sampler.Sampler; import org.netbeans.spi.editor.hints.ErrorDescription; import org.netbeans.spi.lsp.CallHierarchyProvider; import org.netbeans.spi.lsp.CodeLensProvider; @@ -244,11 +249,16 @@ import org.netbeans.spi.project.ActionProvider; import org.netbeans.spi.project.ProjectConfiguration; import org.netbeans.spi.project.ProjectConfigurationProvider; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.NotifyDescriptor.Message; import org.openide.cookies.EditorCookie; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.filesystems.URLMapper; import org.openide.loaders.DataObject; +import org.openide.modules.Places; import org.openide.text.CloneableEditorSupport; import org.openide.text.NbDocument; import org.openide.text.PositionBounds; @@ -256,6 +266,7 @@ import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.openide.util.Pair; import org.openide.util.RequestProcessor; import org.openide.util.WeakSet; @@ -273,6 +284,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli private static final String COMMAND_RUN_SINGLE = "nbls.run.single"; // NOI18N private static final String COMMAND_DEBUG_SINGLE = "nbls.debug.single"; // NOI18N private static final String NETBEANS_JAVADOC_LOAD_TIMEOUT = "javadoc.load.timeout";// NOI18N + private static final String NETBEANS_COMPLETION_WARNING_TIME = "completion.warning.time";// NOI18N private static final String NETBEANS_JAVA_ON_SAVE_ORGANIZE_IMPORTS = "java.onSave.organizeImports";// NOI18N private static final String URL = "url";// NOI18N private static final String INDEX = "index";// NOI18N @@ -334,8 +346,36 @@ public void indexingComplete(Set indexedRoots) { private final AtomicInteger javadocTimeout = new AtomicInteger(-1); private List lastCompletions = null; + private static final int INITIAL_COMPLETION_SAMPLING_DELAY = 1000; + private static final int DEFAULT_COMPLETION_WARNING_LENGTH = 10_000; + private static final RequestProcessor COMPLETION_SAMPLER_WORKER = new RequestProcessor("java-lsp-completion-sampler", 1, false, false); + @Override + @Messages({ + "# {0} - the timeout elapsed", + "# {1} - path to the saved sampler file", + "INFO_LongCodeCompletion=Analyze completions taking longer than {0}. A sampler snapshot has been saved to: {1}" + }) public CompletableFuture, CompletionList>> completion(CompletionParams params) { + AtomicBoolean done = new AtomicBoolean(); + AtomicReference samplerRef = new AtomicReference<>(); + AtomicLong samplingStart = new AtomicLong(); + AtomicLong samplingWarningLength = new AtomicLong(DEFAULT_COMPLETION_WARNING_LENGTH); + long completionStart = System.currentTimeMillis(); + COMPLETION_SAMPLER_WORKER.post(() -> { + if (!done.get()) { + Sampler sampler = Sampler.createSampler("completion"); + if (sampler != null) { + sampler.start(); + samplerRef.set(sampler); + samplingStart.set(System.currentTimeMillis()); + if (done.get()) { + sampler.stop(); + } + } + } + }, INITIAL_COMPLETION_SAMPLING_DELAY); + lastCompletions = new ArrayList<>(); AtomicInteger index = new AtomicInteger(0); final CompletionList completionList = new CompletionList(); @@ -358,9 +398,21 @@ public CompletableFuture, CompletionList>> completio ConfigurationItem conf = new ConfigurationItem(); conf.setScopeUri(uri); conf.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + NETBEANS_JAVADOC_LOAD_TIMEOUT); - return client.configuration(new ConfigurationParams(Collections.singletonList(conf))).thenApply(c -> { + ConfigurationItem completionWarningLength = new ConfigurationItem(); + completionWarningLength.setScopeUri(uri); + completionWarningLength.setSection(client.getNbCodeCapabilities().getConfigurationPrefix() + NETBEANS_COMPLETION_WARNING_TIME); + return client.configuration(new ConfigurationParams(Arrays.asList(conf, completionWarningLength))).thenApply(c -> { if (c != null && !c.isEmpty()) { - javadocTimeout.set(((JsonPrimitive)c.get(0)).getAsInt()); + if (c.get(0) instanceof JsonPrimitive) { + JsonPrimitive javadocTimeSetting = (JsonPrimitive) c.get(0); + + javadocTimeout.set(javadocTimeSetting.getAsInt()); + } + if (c.get(1) instanceof JsonPrimitive) { + JsonPrimitive samplingWarningsLengthSetting = (JsonPrimitive) c.get(1); + + samplingWarningLength.set(samplingWarningsLengthSetting.getAsLong()); + } } final int caret = Utils.getOffset(doc, params.getPosition()); List items = new ArrayList<>(); @@ -439,6 +491,36 @@ public CompletableFuture, CompletionList>> completio } else { prefs.remove("classMemberInsertionPoint"); } + + done.set(true); + Sampler sampler = samplerRef.get(); + if (sampler != null) { + long samplingTime = (System.currentTimeMillis() - completionStart); + long minSamplingTime = Math.min(1_000, samplingWarningLength.get()); + if (samplingTime >= minSamplingTime && + samplingTime >= samplingWarningLength.get() && + samplingWarningLength.get() >= 0) { + Lookup lookup = Lookup.getDefault(); + new Thread(() -> { + Lookups.executeWith(lookup, () -> { + Path logDir = Places.getUserDirectory().toPath().resolve("var/log"); + try { + Path target = Files.createTempFile(logDir, "completion-sampler", ".npss"); + try (OutputStream out = Files.newOutputStream(target); + DataOutputStream dos = new DataOutputStream(out)) { + sampler.stopAndWriteTo(dos); + + NotifyDescriptor notifyUser = new Message(Bundle.INFO_LongCodeCompletion(samplingWarningLength.get(), target.toAbsolutePath().toString())); + + DialogDisplayer.getDefault().notifyLater(notifyUser); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + }); + }).start(); + } + } } completionList.setItems(items); return Either.forRight(completionList); diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json index ac61857d1df4..5751c063734d 100644 --- a/java/java.lsp.server/vscode/package.json +++ b/java/java.lsp.server/vscode/package.json @@ -187,6 +187,11 @@ "default": 100, "description": "Timeout (in milliseconds) for loading Javadoc in code completion (-1 for unlimited)" }, + "netbeans.completion.warning.time": { + "type": "integer", + "default": 10000, + "description": "When code completion takes longer than this specified time (in milliseconds), there will be a warning produced (-1 to disable)" + }, "netbeans.format.codeFormatter": { "type": "string", "enum": [