diff --git a/USAGE_DATA.md b/USAGE_DATA.md index 532398eed1..2944ed64d3 100644 --- a/USAGE_DATA.md +++ b/USAGE_DATA.md @@ -13,6 +13,12 @@ When telemetry events are enabled, the following information is emitted when the * The free, total, and max VM memory * Version information: * The server version number + * Text Document Information + * When a document is opened : + * The file extension (eg. `xml`, `xsd`, `dtd`) + * The associated grammar types (eg. `none`, `doctype`, `xml-model`, `xsi:schemaLocation`, `xsi:noNamespaceSchemaLocation`) + * The system/public identifier used for the grammar, if any + * The resolver used to resolve the grammar identifier (eg. `catalog`, `file association`, `embedded catalog.xsd`, `embedded xml.xsd`, `embedded xslt.xsd`) * Note: Does NOT include the `JAVA_HOME` environment variable for privacy reasons Currently, the startup event is the only telemetry event that is emitted. diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java index fdf7a3ab3b..068ccebba3 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java @@ -115,6 +115,7 @@ public CompletableFuture initialize(InitializeParams params) { this.parentProcessId = params.getProcessId(); + xmlLanguageService.setTelemetryManager(getTelemetryManager()); // Update XML language service extensions with InitializeParams xmlLanguageService.initializeParams(params); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java index 711dcc4fb7..94fdba82fd 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java @@ -27,6 +27,7 @@ import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelHoverParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelSymbolsProviderParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelTypeDefinitionParticipant; +import org.eclipse.lemminx.extensions.contentmodel.participants.DocumentTelemetryParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.diagnostics.ContentModelDiagnosticsParticipant; import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings; import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings; @@ -80,6 +81,8 @@ public class ContentModelPlugin implements IXMLExtension { private XMLValidationSettings currentValidationSettings; + private DocumentTelemetryParticipant documentTelemetryParticipant; + public ContentModelPlugin() { completionParticipant = new ContentModelCompletionParticipant(); hoverParticipant = new ContentModelHoverParticipant(); @@ -184,6 +187,8 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { registry.registerSymbolsProviderParticipant(symbolsProviderParticipant); codeLensParticipant = new ContentModelCodeLensParticipant(contentModelManager); registry.registerCodeLensParticipant(codeLensParticipant); + documentTelemetryParticipant = new DocumentTelemetryParticipant(registry.getTelemetryManager(), contentModelManager); + registry.registerDocumentLifecycleParticipant(documentTelemetryParticipant); // Register custom commands to re-validate XML files IXMLCommandService commandService = registry.getCommandService(); @@ -211,6 +216,7 @@ public void stop(XMLExtensionsRegistry registry) { registry.unregisterTypeDefinitionParticipant(typeDefinitionParticipant); registry.unregisterSymbolsProviderParticipant(symbolsProviderParticipant); registry.unregisterCodeLensParticipant(codeLensParticipant); + registry.unregisterDocumentLifecycleParticipant(documentTelemetryParticipant); // Un-register custom commands to re-validate XML files IXMLCommandService commandService = registry.getCommandService(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DocumentTelemetryParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DocumentTelemetryParticipant.java new file mode 100644 index 0000000000..08316fde0a --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DocumentTelemetryParticipant.java @@ -0,0 +1,44 @@ +/******************************************************************************* +* Copyright (c) 2021 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.contentmodel.participants; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; +import org.eclipse.lemminx.services.extensions.IDocumentLifecycleParticipant; +import org.eclipse.lemminx.telemetry.TelemetryManager; + +public class DocumentTelemetryParticipant implements IDocumentLifecycleParticipant { + + private TelemetryManager telemetryManager; + + private ContentModelManager contentManager; + + public DocumentTelemetryParticipant(TelemetryManager telemetryManager, ContentModelManager contentManager) { + this.telemetryManager = telemetryManager; + this.contentManager = contentManager; + } + + @Override + public void didOpen(DOMDocument document) { + telemetryManager.onDidOpen(document, contentManager); + } + + @Override + public void didChange(DOMDocument document) {} + + @Override + public void didSave(DOMDocument document) {} + + @Override + public void didClose(DOMDocument document) {} + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java index 80369fca62..6cf516f3d6 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java @@ -32,6 +32,7 @@ import org.eclipse.lemminx.services.extensions.format.IFormatterParticipant; import org.eclipse.lemminx.services.extensions.save.ISaveContext; import org.eclipse.lemminx.services.extensions.save.ISaveContext.SaveContextType; +import org.eclipse.lemminx.telemetry.TelemetryManager; import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager; import org.eclipse.lsp4j.InitializeParams; @@ -74,6 +75,8 @@ public class XMLExtensionsRegistry implements IComponentProvider { private final Map, Object> components; + private TelemetryManager telemetryManager; + public XMLExtensionsRegistry() { extensions = new ArrayList<>(); completionParticipants = new ArrayList<>(); @@ -93,6 +96,7 @@ public XMLExtensionsRegistry() { documentLifecycleParticipants = new ArrayList<>(); resolverExtensionManager = new URIResolverExtensionManager(); components = new HashMap<>(); + telemetryManager = new TelemetryManager(null); registerComponent(resolverExtensionManager); } @@ -505,4 +509,12 @@ public void setCommandService(IXMLCommandService commandService) { this.commandService = commandService; } + public TelemetryManager getTelemetryManager() { + return telemetryManager; + } + + public void setTelemetryManager(TelemetryManager telemetryManager) { + this.telemetryManager = telemetryManager; + } + } \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/telemetry/DocumentTelemetryInfo.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/telemetry/DocumentTelemetryInfo.java new file mode 100644 index 0000000000..da2f58a8ca --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/telemetry/DocumentTelemetryInfo.java @@ -0,0 +1,82 @@ +/******************************************************************************* +* Copyright (c) 2021 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.telemetry; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; +import org.eclipse.lemminx.extensions.contentmodel.model.ReferencedGrammarInfo; + +public class DocumentTelemetryInfo { + + private static final String DOC_PROP_EXT = "file.extension"; + private static final String DOC_PROP_RESOLVER = "file.resolver"; + private static final String DOC_PROP_IDENTIFIER = "file.identifier"; + private static final String DOC_PROP_GRAMMAR_NONE = "file.grammar.none"; + private static final String DOC_PROP_GRAMMAR_DOCTYPE = "file.grammar.doctype"; + private static final String DOC_PROP_GRAMMAR_XMLMODEL = "file.grammar.xmlmodel"; + private static final String DOC_PROP_GRAMMAR_SCHEMALOC = "file.grammar.schemalocation"; + private static final String DOC_PROP_GRAMMAR_NONSSCHEMALOC = "file.grammar.nonsschemalocation"; + + public static Map getDocumentTelemetryInfo (DOMDocument doc, ContentModelManager manager) { + String uri = doc.getDocumentURI(); + int index = uri.lastIndexOf('.'); + String fileExtension = uri.substring(index + 1, uri.length()).toLowerCase(); + Set referencedGrammarInfos = manager.getReferencedGrammarInfos(doc); + HashMap props = new HashMap<>(); + props.put(DOC_PROP_EXT, fileExtension); + + if (referencedGrammarInfos.isEmpty()) { + if ("xml".equals(fileExtension)) { + props.put(DOC_PROP_GRAMMAR_NONE, true); + } + } else { + List resolvers = new ArrayList(); + List identifiers = new ArrayList(); + for (ReferencedGrammarInfo info : referencedGrammarInfos) { + String kind = info.getBindingKind() == null ? "none" : info.getBindingKind(); + String resolver = info.getResolvedBy(); + String id = info.getIdentifierURI(); + if ("xml".equals(fileExtension)) { + switch (kind) { + case "none": + props.put(DOC_PROP_GRAMMAR_NONE, true); + break; + case "doctype": + props.put(DOC_PROP_GRAMMAR_DOCTYPE, true); + break; + case "xml-model": + props.put(DOC_PROP_GRAMMAR_XMLMODEL, true); + break; + case "xsi:schemaLocation": + props.put(DOC_PROP_GRAMMAR_SCHEMALOC, true); + break; + case "xsi:noNamespaceSchemaLocation": + props.put(DOC_PROP_GRAMMAR_NONSSCHEMALOC, true); + break; + } + } + identifiers.add(id == null ? "none" : id); + resolvers.add(resolver == null ? "default" : resolver); + } + + props.put(DOC_PROP_IDENTIFIER, identifiers); + props.put(DOC_PROP_RESOLVER, resolvers); + } + return props; + } +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/telemetry/TelemetryManager.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/telemetry/TelemetryManager.java index 5fe039d428..5f3c35d54a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/telemetry/TelemetryManager.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/telemetry/TelemetryManager.java @@ -9,6 +9,8 @@ *******************************************************************************/ package org.eclipse.lemminx.telemetry; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; import org.eclipse.lsp4j.InitializedParams; import org.eclipse.lsp4j.services.LanguageClient; @@ -24,6 +26,8 @@ public class TelemetryManager { */ private static final String STARTUP_EVENT_NAME = "server.initialized"; + private static final String DOC_OPEN_EVENT_NAME = "server.document.open"; + private final LanguageClient languageClient; private boolean enabled; @@ -51,6 +55,10 @@ public void onInitialized(InitializedParams params) { } } + public void onDidOpen(DOMDocument document, ContentModelManager manager) { + telemetryEvent(DOC_OPEN_EVENT_NAME, DocumentTelemetryInfo.getDocumentTelemetryInfo(document, manager)); + } + /** * The telemetry notification is sent from the server to the client to ask the * client to log a telemetry event.