From b79a110f01b91b1164dfb85458e11a398b704053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=A4der?= Date: Fri, 26 Sep 2025 15:14:44 +0200 Subject: [PATCH 1/4] Add more telemetry for project import & workspaces hash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Mäder --- src/standardLanguageClient.ts | 45 +++++++++++++++++++++++++++++++---- src/telemetry.ts | 26 ++++++++++---------- src/utils.ts | 19 +++++++++++++++ 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts index 338a5d849..15be19ccd 100644 --- a/src/standardLanguageClient.ts +++ b/src/standardLanguageClient.ts @@ -2,8 +2,8 @@ import * as net from 'net'; import * as path from 'path'; -import { CancellationToken, CodeActionKind, commands, ConfigurationTarget, DocumentSelector, EventEmitter, ExtensionContext, extensions, languages, Location, ProgressLocation, TextEditor, Uri, ViewColumn, window, workspace } from "vscode"; -import { ConfigurationParams, ConfigurationRequest, LanguageClientOptions, Location as LSLocation, MessageType, Position as LSPosition, TextDocumentPositionParams, WorkspaceEdit, StaticFeature, ClientCapabilities, FeatureState } from "vscode-languageclient"; +import { CancellationToken, CodeActionKind, commands, ConfigurationTarget, DocumentSelector, EventEmitter, ExtensionContext, extensions, languages, Location, ProgressLocation, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceConfiguration } from "vscode"; +import { ConfigurationParams, ConfigurationRequest, LanguageClientOptions, Location as LSLocation, MessageType, Position as LSPosition, TextDocumentPositionParams, WorkspaceEdit, StaticFeature, ClientCapabilities, FeatureState, TelemetryEventNotification } from "vscode-languageclient"; import { LanguageClient, StreamInfo } from "vscode-languageclient/node"; import { apiManager } from "./apiManager"; import * as buildPath from './buildpath'; @@ -336,10 +336,45 @@ export class StandardLanguageClient { return result; }); + function getJavaSettingsForTelemetry(config: WorkspaceConfiguration) { + // settings whose values we can record + const SETTINGS_BASIC = [ + "java.quickfix.showAt", "java.symbols.includeSourceMethodDeclarations", + "java.completion.collapseCompletionItems", "java.completion.guessMethodArguments", + "java.cleanup.actionsOnSave", "java.completion.postfix.enabled", + "java.sharedIndexes.enabled", "java.inlayHints.parameterNames.enabled", + "java.server.launchMode", "java.autobuild.enabled" + ]; + // settings where we only record their existence + const SETTINGS_CUSTOM = [ + "java.settings.url", "java.format.settings.url" + ]; + + let value: any; + const properties = {}; + + for (const key of SETTINGS_CUSTOM) { + if (config.get(key)) { + properties[key] = true; + } + } + for (const key of SETTINGS_BASIC) { + value = config.get(key); + if (value !== undefined) { + properties[key] = value; + } + } + + return properties; + } + this.languageClient.onTelemetry(async (e: TelemetryEvent) => { apiManager.fireTraceEvent(e); if (e.name === Telemetry.SERVER_INITIALIZED_EVT) { - return Telemetry.sendTelemetry(Telemetry.STARTUP_EVT, e.properties); + const javaSettings = getJavaSettingsForTelemetry(workspace.getConfiguration()); + + const properties= { ...e.properties, ...javaSettings }; + await Telemetry.sendTelemetry(Telemetry.STARTUP_EVT, ); } else if (e.name === Telemetry.LS_ERROR) { const tags = []; const exception: string = e?.properties.exception; @@ -357,8 +392,10 @@ export class StandardLanguageClient { if (tags.length > 0 || DEBUG || isPrereleaseOrInsiderVersion(context)) { e.properties['tags'] = tags; - return Telemetry.sendTelemetry(Telemetry.LS_ERROR, e.properties); + await Telemetry.sendTelemetry(Telemetry.LS_ERROR, e.properties); } + } else if (e.name === Telemetry.IMPORT_PROJECT) { + await Telemetry.sendTelemetry(Telemetry.IMPORT_PROJECT, e.properties); } }); diff --git a/src/telemetry.ts b/src/telemetry.ts index 89796a5ff..ddf178653 100644 --- a/src/telemetry.ts +++ b/src/telemetry.ts @@ -1,5 +1,6 @@ import { TelemetryService, getRedHatService } from "@redhat-developer/vscode-redhat-telemetry"; import { ExtensionContext, workspace, WorkspaceConfiguration } from "vscode"; +import { cyrb53 } from "./utils"; /** * Wrap vscode-redhat-telemetry to suit vscode-java @@ -10,7 +11,10 @@ export namespace Telemetry { export const COMPLETION_EVENT = "textCompletion"; export const SERVER_INITIALIZED_EVT = "java.workspace.initialized"; export const LS_ERROR = "java.ls.error"; + export const IMPORT_PROJECT = "java.workspace.importProject"; + let telemetryManager: TelemetryService = null; + let workspaceHash; /** * Starts the telemetry service @@ -22,6 +26,10 @@ export namespace Telemetry { if (!!telemetryManager) { throw new Error("The telemetry service for vscode-java has already been started"); } + workspaceHash = cyrb53(workspace.workspaceFolders.map(f => f.uri.toString()).join('|')); + workspace.onDidChangeWorkspaceFolders(() => { + workspaceHash = cyrb53(workspace.workspaceFolders.map(f => f.uri.toString()).join('|')); + }); const redhatService = await getRedHatService(context); const telemService = await redhatService.getTelemetryService(); telemetryManager = telemService; @@ -35,23 +43,17 @@ export namespace Telemetry { * @param data the telemetry data * @throws Error if the telemetry service has not been started yet */ - export async function sendTelemetry(eventName: string, data?: any): Promise { + export async function sendTelemetry(eventName: string, data?: object): Promise { + console.log(`Sending telemetry event: ${eventName} with data: ${JSON.stringify(data)}`); if (!telemetryManager) { throw new Error("The telemetry service for vscode-java has not been started yet"); } - const javaSettings = getJavaSettingsForTelemetry(workspace.getConfiguration()); - let properties: any; - if (eventName === STARTUP_EVT) { - properties= { ...data, ...javaSettings }; - } else { - properties= { ...data}; - } - - return telemetryManager.send({ + const event = { name: eventName, - properties - }); + properties: { workspaceHash, ...data} + }; + return telemetryManager.send(event); } function getJavaSettingsForTelemetry(config: WorkspaceConfiguration) { diff --git a/src/utils.ts b/src/utils.ts index 8e3972c25..02a0becd5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -391,3 +391,22 @@ export function isPreReleaseVersion(context: ExtensionContext | string): boolean export function isInsiderEditor(): boolean { return version.includes("insider"); } +/* +* cyrb53 (c) 2018 bryc (github.com/bryc) +* Public domain (or MIT if needed). Attribution appreciated. +* A fast and simple 53-bit string hash function with decent collision resistance. +* Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity. +*/ +export function cyrb53 (str, seed = 0) { + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); + h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); + return 4294967296 * (2097151 & h2) + (h1 >>> 0); +}; From c8298b0fff560b441a7340c72bde8dd98824081b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=A4der?= Date: Tue, 21 Oct 2025 15:56:23 +0200 Subject: [PATCH 2/4] Don't send telemetry for import project error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Mäder --- src/standardLanguageClient.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts index 15be19ccd..5e6e282e4 100644 --- a/src/standardLanguageClient.ts +++ b/src/standardLanguageClient.ts @@ -394,8 +394,6 @@ export class StandardLanguageClient { e.properties['tags'] = tags; await Telemetry.sendTelemetry(Telemetry.LS_ERROR, e.properties); } - } else if (e.name === Telemetry.IMPORT_PROJECT) { - await Telemetry.sendTelemetry(Telemetry.IMPORT_PROJECT, e.properties); } }); From 3c556e21f926bfc5ebf7442d0a3bf64d29cde7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=A4der?= Date: Tue, 21 Oct 2025 16:24:15 +0200 Subject: [PATCH 3/4] Address review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Mäder --- src/standardLanguageClient.ts | 6 +++++- src/telemetry.ts | 36 ----------------------------------- 2 files changed, 5 insertions(+), 37 deletions(-) diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts index 5e6e282e4..82eceabef 100644 --- a/src/standardLanguageClient.ts +++ b/src/standardLanguageClient.ts @@ -343,8 +343,12 @@ export class StandardLanguageClient { "java.completion.collapseCompletionItems", "java.completion.guessMethodArguments", "java.cleanup.actionsOnSave", "java.completion.postfix.enabled", "java.sharedIndexes.enabled", "java.inlayHints.parameterNames.enabled", + "java.inlayHints.parameterNames.suppressWhenSameNameNumbered", + "java.inlayHints.variableTypes.enabled", + "java.inlayHints.parameterTypes.enabled", "java.server.launchMode", "java.autobuild.enabled" ]; + // settings where we only record their existence const SETTINGS_CUSTOM = [ "java.settings.url", "java.format.settings.url" @@ -374,7 +378,7 @@ export class StandardLanguageClient { const javaSettings = getJavaSettingsForTelemetry(workspace.getConfiguration()); const properties= { ...e.properties, ...javaSettings }; - await Telemetry.sendTelemetry(Telemetry.STARTUP_EVT, ); + await Telemetry.sendTelemetry(Telemetry.STARTUP_EVT, properties); } else if (e.name === Telemetry.LS_ERROR) { const tags = []; const exception: string = e?.properties.exception; diff --git a/src/telemetry.ts b/src/telemetry.ts index ddf178653..52ab96531 100644 --- a/src/telemetry.ts +++ b/src/telemetry.ts @@ -55,40 +55,4 @@ export namespace Telemetry { }; return telemetryManager.send(event); } - - function getJavaSettingsForTelemetry(config: WorkspaceConfiguration) { - // settings whose values we can record - const SETTINGS_BASIC = [ - "java.quickfix.showAt", "java.symbols.includeSourceMethodDeclarations", - "java.completion.collapseCompletionItems", "java.completion.guessMethodArguments", - "java.cleanup.actionsOnSave", "java.completion.postfix.enabled", - "java.sharedIndexes.enabled", "java.inlayHints.parameterNames.enabled", - "java.inlayHints.parameterNames.suppressWhenSameNameNumbered", - "java.inlayHints.variableTypes.enabled", - "java.inlayHints.parameterTypes.enabled", - "java.server.launchMode", "java.autobuild.enabled" - ]; - - // settings where we only record their existence - const SETTINGS_CUSTOM = [ - "java.settings.url", "java.format.settings.url" - ]; - - let value: any; - const properties = {}; - - for (const key of SETTINGS_CUSTOM) { - if (config.get(key)) { - properties[key] = true; - } - } - for (const key of SETTINGS_BASIC) { - value = config.get(key); - if (value !== undefined) { - properties[key] = value; - } - } - - return properties; - } } From 281f8587bbff985f3916c80503221a479beebed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=A4der?= Date: Thu, 23 Oct 2025 14:36:13 +0200 Subject: [PATCH 4/4] Address more review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Mäder --- src/telemetry.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/telemetry.ts b/src/telemetry.ts index 52ab96531..382b4c940 100644 --- a/src/telemetry.ts +++ b/src/telemetry.ts @@ -11,7 +11,6 @@ export namespace Telemetry { export const COMPLETION_EVENT = "textCompletion"; export const SERVER_INITIALIZED_EVT = "java.workspace.initialized"; export const LS_ERROR = "java.ls.error"; - export const IMPORT_PROJECT = "java.workspace.importProject"; let telemetryManager: TelemetryService = null; let workspaceHash; @@ -44,7 +43,6 @@ export namespace Telemetry { * @throws Error if the telemetry service has not been started yet */ export async function sendTelemetry(eventName: string, data?: object): Promise { - console.log(`Sending telemetry event: ${eventName} with data: ${JSON.stringify(data)}`); if (!telemetryManager) { throw new Error("The telemetry service for vscode-java has not been started yet"); }