Skip to content

Commit c09f85f

Browse files
committed
Add support for the telemetry/event notification.
- Fixes #21 - Disable by default, and require setting of java.telemetry.enabled - Report build tool used for each project URI (eg. Maven, Gradle, etc.) - Report the lowest & highest JDT compiler source level (eg. 11, 17) - Report whether the project(s) are being imported for the first time - Report number of libraries & total size - Report information about timing of various stages Signed-off-by: Roland Grunberg <rgrunber@redhat.com>
1 parent 335ad83 commit c09f85f

File tree

9 files changed

+249
-2
lines changed

9 files changed

+249
-2
lines changed

USAGE_DATA.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Telemetry data collection
2+
3+
If `java.telemetry.enabled` is set to `true`, JDT-LS emits telemetry events.
4+
These events can be collected by the LSP client program.
5+
6+
When telemetry events are enabled, the following information is emitted :
7+
8+
* The name of the build tool used to import a project (eg. Maven, Gradle, Invisible (project), etc.)
9+
* The total number of Java projects within the workspace
10+
* The lowest and highest Java compiler source level used (eg. 11 & 17)
11+
* Whether the project(s) are being imported for the first time (eg. true)
12+
* The current time (in milliseconds) at which the language server started, initialized the workspace project(s), and completed building the project(s)
13+
* The number of libraries that were indexed after project initialization
14+
* The total size (in bytes) of libraries that were indexed after project initialization

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JavaLanguageServerPlugin.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.eclipse.jdt.ls.core.internal.managers.MavenSourceDownloader;
5858
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
5959
import org.eclipse.jdt.ls.core.internal.managers.StandardProjectsManager;
60+
import org.eclipse.jdt.ls.core.internal.managers.TelemetryManager;
6061
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
6162
import org.eclipse.jdt.ls.core.internal.preferences.StandardPreferenceManager;
6263
import org.eclipse.jdt.ls.core.internal.syntaxserver.SyntaxLanguageServer;
@@ -314,12 +315,15 @@ public IProxyService getProxyService() {
314315
}
315316

316317
private void startConnection() throws IOException {
318+
TelemetryManager telemetryManager = new TelemetryManager();
319+
boolean firstTimeInitialization = ProjectUtils.getAllProjects().length == 0;
320+
telemetryManager.onLanguageServerStart(System.currentTimeMillis(), firstTimeInitialization);
317321
Launcher<JavaLanguageClient> launcher;
318322
ExecutorService executorService = getExecutorService();
319323
if (JDTEnvironmentUtils.isSyntaxServer()) {
320324
protocol = new SyntaxLanguageServer(contentProviderManager, projectsManager, preferenceManager);
321325
} else {
322-
protocol = new JDTLanguageServer(projectsManager, preferenceManager);
326+
protocol = new JDTLanguageServer(projectsManager, preferenceManager, telemetryManager);
323327
}
324328
if (JDTEnvironmentUtils.inSocketStreamDebugMode()) {
325329
String host = JDTEnvironmentUtils.getClientHost();

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandler.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141
import org.eclipse.jdt.ls.core.internal.JavaClientConnection;
4242
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
4343
import org.eclipse.jdt.ls.core.internal.JobHelpers;
44+
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
4445
import org.eclipse.jdt.ls.core.internal.ServiceStatus;
4546
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
47+
import org.eclipse.jdt.ls.core.internal.managers.TelemetryManager;
4648
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
4749
import org.eclipse.jdt.ls.core.internal.preferences.Preferences;
4850
import org.eclipse.jdt.ls.internal.gradle.checksums.WrapperValidator;
@@ -76,11 +78,18 @@ final public class InitHandler extends BaseInitHandler {
7678

7779
private WorkspaceExecuteCommandHandler commandHandler;
7880

81+
private TelemetryManager telemetryManager;
82+
7983
public InitHandler(ProjectsManager manager, PreferenceManager preferenceManager, JavaClientConnection connection, WorkspaceExecuteCommandHandler commandHandler) {
84+
this(manager, preferenceManager, connection, commandHandler, new TelemetryManager());
85+
}
86+
87+
public InitHandler(ProjectsManager manager, PreferenceManager preferenceManager, JavaClientConnection connection, WorkspaceExecuteCommandHandler commandHandler, TelemetryManager telemetryManager) {
8088
super(manager, preferenceManager);
8189
this.connection = connection;
8290
this.preferenceManager = preferenceManager;
8391
this.commandHandler = commandHandler;
92+
this.telemetryManager = telemetryManager;
8493
}
8594

8695
@Override
@@ -247,6 +256,7 @@ public IStatus runInWorkspace(IProgressMonitor monitor) {
247256
projectsManager.configureFilters(monitor);
248257
JavaLanguageServerPlugin.logInfo("Workspace initialized in " + (System.currentTimeMillis() - start) + "ms");
249258
connection.sendStatus(ServiceStatus.Started, "Ready");
259+
telemetryManager.onProjectsInitialized(projectsManager, System.currentTimeMillis());
250260
} catch (OperationCanceledException e) {
251261
connection.sendStatus(ServiceStatus.Error, "Initialization has been cancelled.");
252262
return Status.CANCEL_STATUS;

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import org.eclipse.jdt.ls.core.internal.lsp.JavaProtocolExtensions;
7676
import org.eclipse.jdt.ls.core.internal.managers.ContentProviderManager;
7777
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
78+
import org.eclipse.jdt.ls.core.internal.managers.TelemetryManager;
7879
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
7980
import org.eclipse.jdt.ls.core.internal.preferences.Preferences;
8081
import org.eclipse.lsp4j.CallHierarchyIncomingCall;
@@ -179,6 +180,7 @@ public class JDTLanguageServer extends BaseJDTLanguageServer implements Language
179180
* The status of the language service
180181
*/
181182
private ServiceStatus status;
183+
private TelemetryManager telemetryManager;
182184

183185
private Job shutdownJob = new Job("Shutdown...") {
184186

@@ -212,12 +214,21 @@ public JDTLanguageServer(ProjectsManager projects, PreferenceManager preferenceM
212214
this(projects, preferenceManager, WorkspaceExecuteCommandHandler.getInstance());
213215
}
214216

217+
public JDTLanguageServer(ProjectsManager projects, PreferenceManager preferenceManager, TelemetryManager telemetryManager) {
218+
this(projects, preferenceManager, WorkspaceExecuteCommandHandler.getInstance(), telemetryManager);
219+
}
220+
215221
public JDTLanguageServer(ProjectsManager projects, PreferenceManager preferenceManager, WorkspaceExecuteCommandHandler commandHandler) {
222+
this(projects, preferenceManager, commandHandler, new TelemetryManager());
223+
}
224+
225+
public JDTLanguageServer(ProjectsManager projects, PreferenceManager preferenceManager, WorkspaceExecuteCommandHandler commandHandler, TelemetryManager telemetryManager) {
216226
this.pm = projects;
217227
this.preferenceManager = preferenceManager;
218228
this.jvmConfigurator = new JVMConfigurator();
219229
JavaRuntime.addVMInstallChangedListener(jvmConfigurator);
220230
this.commandHandler = commandHandler;
231+
this.telemetryManager = telemetryManager;
221232
}
222233

223234
@Override
@@ -229,6 +240,8 @@ public void connectClient(JavaLanguageClient client) {
229240
pm.setConnection(client);
230241
WorkingCopyOwner.setPrimaryBufferProvider(this.workingCopyOwner);
231242
this.documentLifeCycleHandler = new DocumentLifeCycleHandler(this.client, preferenceManager, pm, true);
243+
this.telemetryManager.setLanguageClient(client);
244+
this.telemetryManager.setPreferenceManager(preferenceManager);
232245
}
233246

234247
// For testing purpose
@@ -249,7 +262,7 @@ public void disconnectClient() {
249262
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
250263
logInfo(">> initialize");
251264
status = ServiceStatus.Starting;
252-
InitHandler handler = new InitHandler(pm, preferenceManager, client, commandHandler);
265+
InitHandler handler = new InitHandler(pm, preferenceManager, client, commandHandler, telemetryManager);
253266
return CompletableFuture.completedFuture(handler.initialize(params));
254267
}
255268

@@ -289,11 +302,13 @@ public IStatus run(IProgressMonitor monitor) {
289302

290303
client.sendStatus(ServiceStatus.ServiceReady, "ServiceReady");
291304
status = ServiceStatus.ServiceReady;
305+
telemetryManager.onServiceReady(System.currentTimeMillis());
292306
pm.projectsImported(monitor);
293307

294308
IndexUtils.copyIndexesToSharedLocation();
295309
JobHelpers.waitForBuildJobs(60 * 60 * 1000); // 1 hour
296310
logInfo(">> build jobs finished");
311+
telemetryManager.onBuildFinished(System.currentTimeMillis());
297312
workspaceDiagnosticsHandler.publishDiagnostics(monitor);
298313
} catch (OperationCanceledException | CoreException e) {
299314
logException(e.getMessage(), e);

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManager.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
200200
List<URI> projectUris = Arrays.stream(getWorkspaceRoot().getProjects())
201201
.map(project -> ProjectUtils.getProjectRealFolder(project).toFile().toURI())
202202
.collect(Collectors.toList());
203+
203204
EventNotification notification = new EventNotification().withType(EventType.ProjectsImported).withData(projectUris);
204205
client.sendEventNotification(notification);
205206
reportProjectsStatus();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Red Hat Inc. and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License 2.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-2.0
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Red Hat Inc. - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.jdt.ls.core.internal.managers;
14+
15+
public class TelemetryEvent {
16+
private String name;
17+
private Object properties;
18+
19+
public TelemetryEvent(String name, Object properties) {
20+
this.name = name;
21+
this.properties = properties;
22+
}
23+
24+
public String getName() {
25+
return name;
26+
}
27+
28+
public Object getProperties() {
29+
return properties;
30+
}
31+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Red Hat Inc. and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License 2.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-2.0
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Red Hat Inc. - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.jdt.ls.core.internal.managers;
14+
15+
import java.util.Arrays;
16+
import java.util.Optional;
17+
18+
import org.eclipse.core.resources.IProject;
19+
import org.eclipse.core.runtime.IPath;
20+
import org.eclipse.jdt.internal.core.JavaModelManager;
21+
import org.eclipse.jdt.internal.core.search.indexing.IndexManager;
22+
import org.eclipse.jdt.ls.core.internal.JavaClientConnection.JavaLanguageClient;
23+
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
24+
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
25+
26+
import com.google.gson.JsonArray;
27+
import com.google.gson.JsonObject;
28+
import com.google.gson.JsonPrimitive;
29+
30+
public class TelemetryManager {
31+
32+
private static final String JAVA_PROJECT_BUILD = "java.workspace.initialized";
33+
34+
private JavaLanguageClient client;
35+
private PreferenceManager prefs;
36+
private ProjectsManager projectsManager;
37+
private long languageServerStartTime;
38+
private long serviceReadyTime;
39+
private long projectsInitializedTime;
40+
private boolean firstTimeInitialization;
41+
public TelemetryManager(JavaLanguageClient client, PreferenceManager prefs) {
42+
this.client = client;
43+
this.prefs = prefs;
44+
}
45+
46+
public TelemetryManager() {
47+
}
48+
49+
public void setLanguageClient(JavaLanguageClient client) {
50+
this.client = client;
51+
}
52+
53+
public void setPreferenceManager(PreferenceManager prefs) {
54+
this.prefs = prefs;
55+
}
56+
57+
public void onLanguageServerStart(long timeMillis, boolean firstTimeInitialization) {
58+
this.languageServerStartTime = timeMillis;
59+
this.firstTimeInitialization = firstTimeInitialization;
60+
}
61+
62+
public void onProjectsInitialized(ProjectsManager projectsManager, long timeMillis) {
63+
this.projectsManager = projectsManager;
64+
this.projectsInitializedTime = timeMillis;
65+
}
66+
67+
public void onServiceReady(long timeMillis) {
68+
this.serviceReadyTime = timeMillis;
69+
}
70+
71+
public void onBuildFinished(long buildFinishedTime) {
72+
JsonObject properties = new JsonObject();
73+
float sourceLevelMin = 0, sourceLevelMax = 0;
74+
int javaProjectCount = 0;
75+
JsonArray buildToolNamesList = new JsonArray();
76+
77+
for (IProject project : ProjectUtils.getAllProjects()) {
78+
Optional<IBuildSupport> bs = this.projectsManager.getBuildSupport(project);
79+
if (bs.isPresent() && !ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getName())) {
80+
String buildToolName = bs.get().buildToolName();
81+
if (!buildToolNamesList.contains(new JsonPrimitive(buildToolName))) {
82+
buildToolNamesList.add(buildToolName);
83+
}
84+
String sourceLevel = ProjectUtils.getJavaSourceLevel(project);
85+
if (sourceLevel != null) {
86+
javaProjectCount++;
87+
if (sourceLevelMin == 0 || Float.parseFloat(sourceLevel) < sourceLevelMin) {
88+
sourceLevelMin = Float.parseFloat(sourceLevel);
89+
}
90+
if (sourceLevelMax == 0 || Float.parseFloat(sourceLevel) > sourceLevelMax) {
91+
sourceLevelMax = Float.parseFloat(sourceLevel);
92+
}
93+
}
94+
}
95+
}
96+
97+
properties.add("buildToolNames", buildToolNamesList);
98+
properties.addProperty("javaProjectCount", javaProjectCount);
99+
properties.addProperty("compiler.source.min", Float.toString(sourceLevelMin));
100+
properties.addProperty("compiler.source.max", Float.toString(sourceLevelMax));
101+
properties.addProperty("timestamp.languageserverstart", Long.toString(this.languageServerStartTime));
102+
properties.addProperty("timestamp.projectsinitialized", Long.toString(this.projectsInitializedTime));
103+
properties.addProperty("timestamp.serviceready", Long.toString(this.serviceReadyTime));
104+
properties.addProperty("timestamp.buildFinished", Long.toString(buildFinishedTime));
105+
properties.addProperty("initialization.first", Boolean.toString(this.firstTimeInitialization));
106+
107+
IndexManager manager = JavaModelManager.getIndexManager();
108+
if (manager != null) {
109+
int indexCount = manager.indexLocations.elementSize;
110+
long librarySize = computeDependencySize(manager);
111+
properties.addProperty("dependency.size", Long.toString(librarySize));
112+
properties.addProperty("dependency.count", Integer.toString(indexCount));
113+
}
114+
115+
telemetryEvent(JAVA_PROJECT_BUILD, properties);
116+
}
117+
118+
/**
119+
* The total size (in bytes) of all dependencies in the workspace that have been
120+
* indexed. This should correspond to the total size of dependencies used by a
121+
* project.
122+
*
123+
* @param manager
124+
* the index manager for the workspace
125+
*
126+
* @return the size (in bytes) of all dependencies indexed in the workspace.
127+
*/
128+
private static long computeDependencySize(IndexManager manager) {
129+
Object[] libraries = manager.indexLocations.keyTable;
130+
return Arrays.asList(libraries).stream().mapToLong(lib -> lib != null ? ((IPath) lib).toFile().length() : 0).sum();
131+
}
132+
133+
private void telemetryEvent(String name, JsonObject properties) {
134+
boolean telemetryEnabled = prefs.getPreferences().isTelemetryEnabled();
135+
if (telemetryEnabled) {
136+
client.telemetryEvent(new TelemetryEvent(name, properties));
137+
}
138+
}
139+
140+
}

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,8 @@ public class Preferences {
458458
// Project encoding settings
459459
public static final String JAVA_PROJECT_ENCODING = "java.project.encoding";
460460

461+
public static final String JAVA_TELEMETRY_ENABLED_KEY = "java.telemetry.enabled";
462+
461463
/**
462464
* The preferences for generating toString method.
463465
*/
@@ -582,6 +584,7 @@ public class Preferences {
582584
private boolean foldingRangeEnabled;
583585
private boolean selectionRangeEnabled;
584586
private boolean guessMethodArguments;
587+
585588
private boolean javaFormatComments;
586589
private boolean hashCodeEqualsTemplateUseJava7Objects;
587590
private boolean hashCodeEqualsTemplateUseInstanceof;
@@ -641,6 +644,7 @@ public class Preferences {
641644
private FeatureStatus nullAnalysisMode;
642645
private List<String> cleanUpActionsOnSave;
643646
private boolean extractInterfaceReplaceEnabled;
647+
private boolean telemetryEnabled;
644648

645649
static {
646650
JAVA_IMPORT_EXCLUSIONS_DEFAULT = new LinkedList<>();
@@ -869,6 +873,7 @@ public Preferences() {
869873
nullAnalysisMode = FeatureStatus.disabled;
870874
cleanUpActionsOnSave = new ArrayList<>();
871875
extractInterfaceReplaceEnabled = false;
876+
telemetryEnabled = false;
872877
}
873878

874879
private static void initializeNullAnalysisClasspathStorage() {
@@ -1223,6 +1228,8 @@ public static Preferences createFrom(Map<String, Object> configuration) {
12231228
prefs.setCleanUpActionsOnSave(cleanupActionsOnSave);
12241229
boolean extractInterfaceReplaceEnabled = getBoolean(configuration, JAVA_REFACTORING_EXTRACT_INTERFACE_REPLACE, false);
12251230
prefs.setExtractInterfaceReplaceEnabled(extractInterfaceReplaceEnabled);
1231+
boolean telemetryEnabled = getBoolean(configuration, JAVA_TELEMETRY_ENABLED_KEY, false);
1232+
prefs.setTelemetryEnabled(telemetryEnabled);
12261233
return prefs;
12271234
}
12281235

@@ -1531,6 +1538,10 @@ public Preferences setFilteredTypes(List<String> filteredTypes) {
15311538
return this;
15321539
}
15331540

1541+
public void setTelemetryEnabled(boolean telemetry) {
1542+
this.telemetryEnabled = telemetry;
1543+
}
1544+
15341545
public Preferences setMaxBuildCount(int maxConcurrentBuilds) {
15351546
this.parallelBuildsCount = maxConcurrentBuilds;
15361547
return this;
@@ -2350,4 +2361,8 @@ private Map<String, String> generateProjectNullAnalysisOptions(String nonnullTyp
23502361
return options;
23512362
}
23522363

2364+
public boolean isTelemetryEnabled() {
2365+
return telemetryEnabled;
2366+
}
2367+
23532368
}

0 commit comments

Comments
 (0)