From fc085d2e1f6ed2ca241fb48ae2d3f8ea5d51fcdf Mon Sep 17 00:00:00 2001 From: Ghislain Piot Date: Tue, 17 Dec 2024 17:15:16 +0100 Subject: [PATCH] SONARPY-2457 Collect data for the Python version (#2252) --- .../org/sonar/plugins/python/IPynbSensor.java | 4 +-- .../sonar/plugins/python/PythonSensor.java | 15 ++++++++++ .../python/SensorTelemetryStorage.java | 8 +++++ .../plugins/python/TelemetryMetricKey.java | 4 ++- .../plugins/python/PythonSensorTest.java | 30 +++++++++++++++++++ 5 files changed, 58 insertions(+), 3 deletions(-) diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/IPynbSensor.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/IPynbSensor.java index addf7c09f9..eaff787fda 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/IPynbSensor.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/IPynbSensor.java @@ -103,7 +103,7 @@ private List parseNotebooks(List pythonFiles, for (PythonInputFile inputFile : pythonFiles) { try { - sensorTelemetryStorage.updateMetric(TelemetryMetricKey.NOTEBOOK_PRESENT_KEY, "1"); + sensorTelemetryStorage.updateMetric(TelemetryMetricKey.NOTEBOOK_PRESENT_KEY, true); var result = IpynbNotebookParser.parseNotebook(inputFile); result.ifPresent(generatedIPythonFiles::add); } catch (Exception e) { @@ -114,7 +114,7 @@ private List parseNotebooks(List pythonFiles, } } - sensorTelemetryStorage.updateMetric(TelemetryMetricKey.NOTEBOOK_EXCEPTION_KEY, String.valueOf(numberOfExceptions)); + sensorTelemetryStorage.updateMetric(TelemetryMetricKey.NOTEBOOK_EXCEPTION_KEY, numberOfExceptions); return generatedIPythonFiles; } diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonSensor.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonSensor.java index 6dbe1ff3cc..39fafe6f56 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonSensor.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/PythonSensor.java @@ -68,6 +68,8 @@ public final class PythonSensor implements Sensor { static final String UNSET_VERSION_WARNING = "Your code is analyzed as compatible with all Python 3 versions by default." + " You can get a more precise analysis by setting the exact Python version in your configuration via the parameter \"sonar.python.version\""; + private final SensorTelemetryStorage sensorTelemetryStorage; + /** * Constructor to be used by pico if neither PythonCustomRuleRepository nor PythonIndexer are to be found and injected. */ @@ -100,6 +102,7 @@ public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactor this.indexer = indexer; this.sonarLintCache = sonarLintCache; this.analysisWarnings = analysisWarnings; + this.sensorTelemetryStorage = new SensorTelemetryStorage(); } @Override @@ -121,15 +124,27 @@ public void execute(SensorContext context) { if (pythonVersionParameter.length != 0){ ProjectPythonVersion.setCurrentVersions(PythonVersionUtils.fromStringArray(pythonVersionParameter)); } + updatePythonVersionTelemetry(context, pythonVersionParameter); CacheContext cacheContext = CacheContextImpl.of(context); PythonIndexer pythonIndexer = this.indexer != null ? this.indexer : new SonarQubePythonIndexer(pythonFiles, cacheContext, context); pythonIndexer.setSonarLintCache(sonarLintCache); TypeShed.setProjectLevelSymbolTable(pythonIndexer.projectLevelSymbolTable()); PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, noSonarFilter, PythonParser.create(), pythonIndexer); scanner.execute(pythonFiles, context); + sensorTelemetryStorage.send(context); durationReport.stop(); } + private void updatePythonVersionTelemetry(SensorContext context, String[] pythonVersionParameter) { + if (context.runtime().getProduct() == SonarProduct.SONARLINT) { + return; + } + sensorTelemetryStorage.updateMetric(TelemetryMetricKey.PYTHON_VERSION_SET_KEY, pythonVersionParameter.length != 0); + if (pythonVersionParameter.length != 0) { + sensorTelemetryStorage.updateMetric(TelemetryMetricKey.PYTHON_VERSION_KEY, String.join(",", pythonVersionParameter)); + } + } + private static List getInputFiles(SensorContext context) { FilePredicates p = context.fileSystem().predicates(); Iterable it = context.fileSystem().inputFiles(p.and(p.hasLanguage(Python.KEY))); diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/SensorTelemetryStorage.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/SensorTelemetryStorage.java index 37b55c4b19..69ab06e197 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/SensorTelemetryStorage.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/SensorTelemetryStorage.java @@ -55,4 +55,12 @@ public void updateMetric(TelemetryMetricKey key, int value) { data.put(key, String.valueOf(value)); } + public void updateMetric(TelemetryMetricKey key, boolean value) { + data.put(key, boolToString(value)); + } + + private static String boolToString(boolean value) { + return value ? "1" : "0"; + } + } diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/TelemetryMetricKey.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/TelemetryMetricKey.java index 65c8db782e..443b8f2227 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/TelemetryMetricKey.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/TelemetryMetricKey.java @@ -20,7 +20,9 @@ public enum TelemetryMetricKey { NOTEBOOK_PRESENT_KEY("python.notebook.present"), NOTEBOOK_TOTAL_KEY("python.notebook.total"), NOTEBOOK_RECOGNITION_ERROR_KEY("python.notebook.recognition_error"), - NOTEBOOK_EXCEPTION_KEY("python.notebook.exceptions"); + NOTEBOOK_EXCEPTION_KEY("python.notebook.exceptions"), + PYTHON_VERSION_SET_KEY("python.version.set"), + PYTHON_VERSION_KEY("python.version"); private final String key; diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java index 06406c22af..a63462cd98 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java @@ -1408,6 +1408,36 @@ void test_scanner_isNotebook() { assertThat(PythonScanner.isNotebook(notebookPythonFile)).isTrue(); } + @Test + void send_telemetry_with_version() { + activeRules = new ActiveRulesBuilder() + .addRule(new NewActiveRule.Builder() + .setRuleKey(RuleKey.of(CheckList.REPOSITORY_KEY, "S930")) + .build()) + .build(); + + context.setSettings(new MapSettings().setProperty("sonar.python.version", "3.10,3.13")); + var contextSpy = spy(context); + PythonSensor sensor = sensor(); + sensor.execute(contextSpy); + verify(contextSpy, times(1)).addTelemetryProperty(TelemetryMetricKey.PYTHON_VERSION_KEY.key(), "3.10,3.13"); + verify(contextSpy, times(1)).addTelemetryProperty(TelemetryMetricKey.PYTHON_VERSION_SET_KEY.key(), "1"); + } + + @Test + void send_telemetry_no_version() { + activeRules = new ActiveRulesBuilder() + .addRule(new NewActiveRule.Builder() + .setRuleKey(RuleKey.of(CheckList.REPOSITORY_KEY, "S930")) + .build()) + .build(); + + PythonSensor sensor = sensor(); + var contextSpy = spy(context); + sensor.execute(contextSpy); + verify(contextSpy, times(1)).addTelemetryProperty(TelemetryMetricKey.PYTHON_VERSION_SET_KEY.key(), "0"); + } + private com.sonar.sslr.api.Token passToken(URI uri) { return com.sonar.sslr.api.Token.builder() .setType(PythonKeyword.PASS)