Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Self sampler for LSP server code completion #7815

Merged
merged 1 commit into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions java/java.lsp.server/nbproject/project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,14 @@
<implementation-version/>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.netbeans.modules.sampler</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<specification-version>1.40</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.netbeans.modules.sendopts</code-name-base>
<build-prerequisite/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -244,18 +249,24 @@
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;
import org.openide.util.BaseUtilities;
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;
Expand All @@ -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
Expand Down Expand Up @@ -334,8 +346,36 @@ public void indexingComplete(Set<URL> indexedRoots) {
private final AtomicInteger javadocTimeout = new AtomicInteger(-1);
private List<Completion> 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<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) {
AtomicBoolean done = new AtomicBoolean();
AtomicReference<Sampler> 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();
Expand All @@ -358,9 +398,21 @@ public CompletableFuture<Either<List<CompletionItem>, 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<CompletionItem> items = new ArrayList<>();
Expand Down Expand Up @@ -439,6 +491,36 @@ public CompletableFuture<Either<List<CompletionItem>, 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);
Expand Down
5 changes: 5 additions & 0 deletions java/java.lsp.server/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
Loading